0%

1 消息队列比较

特性 RabbitMQ RocketMQ kafka
TOPIC(主题模型实现) 队列模型 没有队列的概念,一个主题有多个分区(Partition)
消费模式 集群消费、广播消费 集群消费,一个消息只能由一个消费者实例消费
支持消息 延时消息、事务消息 事务消息,不支持延时消息
依赖与管理 Name Server 进行服务发现和路由管理 依赖 Zookeeper 进行分区的元数据管理、Leader 选举和消费组协调
使用场景 RocketMQ 的 Queue 更加灵活,支持更复杂的消息路由和消费策略,适合需要精细化控制和顺序性保证的场景,如金融交易、电商订单处理等。 Kafka 的 Partition 注重高吞吐量和简单性,适合需要处理大量数据流的场景,如实时日志收集、大数据处理等。
消息吞吐量 万级 10万级 10万级,吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景
时效性 us级,这是rabbitmq的一大特点,延迟是最低的 ms级 ms级
可用性 高(主从架构) 非常高(分布式架构) 非常高(分布式架构),kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
优劣势总结 erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒 提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集
  • 消费推拉模式
    客户端消费者获取消息的方式,Kafka和RocketMQ是通过长轮询Pull的方式拉取消息,RabbitMQ、Pulsar、NSQ都是通过Push的方式。

    pull类型的消息队列更适合高吞吐量的场景,允许消费者自己进行流量控制,根据消费者实际的消费能力去获取消息。而push类型的消息队列,实时性更好,但需要有一套良好的流控策略(backpressure)当消费者消费能力不足时,减少push的消费数量,避免压垮消费端。

  • 延迟队列
    延迟消息的使用场景比如异常检测重试,订单超时取消等,例如:

    • 服务请求异常,需要将异常请求放到单独的队列,隔5分钟后进行重试;
    • 用户购买商品,但一直处于未支付状态,需要定期提醒用户支付,超时则关闭订单;
    • 面试或者会议预约,在面试或者会议开始前半小时,发送通知再次提醒。
      Kafka不支持延迟消息,RocketMQ开源版本延迟消息临时存储在一个内部主题中,不支持任意时间精度,支持特定的level,例如定时5s,10s,1m等。
  • 死信队列
    Kafka、RocketMQ、Pulsar、NSQ不支持优先级队列,可以通过不同的队列来实现消息优先级。
    RabbitMQ支持优先级消息。

  • 优先级队列

2 消息队列选择建议

1.Kafka
Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。

大型公司建议可以选用,如果有日志采集功能,肯定是首选kafka了。

2.RocketMQ
天生为金融互联网领域而生,对于高可靠性的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。

RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。

3.RabbitMQ
RabbitMQ :结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。不过,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug。

如果你的数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ。

3 Kafka和RocketMQ

Apache Kafka和Apache RocketMQ都是广泛使用的分布式消息中间件,它们在大数据处理、高并发和高性能场景中发挥着关键作用。尽管它们有相似的应用场景,但两者之间存在一些设计哲学和功能上的差异。下面概述了Kafka和RocketMQ的主要区别与联系:
联系

  1. 分布式架构:Kafka和RocketMQ都采用了分布式架构,支持高可用性和水平扩展,能够应对大规模数据传输和处理需求。

  2. 发布/订阅模式:两者都支持发布/订阅(Pub/Sub)模式,允许生产者发布消息到特定主题,而多个消费者可以订阅这些主题来接收消息。
    3. 高吞吐量与低延迟:它们都被设计用于高吞吐量和低延迟的消息传递,特别适合实时数据处理和流处理场景。
    4. 持久化与可靠性:Kafka和RocketMQ都能保证消息的持久化存储,确保即使在系统故障时也不会丢失数据。
    区别

  3. 设计初衷与背景: Kafka主要用于日志处理和实时数据管道。它更侧重于流处理和数据分析领 。RocketMQ设计时更多考虑了电商等互联网业务场景的需求,如订单处理、分布式事务支持等。
    2. 消息模型: Kafka采用分区(Partition)的概念,每个主题可以分为多个分区,分区内部有序,分区间可以并行处理,适合大量数据的并行处理。 RocketMQ除了基本的消息队列模型,还引入了顺序消息和事务消息的支持,更适合需要严格消息顺序或事务一致性的场景。
    3. 消息存储: Kafka将消息持久化到磁盘,并使用文件系统特性优化读写性能,通过日志压缩机制管理存储空间。 RocketMQ虽然也支持消息持久化,但它采用一种环形缓冲区的设计来提高内存使用效率,同时支持异步刷盘減少磁盘1/0影响。

Kafka 和 RocketMQ 都是分布式消息队列系统,主要用于处理大规模的数据流和事件流。两者在架构、功能特性、使用场景和性能方面有一些差异。下面是两者的主要区别:

3.1.1 架构与设计

  • Kafka

    • Kafka 设计简单,核心架构围绕主题(Topic)和分区(Partition)展开。Producer 将消息写入分区,Consumer 从分区读取消息。
    • Kafka 的 Broker 是无状态的,消费者的位移存储在 Kafka 自身(基于内部主题或外部系统,如 Zookeeper)。
    • 强依赖 Zookeeper,用于集群管理、Leader 选举和消费者协调。
  • RocketMQ

    • RocketMQ 是阿里巴巴开源的消息中间件,架构较为复杂,采用多层次设计,包括 Name Server、Broker、Producer、Consumer 等多个组件。
    • RocketMQ 的 Broker 具有更多的功能,比如支持消息过滤、延时消息等。
    • Name Server 负责服务发现和路由管理,而 Kafka 由 Zookeeper 处理类似功能。

3.1.2 消息模型

  • Kafka:支持发布-订阅模型和点对点模型(通过消费组实现)。消息按分区顺序处理,且默认不支持延迟消息和消息过滤。

  • RocketMQ:支持发布-订阅模型,支持标签(Tag)和关键字(Key)来实现消息过滤。此外,支持定时/延迟消息。

3.1.3 消息可靠性

  • Kafka:通过副本机制(Replication)实现高可用性和数据持久化。副本数量和一致性可以配置。Kafka 允许“至少一次”或“最多一次”的消息投递语义。

  • RocketMQ:也支持多副本,但与 Kafka 不同,RocketMQ 可以配置同步或异步刷盘,并且可以配置主从同步机制来提高可靠性。

3.1.4 性能

  • Kafka:专注于吞吐量,适合处理海量数据。通过顺序写入、零拷贝等优化手段实现了高吞吐量和低延迟。

  • RocketMQ:在性能和功能之间进行了平衡,虽然单节点的吞吐量可能不如 Kafka,但它在功能性(例如消息过滤、延时消息)和灵活性方面更强。

3.1.5 使用场景

  • Kafka:适用于大数据实时处理、日志收集、流式处理等场景,特别是在数据管道和事件流处理方面有较好的表现。

  • RocketMQ:适用于电商、金融等需要高可靠性、低延迟和复杂业务逻辑的场景。阿里巴巴内部广泛使用,尤其在交易系统中。

3.1.6 轻量性

  • Kafka:从功能和架构上看,Kafka 更加简单和轻量。核心组件只有 Broker 和 Zookeeper,易于部署和运维。

  • RocketMQ:架构相对复杂,功能更为丰富,依赖更多的组件,如 Name Server 和多个 Broker。相较于 Kafka,RocketMQ 的功能性增强带来了更高的复杂度。

3.1.7 总结

如果从架构和使用的简洁性来看,Kafka 更加轻量,特别是在分布式消息队列系统的核心场景下。但如果需要更丰富的功能,如消息过滤、延时消息和复杂的消息路由,RocketMQ 可能是更好的选择。

4 分区和队列

Kafka 的 Partition(分区)和 RocketMQ 的 Queue(队列)是两者在消息存储和并行处理方面的关键概念。它们虽然功能类似,但在实现和设计上有一些差异。

4.1.1 概念对比

  • Kafka Partition

    • Kafka 中的 Partition 是一个物理日志文件,Topic 下可以有多个 Partition,每个 Partition 代表一个消息队列。
    • 每个 Partition 是独立的消息序列,具有顺序性,但不同 Partition 之间的消息是无序的。
    • 分区的数量决定了并行消费的能力,分区越多,可以并行处理的消费者越多。
    • Kafka 的分区设计使得消息在写入时可以按分区键(Key)进行分区,使得相同 Key 的消息进入同一分区,保证局部有序。
  • RocketMQ Queue

    • RocketMQ 的 Queue 是 Topic 下的一个逻辑队列,一个 Topic 可以包含多个 Queue,每个 Queue 是一个 FIFO(先进先出)队列。
    • 消息按顺序写入 Queue,消费者从 Queue 中顺序读取消息。
    • Queue 也是并行消费的基本单元,每个 Queue 可以被不同的消费者并行消费。
    • RocketMQ 的 Queue 与 Kafka 的 Partition 类似,都是将消息划分为多个并行处理的单元。

4.1.2 消息路由与分配

  • Kafka

    • 消息的路由由 Producer 根据分区键(Key)决定。如果设置了 Key,相同 Key 的消息会被路由到同一个 Partition,从而保证有序性。如果没有指定 Key,Kafka 默认采用轮询方式分配消息到各个分区。
    • 消费者在消费时,根据消费组的配置,可以分配多个分区,每个分区内的消息是顺序消费的。
  • RocketMQ

    • RocketMQ 的消息路由也是由 Producer 决定,Producer 可以指定消息发送到某个特定的 Queue(通过 MessageQueueSelector),或者使用默认的轮询机制。
    • 消费者可以消费多个 Queue 的消息,每个 Queue 内的消息是按顺序消费的。

4.1.3 消费模型

  • Kafka

    • Kafka 采用消费组(Consumer Group)的概念,组内的消费者实例共同消费一个 Topic 的所有分区,每个分区只能被一个消费者实例消费。不同的消费组之间相互独立。
    • 分区的数量决定了最大并行消费的消费者数量。
  • RocketMQ

    • RocketMQ 也采用消费组的概念,并支持两种消费模式:集群消费(Clustering)和广播消费(Broadcasting)。
    • 在集群消费模式下,每个 Queue 只能被组内一个消费者实例消费,多个消费者实例可以并行处理不同的 Queue。
    • 在广播模式下,每个消费者实例都会消费所有 Queue 的消息,即消息会被所有消费者实例都消费一次。

4.1.4 消息顺序性

  • Kafka

    • Kafka 在分区内部保证消息的顺序性。如果需要严格的全局有序,可以将所有消息发送到同一个分区,但这会限制并行处理能力。
    • 对于局部有序的场景,可以通过分区键来保证同一类消息(相同 Key)按顺序进入同一分区。
  • RocketMQ

    • RocketMQ 通过 Queue 实现消息顺序性,每个 Queue 内部保证消息顺序。
    • 可以通过自定义的 MessageQueueSelector 来实现顺序消息的路由,确保相同类型的消息进入同一个 Queue。

4.1.5 扩展性

  • Kafka

    • Kafka 的 Partition 是扩展性的基础,分区数量可以在 Topic 创建后增加,但无法减少。增加分区后,可能会打破现有的消息顺序。
    • Kafka 在分区的伸缩性方面表现较好,分区数量增加时可以提高并行处理能力。
  • RocketMQ

    • RocketMQ 的 Queue 数量也是固定的,但在需要时也可以增加 Queue。与 Kafka 相比,Queue 的扩展相对更灵活,但同样会有顺序性破坏的风险。

4.1.6 总结

Kafka 的 Partition 和 RocketMQ 的 Queue 都是为了解决消息系统的并行处理和水平扩展问题。Kafka 的 Partition 更加简单直接,而 RocketMQ 的 Queue 提供了更灵活的路由和消费控制机制。根据具体业务需求,选择适合的模型非常重要。

5 参考

  1. 10分钟搞懂!消息队列选型全方位对比-腾讯云开发者社区-腾讯云 (tencent.com)

  2. 一文详解七大主流消息队列(MQ):特性、应用场景与对比分析_常见的mq消息队列-CSDN博客

  3. MQ消息队列详解、四大MQ的优缺点分析_四大消息队列的优缺点-CSDN博客

  4. 一文详解七大主流消息队列(MQ):特性、应用场景与对比分析_常见的mq消息队列-CSDN博客

  5. 消息队列黄金三剑客:RabbitMQ、RocketMQ和Kafka全面对决,谁是最佳选择? (qq.com)

1 HashMap

HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的

HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个

JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于等于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap 总是使用 2 的幂作为哈希表的大小。

2 底层数据结构

  • loadFactor 负载因子
    loadFactor 负载因子是控制数组存放数据的疏密程度,loadFactor 越趋近于 1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor 越小,也就是趋近于 0,数组中存放的数据(entry)也就越少,也就越稀疏。

    loadFactor 太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor 的默认值为 0.75f 是官方给出的一个比较好的临界值

    给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量超过了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。

  • threshold

    threshold = capacity * loadFactor当 Size>threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是 衡量数组是否需要扩增的一个标准

3 HashMap源码分析

1 ZAB

1.1 Raft算法和ZAB算法的异同点

Leader选举 选举已提交(commit)最大编号日志所在节点 选举最大ZXID的Proposal(含未提交)所在节点
新Leader是否提交前任Leader未提交的消息 提交 提交
Leader数量 1 1
Quorum作用范围 Leader任内有效,以自身消息为准 Leader任内有效,以自身消息为准

2 Zookeeper理论知识

2.1 数据模型

使用了 znode 作为数据节点 。znode 是 zookeeper 中的最小数据单元,每个 znode 上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。

每个 znode 都有自己所属的 节点类型节点状态

其中节点类型可以分为 持久节点持久顺序节点临时节点临时顺序节点

  • 持久节点:一旦创建就一直存在,直到将其删除。

  • 持久顺序节点:一个父节点可以为其子节点 维护一个创建的先后顺序 ,这个顺序体现在 节点名称 上,是节点名称后自动添加一个由 10 位数字组成的数字串,从 0 开始计数。

  • 临时节点:临时节点的生命周期是与 客户端会话 绑定的,会话消失则节点消失 。临时节点 只能做叶子节点 ,不能创建子节点。

  • 临时顺序节点:父节点可以创建一个维持了顺序的临时节点(和前面的持久顺序性节点一样)。

2.2 会话

2.3 ACL

2.4 Watcher机制

Watcher 为事件监听器,是 zk 非常重要的一个特性,很多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端 注册 指定的 watcher ,当服务端符合了 watcher 的某些事件或要求则会 向客户端发送事件通知 ,客户端收到通知后找到自己定义的 Watcher 然后 执行相应的回调方法

image.png

3 Zookeeper应用场景

3.1 选主

3.2 数据发布/订阅

3.3 分布式锁

创建节点的唯一性,我们可以让多个客户端同时创建一个临时节点,创建成功的就说明获取到了锁 。然后没有获取到锁的客户端也像上面选主的非主节点创建一个 watcher 进行节点状态的监听,如果这个互斥锁被释放了(可能获取锁的客户端宕机了,或者那个客户端主动释放了锁)可以调用回调函数重新获得锁。

共享锁和独占锁

有序的节点
规定所有创建节点必须有序,当你是读请求(要获取共享锁)的话,如果 没有比自己更小的节点,或比自己小的节点都是读请求 ,则可以获取到读锁,然后就可以开始读了。若比自己小的节点中有写请求 ,则当前客户端无法获取到读锁,只能等待前面的写请求完成。

如果你是写请求(获取独占锁),若 没有比自己更小的节点 ,则表示当前客户端可以直接获取到写锁,对数据进行修改。若发现 有比自己更小的节点,无论是读操作还是写操作,当前客户端都无法获取到写锁 ,等待所有前面的操作完成。

这就很好地同时实现了共享锁和独占锁,当然还有优化的地方,比如当一个锁得到释放它会通知所有等待的客户端从而造成 羊群效应 。此时你可以通过让等待的节点只监听他们前面的节点。

读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点

3.4 命名服务

3.5 集群管理和注册中心

1 项目难点

对于业务场景项目,很多人自嘲增产改查。在具体去实现的时候,发现确实有些如此,但是这都是在项目成熟之后。难点在于从需求中最初对于整个项目结构、架构等的设计,从需求中分析出核心功能,在确定了核心功能,围绕功能的具体逻辑实现反而简单许多

1.1 快照,每一天的活动状态可追溯

方案一:每一天通过定时任务记录所有项目的状态快照点+状态变更(涉及到延期申请等,无法依据数据库的实际结束时间进行判断)

1.2 多条件查询

  • 动态SQL语句

  • 定义查询条件参数,过滤后封装传递给SQL语句

  • Mybatis-plus插件

某一条件设置查询条件某一条件未设置查询条件

1.3 统计显示

考核日前查当月统计结果是当前查,考核日后查当月统计结果是快照查,考核日后的状态变更与当月无关,记录到下一个月中当前项目状态统计历史归档状态统计

跨考核日节点的统计

2 领域

项目上报、项目审批、项目状态变更、延期申请、延期审批、项目进度展示、数据统计用户权限配置、项目填报、项目审核、延期申请、延期审核、项目展示、统计界面

【题目1】生产者消费者 
【题目2】实现BASE256编码 
【题目3】实现数字的16、8进制输出,10位长度,不足补0

【题目4】一对兔子,3个月后每个月产出一对兔子,n个月之后的兔子数量
$f(n)=f(n-1)+f(n-2)$

有一个文件,文件内容是E-Mail地址,已知文件大小约2GB,每行一个E-Mail地址, 但有部分E-Mail地址是重复的,现在有一台VM,VM内存为1GB,业务需要向这些 E-Mail发送邮件,但不能重复发,所以需要写一个程序对E-Mail地址进行去重。

未命名绘图.png

1 传统IO方式

传统的 IO 读写其实就是 read + write 的操作,整个过程会分为如下几步

  • 用户调用 read()方法,开始读取数据,此时发生一次上下文从用户态到内核态的切换,也就是图示的切换 1

  • 将磁盘数据通过 DMA 拷贝到内核缓存区

  • 将内核缓存区的数据拷贝到用户缓冲区,这样用户,也就是我们写的代码就能拿到文件的数据

  • read()方法返回,此时就会从内核态切换到用户态,也就是图示的切换 2

  • 当我们拿到数据之后,就可以调用 write()方法,此时上下文会从用户态切换到内核态,即图示切换 3

  • CPU 将用户缓冲区的数据拷贝到 Socket 缓冲区

  • 将 Socket 缓冲区数据拷贝至网卡

  • write()方法返回,上下文重新从内核态切换到用户态,即图示切换 4

2 零拷贝技术

基于零拷贝技术,可以减少 CPU 的拷贝次数和上下文切换次数,从而可以实现文件高效的读写操作。

2.1 mmap

将一个文件映射到进程的地址空间,省掉了数据在内核缓冲区和用户缓冲区之间的CPU拷贝环节

mmap(memory map)是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。

简单地说就是内核缓冲区和应用缓冲区共享,从而减少了从读缓冲区到用户缓冲区的一次 CPU 拷贝。

基于 mmap IO 读写其实就变成 mmap + write 的操作,也就是用 mmap 替代传统 IO 中的 read 操作。

当用户发起 mmap 调用的时候会发生上下文切换 1,进行内存映射,然后数据被拷贝到内核缓冲区,mmap 返回,发生上下文切换 2;随后用户调用 write,发生上下文切换 3,将内核缓冲区的数据拷贝到 Socket 缓冲区,write 返回,发生上下文切换 4。

2.2 sendfile

sendfile()跟 mmap()一样,也会减少一次 CPU 拷贝,但是它同时也会减少两次上下文切换。

用户在发起 sendfile()调用时会发生切换 1,之后数据通过 DMA 拷贝到内核缓冲区,之后再将内核缓冲区的数据 CPU 拷贝到 Socket 缓冲区,最后拷贝到网卡,sendfile()返回,发生切换 2。发生了 3 次拷贝和两次切换。

sendfile并没有文件的读写操作,而是直接将文件的数据传输到 target 目标缓冲区,也就是说,sendfile 是无法知道文件的具体的数据的;但是 mmap 不一样,他是可以修改内核缓冲区的数据的。假设如果需要对文件的内容进行修改之后再传输,只有 mmap 可以满足。

3 参考

一张图带你看懂 IO 零拷贝技术! (qq.com)

1 自定义排序

class Solution {
public String largestNumber(int[] nums) {
int n=nums.length;
Integer[] arr=new Integer[n];
for(int i=0;i<n;i++){
arr[i]=nums[i];
}
//从大到小排序
Arrays.sort(arr,(x,y)->{
long sx=10,sy=10;
while(sx<=x){
sx=sx*10;
}
while(sy<=y){sy=sy*10;}

return (int) (y*sx+x-(x*sy+y));
});

if(arr[0]==0){
return "0";
}
StringBuilder sb = new StringBuilder();
for(int num:arr){
sb.append(num);
}
return sb.toString();
}
}

2 数组

//二维数组
List<int[]>[] g=new ArrayList[n];
Arrays.setAll(g,i->new ArrayList<>());


//排序
int[][] points;
Arrays.sort(points,(a,b)->Integer.compare(a[0],b[0]));

int[][] grid;
int m=grid.length,n=grid[0].length;
int[] row=new int[m];
int[] col=new int[n];


int[] nums=new int[n];
// Arrays.stream(nums).sum();
int n=nums.length;
Arrays.fill(nums,1);


return new int[]{-1,-1};


//数组排序
int[] nums;
Arrays.sort(nums);
操作 说明
nums.set(index,last) 设置Index下标元素值
nums.remove(index)
nums.size()

3 List

//二维数组
List<List<Integer>> ans=new ArrayList<List<Integer>>();

4 String

String s;
//获取长度
s.length();

//某一下标字符
s.charAt(index);

//去掉最后一个字符
StringBuilder s=new StringBuilder();
s.deleteCharAt(s.length()-1);

5 Map

操作 说明
s.containsKey(val)
s.remove(key)
s.get(key)
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};

6 Set

操作 说明
Set<Character> s=new HashSet()
s.contains(ch)
s.add(ch) s中包含ch,返回false
s.remove(ch)

7 Stack

 Deque<Character> stack=new LinkedList<Character>();
 stack.isEmpty()
 stack.peek()
 stack.pop()
 stack.push(ch)