diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" index 7af02610d0f4341aa3b51a2fb6a5f58c899ee5e7..0a85293458feebe33cc527b545c70ddbb909d9d6 100644 --- "a/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" +++ "b/docs/java/collection/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" @@ -1,47 +1,81 @@ 点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。 - -- [剖析面试最常见问题之Java集合框架](#剖析面试最常见问题之java集合框架) - - [说说List,Set,Map三者的区别?](#说说listsetmap三者的区别) - - [Arraylist 与 LinkedList 区别?](#arraylist-与-linkedlist-区别) - - [补充内容:RandomAccess接口](#补充内容randomaccess接口) - - [补充内容:双向链表和双向循环链表](#补充内容双向链表和双向循环链表) - - [ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢?](#arraylist-与-vector-区别呢为什么要用arraylist取代vector呢) - - [说一说 ArrayList 的扩容机制吧](#说一说-arraylist-的扩容机制吧) - - [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别) - - [HashMap 和 HashSet区别](#hashmap-和-hashset区别) - - [HashSet如何检查重复](#hashset如何检查重复) - - [HashMap的底层实现](#hashmap的底层实现) - - [JDK1.8之前](#jdk18之前) - - [JDK1.8之后](#jdk18之后) - - [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方) - - [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题) - - [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别) - - [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现) - - [JDK1.7(上面有示意图)](#jdk17上面有示意图) - - [JDK1.8 (上面有示意图)](#jdk18-上面有示意图) - - [comparable 和 Comparator的区别](#comparable-和-comparator的区别) - - [Comparator定制排序](#comparator定制排序) - - [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序) - - [集合框架底层数据结构总结](#集合框架底层数据结构总结) - - [Collection](#collection) - - [1. List](#1-list) - - [2. Set](#2-set) - - [Map](#map) - - [如何选用集合?](#如何选用集合) +* [1.1 集合概述](#) + * [1.1.1 说说List,Set,Map三者的区别?](#ListSetMap) + * [1.1.2 集合框架底层数据结构总结](#-1) + * [Collection](#Collection) + * [Map](#Map) + * [1.1.3 如何选用集合?](#-1) +* [1.2 Iterator迭代器接口](#Iterator) +* [1.3 Collection子接口之List](#CollectionList) + * [ 1.3.1 Arraylist 与 LinkedList 区别?](#ArraylistLinkedList) + * [**补充内容:RandomAccess接口**](#:RandomAccess) + * [补充内容:双向链表和双向循环链表](#:) + * [1.3.2 ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢?](#ArrayListVectorArraylistVector) + * [1.3.3 说一说 ArrayList 的扩容机制吧](#ArrayList) +* [1.4 Collection子接口之Set](#CollectionSet) + * [1.4.1 comparable 和 Comparator的区别](#comparableComparator) + * [Comparator定制排序](#Comparator) + * [重写compareTo方法实现按年龄来排序](#compareTo) +* [1.5 Map接口](#Map-1) + * [1.5.1 HashMap 和 Hashtable 的区别](#HashMapHashtable) + * [1.5.2 HashMap 和 HashSet区别](#HashMapHashSet) + * [1.5.3 HashSet如何检查重复](#HashSet) + * [1.5.4 HashMap的底层实现](#HashMap) + * [JDK1.8之前](#JDK1.8) + * [JDK1.8之后](#JDK1.8-1) + * [1.5.5 HashMap 的长度为什么是2的幂次方](#HashMap2) + * [ 1.5.6 HashMap 多线程操作导致死循环问题](#HashMap-1) + * [1.5.7 ConcurrentHashMap 和 Hashtable 的区别](#ConcurrentHashMapHashtable) + * [1.5.8 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#ConcurrentHashMap) + * [JDK1.7(上面有示意图)](#JDK1.7) + * [JDK1.8 (上面有示意图)](#JDK1.8-1) +* [1.6 Collections工具类](#Collections) +* [公众号](#-1) # 剖析面试最常见问题之Java集合框架 -## 说说List,Set,Map三者的区别? +## 1.1 集合概述 +### 1.1.1 说说List,Set,Map三者的区别? - **List(对付顺序的好帮手):** List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象 - **Set(注重独一无二的性质):** 不允许重复的集合。不会有多个元素引用相同的对象。 - **Map(用Key来搜索的专家):** 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。 -## Arraylist 与 LinkedList 区别? +### 1.1.2 集合框架底层数据结构总结 + +#### Collection + +##### List + +- **Arraylist:** Object数组 +- **Vector:** Object数组 +- **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) + +##### Set + +- **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素 +- **LinkedHashSet:** LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的 +- **TreeSet(有序,唯一):** 红黑树(自平衡的排序二叉树) + +#### Map + +- **HashMap:** JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间 +- **LinkedHashMap:** LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931) +- **Hashtable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 +- **TreeMap:** 红黑树(自平衡的排序二叉树) + +### 1.1.3如何选用集合? + +主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。 + +## 1.2 Iterator迭代器接口 + +## 1.3 Collection子接口之List +### 1.3.1 Arraylist 与 LinkedList 区别? - **1. 是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全; @@ -53,7 +87,7 @@ - **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 -### **补充内容:RandomAccess接口** +#### **补充内容:RandomAccess接口** ```java public interface RandomAccess { @@ -81,7 +115,7 @@ public interface RandomAccess { - 实现了 `RandomAccess` 接口的list,优先选择普通 for 循环 ,其次 foreach, - 未实现 `RandomAccess`接口的list,优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环 -### 补充内容:双向链表和双向循环链表 +#### 补充内容:双向链表和双向循环链表 **双向链表:** 包含两个指针,一个prev指向前一个节点,一个next指向后一个节点。 @@ -93,17 +127,150 @@ public interface RandomAccess { ![双向循环链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/双向循环链表.png) -## ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢? +### 1.3.2 ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢? `Vector`类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。 `Arraylist`不是同步的,所以在不需要保证线程安全时建议使用Arraylist。 -## 说一说 ArrayList 的扩容机制吧 +### 1.3.3 说一说 ArrayList 的扩容机制吧 详见笔主的这篇文章:[通过源码一步一步分析ArrayList 扩容机制](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/ArrayList-Grow.md) -## HashMap 和 Hashtable 的区别 +## 1.4 Collection子接口之Set +### 1.4.1 comparable 和 Comparator的区别 + +- comparable接口实际上是出自java.lang包 它有一个 `compareTo(Object obj)`方法用来排序 +- comparator接口实际上是出自 java.util 包它有一个`compare(Object obj1, Object obj2)`方法用来排序 + +一般我们需要对一个集合使用自定义排序时,我们就要重写`compareTo()`方法或`compare()`方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写`compareTo()`方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 `Collections.sort()`. + +#### Comparator定制排序 + +```java + ArrayList arrayList = new ArrayList(); + arrayList.add(-1); + arrayList.add(3); + arrayList.add(3); + arrayList.add(-5); + arrayList.add(7); + arrayList.add(4); + arrayList.add(-9); + arrayList.add(-7); + System.out.println("原始数组:"); + System.out.println(arrayList); + // void reverse(List list):反转 + Collections.reverse(arrayList); + System.out.println("Collections.reverse(arrayList):"); + System.out.println(arrayList); + + // void sort(List list),按自然排序的升序排序 + Collections.sort(arrayList); + System.out.println("Collections.sort(arrayList):"); + System.out.println(arrayList); + // 定制排序的用法 + Collections.sort(arrayList, new Comparator() { + + @Override + public int compare(Integer o1, Integer o2) { + return o2.compareTo(o1); + } + }); + System.out.println("定制排序后:"); + System.out.println(arrayList); +``` + +Output: + +``` +原始数组: +[-1, 3, 3, -5, 7, 4, -9, -7] +Collections.reverse(arrayList): +[-7, -9, 4, 7, -5, 3, 3, -1] +Collections.sort(arrayList): +[-9, -7, -5, -1, 3, 3, 4, 7] +定制排序后: +[7, 4, 3, 3, -1, -5, -7, -9] +``` + +#### 重写compareTo方法实现按年龄来排序 + +```java +// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列 +// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他 +// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了 + +public class Person implements Comparable { + private String name; + private int age; + + public Person(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + /** + * TODO重写compareTo方法实现按年龄来排序 + */ + @Override + public int compareTo(Person o) { + // TODO Auto-generated method stub + if (this.age > o.getAge()) { + return 1; + } + if (this.age < o.getAge()) { + return -1; + } + return 0; + } +} + +``` + +```java + public static void main(String[] args) { + TreeMap pdata = new TreeMap(); + pdata.put(new Person("张三", 30), "zhangsan"); + pdata.put(new Person("李四", 20), "lisi"); + pdata.put(new Person("王五", 10), "wangwu"); + pdata.put(new Person("小红", 5), "xiaohong"); + // 得到key的值的同时得到key所对应的值 + Set keys = pdata.keySet(); + for (Person key : keys) { + System.out.println(key.getAge() + "-" + key.getName()); + + } + } +``` + +Output: + +``` +5-小红 +10-王五 +20-李四 +30-张三 +``` + +## 1.5 Map接口 +### 1.5.1 HashMap 和 Hashtable 的区别 1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); 2. **效率:** 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; @@ -148,7 +315,7 @@ public interface RandomAccess { } ``` -## HashMap 和 HashSet区别 +### 1.5.2 HashMap 和 HashSet区别 如果你看过 `HashSet` 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 `clone() `、`writeObject()`、`readObject()`是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。 @@ -159,7 +326,7 @@ public interface RandomAccess { | 调用 `put()`向map中添加元素 | 调用 `add()`方法向Set中添加元素 | | HashMap使用键(Key)计算Hashcode | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性, | -## HashSet如何检查重复 +### 1.5.3 HashSet如何检查重复 当你把对象加入`HashSet`时,HashSet会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用`equals()`方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。(摘自我的Java启蒙书《Head fist java》第二版) @@ -177,9 +344,9 @@ public interface RandomAccess { 2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较 3. ==指引用是否相同 equals()指的是值是否相同 -## HashMap的底层实现 +### 1.5.4 HashMap的底层实现 -### JDK1.8之前 +#### JDK1.8之前 JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** @@ -218,7 +385,7 @@ static int hash(int h) { ![jdk1.8之前的内部结构-HashMap](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/jdk1.8之前的内部结构-HashMap.jpg) -### JDK1.8之后 +#### 5.4.2. JDK1.8之后 相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。 @@ -230,7 +397,7 @@ static int hash(int h) { - 《Java 8系列之重新认识HashMap》 : -## HashMap 的长度为什么是2的幂次方 +### 1.5.5 HashMap 的长度为什么是2的幂次方 为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash`”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 @@ -238,13 +405,13 @@ static int hash(int h) { 我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。** -## HashMap 多线程操作导致死循环问题 +### 1.5.6 HashMap 多线程操作导致死循环问题 主要原因在于并发下的Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。 详情请查看: -## ConcurrentHashMap 和 Hashtable 的区别 +### 1.5.7 ConcurrentHashMap 和 Hashtable 的区别 ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 @@ -267,9 +434,9 @@ ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方 ![JDK1.8的ConcurrentHashMap](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JDK1.8-ConcurrentHashMap-Structure.jpg) -## ConcurrentHashMap线程安全的具体实现方式/底层具体实现 +### 1.5.8 ConcurrentHashMap线程安全的具体实现方式/底层具体实现 -### JDK1.7(上面有示意图) +#### JDK1.7(上面有示意图) 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 @@ -284,171 +451,17 @@ static class Segment extends ReentrantLock implements Serializable { 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 -### JDK1.8 (上面有示意图) +#### JDK1.8 (上面有示意图) ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N))) synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 -## comparable 和 Comparator的区别 - -- comparable接口实际上是出自java.lang包 它有一个 `compareTo(Object obj)`方法用来排序 -- comparator接口实际上是出自 java.util 包它有一个`compare(Object obj1, Object obj2)`方法用来排序 - -一般我们需要对一个集合使用自定义排序时,我们就要重写`compareTo()`方法或`compare()`方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写`compareTo()`方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 `Collections.sort()`. - -### Comparator定制排序 - -```java - ArrayList arrayList = new ArrayList(); - arrayList.add(-1); - arrayList.add(3); - arrayList.add(3); - arrayList.add(-5); - arrayList.add(7); - arrayList.add(4); - arrayList.add(-9); - arrayList.add(-7); - System.out.println("原始数组:"); - System.out.println(arrayList); - // void reverse(List list):反转 - Collections.reverse(arrayList); - System.out.println("Collections.reverse(arrayList):"); - System.out.println(arrayList); - - // void sort(List list),按自然排序的升序排序 - Collections.sort(arrayList); - System.out.println("Collections.sort(arrayList):"); - System.out.println(arrayList); - // 定制排序的用法 - Collections.sort(arrayList, new Comparator() { - - @Override - public int compare(Integer o1, Integer o2) { - return o2.compareTo(o1); - } - }); - System.out.println("定制排序后:"); - System.out.println(arrayList); -``` - -Output: - -``` -原始数组: -[-1, 3, 3, -5, 7, 4, -9, -7] -Collections.reverse(arrayList): -[-7, -9, 4, 7, -5, 3, 3, -1] -Collections.sort(arrayList): -[-9, -7, -5, -1, 3, 3, 4, 7] -定制排序后: -[7, 4, 3, 3, -1, -5, -7, -9] -``` - -### 重写compareTo方法实现按年龄来排序 - -```java -// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列 -// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他 -// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了 - -public class Person implements Comparable { - private String name; - private int age; - - public Person(String name, int age) { - super(); - this.name = name; - this.age = age; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - /** - * TODO重写compareTo方法实现按年龄来排序 - */ - @Override - public int compareTo(Person o) { - // TODO Auto-generated method stub - if (this.age > o.getAge()) { - return 1; - } - if (this.age < o.getAge()) { - return -1; - } - return 0; - } -} - -``` - -```java - public static void main(String[] args) { - TreeMap pdata = new TreeMap(); - pdata.put(new Person("张三", 30), "zhangsan"); - pdata.put(new Person("李四", 20), "lisi"); - pdata.put(new Person("王五", 10), "wangwu"); - pdata.put(new Person("小红", 5), "xiaohong"); - // 得到key的值的同时得到key所对应的值 - Set keys = pdata.keySet(); - for (Person key : keys) { - System.out.println(key.getAge() + "-" + key.getName()); - - } - } -``` - -Output: - -``` -5-小红 -10-王五 -20-李四 -30-张三 -``` - -## 集合框架底层数据结构总结 +## 1.6 Collections工具类 -### Collection -#### 1. List - -- **Arraylist:** Object数组 -- **Vector:** Object数组 -- **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) - -#### 2. Set - -- **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素 -- **LinkedHashSet:** LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的 -- **TreeSet(有序,唯一):** 红黑树(自平衡的排序二叉树) - -### Map - -- **HashMap:** JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间 -- **LinkedHashMap:** LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931) -- **Hashtable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 -- **TreeMap:** 红黑树(自平衡的排序二叉树) - -## 如何选用集合? - -主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。 -## 公众号 +## 公众号 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 @@ -456,4 +469,4 @@ Output: **Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 -![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) +![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) \ No newline at end of file