目录
1.什么是集合
1.集合
2.数组存储多个数据方面的缺点:即集合存储的优势
3.集合的选用方法
2.集合框架的两大接口
3.Collection之List接口
1.ArrayList
2.LinkedList
3.Vector
4.ArrayList和LinkedList对比
5.ArrayList的源码
4.Collection之Set接口
1.HashSet
2.LinkedHashSet
3.TreeSet
4.如何理解Set的无序、不可重复特性(以HashSet为例)
5.TreeSet的比较实现
6.HashSet 、LinkedHashSet、TreeSet的异同
5.Map
1.HashMap
1.原理
解决哈希冲突的方法:
HashMap添加键值对的原理:
HashMap扩容问题:
2.HashMap和HashSet对比
3.HashMap和Hashtable对比
4.HashMap和TreeMap对比
5.HashMap多线程下存在的问题
1)环形链表问题
2)数据覆盖问题
6.HashMap的遍历方式
2.LinkedHashMap
3.TreeMap
4.Hashtable
5.Properties
6.ConcorrentHashMap
底层结构
1.什么是集合
Java集合,是对多个数据进行存储的结构,简称java容器
1).初始化后,其长度无法修改;集合可以动态扩容
2).数组中提供的方法极其有限,对于增删改查操作不方便;集合提供了多种操作方法
3).数组是不支持泛型的,集合支持泛型;
4).数组对于无序、不可重复的需求,不能满足;集合中的Set接口就能够满足这一点
1)只存放元素值:使用Collection接口实现
保证元素唯一:选择Set接口下的TreeSet或者HashSet
不保证元素唯一:先择List接口下的ArrayList或LinkedList
2)存放键值对:使用Map接口实现
需要排序:使用TreeMap
不需要排序:使用HashMap
2.集合框架的两大接口
1. Collection接口:单列集合,存储一个一个的对象
List接口:存储有序的、可重复的数据;主要实现类:ArrayList,LinkedList,Vector
Set接口:存储无序的、不可重复的数据;主要实现类:HashSet,LinkedHashSet,TreeSet
2.Map接口:双列集合,存储一对(key-value)的数据
主要实现类:HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
3.Collection之List接口
底层实现:底层使用的是Object[ ]数组
是否可以添加null:可以添加null(慎用,可能报NullPointerException:空指针异常)
是否线程安全:线程不安全
继承与实现:继承了AbstarctList,实现了List、RandomAccess、Cloneable、Serializable
RandomAccess:表示ArrayList支持随机访问,根据元素索引就能获取元素
Cloneable:能够进行浅拷贝与深拷贝
Serializable:能够实现序列化与反序列化
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable{
}
ArrayList扩容机制
使用无参数构造器创建ArrayList对象时,初始化为一个空数组,当开始使用add()方法后,数组容量赋值为10;当数组已满继续添加元素时,调用grow()方法进行扩容,grow方法中将新的数组容量变为原来的1.5倍,并将原来数组的数据复制到新数组中
底层实现:底层是双向链表
是否线程安全:线程不安全
底层实现:底层使用的是Object[ ]数组
是否线程安全:线程安全
ArrayList:底层是Object[]数组
LinkedList:底层是双向链表
都是线程不安全
ArrayList:继承了RandomAccess,支持随机访问;LinkedList:不支持
ArrayList:适合查找元素,不适合插入、删除操作
插入和删除的时间复杂度:
头部插入/删除、指定位置插入/删除:时间复杂度O(n)
尾部插入/删除:时间复杂度O(1)
LinkedList:对于频繁的插入、删除操作,效率比ArrayList高
插入和删除的时间复杂度:
头部插入/删除、尾部插入/删除:时间复杂度O(1)
指定位置插入/删除:时间复杂度O(n)
实用性:一般使用LinkedList的场景都可以用ArrayList代替,性能会更好,所以LinkedList用的很少
4.Collection之Set接口
set接口的主要实现类;是线程不安全的;可以存储null值;
作为HashSet的子类,遍历内部数据时,可以按照添加的顺序遍历
可以按照添加的对象指定属性,进行排序
无序性:不等于随机性;存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定,添加时是无序的
不可重复性:保证添加的元素按照equals()判断时,不能返回true;即相同的元素只能添加一个。
1.向TreeSet中添加的数据,要求是相同类的对象,不能添加不同类的对象。
2.两种排序方式:Comparable接口、Comparator接口
3.自然排序中,比较两个对象是否相同的标准为compareTo()方法返回0;不再是equals()方法
4.定制排序中,比较两个对象是否相同的标准是compare()返回0,不再是equals()方法
代码示例
//Comparable 自然排序
public class Person implements Comparable {
@Override
public int compareTo(Object o) {
if (o instanceof Person){
Person oo = (Person)o;
return this.age - (oo.getAge());
}else {
throw new RuntimeException("输入的类型不一致");
}
}
}
//comparator:定制排序
Set set3 = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person){
Person oo1 = (Person)o1;
Person oo2 = (Person)o2;
return oo1.getName().compareTo(oo2.getName());
}else{
throw new RuntimeException("输入的类型不一致。");
}
}
});
相同点:都实现了set接口;都是线程不安全的
不同点:
底层结构:
HashSet:底层是哈希表
LinkedHashSet:链表+哈希表
TreeSet:红黑树
功能:
HashSet:底层能够存取null值
LinkedHashSet:能够保证元素按照添加的顺序遍历,且元素的添加与取出满足FIFO
TreeSet:能够实现指定规则排序
5.Map
线程是否安全:线程不安全,效率高;可以存储null的key和value。
底层:jdk7之前:数组+链表
jdk8:数组+链表+红黑树
jdk1.8之后,HashMap解决哈希冲突时,需要满足两个条件才将链表转为红黑树:链表长度大于阈值(默认为8);数组长度大于64(若小于64,则先进行扩容而不是转为红黑树)
HashMap被实例化后,底层创建长度为16的一维数组Entry[ ] table。
调用put(key1,value1)后,会先调用key1所在类的hashCode()方法计算key1的哈希值,此哈希值经过计算后得到Entry数组在底层的存放位置:
若此位置为空,则key1-value1添加成功
若此位置不为空,则比较key1和该位置元素(假设为key2-value2)的哈希值:
若key1的哈希值和该元素key2哈希值不相同,则key1-value1添加成功
若key1的哈希值和该元素key2哈希值相同,则比较key1所在类的equals(key2)方法
若equals()返回false,则key1-value1添加成功
若equals返回true,则value1将value2进行替换
HashMap默认初始化大小为16,每次扩容容量转为原来的2倍;若是创建时指定初始容量,则会扩充为2的幂次方;
2的幂次方的由来:
Hash值是一个很大的值,大概有40亿的映射空间;但这个值不内存中存放不下,因此需要进行一定的运算才能得到对应的数组下标,计算方法是 hash & (n-1)(n代表数组长度);而这个方法得到的结果,和对2的幂次方取余是一样的,只是和 “&” 相比, “%”效率更高
存储对象:
HashMap存储的为键值对,是双列集合;是因为HashMap实现的是Map接口
HashSet存储的为单列集合;是因为HashSet实现的是Collection接口下的Set接口
哈希值计算:
HashMap基于键计算哈希值
HashSet基于存储对象计算哈希值
底层结构:
HashMap底层是数组+链表+红黑树;
Hashtable底层是数组+链表;
是否线程安全:
HashMap线程不安全,Hashtable是线程安全的;
因此HashMap的效率高于Hashtable
是否能存Null值:
HashMap可以存储为null的键与值,但是为null的键只能有一个,为null的value可以有多个; Hashtable不能存储为null的键和值
扩容机制:
HashMap默认初始化大小为16,每次扩容容量转为原来的2倍;
Hashtable默认初始化大小为11,每次扩容转为原来的2n+1倍;
底层结构:
HashMap底层是数组+链表+红黑树;
TreeMap底层是红黑树
实现接口:
HashMap和TreeMap都继承了AbstractMap;
TreeMap比HashMap多实现了一个NavigableMap,而NavigableMap又继承了SortedMap;因此TreeMap能够对集合元素根据key排序(默认按照升序排列)
public class HashMap extends AbstractMap
implements Map, Cloneable, Serializable {
}
public class TreeMap extends AbstractMap
implements NavigableMap, Cloneable, java.io.Serializable{
}
jdk1.7:若产生哈希冲突,对比equals()方法不同后,将新元素存入数组中,旧元素作为链表存在,新元素指向旧元素,这就是头插法;若在多线程情况下,若多个线程同时put()出现哈希冲突,很有可能造成链表的环形链表情况,即多线程下:由于使用头插法存入元素,元素A与元素B相互指向
jdk1.8:此时采用尾插法,插入的元素都放在链表末尾,指向前一个元素,避免了环形链表问题
建议:在多线程环境下,使用ConcurrentHashMap
在多线程环境下,若线程A和线程B同时put(),并且发生了哈希冲突,线程A执行哈希冲突判断和可能会被挂起,线程B此时完成插入操作,随后线程A继续执行,但A已经进行了哈希判断,所以直接插入,此时A的数据会覆盖B数据
1.Iterator迭代器遍历:分为EntrySet 和 KeySet方式
2.For Each遍历:分为EntrySet 和 KeySet方式
3.Lambda表达式遍历
4.Stream API遍历
一般采用EntrySet遍历
详细可以参考:
HashMap 的 7 种遍历方式与性能分析!「修正篇」 (qq.com)
保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,效率高于HashMap
可以按照添加的key- value进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。底层使用红黑树。
作为古老的实现类;线程安全的,效率低;不能存储null的key和value
常用来处理配置文件;key和value都是String类型
jdk1.7以前:Segment数组加链表,每个Segment数组结构有一个HashEntry数组,每一个HashEntry数组都可以构成链表结构;与此同时,对Segment数组进行分段加锁,在多线程情况下可以访问不同的Segment分段数组,从而实现线程安全
jdk1.8:使用Node数组+链表+红黑树,不再采用Segment分段锁,而是synchronized锁定链表或红黑树的首节点,从而实现线程安全。