容器总览关系
List族
ArrayList
-
特点-适用场景
查找快,非尾部插入和删除需要移动插入处后续的元素,所以插入慢
适用于频繁访问,非尾部修改少的场景
-
实现原理
数组
非线程安全
是连续空间,内存空间不足时,会主动扩容
无参数构造,初始化容量为0,当真正添加元素时候容量为10
动态扩容过程: 当添加元素时候发现容量已满,则动态扩容为当前容量的1.5倍
动态扩容采用 Arrays.copyOf(elementData, newCapacity) 实现,返回新的数组,但是拷贝是浅拷贝, 两个数组里面的内容都是指向同一个引用的
Vector
-
特点-适用场景
和ArrayList一样,并适用于多线程访问场景
-
实现原理
数组
线程安全,安全采用synchronized整个数组的方式,所以加锁范围广,并发效率低
无参构造初始化容量为10
是连续空间,内存空间不足时,会主动扩容
动态扩容过程: 初始化可以传入增量大小,当容量已满,扩容为 当前容量 + 增量大小,如果没有传递增量大小,则 扩容为 当前容量的2倍
动态扩容同样采用: Arrays.copyOf(elementData, newCapacity),返回新的数组,但是拷贝是浅拷贝, 两个数组里面的内容都是指向同一个引用的
LinkedList 双向链表
-
特点-适用场景 插入快,删除快,查找慢,适用于插入多,访问少的
删除直接修改元素记录的地址值即可,不需要大量移动元素,所以删除快
-
实现原理
链表头节点、链表尾节点和节点数据对象 LinkedList是离散空间所以不需要主动扩容
单链表结构特点: 头结点 尾结点 数据对象data中有next指向
双向链表: 头结点 尾结点 数据对象data中有 pre和next指向
循环链表: 结构是单链表,特点是无头结点,尾结点指向第一个节点对象
自我构建链表对象类似:
public class LinkList {
public Node root = null; //定义头节点,默认为空
public Node tail; //定义尾节点
public int length; //定义链表长度
public class Node{ //声明节点类
public int data; //定义数据对象
public Node next = null; //定义指针,默认为空
public Node(int data) {this.data = data; } //重写构造方法,初始化节点
}
Stack
-
特点-适用场景
先进后出
线程安全
-
实现原理
继承自vector,具有vector的特点和原理特性
Set族
元素存储和取出顺序是无序的
HashSet
-
特点-适用场景
元素存储和取出顺序是无序的 没有角标 不能进行精确地定位 不允许重复的元素 内容相同的只保留一个 它是通过复写equals()方法来判断是否的重复元素
-
实现原理
内部通过使用hashmap来实现内容的存储,key值即为add进去的对象,value值为固定对象
public HashSet() {
105 map = new HashMap<>();
106 }
public boolean add(E e) {
219 return map.put(e, PRESENT)==null;
220 }
221
222 /**
223 * Removes the specified element from this set if it is present.
224 * More formally, removes an element <tt>e</tt> such that
225 * <tt>(o==null ? e==null : o.equals(e))</tt>,
226 * if this set contains such an element. Returns <tt>true</tt> if
227 * this set contained the element (or equivalently, if this set
228 * changed as a result of the call). (This set will not contain the
229 * element once the call returns.)
230 *
231 * @param o object to be removed from this set, if present
232 * @return <tt>true</tt> if the set contained the specified element
233 */
234 public boolean remove(Object o) {
235 return map.remove(o)==PRESENT;
236 }
TreeSet
-
特点-适用场景
元素按照comparable排序,插入到内部二叉树的TreeMap中
能够对元素按照某种规则进行排序。
特点:排序和唯一
底层数据结构是红黑树(红黑树是一种自平衡的二叉树),保证了元素的排序和唯一性
内部实现排序,也可以自定义排序规则
自然排序、比较器排序保证元素排序
根据比较的返回值是否是0来保证元素唯一性
-
实现原理
内部封装了treemap,value对象是固定对象
3 public TreeSet() {
124 this(new TreeMap<E,Object>());
125 }
126
127 /**
128 * Constructs a new, empty tree set, sorted according to the specified
129 * comparator. All elements inserted into the set must be <i>mutually
130 * comparable</i> by the specified comparator: {@code comparator.compare(e1,
131 * e2)} must not throw a {@code ClassCastException} for any elements
132 * {@code e1} and {@code e2} in the set. If the user attempts to add
133 * an element to the set that violates this constraint, the
134 * {@code add} call will throw a {@code ClassCastException}.
135 *
136 * @param comparator the comparator that will be used to order this set.
137 * If {@code null}, the {@linkplain Comparable natural
138 * ordering} of the elements will be used.
139 */
140 public TreeSet(Comparator<? super E> comparator) {
141 this(new TreeMap<>(comparator));
142 }
143
map族
HashTable
-
特点-适用场景
多线程的hashmap,由于效率不高,当前一般使用ConcurrentHashMap锁分段的并发容器代替
-
实现原理
数组 + 链表
线程安全
HashTable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,所以锁的效率不高
HashMap
-
特点-适用场景
是无序的
允许null值做key和value
扩容系数即负载因子为0.75 , 初始大小 16 ,是否需要扩容取决于添加元素时候,判断当前存储大小是否已经达到3/4的容量,达到则需要扩容一倍
HashMap 的容量值都是 2^n
容器初始化大小 16,最大容量2^30(1位符号位,最多左移30位,左移计算容量,0到31位,1位符号位则是0到30位)
-
实现原理
数组 + 链表 (单链表,next记录下一个元素位置)
非线程安全
容量大小需要为大小2^n ,因为hash计算规则原因
利用hash值确定index方式:
n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n
hash值生成方式:
hash是 32位的,但是容器的个数n是2^4 四位 ~ 30位的,为了让高位也参与其中,所以采用高位和hashcode异或的形式得到 hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 :(h = key.hashCode()) ^ (h >>> 16);
}
String的hashCode实现
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
这里有个数字 31 ,为什么选择31作为乘积因子,而且没有用一个常量来声明?主要原因有两个:
①、31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。
②、31可以被 JVM 优化,31 * i = (i << 5) - i。因为移位运算比乘法运行更快更省性能。
源码添加元素时候的判断时序
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
625 boolean evict) {
626 Node<K,V>[] tab; Node<K,V> p; int n, i;
//首先查看是否需要扩容
627 if ((tab = table) == null || (n = tab.length) == 0)
628 n = (tab = resize()).length;
//然后查找hashcode为此值的 index是否存在,不存在才newNode节点
629 if ((p = tab[i = (n - 1) & hash]) == null)
630 tab[i] = newNode(hash, key, value, null);
631 else {
632 Node<K,V> e; K k;
//如果存在,则判断hash值,key值均相同的情况下,则返回此值
633 if (p.hash == hash &&
634 ((k = p.key) == key || (key != null && key.equals(k))))
635 e = p;
//如果hash值相同,可以值不同则发下是TreeNode则插入
636 else if (p instanceof TreeNode)
637 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
638 else {
//否则 生成节点,插入到数组下的链表中,即hash值相同,key值不同的情况
639 for (int binCount = 0; ; ++binCount) {
640 if ((e = p.next) == null) {
641 p.next = newNode(hash, key, value, null);
642 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
643 treeifyBin(tab, hash);
644 break;
645 }
646 if (e.hash == hash &&
647 ((k = e.key) == key || (key != null && key.equals(k))))
648 break;
649 p = e;
650 }
651 }
//对于放置相同key值的情况,返回相同的对象,但是用新的value替换旧的value
652 if (e != null) { // existing mapping for key
653 V oldValue = e.value;
654 if (!onlyIfAbsent || oldValue == null)
655 e.value = value;
656 afterNodeAccess(e);
657 return oldValue;
658 }
659 }
660 ++modCount;
661 if (++size > threshold)
662 resize();
663 afterNodeInsertion(evict);
664 return null;
665 }
TreeMap
-
特点-适用场景
可根据comparable排序的map集合
无序,不允许重复(无序指元素顺序与添加顺序不一致)
TreeMap集合默认会对键进行排序,所以键必须实现自然排序和定制排序中的一种
底层使用的数据结构是二叉树
-
实现原理
内部元素是以 entry这种key value形式的结构,并且entry有 left 和 right元素,按照comparable比较决定插入左节点 还是 右节点
可以接收传入SortedMap对象 或者 comparator接口对象实现比较排序
使用方式:
Map<Student, Integer> map = new TreeMap<>(new Comparator<Student>() {
public int compare(Student p1, Student p2) {
return p1.score > p2.score ? -1 : 1;
}
});
map.put(new Student("Tom", 77), 1);
map.put(new Student("Bob", 66), 2);
map.put(new Student("Lily", 99), 3);
for (Student key : map.keySet()) {
System.out.println(key);
}
LinkedHashMap
-
特点-适用场景
继承自hashmap,具有hashmap的特点,并且对于添加进去的元素按照先后顺序排序
-
实现原理
HashMap+双向链表
初始化容量和装载因子(默认为0.75)。accessOrder为是否按照访问顺序排列,如果是,则被访问的entry将排列到最后一个,默认是false,即按照输入顺序排列,即遍历输出的顺序与put顺序一致
accessOrder默认是false,get操作不会改变顺序,由put决定,如何设置true则get方法将元素移动到队列尾部,容量超过从头部移除
hashmap对应的数组被修改为下面的对象:
static class Entry<K,V> extends HashMap.Node<K,V> {
193 Entry<K,V> before, after;
194 Entry(int hash, K key, V value, Node<K,V> next) {
195 super(hash, key, value, next);
196 }
197 }
/**
202 * The head (eldest) of the doubly linked list.
203 */
204 transient LinkedHashMap.Entry<K,V> head;
205
206 /**
207 * The tail (youngest) of the doubly linked list.
208 */
209 transient LinkedHashMap.Entry<K,V> tail;
利用hashmap中的增加、删除、访问的钩子方法,对于每次新增加数组元素的时候,建立元素的前后引用关系
void afterNodeAccess(Node<K,V> p) { }
1767 void afterNodeInsertion(boolean evict) { }
1768 void afterNodeRemoval(Node<K,V> p) { }
每次newNode方法,使用LinkedHashMap.Entry:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
257 new LinkedHashMap.Entry<K,V>(hash, key, value, e);
static class Entry<K,V> extends HashMap.Node<K,V> {
193 Entry<K,V> before, after;
194 Entry(int hash, K key, V value, Node<K,V> next) {
195 super(hash, key, value, next);
196 }
197 }
ConcurrentHashMap
-
特点-适用场景
具有hashmap的特点,并且多线程安全,使用锁分段技术
-
实现原理
(1)
在JDK1.7中,ConcurrentHashMap使用的是segment锁,是继承自ReentrantLock,一旦初始化完成,就不能再改变了,但是segment数组中的HashEntry数组是可以改变的
结构是 segment锁数组 + hashentry数组 + 链表数据
定位一个元素的过程需要进行两次 Hash 操作。 第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的链表的头部
segment含义 segment数组,segment结构- hashentry数组,每个hashentry是个链表结构
segment结构继承自 ReentrantLock
一个 ConcurrentHashMap 里包含一个 Segment 数组,Segment 的结构和 HashMap 类似,是一种数组和链表结构,
一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,
每个 Segment 守护者一个 HashEntry 数组里的元素, 当对 HashEntry 数组的数据进行修改时,必须首先获得它对应的 Segment 锁。
(2) jdk1.8中的ConcurrentHashMap中废弃了segment锁,直接使用了数组元素,数组中的每个元素都可以作为一个锁,
在元素中没有值的情况下,可以直接通过CAS操作来设值,同时保证并发安全,
如果元素里面已经存在值的话,那么就使用synchronized关键字对元素加锁,再进行之后的hash冲突处理
重点:synchronized根据操作的链表对象进行加锁,来保证同一个锁中 操作相同的对象
LruCache
-
特点-适用场景
内部封装了 LinkedHashMap
-
实现原理
当序列达到设置的内存上限时, 丢弃序列中最近最少使用的元素
//初始容量 0, 扩容系数),0.75,排序方式 true表示按照get访问排序,false按照 put插入顺序排序
this.maxSize = maxSize;初始化传入 最大容量大小
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
Queue队列
-
特点-适用场景
先进先出 队列是一种受限的数据结构,插入操作只能从一端操作,这一端叫作队尾;而移除操作也只能从另一端操作,这一端叫作队头
-
实现原理
队列的实现有两种方式:数组和链表
参考链接
数组:
www.cnblogs.com/kuoAT/p/677… blog.csdn.net/dietime1943… blog.csdn.net/qq_42592994…
set
zhuanlan.zhihu.com/p/105958354
map
blog.csdn.net/hm_135/arti… cloud.tencent.com/developer/a… blog.csdn.net/baidu_37107… www.liaoxuefeng.com/wiki/125259… www.jianshu.com/p/181df0893… blog.csdn.net/wxgxgp/arti… 锁分段 www.infoq.cn/article/Con…
queue