史上最全Java集合面试题

1、你所知道的集合类都有哪些?主要方法?
最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变
大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索
引访问元素的情形。
Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),
其中每个键映射到一个值。
ArrayList/Vector List
Collection
HashSet/TreeSet Set
HashTable
Treemap/HashMap

我记的不是方法名,而是思想,我知道它们都有增删改查的方法,但这些方法的具体名称,
我记得不是很清楚,对于 set,大概的方法是 add,remove, contains;对于 map,大概的方
法就是 put,remove,contains 等,因为,我只要在 eclispe 下按点操作符,很自然的这些方
法就出来了。我记住的一些思想就是 List 类会有 get(int index)这样的方法,因为它可以按
顺序取元素,而 set 类中没有 get(int index)这样的方法。List 和 set 都可以迭代出所有元素,
迭代时先要得到一个 iterator 对象,所以,set 和 list 类都有一个 iterator 方法,用于返回那
个 iterator 对象。map 可以返回三个集合,一个是返回所有的 key 的集合,另外一个返回的
是所有 value 的集合,再一个返回的 key 和 value 组合成的 EntrySet 对象的集合,map 也
有 get 方法,参数是 key,返回值是 key 对应的 value。
2、Collection 和 Collections 的 的区别。
Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List.
Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、
排序、线程安全化等操作。
3、Collection 框架中实现比较要实现什么接口
comparable/comparator
4、ArrayList 和Vector 的区别
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存
储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位
置索引号取出某个元素,,并且其中的数据是允许重复的,这是 HashSet 之类的集合的最大
不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素
(本来题目问的与 hashset 没有任何关系,但为了说清楚 ArrayList 与 Vector 的功能,我们使用对比方式,更有利于说明问题)。接着才说 ArrayList 与 Vector 的区别,
这主要包括两个方面:.
(1)同步性:
Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程
序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使
用 ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最
好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
备注:对于 Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住 Vector
与 Hashtable 是旧的,是 java 一诞生就提供了的,它们是线程安全的,ArrayList 与 HashMap
是 java2时才提供的,它们是线程不安全的。所以,我们讲课时先讲老的。
(2)数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过
了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认增长为原来两倍,而 ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList 与 Vector 都以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空间的方法。
总结:即 Vector 增长原来的一倍,ArrayList 增加原来的0.5倍。
5、HashMap 和 和 Hashtable 的区别
(条理上还需要整理,也是先说相同点,再说不同点)
HashMap 是 Hashtable 的轻量级实现(非线程安全的实现),他们都完成了 Map 接口,主
要区别在于 HashMap 允许空(null)键值(key),由于非线程安全,在只有一个线程访问
的情况下,效率要高于 Hashtable。
HashMap 允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsvalue 和 containsKey。因为contains 方法容易让人引起误解。Hashtable 继承自 Dictionary 类,而 HashMap 是 Java1.2引进的 Map interface 的一个实现。
最大的不同是,Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问
Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步。
Hashtable 和 HashMap 采用的 hash/rehash 算法都大概一样所以性能不会有很大的差异。
就 HashMap 与 HashTable 主要从三方面来说。
一.历史原因:Hashtable 是基于陈旧的 Dictionary 类的,HashMap 是 Java 1.2引进的 Map
接口的一个实现
二.同步性:Hashtable 是线程安全的,也就是说是同步的,而 HashMap 是线程序不安全的,
不是同步的
三.值:只有 HashMap 可以让你将空值作为一个表的条目的 key 或 value
1.HashMap 允许空键值对,HashTable 不允许
2.HashMap 不是线程安全的,HashTable 是
3.HashMap 直接实现 Map 接口,HashTable 继承 Dictionary 类
6 、List 和 Map 区别?
一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数
据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的
值是可以有重复的。
7 、List, Set, Map 是否继承自 Collection 接口?
List,Set 是,Map 不是
8、List 、Map 、Set 三个接口,存取元素时,各有什么特点?
这样的题属于随意发挥题:这样的题比较考水平,两个方面的水平:一是要真正明白这些内
容,二是要有较强的总结和表述能力。如果你明白,但表述不清楚,在别人那里则等同于不
明白。首先,List 与 Set 具有相似性,它们都是单列元素的集合,所以,它们有一个功共同的父接口,叫 Collection。Set 里面不允许有重复的元素,所谓重复,即不能有两个相等(注意,不是仅仅是相同)的对象,即假设 Set 集合中有了一个 A 对象,现在我要向 Set 集合再存入一个 B 对象,但 B 对象与 A 对象 equals 相等,则 B 对象存储不进去,所以,Set 集合的add 方法有一个 boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该元素时,则返回 true,当集合含有与某个元素 equals 相等的元素时,此时 add 方法无法加入该元素,返回结果为 false。Set 取元素时,没法说取第几个,只能以 Iterator 接口取得所有的元素,再逐一遍历各个元素。
List 表示有先后顺序的集合,注意,不是那种按年龄、按大小、按价格之类的排序。
当我们多次调用 add(Obj e)方法时,每次加入的对象就像火车站买票有排队顺序一样,按先
来后到的顺序排序。有时候,也可以插队,即调用 add(int index,Obj e)方法,就可以指定当
前对象在集合中的存放位置。一个对象可以被反复存储进 List 中,每调用一次 add 方法,
这个对象就被插入进集合中一次,其实,并不是把这个对象本身存储进了集合中,而是在集
合中用一个索引变量指向这个对象,当这个对象被 add 多次时,即相当于集合中有多个索
引指向了这个对象,如图 x 所示。List 除了可以以 Iterator 接口取得所有的元素,再逐一遍历各个元素之外,还可以调用 get(index i)来明确说明取第几个。
Map 与 List 和 Set 不同,它是双列的集合,其中有 put 方法,定义如下:put(obj
key,objvalue),每次存储时,要存储一对 key/value,不能存储重复的 key,这个重复的规
则也是按 equals 比较相等。取则可以根据 key 获得相应的 value,即 get(Object key)返回
值为 key 所对应的 value。另外,也可以获得所有的 key 的结合,还可以获得所有的 value
的结合,还可以获得 key 和 value 组合成的 Map.Entry 对象的集合。
List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存
key-value 值,value 可多值。
HashSet 按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode 值的大小进行存储。例如,“abc”—> 78,“def” —> 62,“xyz” —> 65在 hashSet 中的存储顺序不是62,65,78, LinkedHashSet 按插入的顺序存储,那被存储对象的 hashcode 方法还有什么作用呢?学员想想!hashset 集合比较两个对象是否相等,首先看hashcode 方法是否相等,然后看 equals 方法是否相等。new 两个 Student 插入到 HashSet中,看 HashSet 的 size,实现 hashcode 和 equals 方法后再看 size。同一个对象可以在 Vector 中加入多次。往集合里面加元素,相当于集合里用一根绳子连接到了目标对象。往 HashSet 中却加不了多次
List 以特定次序来持有元素,可有重复元素。
Set 无法拥有重复元素,内部排序。
Map 保存 key-value 值,value 可多值
9 、说出 ArrayList,Vector, LinkedList 的存储性能和特性
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增
加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存
操作,所以索引数据快而插入数据慢,Vector 由于使用了 synchronized 方法(线程安全),
通常性能上较 ArrayList 差,而 LinkedList 使用双向链表实现存储,按序号索引数据需要进
行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
LinkedList 也是线程不安全的,LinkedList 提供了一些方法,使得 LinkedList 可以被当作堆 可以被当作堆栈和队列来使用。
它们都实现 List 接口
ArrayList 和 Vector 都是基于数组实现的
LinkedList 基于双向循环链表(查找效率低,添加删除容易)
ArrayList 不是线程安全的而 Vector 是线程安全的,所以速度上 ArrayList 高于 Vector
10、去掉一个 Vector集合中重复的元素
Vector newVector = new Vector();
For (int i=0;i<vector.size();i++)
{
Object obj = vector.get(i);
if(!newVector.contains(obj);
newVector.add(obj);
}
还有一种简单的方式,HashSet set = new HashSet(vector);
11、Collection 和 Collections 的 的区别。
Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List.
Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、
排序、线程安全化等操作。
12、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还
是 equals()? 它们有何区别?

Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。
equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个
分离的对象的内容和类型相配的话,返回真值。

13、HashMap 与 TreeMap 的区别?
解答:HashMap 通过 hashcode 对其内容进行快速查找,而 TreeMap 中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用 TreeMap(HashMap 中元素的排列顺序是不固定的)。
14、ArrayList 和 Vector 的区别?
解答:同步性:Vector 是线程安全的,也就是说是同步的,而 ArrayList 是线程不安全的,不是同步的;
数据增长:当需要增长时,Vector 默认增长为原来一培,而 ArrayList 却是原来的一半。
15、详细阐述一下 Collection 接口所包含的内容 ?
答:List:有序允许重复存放
ArrayList----按顺序存放数据的数组
LinkList-----按顺序存放数据的链表
Vector-------线程安全的按顺序存放数据的数组
Set:无序不允许重复存放
HashSet—根据 HashCode()和 equals()方法来判断是否有重复
SortedSet:有序
TreeSet------通过实现 Comparable 接口和 Comparator 接口而具有排序功
能的集合
16、heap (堆)和 stack (栈)有什么区别?【基础】
答:栈是一种线形集合,其添加和删除元素的操作应在同一段完成,栈按照后进
先出的方式进行处理;堆是栈的一个组成元素。
17、介绍 JAVA 中的 Collection FrameWork(及如何写自己的数据结构)【基础】
答:Collection FrameWork 如下:
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即
Collection 的元素(Elements);Map 提供 key 到 value 的映射
18、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是 equals()? 它们有何区别? 【基础】
答:Set 里的元素是不能重复的,用 equals ()方法来区分重复与否。覆盖 equals()
方法用来判断对象的内容是否相同,而”==”判断地址是否相等,用来决定引用
值是否指向同一对象。
20、Java 集合类框架的基本接口有哪些?

Java 集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java 集合类里面最 基本的接口有:
Collection:代表一组对象,每一个对象都是它的子元素。 Set:不包含重复元素的 Collection。
List:有顺序的 collection,并且可以包含重复元素。 Map:可以把键(key)映射到值(value)的对象,键不能重复。

21、为什么集合类没有实现 Cloneable 和 Serializable 接口?
集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它 自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。

22.什么是迭代器(Iterator)?
Iterator 接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代 器实例的 迭代方法。迭代器可以在迭代的过程中删除底层集合的元素。
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由 集合类的具体实现来决定如何被克隆或者是序列化。
23.Iterator 和 ListIterator 的区别是什么?
Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List。 Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。 ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取前 一个和后一个元素的索引,等等。
24.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
Iterator 的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util 包下面的所有的集合类都是快速失败的,而 java.util.concurrent 包下面的所有的类都是安全 失败的。快速失败的迭代器会抛出 ConcurrentModificationException 异常,而安全失败的迭 代器永远不会抛出这样的异常。

25.Java 中的 HashMap 的工作原理是什么?

Java 中的 HashMap 是以键值对(key-value)的形式存储元素的。HashMap 需要一个 hash 函数, 它使用 hashCode()和 equals()方法来向集合/从集合添加和检索元素。当调用 put()方法的时 候,HashMap 会计算 key 的 hash 值,然后把键值对存储在集合中合适的索引上。如果 key 已经存在了,value 会被更新成新值。HashMap 的一些重要的特性是它的容量(capacity),负 载因子(load factor)和扩容极限(threshold resizing)。

26.hashCode()和 equals()方法的重要性体现在什么地方?
Java 中的 HashMap 使用 hashCode()和 equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的 hash 值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所 以这两个方法的实现对 HashMap 的精确性和正确性是至关重要的。
27、数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用 Array 而不是 ArrayList? 下面列出了 Array 和 ArrayList 的不同点:
Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。 Array 大小是固定的,ArrayList 的大小是动态变化的。 ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数 据类型的时候,这种方式相对比较慢。
27-1. ArrayList 和 LinkedList 有什么区别?
ArrayList 和 LinkedList 都实现了 List 接口,他们有以下的不同点:
ArrayList 是基于索引的数据接口,它的底层是数组。它可以以 O(1)时间复杂度对元素进行随 机访问。与此对应,LinkedList 是以元素列表的形式存储它的数据,每一个元素都和它的前 一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是 O(n)。 相对于 ArrayList,LinkedList 的插入,添加,删除操作速度更快,因为当元素被添加到集合任 意位置的时候,不需要像数组那样重新计算大小或者是更新索引。 LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用,一个指向前 一个元素,一个指向下一个元素。 也可以参考 ArrayList vs. LinkedList。
28.Comparable 和 Comparator 接口是干什么的?列出它们的区别
Java 提供了只包含一个 compareTo()方法的 Comparable 接口。这个方法可以个给两个对象排 序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。 Java 提供了包含 compare()和 equals()两个方法的 Comparator 接口。compare()方法用来给两 个输入参数排序,返回负数, 0,正数表明第一个参数是小于,等于,大于第二个参数。equals() 方法需要一个对象作为参数,它用来决定输入参数是否和 comparator 相等。只有当输入参 数也是一个 comparator 并且输入参数和当前 comparator 的排序结果是相同的时候,这个方 法才返回 true。
29.什么是 Java 优先级队列(Priority Queue)?
PriorityQueue 是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序 的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue 不允许 null 值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue 不是线程安全的,入队和出队的时间复杂度是 O(log(n))。
30.你了解大 O 符号(big-O notation)么?你能给出不同数据结构的例子么?
大 O 符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景 下有多么好。 大 O 符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我 们一般使用大 O 符号基于时间,内存和性能来选择最好的实现。大 O 符号可以对大量数据 的性能给出一个很好的说明。
31.如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是 O(log n),而无序数组是 O(n)。有序数组的缺 点是插入操作的时间复杂度是 O(n),因为值大的元素需要往后移动来给新元素腾位置。相反, 无序数组的插入时间复杂度是常量 O(1)。
32.Java 集合类框架的最佳实践有哪些?
根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固 定的,而且能事先知道,我们就应该用 Array 而不是 ArrayList。 有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置 初始容量来避免重新计算 hash 值或者是扩容。 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时 的 ClassCastException。 使用 JDK 提供的不变类(immutable class)作为 Map 的键可以避免为我们自己的类实现 hashCode()和 equals()方法。 编程的时候接口优于实现。 底层的集合实际上是空的情况下,返回长度是 0 的集合或者是数组,不要返回 null。

33.Enumeration 接口和 Iterator 接口的区别有哪些?
Enumeration 速度是 Iterator 的 2 倍,同时占用更少的内存。但是,Iterator 远远比 Enumeration 安全,因为其他线程不能够修改正在被 iterator 遍历的集合里面的对象。同时,Iterator 允许 调用者删除底层集合里面的元素,这对 Enumeration 来说是不可能的。

34.HashSet 和 TreeSet 有什么区别?
HashSet 是由一个 hash 表来实现的,因此,它的元素是无序的。add(),remove(),contains() 方法的时间复杂度是 O(1)。 另一方面,TreeSet 是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(), remove(),contains()方法的时间复杂度是 O(logn)。
35、List 和 Map 区别?
一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数 据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的 值是可以有重复的

36、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
对。 如果对象要保存在 HashSet 或 HashMap 中,它们的 equals 相等,那么,它们的 hashcode 值 就必须相等。 如果不是要保存在 HashSet 或 HashMap,则与 hashcode 没有什么关系了,这时候 hashcode 不等是可以的,例如 arrayList 存储的对象就不用实现 hashcode,当然,我们没有理由不实 现,通常都会去实现的。

37、hashCode()方法的作用?
hashCode()方法与 equals()方法相似,都是来自 java.lang.Object 类的方法,都允许用户 定义的子类重写这两个方法。 一般来说,equals()这个方法是给用户调用的,如果你想根据自己的业务规则来判断两 个对象是否相等,你可以重写 equals()方法。简单来讲,equals 方法主要是用来判断从表面 上看或者从内容上看,两个对象是不是相等。 而hashCode()方法通常是给其他类来调用的,比如当我们要把两个对象放入HashSet时, 由于 HashSet 要求两个对象不能相等,而 HashSet 判断两个对象是否相等的标准是通过 equals()比较返回 false、或两个对象的 hashCode()方法返回值不相等——只要满足任意一个 条件都可会认为两个对象不相等。 从这个角度来看,我们可以把 hashCode()方法的返回值当成这个对象的“标识符”,如 果两个对象的 hashCode()相等,即可认为这两个对象是相等的。因此当我们重写一个类的 equals()方法时,也应该重写它的 hashCode()方法,而且这两个方法判断两个对象相等的标 准也应该是一样的。

38、写 clone()方法时,通常都有一行代码,是什么?
clone()有默认行为:super.clone();,因为首先要把父类中的成员复制到位,然后才是复 制自己的成员。
39. 接口继承关系和实现思维导图
集合类存放于Java.util包中,主要有3种:set(集)、list(列表包含Queue)和map(映射)。

  1. Collection:Collection是集合List、Set、Queue的基本的接口。
  2. Iterator:迭代器,可以通过迭代器遍历集合中的数据
  3. Map:是映射表的基础接口
    3.2. List
    3.2.1 ArrayList(数组)
    ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数
    组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数
    组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进
    行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
    3.2.2. Vector(数组实现、线程同步)
    Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。
    3.2.3. LinkList(链表)
    LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
    3.3 set
    Set 注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。对象的相等性本质是对象 hashCode 值(java 是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的 hashCode 方法和 equals 方法。

3.3.1.1. HashSet(Hash 表)
哈希表边存放的是哈希值。HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果 equls 结果为 true ,HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。
哈希值相同 equals 为 false 的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。如图 1 表示 hashCode 值不相同的情况;图 2 表示 hashCode 值相同,但 equals 不相同的情况。

HashSet 通过 hashCode 值来确定元素在内存中的位置。一个 hashCode 位置上可以存放多个元素。
3.4 Map
3.4.1. HashMap(数组+链表+红黑树)
HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。我们用下面这张图来介绍HashMap 的结构。大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。1. capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
2. loadFactor:负载因子,默认为 0.75。
3. threshold:扩容的阈值,等于 capacity * loadFactor
3.4.1.2. JAVA8 实现
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
3.4.2. ConcurrentHashMap
3.4.2.1. Segment 段
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。注意,行文中,我很多地方用了“槽”来代表一个segment。
3.4.2.2. 线程安全(Segment 继承 ReentrantLock 加锁)
简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承
ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

3.4.2.3. 并行度(默认 16)
concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,
也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支
持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时
候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实
每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。
3.4.2.4. Java8 实现 (引入了红黑树)
Java8 对 ConcurrentHashMap 进行了比较大的改动,Java8 也引入了红黑树。
3.4.3. HashTable(线程安全)
Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,
并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,
因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全
的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。
3.4.4. TreeMap(可排序)
TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。
在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
参考:https://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html
3.4.5. LinkHashMap(记录插入顺序)
LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

发布了33 篇原创文章 · 获赞 50 · 访问量 2677

猜你喜欢

转载自blog.csdn.net/qq_43107323/article/details/103637960