03-Java核心类库_集合

270 阅读30分钟

二,集合

1,类集概述

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。

但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。

在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完整的提出类集的完整概念。

  • 类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。
  • 所有的类集操作的接口或类都在 java.util 包中。

2,链表与二叉树思路

2.1 数组存在的缺点

1,动态扩容

步骤:声明更大的数组-》将数据拷贝进去-》引用类型的变量指向新的地址;

缺点:浪费空间且耗时;

2,删除数据

删除一个数据后,需要将其后的数据全部前移一位;

2.2 链表

1,什么是链表

链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续)的内存结构(节点),按特定的顺序链接在一起的抽象数据类型。

补充:

  • 抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。
  • 内存结构:内存中的结构,如:struct、特殊内存块...等等之类;

2,数组和链表的区别和优缺点:

数组是一种连续存储线性结构,元素类型相同,大小相等

数组的优点:

  • 存取速度快

数组的缺点:

  • 事先必须知道数组的长度
  • 插入删除元素很慢
  • 空间通常是有限制的
  • 需要大块连续的内存块
  • 插入删除元素的效率很低

链表是离散存储线性结构。n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。

链表优点:

  • 空间没有限制
  • 插入删除元素很快

链表缺点:

  • 存取速度很慢

3,链表分类

链表常用的有 3 类: 单链表、双向链表、循环链表。

链表的核心操作集有 3 种:插入、删除、查找(遍历)

4,单链表实现

单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域和 Next 指针域组成。

单链表实现图示:

解析:

  • Data 数据 + Next 指针,组成一个单链表的内存结构 ;
  • 第一个内存结构称为 链头,最后一个内存结构称为 链尾;
  • 链尾的 Next 指针设置为 NULL [指向空];

单链表的遍历方向单一(只能从链头一直遍历到链尾)

单链表操作集:

5,双向链表

双向链表 [Double Linked List]:由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后继),内存结构由数据域、Prev 指针域和 Next 指针域组成。

双向链表实现图示:

解析:

  • Data 数据 + Next 指针 + Prev 指针,组成一个双向链表的内存结构;
  • 第一个内存结构称为 链头,最后一个内存结构称为 链尾;
  • 链头的 Prev 指针设置为 NULL, 链尾的 Next 指针设置为 NULL;
  • Prev 指向的内存结构称为 前驱, Next 指向的内存结构称为 后继;
  • 双向链表的遍历是双向的,即如果把从链头的 Next 一直到链尾的[NULL] 遍历方向定义为正向,那么从链尾的 Prev 一直到链头 [NULL ]遍历方向就是反向;

6,循环链表

单向循环链表 [Circular Linked List] : 由各个内存结构通过一个指针 Next 链接在一起组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成。

双向循环链表 [Double Circular Linked List] : 由各个内存结构通过指针 Next 和指针Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由数据域、Prev 指针域和 Next 指针域组成。

循环链表的单向与双向实现图示:

  • 循环链表分为单向、双向两种;
  • 单向的实现就是在单链表的基础上,把链尾的 Next 指针直接指向链头,形成一个闭环;
  • 双向的实现就是在双向链表的基础上,把链尾的 Next 指针指向链头,再把链头的 Prev 指针指向链尾,形成一个闭环;
  • 循环链表没有链头和链尾的说法,因为是闭环的,所以每一个内存结构都可以充当链头和链尾;

循环链表操作集:

2.3 二叉树

1,什么是二叉树

二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥有的子树数)。

二叉树就是每个节点不能多于有两个儿子,上面的图就是一颗二叉树,而且还是一种特殊的二叉树:二叉查找树(binary search tree)。当前根节点的左边全部比根节点小,当前根节点的右边全部比根节点大。可以看出,这对我们来找一个数是非常方便快捷的

树由节点组成,每个节点的数据结构是这样的:

因此,我们定义树的时候往往是**->定义节点->节点连接起来就成了树**,而节点的定义就是:一个数据、两个指针(如果有节点就指向节点、没有节点就指向 null)

2,二叉树的种类

斜树:所有结点都只有左子树,或者右子树。

满二叉树:所有的分支节点都具有左右节点。

完全二叉树:若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h
层所有的结点都连续集中在最左边,这就是完全二叉树。

3,二叉树的性质

  1. 二叉树第 i 层上的结点数目最多为 2^(i-1) (i≥1)
  2. 深度为 h 的二叉树至多有 2^h-1 个结点(h≥1)
  3. 包含 n 个结点的二叉树的高度至少为 log2 (n+1)
  4. 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1

4,二叉树的遍历方式

二叉树的遍历方式,一般分为先序遍历,中序遍历,后序遍历。

  • 先序遍历:先访问根节点,然后访问左节点,最后访问右节点(根->左->右)
  • 中序遍历:先访问左节点,然后访问根节点,最后访问右节点(左->根->右)
  • 后序遍历:先访问左节点,然后访问右节点,最后访问根节点(左->右->根)

先序遍历(根-左-右):1-2-4-8-9-5-10-3-6-7

中序遍历:(左-根-右):8-4-9-2-10-5-1-6-3-7

后序遍历(左-右-根):8-9-4-10-5-2-6-7-3-1

3,常见数据结构

3.1 栈

定义与特点:

  • 栈:stack,又称堆栈, 栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
  • 我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。
  • 栈又称为先进后出的线性表 。

特点:

  • 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
  • 栈的入口、出口的都是栈的顶端位置。

注意:

  • 压栈:存元素。
  • 弹栈:取元素。

3.2 队列

1,定义

  • queue,简称队, 队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的一端进行插入,而在另一端进行删除元素的线性表。
  • 队尾(rear)是允许插入的一端。队头(front)是允许删除的一端。
  • 空队列是不含元素的空表。

2,特点

  • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
  • 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。

3.3 数组

1,定义

  • Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。
  • 就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

2,特点

查找元素快:通过索引,可以快速访问指定位置的元素

增删元素慢

  • 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
  • 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。

3.4 链表

1,特点:

  • linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
  • 每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
  • 我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。

2,特点

  • 多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。

  • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
  • 增删元素快:

增加元素:只需要修改连接下个元素的地址即可。

  • 删除元素:只需要修改连接下个元素的地址即可。

3.5 红黑树

1,相关概念

  • 二叉树:binary tree ,是每个结点不超过2的有序树(tree)
  • 简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。
  • 二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。如图:

我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。

2,红黑树的约束:

  • 1. 节点可以是红色的或者黑色的
  • 2. 根节点是黑色的
  • 3. 叶子节点(特指空节点)是黑色的
  • 4. 每个红色节点的子节点都是黑色的
  • 5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同

3,红黑树的特点:

  • 速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍

4,Collection

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。

此接口定义在 java.util 包中。 此接口定义如下:

public interface Collection<E> extends Iterable<E>

此接口使用了泛型技术,在 JDK 1.5 之后为了使类集操作的更加安全,所以引入了泛型。

此接口的常用方法如下所示。

本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。

但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。 之所以有这样的明文规定,也是在 JDK 1.2 之后才有的。

一开始在 EJB 中的最早模型中全部都是使用 Collection 操作 的,所以很早之前开发代码都是以Collection 为准,但是后来为了更加清楚的区分,集合中是否允许有重复元素所以 SUN 在其开源项目 —— PetShop(宠物商店)中就开始推广 List 和 Set 的使用。

5,List接口

在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。

List 子接口的定义:

public interface List<E> extends Collection<E>

在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。

所以证明,List 接口拥有比 Collection 接口更多的操作方法。

了解了 List 接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个: · ArrayList(95%)、Vector(4%)、LinkedList(1%)

6,ArrayList

ArrayList 是 List 接口的子类,此类的定义如下:

public class ArrayList<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable

此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。

6.1 构造方法

1,API文档

如果需要存储大量数据,建议使用一参构造方法,避免重复扩容;

2,源码解析(以add函数为例)

1,无参构造函数创建ArrayList集合

2,进入ArrayList无参构造方法

其中elementData为:

DEFAULTCAPACITY_EMPTY_ELEMENTDATA为:

可以看出,通过无参构造方法创建的是一个长度为0 的数组,并不是长度为10的空列表;(并不是API错误)

3,添加元素时:

size指有效数据长度;

4,再次进入add函数

5,进入grow扩容函数

6,再次进入重写的扩容函数grow

7,进入newCapacity函数

其中MAX_ARRAY_SIZE为:

6.2 范例

1,增加及取得元素

以上的操作向集合中增加了三个元素,其中在指定位置增加的操作是 List 接口单独定义的。随后进行输出的时候, 实际上调用的是 toString()方法完成输出的。

可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行

2,进一步操作

使用 remove()方法删除若干个元素,并且使用循环的方式输出。

根据指定位置取的内容的方法,只有 List 接口才有定义,其他的任何接口都没有任何的定义。

7,Vector

与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:

public class Vector<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable

此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。

从Java 2平台v1.2开始,该类被改进以实现List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector是同步的。 如果不需要线程安全实现,建议使用ArrayList代替Vector 。

7.1 构造方法

​当增量为0(增量:每次扩容,增加的容量)时,扩容是每次翻一番;

7.2 范例

​ 以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。

但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此 类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。

但是为了照顾很多已经习惯于使用 Vector 的用户,所以在 JDK 1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。

7.3 Vector类与ArrayList类的区别

8,LinkedList

8.1 说明

使用双向链表实现

此类的使用几率是非常低的,但是此类的定义如下:

public class LinkedList<E> extends AbstractSequentialList<E> 
implements List<E>, Deque<E>, Cloneable, Serializable

此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:

8.2 范例

8.3 常用方法

9,Iterator与ListIterator

9.1 问题引入

对于ArrayList来说,普通的遍历方法(如下图)效率差别不大,但对于LinkedList来说,并不是最优的。

因此可以使用迭代器实现

9.2 概述

1,简述

  • 在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator 。
  • Iterator 接口也是Java集合中的一员,但它与 Collection 、 Map 接口有所不同, Collection 接口与 Map 接口主要用于存储元素,而 Iterator 主要用于迭代访问(即遍历)Collection 中的元素,因此 Iterator 对象也被称为迭代器 。
  • Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。
  • 此接口定义如下:public interface Iterator
  • 要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。 此接口规定了以下的三个方法:

通过 Collection 接口为其进行实例化之后,一定要记住,Iterator 中的操作指针是在第一条元素之上,当调用 next()方 法的时候,获取当前指针指向的值并向下移动,使用 hasNext()可以检查序列中是否还有元素。

2,Iterator与ListIterator

Iterator:迭代Collection下所有集合,包括List和Set;

ListIterator:只用于遍历List

9.3 基本使用方法

9.4 常用方法详述

1,hasNext

2,next

3,remove

使用 Iterator 输出的时候有一点必须注意,在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator 接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。

例1:(直接删除)

声明迭代器后直接删除元素

原因:迭代器相当于一个指针,声明完毕后还没指向任何地方,必须通过next指向下一个元素,才能正常使用

例2:(使用集合中的 remove()方法)

使用Iterator中的remove方法

9.5 ListIterator

1,概述

Iterator 接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须 使用其子接口 —— ListIterator。

此接口定义如下:public interface ListIterator extends Iterator

2,方法摘要

3,previous

初始时指向其他地方,必须先用next指向下一个元素,才能使用previous指向上一个

4,综合应用

1,插入和修改元素,并重新遍历

2,发现新插入的元素未展示出来,原因是:迭代器指针刚声明完毕,并不是指向第一个元素,所以此时插入元素,是在第一个元素之前插入

9.6 废弃的接口:Enumeration(了解)

1,概述

  • Enumeration 是一个非常古老的输出接口,其也是一个元老级的输出接口,最早的动态数组使用 Vector 完成,那么只 要是使用了 Vector 则就必须使用 Enumeration 进行输出。
  • 接口定义如下:public interface Enumeration
  • 在 JDK 1.5 之后,此接口实际上也已经加入了泛型操作。此接口常用方法如下:
  • 但是,与 Iterator 不同的是,如果要想使用 Enumeration 输出的话,则还必须使用 Vector 类完成,在类中定义了如下 方法:public Enumeration elements()
  • 需要注意的是,在大部分的情况下,此接口都不再使用了,但是对于一些古老的类库,本身依然要使用此接口进行 操作,所以此接口一定要掌握。所有的代码最好可以用记事本独立写出。

10,forEach

10.1 概述

增强for循环,最早出现在C#中;

用于迭代数组 或 集合(Collection下的);

10.2 实际应用

在使用 foreach 输出的时候一定要注意的是,里面的操作泛型要指定具体的类型,这样在输出的时候才会更加有针对 性;

11,Set

11.1 概述

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。

Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法通过下标获取数据进行输出。

在此接口中有两个常用的子类:HashSet、TreeSet

12,HashSet

12.1 概述

java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。

java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持。

HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与 equals 方法。

有插入和删除的方法,但是没有get方法,所以为了获取元素需要通过迭代器 或 转换成数组 来进行;

TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的;

12.2 注意

1, HashSet 底层的实现其实是一个HashMap 支持

2,插入操作有返回值

12.3 存储数据结构(哈希表)

在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示

JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

13,TreeSet与Comparable

13.1 概述

与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:

public class TreeSet<E> extends AbstractSet<E> 
implements NavigableSet<E>, Cloneable, Serializable

此类的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

补充:

快速失败:遍历的是集合本身,如果遍历过程中,通过其他操作使得集合本身发生改变,就会抛出异常;

安全失败:失败不会出错,在遍历的时候先将集合复制一份,即使原集合发生改变,也不会抛出异常;

13.2 举例

1,使用特定的数据类型作为填充元素

虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。

2,使用自定义对象作为填充元素

也就是说,如果你的类想要被识别,并用来判断大小,就要实现Comparable接口中的方法。因此可以对Person对象进行修改:

由于集合中不能存储相同的元素,所以按上述代码,当存在两个对象年龄相同时,后面的 对象不会被插入到data中

14,Map

14.1 概述

Map与Collection同一级别,而不是与List和Set同一级别;

Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了;

取数据操作不同:ArrayList可以通过下标,Set可以通过迭代器,Map需要先取出键形成Set集合,再根据具体的键取出值

Map中的键(key)不可重复

HashSet使用的HashMap,TreeSet使用的TreeMap,LinkedHashSet使用的LinkedHashMap。即Set集合使用Map的键来存储数据

14.2 相关API

15,哈希表概述

Object中的hashCode可以提高哈希表的性能;

在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

哈希桶(数组table中每个方格)中的数据量大于8时,从链表转换为红黑二叉树.当哈希桶中的数据量减少到6时,从红黑二叉树转换为链表;

初始桶的数量16,散列因子0.75。当使用桶的数量所占比例超过0.75后,需要扩容为原先的两倍;

16,HashMap源码分析

16.1 概述

1,构造方法

作为一般规则,默认加载因子(.75)在时间和空间成本之间提供了良好的折衷。 较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put )。 在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以便最小化重新散列操作的数量。 如果初始容量大于最大条目数除以加载因子,则不会发生重新加载操作。

2,进入源代码

3,装载因子

4,初始容量

16.2 put方法

1,进入put函数

2,进入putVal方法

1)找到存储位置并判断是否满足存储条件

判断表是否为空以及长度是否为0,若是则进行扩容算法

2)根据hash值,计算求得位置对应下标(n-1 & hash 等价于 hash % n,但前一种方法更快),判断并进行插入操作

3)table对应位置中已有元素,需判断覆盖原值还是插入新值,插入时也要区分链表插入还是红黑树插入

17,Map集合使用案例

17.1 HashMap

线程不安全(效率高)

17.2 HashTable

线程安全(效率低)

17.3 CurrentHashMap

采用分段机制,线程安全,效率又比较高

17.4 TreeMap

TreeSet借助TreeMap实现。TreeMap保证存储顺序

20,存储自定义对象

21,JDK9集合新特性

List,Set,Map三个接口存在of方法,可以存入任意数量的元素,但大小不能改变。