Java集合类详解

目录

一、集合类的概念

二、集合类体系

三、集合框架

四、集合接口类

1.Collection接口

1.1 List接口

1.2 Set接口

1.3 Queue接口

2.Map接口

2.1 SortedMap接口

五、集合实现类(集合类)

1.List接口实现类

1.1 ArrayList

1.2 LinkedList

1.3 Vector

2.Set接口实现类

2.1 HashSet

2.2 TreeSet

3.Queue接口实现类

3.1 LinkedList

4.Map接口实现类

4.1 HashMap

4.2 TreeMap

4.3 WeakHashMap

4.4 HashTable

六、常用类

1.Iterator

2.Comparator

3.Collections

4.Arrays

七、补充说明

1.哈希表算法

2.红黑树算法

3.JDK1.8中HashMap的性能优化


一、集合类的概念

在Java当中,如果有一个类专门用来存放其它类的对象,这个类就叫做容器,或者就叫做集合,集合就是将若干性质相同或相近的类对象组合在一起而形成的一个整体。简而言之,集合类就是一个动态对象数组

二、集合类体系

从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射

Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。

除了集合,该框架也定义了几个 Map 接口和类。Map 里存储的是键/值对。尽管 Map 不是集合,但是它们完全整合在集合中。

三、集合框架

集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:

  • 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象

  • 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。

  • 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

四、集合接口类

1.Collection接口

Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和Set)。

Collection 接口存储一组不唯一,无序的对象。

查看源码,Collection接口继承于Iterable接口,Iterable接口位于java.lang包,是最基本的接口,但不是最基本的集合接口。集合接口貌似都位于java.util包。因为继承了该接口,Collection的实现类可以使用collection.iterator()实例化之后,next()迭代输出集合对象。在使用迭代输出的时候,不要使用集合类的remove方法,而是要用iterator的remove方法,尽量不要再输出时删除。

1.1 List接口

List接口继承Collection接口,是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。能够通过索引对指定位置进行增删查数据。

List 接口存储一组不唯一,有序(插入顺序)的对象。

1.2 Set接口

Set接口继承Collection接口,具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。

Set 接口存储一组唯一,无序的对象。

备注:SortedSet接口继承Set接口,有序。

1.3 Queue接口

Queue接口继承Collection接口,在集合的基础上添加了增删改查操作,并且队列默认使用FIFO(先进先出)规则

备注:Deque(双端队列)接口继承Queue接口,是一种线性集合,可以从两端操作的队列。

2.Map接口

Map 接口存储一组键值对象,提供key(键)到value(值)的映射。key不允许重复,value 可以。

备注:Map.Entry是Map的内部接口,指的是一个key->value的对象。实际上Map保存的是Map.Entry的对象,其实也相当于一组对象的集合。Map其实就是保存了两个对象之间的映射关系的一种集合

2.1 SortedMap接口

SortedMap接口,继承于 Map,使 Key保持在升序排列。

五、集合实现类(集合类)

接口CollectionMap
接口List (有序,可重复)Set (无序,不可重复)Map (无序,key不可重复)
实现类ArrayListLinkedListHashSetTreeSetHashMapTreeMap
内部存储结构数组,有序链表,有序哈希表,无序红黑树,默认升序哈希表,无序红黑树,默认升序
自定义类

元素比较时需要实现Comparable接口并重写compareTo()方法

hashcode()

equals()

(必须)实现Comparable接口并重写compareTo()方法

hashcode()

equals()

(必须)实现Comparable接口并重写compareTo()方法

1.List接口实现类

底层实现与扩容:https://blog.csdn.net/hqy1719239337/article/details/83041062

1.1 ArrayList

该类实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低

ArrayList的内部实现是基于基础的对象数组的,其内部是一个对象数组,可以通过二分查找等算法快速查找数据。

扩容时机:当数组的大小大于初始容量的时候(比如初始为10,当添加第11个元素的时候),就会进行扩容,新的容量为旧的容量的1.5倍。

扩容方式:扩容的时候,会以新的容量建一个原数组的拷贝,修改原数组,指向这个新数组,原数组被抛弃,会被GC回收。

1.2 LinkedList

该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:

List list=Collections.synchronizedList(new LinkedList(...));

与ArrayList相比,LinkedList的增删操作效率更高,而查改操作效率较低。

LinkedList 实现了List 接口,能对它进行列表操作。(linkedList.add("xxx");)

LinkedList 实现了Deque 接口,即能将LinkedList当作双端队列使用。(linkedList.addFirst("xxx");)

LinkedList 实现了Cloneable接口,能克隆。

LinkedList内部是基于双向链表实现的,内部有一个私有的内部类Node,查找数据时只能按照顺序从列表的一端开始检查,直到另外一端。

对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。

1.3 Vector

该类(向量类)和ArrayList非常相似,但是该类是同步的,可以用在多线程的情况,该类允许设置默认的增长长度,默认扩容方式为原来的2倍。效率低,几乎已经淘汰了这个集合。

1.3.1 Stack

Stack (栈)是Vector的一个子类,它实现了一个标准的后进先出的栈。

2.Set接口实现类

建议:HashSet和TreeSet其内部实现都是基于Map,建议先学习下文的Map部分。

2.1 HashSet

HashSet是Set集合的典型实现,HashSet按照Hash算法来存储集合中的元素,存在以下特点:

  • 不能保证元素的顺序,元素是无序的
  • HashSet是不同步的,需要外部保持线程之间的同步问题,Collections.synchronizedSet(new XXSet());
  • 集合元素值允许为null

HashSet底层是采用HashMap实现的。

PRESENT为HashSet类中定义的一个使用static final修饰的常量,其实无实际意义,用来填充Map中的value。

HashSet的add()方法调用HashMap的put()方法实现,如果添加成功map.put()方法返回的是null,HashSet.add()方法返回的true,则添加的元素可以作为map中的key;如果键已经存在,map.put()放回的是旧值,添加失败。(Set只需要key,失败就失败呗,反正这个key已经存在了。)

HashSet不存入重复元素的规则:使用hashcode和equals。 原理:HashSet会通过元素的hashcode()和equals()方法进行判断,当试图将元素加入到Set集合中,HashSet首先会使用对象的hashcode来判断对象加入的位置。同时也会与其他已经加入的对象的hashcode进行比较,如果没有相等的hashcode,HashSet就认为这个对象之前不存在,如果之前存在同样的hashcode值,就会进一步的比较equals()方法,如果equals()比较返回结果是true,那么认为该对象在集合中的对象是一模一样的,不会将其加入;如果比较返回的是false,那么HashSet认为新加入的对象没有重复,可以正确加入。

如图所示:当两个对象的hashcode不一样时,说明两个对象是一定不相等的,在存储时如左图所示,当两个对象的hashcode相等,但是equals()不相等,在实际中,会在同一个位置,用链式结构来保存多个对象,而HashSet访问集合元素时也是根据元素的hashCode值快速定位,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。

2.1.1 LinkedHashSet

只继承了HashSet,HashSet预留了使用LinkedHashMap存储的构造方法,其参数多一个dummy。

 

2.2 TreeSet

TreeSet 是一个有序的集合,它的作用是提供有序的Set集合

TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。

TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。

TreeSet 实现了Cloneable接口,意味着它能被克隆。

TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。

TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。

TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。(红黑树算法)

另外,TreeSet是非同步的,线程不安全。 虽然TreeSet不是线程安全的,但可以使用Collections.synchronizedSet()包装器在外部进行同步。

它的iterator 方法返回的迭代器是fail-fast的。

3.Queue接口实现类

参考:https://baijiahao.baidu.com/s?id=1637126358227832423&wfr=spider&for=pc

           https://baijiahao.baidu.com/s?id=1664270643322080281&wfr=spider&for=pc

           https://www.cnblogs.com/lemon-flm/p/7877898.html

           https://blog.csdn.net/21aspnet/article/details/89318049(只看最后,区别与使用)

需学习BlockingQueue

3.1 LinkedList

上文提到,LinkedList实现了Deque接口,而Deque接口继承于Queue接口。

4.Map接口实现类

HashMap和TreeMap对比:https://blog.csdn.net/weixin_40803329/article/details/89889071

4.1 HashMap

HashMap采用哈希表算法,它的主干是一个Node数组。Node是HashMap的基本组成单元。Node实现了Map.Entry接口,其成员变量除了key-value外,还有哈希值和next节点。

HashMap内部采用了链地址法,也就是数组+链表的方式。

备注:Set<Map.Entry<K,V>> entrySet并非HashMap的底层实现,而是用于HashMap.entrySet()时获取转化集合用的。

HashMap的总体结构如下:

个人理解1:为什么不用纯数组实现HashMap而是采用数据+链表

答:数组的顺序是插入顺序,与Key无关,随机查询时速度不如通过Hash计算快。(个人理解)

个人理解2:负载因子(扩容)默认0.75

答:因为Hash计算的结果不一定能完全覆盖数据,比如有一个位置没有覆盖到,如果设置了1,则必须把数组全部覆盖才行,这种情况下不会扩容,导致链表变长,影响性能。

补充扩容:https://blog.csdn.net/lkforce/article/details/89521318(创建新数组,rehash,链表倒序插入新数组链表)

4.1.1 LinkedHashMap

与HashMap对比:从“数组+链表”变为“双链表+单链表”。

4.1.2 ConcurrentHashMap

参考:https://www.cnblogs.com/shu-java-net/p/13503406.html(JDK1.7分段锁segment,JDK1.8细化分段到具体的entry,即数组中的节点,上锁仅在发生hash冲突时才上锁,且仅影响发生冲突的那一个链表的更新操作。)

4.2 TreeMap

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。

TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。

TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。

TreeMap 实现了Cloneable接口,意味着它能被克隆。

TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。

TreeMap基于红黑树(Red-Black tree)实现支持两种排序方式:自然排序和自定义排序。如果在调用TreeMap的构造函数时没有自定义比较器Comparator,则根据key执行自然排序。(如果添加到集合的元素类无法自然排序,而又没有实现Comparable接口并重写compareTo()方法,添加元素时可能因为元素比较时的类型抛出“类型转换异常”。)

TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。(红黑树算法)

另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

比较:HashMap可用作提供快速存储和检索操作的通用映射实现。然而,它不足是由于条目的混乱和不规则安排。这导致它在存在大量迭代的情况下表现不佳,因为底层数组的整个容量影响遍历而不仅仅是条目数。而TreeMap查找最坏时间复杂度为O(2lgN),也即整颗树刚好红黑相隔的时候。

TreeMap的遍历(获取Set后通过Iterator()迭代器遍历,或者for-each):

(1)遍历TreeMap的键值对:TreeMap.entrySet();

(2)遍历TreeMap的键:TreeMap.keySet();

(3)遍历TreeMap的值:TreeMap.values();

4.3 WeakHashMap

4.4 HashTable

六、常用类

集合框架定义了几种算法,可用于集合和映射。这些算法被定义为集合类的静态方法。

在尝试比较不兼容的类型时,一些方法能够抛出 ClassCastException异常。当试图修改一个不可修改的集合时,抛出UnsupportedOperationException异常。

集合定义三个静态的变量:EMPTY_SET,EMPTY_LIST,EMPTY_MAP的。这些变量都不可改变。

1.Iterator

2.Comparator

3.Collections

4.Arrays

七、补充说明

1.哈希表算法

哈希表也叫散列表。

我们知道,数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组

比如我们要新增或查找某个元素,我们通过把当前元素的关键字 通过某个函数映射到数组中的某个位置,通过数组下标一次定位就可完成操作。
  
这个函数可以简单描述为:存储位置 = f(关键字) ,这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。(散列函数在加密、校验等安全领域有广泛的应用)举个例子,比如我们要在哈希表中执行插入操作:

查找操作同理,先通过哈希函数计算出实际存储地址,然后从数组中对应地址取出即可。

哈希冲突

然而万事无完美,如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式

2.红黑树算法

红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:

  • 性质1:每个节点要么是黑色,要么是红色。
  • 性质2:根节点是黑色。
  • 性质3:每个叶子节点(NIL)是黑色。
  • 性质4:每个红色结点的两个子结点一定都是黑色。
  • 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

从性质5又可以推出:

  • 性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点

红黑树能自平衡,依靠三种操作:左旋、右旋和变色。

3.JDK1.8中HashMap的性能优化

假如一个数组槽位上链上数据过多(即拉链过长的情况)导致性能下降该怎么办?
JDK1.8在JDK1.7的基础上针对增加了红黑树来进行优化。即当链表超过8时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。
附:HashMap put方法逻辑图(JDK1.8)

 

queue也要学习一下。

 

参考资料:

https://www.runoob.com/java/java-collections.html

https://www.cnblogs.com/Coder-Pig/p/6513338.html

https://www.cnblogs.com/Jacck/p/8034900.html

https://blog.csdn.net/woshimaxiao1/article/details/83661464

 

https://blog.csdn.net/a2011480169/article/details/52047600

https://www.cnblogs.com/ysocean/p/6555373.html

https://www.cnblogs.com/wmyskxz/p/9381848.html

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值