集合容器知识点总结

集合概述

在这里插入图片描述

集合和数组的区别:

1.数组的效率高于集合类.

2.数组能存放基本数据类型和对象(但是类型必须一致),而集合类中只能放对象(但类型可以不一致)。

3.数组容量固定且无法动态改变,集合类容量动态改变。

4.数组无法判断其中实际存有多少元素,length只告诉了array的容量。

5.集合有多种实现方式和不同的适用场合,而不像数组仅采用顺序表方式。

6.集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性调用即可实现各种复杂操作,大大提高软件的开发效率。(体现在成员方法)



List接口

List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。List集合默认按照元素的添加顺序设置元素的索引,可以通过索引(类似数组的下标)来访问指定位置的集合元素。

List接口的实现类主要有:ArrayListLinkedListVector

ArrayList

  • 内部数据存储的结构是数组,查询速度快、增删元素慢

ArrayList是一个动态数组,允许任何符合规则的元素插入包括null。

ArrayList的JDK1.8之前与之后的实现区别:

JDK1.7:直接创建一个初始容量为10的数组

JDK1.8:一开始先创建一个长度为0的数组,当添加第一个元素时再创建一个初始容量为10的数组


LinkedList

  • 内部数据存储的结构是双向链表,查询速度慢、增删元素快。

LinkedList除了可以根据索引访问集合元素外,还实现了Deque接口。


Vector

Vector大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。



Set接口

Set集合不可重复无序

Set集合判断两个对象是否相同是根据 equals() 方法。

Set接口的实现类主要有:HashSetLinkedHashSetTreeSet

HashSet

  • 底层的实现是HashMap,所以内部数据存储的结构是哈希表、非线程安全、集合元素可以是null

HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。保证元素唯一性的方式依赖于:hashCodeequals方法。

HashMap的存储结构

JDK8之前版本:

JDK8之前HashMap的存储结构是数组+链表结构(即为链地址法) ,当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之“桶”(bucket),每个桶都有自己的索引,系统可以根据索引快速的查找桶中的元素。

每个桶中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 而且新添加的元素作为链表的head。

JDK8之后版本:

JDK8之后HashMap的存储结构是数组+链表+红黑树实现。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个桶都有自己的索引,系统可以根据索引快速的查找桶中的元素。

每个桶中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

在这里插入图片描述

HashSet存储自定义类型

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

为什么要重写hashCode和equals方法?

建立自己的比较方式,保证集合中对象的唯一性。Object类定义的equals方法比较的是对象的地址,hashCode是根据这个地址算出来的一个整数。所以把自己定义的对象存入集合中,判断它是否重复就不能直接用继承过来的equals了,就需要自己重写了。

重写hashCode() 方法的基本原则

在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值

当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法的返回值应该相等。

对象中用作equals()方法比较的实例变量,都应该用来计算hashCode值。

HashSet添加元素的原理如下:

先调hashCode()方法去找,没找到就存在该位置,找到有该hashCode再调equlas方法,equlas结果为true则添加失败,equlas结果为false则通过链表的方式继续链接存储。

(注意:如果两个元素equals() 后是true,但它们的 hashCode() 返回值不相等,hashSet将会把它们存储在不同的位置,但依然可以添加成功。因为是先调的hashCode()方法,这个时候的equlas()能返回true是因为重写了equlas)


LinkedHashSet

  • HashSet的子类,所以内部数据存储的结构是哈希表+链表、非线程安全、有序,所以性能低于HashSet

LinkedHashSet也是根据元素的hashCode值来决定元素的存储位置。但它同时使用双向链表维护元素的次序,元素的顺序与添加顺序一致。

由于LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。


TreeSet

  • 底层实现是TreeMap、数据存储的结构是红黑树、非线程安全、有序、两种排序方法:自然排序和定制排序。默认情况下采用自然排序

自然排序:

TreeSet会调用集合元素的compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。

如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口。

实现Comparable的类必须实现compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比 较大小。

Comparable的一些典型实现类:

  • BigDecimal、BigInteger 以及所有的数值型对应的包装类:按它们对应的数值大小进行比较
  • Character:按字符的 unicode值来进行比较
  • Boolean:true 对应的包装类实例大于 false 对应的包装类实例
  • String:按字符串中字符的 unicode 值进行比较
  • Date、Time:后边的时间、日期比前面的时间、日期大

因为只有相同类的两个实例才会比较大小,所以向TreeSet中添加的应该是同一个类的对象。

对于TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj) 方法比较返回值。

当需要把一个对象放入TreeSet 中,重写该对象对应的equals() 方法时,应保证该方法与compareTo(Object obj) 方法有一致的结果。

对于TreeSet集合而言,它判断两个对象是否相等的标准是:两个对象通过compareTo(Object obj)方法比较是否返回0,如果返回0则相等。

定制排序

TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素,希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。

利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2。

要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。

使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。



Map接口

Map与Collection并列存在。用于保存具有映射关系的数据:key-value,Map中的key用Set来存放,不允许重复,即同一个Map对象所对应的类,须重写hashCode()和equals()方法。

Map接口的实现类主要有:HashMapLinkedHashMapTreeMapHashTable


HashMap

  • 数据存储的结构是哈希表、非线程安全、无序、键唯一、值可重复、键和值可为null

所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()

所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()

HashMap判断两个key相等的标准是:两个key通过equals() 方法返回true, hashCode值也相等。

HashMap源码中的重要常量

DEFAULT_INITIAL_CAPACITY: HashMap的默认容量,16

MAXIMUM_CAPACITY: HashMap的最大支持容量,2^30

TREEIFY_THRESHOLD:桶(Bucket)中链表长度大于该默认值,转化为红黑树。

UNTREEIFY_THRESHOLD:桶中红黑树存储的Node小于该默认值,转化为链表

table:存储元素的数组,总是2的n次幂

entrySet:存储具体元素的集

size:HashMap中存储的键值对的数量

modCount:HashMap扩容和结构改变的次数。

HashMap的存储结构

JDK8之前版本:

JDK8之前HashMap的存储结构是数组+链表结构(即为链地址法) ,当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之“桶”(bucket),每个桶都有自己的索引,系统可以根据索引快速的查找桶中的元素。

每个桶中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 而且新添加的元素作为链表的head。

JDK8之后版本:

JDK8之后HashMap的存储结构是数组+链表+红黑树实现。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个桶都有自己的索引,系统可以根据索引快速的查找桶中的元素。

每个桶中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

在这里插入图片描述

HashMap存储自定义键值

给HashMap中存放自定义键值时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

重写hashcode方法和equals方法确保key唯一,不重复

为什么要重写hashCode和equals方法?

建立自己的比较方式,保证集合中对象的唯一性。Object类定义的equals方法比较的是对象的地址,hashCode是根据这个地址算出来的一个整数。所以把自己定义的对象存入集合中,判断它是否重复就不能直接用继承过来的equals了,就需要自己重写了。


LinkedHashMap

  • HashMap的子类,所以内部数据存储的结构是哈希表+链表、非线程安全、有序,所以性能低于HashMap

在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序。 该链表负责维护Map的迭代顺序,与插入顺序一致,因此性能比HashMap低,但在迭代访问Map里的全部元素时有较好的性能。


TreeMap

  • 数据存储的结构是红黑树、非线程安全、有序

TreeMap存储Key-Value对时,需要根据key-value对进行排序。TreeMap可以保证所有的Key-Value对处于有序状态。

TreeMap也有两种排序方式,自然排序和定制排序。

treeMap和HashMap的区别:

1、HashMap:基于hash表实现的;为了优化HashMap的空间使用,可以调优初始容量和负载因子;

​ TreeMap:基于红黑树实现的;TreeMap就没有调优选项,因为红黑树总是处于平衡的状态;

2、HashMap:适用于Map插入,删除,定位元素;

​ TreeMap:适用于按自然顺序或自定义顺序遍历键;

HashTable

  • 数据存储的结构是哈希表、线程安全、无序、不允许使用null作为key和value

跟HashMap差不多,区别就是线程安全问题和键值是否可为null。


其他

快速失败和安全失败

一、快速失败(fail—fast)

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出 Concurrent Modification Exception。

原理: 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用 hashNext()/next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。

注意: 这里异常的抛出条件是检测到 modCount != expectedmodCount 这个条件。如果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的 bug。

场景: java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

二、安全失败(fail—safe)

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

原理: 由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 Concurrent Modification Exception。

缺点: 基于拷贝内容的优点是避免了 Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

场景: java.util.concurrent 包下的容器都是安全失败,可以在多线程下并发使用,并发修改。



参考链接

https://www.cnblogs.com/hgao/p/13389322.html

https://www.cnblogs.com/bubbleboom/p/12694013.html


后续会通过学习继续补充。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值