1.集合
- 本质上是一种可以存放任意类型对象的容器(可以结合泛型存储指定的类型)
- 1.Java集合的长度根据元素的大小可变化
- 2.Java集合只能存放引用类型数据,不能存放基本数据类型
- 3.Java可以通过包装类将基本数据类型转换为对象类型存放在集合中(自动封箱机制)
- 4.Java集合中实际存放的只是对象的引用,每个集合元素都是一个引用变量,实际内容都存放在堆内存或者方法区中
1.集合架构
- 1.架构图
- 2.集合按照其存储结构分为两大类
- 1.单列集合
Collection
- 2.双列集合
Map
2.Collection接口
- 1.Collection是单列集合类的根接口
- 2.其继承自Iterable接口(为对象使用迭代器Iterator提供统一调用)
- 3.Collection包含多个子接口,其中比较重要的有
- 1.
List
接口- 2.
Set
接口- 3.
Queue
接口- 4.
AbstractCollection
抽象类
1.Iterable接口
public interface Iterable<T> { //作用是为集合类提供for-each循环的支持 //Implementing this interface allows an object to be the target of the "for-each loop" statement. Iterator<T> iterator(); //返回一个内部元素为T类型的顺序迭代器 // 对Iterable中的元素进行指定的操作 default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } // 返回一个内部元素为T类型的并行迭代器 default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } }
- 1.Iterable接口的核心方法是iterator(),该方法的作用是返回一个Iterator对象,为Java对象提供迭代支持
- 2.Iterable接口也提供forEach方法用来迭代元素,执行消费器的accept方法
- 3.Iterable接口提供获取Spliterator可分割迭代器方法,用以支持集合的并发遍历
- 4.iterable接口主要是为了获取iterator从而获取迭代器的能力,附带了一个foreach()方法
2.Iterator接口
public interface Iterator<E> { // 判断一个对象集合是否还有下一个元素 boolean hasNext(); // 获取下一个元素 E next(); // 删除最后一个元素。默认是不支持的,因为在很多情况下其结果不可预测 default void remove() { throw new UnsupportedOperationException("remove"); } // 主要将每个元素作为参数发给action来执行特定操作 default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
- 1.iterator接口是为了定义遍历集合的规范,也是一种抽象,把在不同集合的遍历方式抽象出来,这样遍历时无需知道不同集合的内部结构
- 2.Iterator提供了集合和流操作等的遍历方式,是迭代器模式的应用
- 3.迭代器接口的函数比较简单,具体的实现交给子类实现
- 4.Iterator和Iterable的区别(为什么有Iterator还需要Iterable)
- 1.很多集合不直接实现
Iterator
接口,而是实现Iterable
- 2.
Iterator
接口的核心方法next()
或者hashNext()
等严重依赖于指针
,即是迭代的目前的位置- 3.如果
Collection
直接实现Iterator
接口,那么集合对象就拥有了指针的能力,内部不同方法传递,就会让next()
方法互相受到阻挠,只有一个迭代位置,互相干扰- 4.
Iterable
每次获取迭代器,就会返回一个从头开始
的不会和其他的迭代器相互影响
3.List接口
- 1.List接口是Collection的子接口
- 2.List的存储特点
- 1.元素可以重复
- 2.元素可以为null
- 3.元素有序(存入和取出的顺序一致)
- 4.有下标(线性存储可以通过索引访问集合中的元素)
- 3.
List
接口的子类需要实现其抽象方法,具体API
可查看List
接口
1.ArrayList
- 1.ArrayList继承了AbstractList抽象类实现了List接口,说明ArrayList继承了AbstractList抽象类的一些方法以及需要实现两者的抽象方法
- 3.ArrayList类实现了RandomAccess接口,说明ArrayList可以对元素进行快速随机访问
- 4.ArrayList类实现了Serializable接口,说明ArrayList可以被序列化
- 5.ArrayList类实现了Cloneable接口,说明ArrayList可以被复制
- 6.ArrayList类在JDK1.2版本发布,线程不安全,但是效率高(线程安全和效率冲突)
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; //默认的容量大小(常量) private static final int DEFAULT_CAPACITY = 10; //定义的空数组(final修饰,大小固定为0) private static final Object[] EMPTY_ELEMENTDATA = {}; //定义的默认空容量的数组(final修饰,大小固定为0) private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //定义的不可被序列化的数组,实际存储元素的数组 transient Object[] elementData; // 数组中元素的个数 private int size; //构造:根据传入的数值大小,创建指定长度的数组 public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //当initialCapacity > 0时,然后将其引用赋给elementData,此时ArrayList的容量为initialCapacity,元素个数size为默认值0 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //当initialCapacity = 0时,elementData被赋予了空数组,因为其被final修饰了,所以此时ArrayList的容量为0,元素个数size为默认值0 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } //无参的构造方法 public ArrayList() { //直接创建ArrayList时,elementData被赋予了默认空容量的数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //传入Collection元素列表的构造方法 public ArrayList(Collection<? extends E> c) { //将列表转化为对应的数组 elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } } public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } }
public static void main(String[] args) { // 1.创建Student对象数组 Student[] students = new Student[] { new Student("小明", 18), new Student("小李", 19), new Student("小张", 21) }; // 2.将其赋值给Object对象数组 Object[] objects = students; // 3.执行if语句前,打印数组的class System.out.println("执行前:" + objects.getClass()); // 4.执行上面的代码 if (objects.getClass() != Object[].class) { objects = Arrays.copyOf(objects, objects.length, Object[].class); } // 5.执行if语句后,打印数组的class System.out.println("执行后:" + objects.getClass()); }
- 源码分析:
- 1.serialVersionUID:
- 1.应用于
JAVA
序列化机制,Java
序列化通过判断类的serialVersionUID
来验证版本是否一致- 2.进行反序列化时,
JVM
会把传来的字节流中的serialVersionUID
于本地相应实体类的serialVersionUID
进行比较- 3.如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即
InvalidCastException
- 4.因此赋予当前类一个固定的
serialVersionUID
,反序列化后即使改变也不会改变serialVersionUID
,即被JVM
认为是同一个class
文件,不会出现版本不一致的问题- 5.具体可参考
JAVA SE
文章中的序列化和反序列化
- 2.ArrayList容器底层是由数组实现
- 1.该数组的类型是
Object
类型,由transient
修饰阻止被序列化,因此集合允许null
的存在- 2.其默认最小容量为
10
,由静态常量DEFAULT_CAPACITY
指定,其和size
不同,size表示当前集合元素个数,而capacity
表示当前集合能存储多少个元素- 3.ArrayList的构造函数
- 1.针对不同的构造函数,
ArrayList
使用不同的静态常量Object
数组,且使用int
类型的size
表示数组中元素的个数- 2.
无参构造ArrayList()
函数的使用静态常量数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 3.
有参构造ArrayList(int initialCapacity)
函数
- 1.如果初始化容量
>0
则使用new
关键字创建指定大小的Object
数组- 2.如果初始化容量
=0
则使用静态常量数组EMPTY_ELEMENTDATA
- 3.否则抛出异常
IllegalArgumentException
- 4.此时集合没有元素
size
为0
,但是elementData
为初始化容量initialCapacity
- 4.
有参构造ArrayList(Collection<? extends E> c)
函数
- 1.首先通过
toArray()
方法将集合转换为Object
类型的数组并将其引用赋值给elementData
- 2.如果集合的容量不为
0
且不是Object[]
类型,则调用Arrays.copyOf()
方法将原对象数组的数组类型转化为Object[]
的数组类型,以便更好的存储- 3.否则使用静态常量数组
EMPTY_ELEMENTDATA
- 4.add()方法
- 1.带下标的
add(int index, E element)
方法比不带下标的add(E e)
方法多了一个下标检验
和数组复制
方法- 2.首先调用
ensureCapacityInternal(int minCapacity)
方法确定容量是否足够添加数据,如果当前集合为空,则赋予默认容量10
- 3.然后调用
ensureExplicitCapacity(int minCapacity)
方法确定容量是否需要扩容- 4.如果最小容量大于集合的长度则需要扩容,调用
grow(int minCapacity)
方法进行扩容- 5.首先将集合容量扩充到原来的
1.5
倍,如果此时容量还不足则直接当前将所需容量设置为集合的容量- 6.判断当前容量是否小于指定的最大容量,如果大于则赋予
2的31次方-1
的最大容量,最后调用Arrays.copyOf(T[] original, int newLength)
方法扩容数组- 5.trimToSize()方法
- 1.由于集合中元素个数的
size
和表示集合容量的elementData.length
可能不同,在不太需要增加集合元素的情况下容量有浪费- 2.可以使用
trimToSize()
方法减小elementData
的大小,其中modCount
是继承自AbstractList
中的字段,用来记录数组修改的次数,数组每修改一次,modCount
加一- 3.然后判断集合中元素个数是否小于集合的容量,如果小于再判断元素个数是否为
0
- 4.如果为
0
就使用EMPTY_ELEMENTDATA
对elementData
进行初始化,否则使用Arrays
工具类中的copyof
方法进行数组复制,大小为size
- 6.ensureCapacity()方法
- 1.保证数组能够包含给定参数的个数,即如果有需要就扩大数组的容量
- 2.首先检查
elementData
是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 3.如果是则使用默认容量
10
,如果不是将minExpand
设置为0
- 4.然后比较
minCapacity
和minExpand
的大小,如果所需最小容量大于最小扩展,则需要调用方法进行数组扩容- 7.ensureCapacityInternal()方法
- 1.首先将
minCapacity
和默认大小10
比较,将较大值设置为最小容量- 2.然后调用
ensureExplicitCapacity()
方法判断是否需要扩容- 8.ensureExplicitCapacity()方法
- 1.首先记录数组修改的次数的
modCount
先加1
- 2.然后将所需最小容量和集合当前长度进行比较
- 3.如果需要扩容则调用
grow()
方法进行扩容- 9.grow()方法
- 1.实际的扩容方法
- 2.首先将容量增加为原来的
1.5
倍(位移运算符,左移一位代表乘以2
,右移一位代表除以2
)- 3.如果还不够则用直接使用所需的容量
minCapacity
- 4
.ArrayList
设置了数组的最大长度MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
,即Intger
最大范围-8
,Intger
类型的最大范围为2的31次方-1
- 5.如果新容量超过
MAX_ARRAY_SIZE
,则调用函数hugeCapacity(minCapacity)
检查是否溢出- 6.如果新容量没有超过
MAX_ARRAY_SIZE
,则调用Arrays.copyOf()
方法进行复制- 10.hugeCapacity()方法
- 1.首先判断最小容量是否小于
0
,如果小于0
则抛出OutOfMemoryError()
- 2.如果没有内存溢出则判断是否超过
MAX_ARRAY_SIZE
,如果超过则赋予Integer
类型的最大值,如果没有超过,则直接赋予MAX_ARRAY_SIZE
- 11.多线程的使用场景中,使用线程安全的CopyOnWriteArrayList
2.LinkedList
- 1.LinkedList继承了AbstractSequentialList抽象类实现了List接口,说明LinkedList继承了AbstractSequentialList抽象类的一些方法以及需要实现两者的抽象方法
- 2.LinkedList实现了实现了Deque(双端队列)接口,说明LinkedList支持队列操作
- 3.LinkedList类实现了Serializable接口,说明LinkedList可以被序列化
- 4.LinkedList类实现了Cloneable接口,说明LinkedList可以被克隆
- 5.ArrayList类在JDK1.2版本发布,线程不安全,但是效率高(线程安全和效率冲突)
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { transient int size = 0; transient Node<E> first; transient Node<E> last; public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); } ... private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } }
- 源码分析:
- 1.LinkedList提供了三个无法被序列化的成员变量
- 1.
size
:为链表的元素个数- 2.
first
:为链表的头节点- 3.
last
:为链表的尾节点- 2.LinkedList提供了静态内部类Node
- 1.用来定义存储的数据结构,一个
Node
代表一个数据节点,可用泛型指定范围- 2.其中
Node
类包含next
和prev
两个属性,prev
代表前一个节点,prev
代表后一个节点,其属于典型的双向链表
- 3.
LinkedList
容器底层由双向链表
实现,因此插入删除
效率高,查找
效率低- 4.链表不存在类似数组容量不足的问题,所以没有扩容的方法
- 3.LinkedList的构造函数
- 1.
LinkedList()
无参构造方法是一个空方法,其创建的是一个空的集合,此时的size
为0
,first
和last
都指向null
- 2.
LinkedList(Collection<? extends E> c)
有参构造
- 1.首先调用无参构造初始化一个空的集合
- 2.然后调用
addAll(Collection<? extends E> c)
方法将集合中的元素都加入其中- 4.add(E e)方法
- 1.调用
linkLast(E e)
方法链接对象E
作为最后一个元素,然后返回true
- 5.linkLast(E e)
- 1.实际添加元素的方法
- 2.首先获取最后一个节点并将其赋给一个常量
Node
对象l
- 3.然后创建一个新的节点,该节点的前一个节点为
Node
对象l
,后一个节点为null
- 4.然后把这个新节点赋予最后一个节点引用
- 5.判断最后一个节点是否是
null
,即该集合是否为空,如果为空,则将新节点作为first
,否则将新节点作为集合最后节点的下一个节点- 6.集合大小
size
加1,并将修改记录modCount
加1
3.Vector
- 1.Vector继承了AbstractList抽象类实现了List接口,说明Vector继承了AbstractList抽象类的一些方法以及需要实现两者的抽象方法
- 2.Vector类实现了RandomAccess接口,说明Vector可以对元素进行快速随机访问
- 3.Vector类实现了Serializable接口,说明Vector可以被序列化
- 4.Vector类实现了Cloneable接口,说明Vector可以被克隆
- 5.Vector类在JDK1.0版本发布,线程安全,但是效率低(线程安全和效率冲突)
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { protected Object[] elementData; protected int elementCount; protected int capacityIncrement; private static final long serialVersionUID = -2767605614048989439L; public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } public Vector(int initialCapacity) { this(initialCapacity, 0); } public Vector() { this(10); } public Vector(Collection<? extends E> c) { elementData = c.toArray(); elementCount = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, elementCount, Object[].class); } public synchronized void copyInto(Object[] anArray) { System.arraycopy(elementData, 0, anArray, 0, elementCount); } public synchronized void trimToSize() { modCount++; int oldCapacity = elementData.length; if (elementCount < oldCapacity) { elementData = Arrays.copyOf(elementData, elementCount); } } public synchronized void ensureCapacity(int minCapacity) { if (minCapacity > 0) { modCount++; ensureCapacityHelper(minCapacity); } } private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
- 源码解析:
- 1.Vector底层是由数组实现
- 1.该数组类型
Object
,与ArrayList
不同的是其可以被序列化- 2.与
ArrayList
不同Vector
用elementCount
表示实际存储的元素个数- 3.
capacityIncrement
属性表示每次扩充的容量- 2.Vector的构造函数
- 1.
Vector()
无参构造实际调用Vector(int initialCapacity)
有参构造,并默认初始值为10
- 2.
Vector(int initialCapacity)
有参构造实际调用Vector(int initialCapacity, int capacityIncrement)
有参构造,并默认初始增量为0
- 3.
Vector(int initialCapacity, int capacityIncrement)
有参构造
- 1.默认调用父类
AbstractList
的无参构造- 2.然后判断初始化容量是否非法,如果非法则抛出
IllegalArgumentException
异常- 3.通过
new
新建一个容量为initialCapacity
的Object
数组且将其引用赋给elementData
- 4.最后将增量赋值给
capacityIncrement
- 4.
Vector(Collection<? extends E> c)
有参构造
- 1.将集合转换为
Object
数组- 2.将数组的长度赋值给
elementCount
- 3.判断集合的类型是否为
Object[]
,如果不是通过调用Arrays.copyOf()
方法将其转换为Object
类型的数组,方便存储- 3.ensureCapacity(int minCapacity)方法
- 1.确保容量,如果所需最小容量大于
0
,则记录修改次数加1
- 2.然后调用
ensureCapacityHelper(minCapacity)
方法- 4.ensureCapacityHelper(int minCapacity)方法
- 1.如果所需最小容量大于当前集合容量,则调用
grow()
方法进行扩容- 5.grow(minCapacity)方法
- 1.首先将集合的长度复制给
oldCapacity
- 2.如果扩容的增量大于
0
,则扩容后大小为原容量+所需扩容的增量
,否则扩容后大小为原容量的两倍
- 3.如果
新的容量
还是小于所需最小容量
,则直接把新的容量设置为所需最小容量- 4.如果新的容量大于设置的最大数组容量则检查是否内存溢出,如果没有溢出则赋值
Intger
类型最大值- 5.最后调用
Arrays.copyOf()
扩容数组并将其引用赋值给elementData
- 6.add(E e)方法
- 1.首先将记录修改次数的
modCount
加1
- 2.然后确保容量是否足够,如果不足则扩容
- 3.将
elementData
数组的末尾的值设置为e
,并将集合元素个数elementCount
加1
- 7.注意
Vector
和ArrayList
最大的不同是Vector
的方法部分使用了synchronized
加锁,多线程安全但效率低
2.Set接口
- 1.Set接口是Collection的子接口,表示唯一对象的集合
- 2.Set的存储特点
- 1.元素不可以重复
- 2.元素可以为null
- 3.元素无序(存入和取出的顺序不一致)
- 4.无下标(不可以指定位置访问)
1.HashSet
- 1.HashSet继承了AbstractSet抽象类实现了Set接口
- 2.HashSet类实现了Serializable接口,说明HashSet可以被序列化
- 3.HashSet类实现了Cloneable接口,说明HashSet可以被克隆
- 4.HashSet类在JDK1.2版本发布,线程不安全
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; //使用 HashMap 的 key 保存 HashSet 中所有元素 private transient HashMap<E,Object> map; //定义一个虚拟的 Object 对象作为 HashMap 的 value private static final Object PRESENT = new Object(); //构造方法,初始化 HashSet,底层会初始化一个 HashMap public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } //以指定的 initialCapacity、loadFactor 创建 HashSet, 其实就是以相应的参数创建 HashMap public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } //以指定的 initialCapacity创建 HashSet, 其实就是以相应的参数创建 HashMap public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } }
- 源码分析:
- 1.HashSet的内部实际使用HashMap存储数据,HashSet本质是操作HashMap的key,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap
- 2.HashSet中的集合元素实际上由HashMap的key保存,而HashMap的value则存储了一个PRESENT,它是一个Object类型的静态常量,即定义一个虚拟的Object对象作为HashMap的value
- 3.
HashSet
不能存放null
值,因为add
时会调用compare()
方法,此时会调用对象的compareTo()
方法进行比较从而会抛出NullPointerException
- 4.注意:试图把某个类的对象当成HashMap的key或试图将这个类的对象放入HashSet中保存时,需要重写该类的equals(Object obj)方法和hashCode()方法
- 1.因为
HashSet
底层使用的是HashMap
- 2.
HashMap
添加元素的时候,先调用key
的hashCode
方法得到key
的哈希值 ,然后通过key
的哈希值
经过移位等运算算出该元素在table
中的存储位置- 3.如果位置目前没有任何元素,则该元素可以直接存储在该位置上
- 4.否则会调用该
key
的equals
方法与该位置的key
再比较一次- 5.如果
equals
方法返回的是true
,那么该位置上的元素视为重复元素,直接覆盖,如果返回的是false
,则添加元素- 5.重写hashcode原因
- 1.通过HashMap中的源码可以看到HashSet在进行添加底层使用到了hash方法,而hash值的获取实际上是在hashCode基础上的一系列操作
- 2.如果不重写hashcode,用的则是从Object继承下来的hashCode,而这个hashCode每次new一个对象就会有不同的值;所以就造成了上面的对象中的内容即使一样 但还是被认为是不同的元素,因为每次存入时都是new了一个新对象,就造成了hash值一直不同
- 6.重写equals原因:
- 因为hash值毕竟只是一个逻辑值,不同的对象有几率出现相同的hash值(称为哈希碰撞)。如果不同的对象出现了相同的hash值就需要使用equals进行再次判断,才能确保两个对象是否相同
2.LinkedHashSet
- 1.LinkedHashSet继承了HashSet类实现了Set接口
- 2.LinkedHashSet类实现了Serializable接口,说明HashSet可以被序列化
- 3.LinkedHashSet类实现了Cloneable接口,说明HashSet可以被复制
- 4.LinkedHashSet类在JDK1.4版本发布,线程不安全
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable { private static final long serialVersionUID = -2851667679971038690L; public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } public LinkedHashSet() { super(16, .75f, true); } public LinkedHashSet(Collection<? extends E> c) { super(Math.max(2*c.size(), 11), .75f, true); addAll(c); } @Override public Spliterator<E> spliterator() { return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED); } }
- 源码分析:
- 1.LinkedHashSet的内部采用了LinkedHashMap作为数据存储
- 2.LinkedHashSet具体内容同HashSet类似,只是HashSet是使用HashMap作为存储数据的容器,LinkedHashSet是使用LinkedHashMap作为存储数据的容器
3.TreeSet
- 1.TreeSet继承了AbstractSet抽象类实现了NavigableSet接口
- 2.TreeSet类实现了Serializable接口,说明TreeSet可以被序列化
- 3.TreeSet类实现了Cloneable接口,说明HashSet可以被复制
- 4.TreeSet类在JDK1.2版本发布,线程不安全
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { private transient NavigableMap<E,Object> m; private static final Object PRESENT = new Object(); TreeSet(NavigableMap<E,Object> m) { this.m = m; } public TreeSet() { this(new TreeMap<E,Object>()); } public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } public TreeSet(Collection<? extends E> c) { this(); addAll(c); } public TreeSet(SortedSet<E> s) { this(s.comparator()); addAll(s); } }
- 源码分析:
- 1.TreeSet的内部采用了TreeMap作为数据存储,TreeSet其实就是在操作TreeMap的key
- 2.TreeSet封装了一个 TreeMap对象来存储所有的集合元素,所有放入 TreeSet中的集合元素实际上由 TreeMap的 key 来保存,而 TreeMap的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象
3.Map接口
- 1.Map接口是双列集合的根接口,其中定义了一个内部接口Entry<K,V> 用于对映射数据进行操作
- 2.Map 接口中键和值一一映射,可以通过键来获取值
- 3.Map的存储特点是
- 1.键(Key)无序,无下标,不可重复
- 2.值(Value)无序,无下标,可以重复
- 3.每个键至多对应一个值,如果重复上面的值会被下面的值覆盖
- 4.键可以为null,值也可以为null
1.HashMap
- 1.HashMap继承了AbstractMap抽象类实现了Map接口,说明HashMap继承了AbstractMap抽象类的一些方法以及需要实现两者的抽象方法
- 2.HashMap类实现了Cloneable接口,说明HashMap可以被克隆
- 3.HashMap类实现了Serializable接口,说明HashMap可以被序列化
- 4.HashMap类在JDK1.2版本发布,线程不安全,但是效率高(线程安全和效率冲突)
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { private static final long serialVersionUID = 362498820763181265L; // 默认初始容量 - 必须是 2 的幂 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 // 最大容量,2的30次幂 static final int MAXIMUM_CAPACITY = 1 << 30; // 构造函数中未指定时使用的默认负载因子0.75 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 树化阈值,链表长度大于8时,转换为红黑树 static final int TREEIFY_THRESHOLD = 8; // 链红黑树转为链表的阈值 static final int UNTREEIFY_THRESHOLD = 6; // 最小树化容量,table容量大于等于64时,才会转换为红黑数否则先进行扩容 static final int MIN_TREEIFY_CAPACITY = 64; // 实际存储数据的节点,Node类型,使用泛型<K,V>限制 static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
// 哈希方法 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
transient Node<K,V>[] table; transient Set<Map.Entry<K,V>> entrySet; transient int size; transient int modCount; int threshold; final float loadFactor;
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { //tab用来保存当前table的引用 //p用来保存table的节点 //n用来保存table的容量 //i用来保存table的下标 Node<K,V>[] tab; Node<K,V> p; int n, i; // 如果table为null或者table的容量为0 if ((tab = table) == null || (n = tab.length) == 0) // 对table进行初始化,HashMap的懒加载策略,当执行put操作时再进行table的初始化 n = (tab = resize()).length; // 通过(n - 1) & hash获得下标,如果当前下标元素为null,说明没有冲突 if ((p = tab[i = (n - 1) & hash]) == null) // 直接创建一个新节点存放在当前位置 tab[i] = newNode(hash, key, value, null); // 否则 else { // 声明两个临时变量e和k Node<K,V> e; K k; // 如果要插入的key和原位置的key完全相同,则此次put为覆盖操作 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 将p赋值给e,用e来记录,便于后面的覆盖操作 e = p; // 如果key不相等,且头节点p为红黑树结点,则此次put为向红黑树中插入节点 else if (p instanceof TreeNode) // 调用红黑树的方法添加到指定位置 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 如果key不相等,且头节点p为链表结点,则此次put为向链表中插入节点(树化发生在此处) else { // 遍历链表,只在两种情况下才会跳出循环 for (int binCount = 0; ; ++binCount) { // 第一种:当前节点的下一个节点为null,说明已经遍历到尾部,在最后插入新节点跳出,因节点数量+1 判断是否需要树化 if ((e = p.next) == null) { // 直接创建一个新节点并将其引用赋予当前节点的下一个节点 p.next = newNode(hash, key, value, null); // 说明此时链表长度超过了树化阈值 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 进行树化操作 treeifyBin(tab, hash); // 跳出循环 break; } // 第二种:e指向的节点与要插入节点的key相同,此次put为覆盖操作 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 相等,跳出循环 break; // 用于遍历节点上的链表,与前面的e = p.next组合,可以遍历链表 p = e; } } // 如果e!=null,表示在桶(当前节点)中找到相同key值的节点,覆盖旧节点 if (e != null) { // existing mapping for key // //记录旧值 V oldValue = e.value; // onlyIfAbsent为false或者旧值为null if (!onlyIfAbsent || oldValue == null) // 用新值替换旧值 e.value = value; // 访问后回调 afterNodeAccess(e); // 返回旧值 return oldValue; } } // 对散列表操作进行记录,用于fast-fail机制 ++modCount; // 如果插入后元素超过了扩容阈值 if (++size > threshold) // 进行扩容操作 resize(); afterNodeInsertion(evict); return null; }
final void treeifyBin(Node<K,V>[] tab, int hash) { // 声明n保存table的长度,index保存数组的下标,e保存当前节点 int n, index; Node<K,V> e; // 如果当前table为空或当前table容量小于最小树化容量64 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // 直接扩容不进行树化 resize(); // 据hash值和数组长度进行取模运算后,得到链表的首节点 else if ((e = tab[index = (n - 1) & hash]) != null) { // 步骤一:遍历链表中每个节点,将Node转化为TreeNode // hd指向头节点,tl指向尾节点 TreeNode<K,V> hd = null, tl = null; // 遍历链表 do { // 将链表Node转换为红黑树TreeNode结构 TreeNode<K,V> p = replacementTreeNode(e, null); // 以hd为头结点,将每个TreeNode用prev和next连接成新的TreeNode双向链表 // 如果尾节点为空,说明还没有头节点 if (tl == null) // 头节点指向当前节点p hd = p; // 否则尾节点不为空,将其转换为双向链表结构 else { // 当前树节点的前一个节点指向尾节点 p.prev = tl; // 尾节点的后一个节点指向当前节点 tl.next = p; } // 把当前节点设为尾节点 tl = p; } while ((e = e.next) != null); // 继续遍历链表直达全部遍历 // 步骤二:如果头结点hd不为null,则对TreeNode双向链表进行树型化操作 if ((tab[index] = hd) != null) // 执行链表转红黑树的操作 hd.treeify(tab); } } TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) { // 根据参数返回一个树节点 return new TreeNode<>(p.hash, p.key, p.value, next); } final void treeify(Node<K,V>[] tab) { // 设置树的根节点为null TreeNode<K,V> root = null; // 根据链表进行遍历,最开始的x表示TreeNode双向链表的头结点 for (TreeNode<K,V> x = this, next; x != null; x = next) { // 将当前节点的下一个节点赋给next指针 next = (TreeNode<K,V>)x.next; // 设置树的左右子节点都为null x.left = x.right = null; // 如果根节点为null,则构建树的根节点 if (root == null) { // 当前节点的父节点为null x.parent = null; // 根节点一定是黑色的 x.red = false; // 将根节点设置为当前节点 root = x; } else { // 获取当前循环节点的key K k = x.key; // 获取当前循环节点的哈希值 int h = x.hash; Class<?> kc = null; // 每次都从根节点开始循环遍历当前红黑树 for (TreeNode<K,V> p = root;;) { // dir表示x节点在parent节点的左侧还是右侧,ph表示parent节点的hash值 int dir, ph; // 获得p的hash值和key K pk = p.key; // 比较hash值,然后根据比较值dir决定插入左边还是右边 if ((ph = p.hash) > h) // x节点在parent节点的左侧 dir = -1; else if (ph < h) // x节点在parent节点的右侧 dir = 1; // k不是可比较类或者比较结果为0 else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) // 使用tieBreakOrder方法比较 dir = tieBreakOrder(k, pk); // xp表示x的父节点 TreeNode<K,V> xp = p; // 如果p节点的左节点/右节点不为空,则令p = p.left/p.right,继续循环 // 直到p.left/p.right为空 if ((p = (dir <= 0) ? p.left : p.right) == null) { // 令待插入节点x的父节点为xp, 即p x.parent = xp; // 根据dir值设置为父节点的左右子节点 if (dir <= 0) xp.left = x; else xp.right = x; // 插入成功后平衡红黑树 root = balanceInsertion(root, x); break; } } } } // 将root节点插入到table中 moveRootToFront(tab, root); }
final Node<K,V>[] resize() { // 将旧table保存到oldTab Node<K,V>[] oldTab = table; // 将旧table的容量保存到oldCap int oldCap = (oldTab == null) ? 0 : oldTab.length; // 将旧table的扩容阈值保存到oldThr int oldThr = threshold; // 设置新table的容量和扩容阈值为0 int newCap, newThr = 0; // 如果旧table的容量不为空 if (oldCap > 0) { // 如果旧table的容量大于最大容量值 if (oldCap >= MAXIMUM_CAPACITY) { // 扩容阈值调整为Int类型的最大值 threshold = Integer.MAX_VALUE; // 返回旧table return oldTab; } // 如果旧table的容量大于默认初始化容量16且其扩大2倍后的值小于最大容量值 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) // 将新table的扩容阈值设置为旧table容量的2倍 newThr = oldThr << 1; // double threshold } // 如果旧table的容量为0,但是旧table的扩容阈值大于0 else if (oldThr > 0) // initial capacity was placed in threshold // 将旧table的扩容阈值设置为新table的容量 newCap = oldThr; // 否则 else { // zero initial threshold signifies using defaults // 新table容量采用默认初始化容量 newCap = DEFAULT_INITIAL_CAPACITY; // 新rable扩容阈值 = 默认初始化容量 * 默认负载因子 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 如果新table的扩容阈值为0 if (newThr == 0) { // 使用新table容量 * 负载因子重新进行计算扩容阈值 float ft = (float)newCap * loadFactor; // 如果新table容量小于最大容量值且重新计算的扩容阈值小于最大容量值则设置新table的扩容阈值为重新计算的,否则为Int类型的最大值 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } // 将新table的扩容阈值赋值给threshold threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) // 根据上述创建一个容量为newCap的Node类型的新table Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 将新table引用赋值给table table = newTab; // 如果旧table不为空 if (oldTab != null) { // 遍历旧table,将其元素移动到新table上 for (int j = 0; j < oldCap; ++j) { // 声明一个Node节点e备用 Node<K,V> e; // 如果旧table指定下标的元素不为空,则将其交给备用节点e if ((e = oldTab[j]) != null) { // 将旧table指定下标的元素设置为null oldTab[j] = null; // 判断旧table中节点是否有下一个节点,如果没有 if (e.next == null) // 表示当前只有一个元素,重新做hash散列并赋值计算 newTab[e.hash & (newCap - 1)] = e; // 否则如果旧table中的节点是一个树节点 else if (e instanceof TreeNode) // 如果旧table中该节点树节点,保留旧table中链表的顺序 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order // 把原有链表拆成两个链表 // 链表1存放在低位(原索引位置) Node<K,V> loHead = null, loTail = null; // 链表2存放在高位(原索引 + 旧table长度) Node<K,V> hiHead = null, hiTail = null; // 下一个节点指针 Node<K,V> next; do { // 遍历旧table当前节点,并将其下一个节点指针赋值给新table的next指针 next = e.next; // 链表1 原索引 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } // 链表2 原索引+oldCap else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); // 链表1 存放原索引位置 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 链表2 存放原索引+旧table容量 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
- 1.HashMap在JDK1.7用数组+链表实现,JDK1.8用数组+链表/红黑树实现
- 2.链表新节点插入链表的顺序不同,jdk7是插入头结点,jdk8因为要把链表变为红黑树所以采用插入尾节点
- 3.HashMap的静态常量
- 1.
DEFAULT_INITIAL_CAPACITY
:默认初始容量为16
,(容量必须为2
的次幂)- 2.
MAXIMUM_CAPACITY
:最大容量为2
的30
次幂- 3.
DEFAULT_LOAD_FACTOR
:默认负载因子为3/4(0.75f)
,当元素的总个数
>当前数组长度
*负载因子
,数组就会进行扩容,扩容大小为原来的两倍- 4.
TREEIFY_THRESHOLD
:默认链表树(红黑树)化的阙值为8
,表示当链表的Node<K,V>
节点个数大于8
时,会将链表转换为红黑树,提高查询效率- 5.
UNTREEIFY_THRESHOLD
:
- 1.红黑树转为链表的阈值为
6
- 2.扩容
resize()
时,此时HashMap
的数据存储位置会重新计算- 3.重新计算存储位置后,当原有的红黑树节点数量小于
6
时,则将红黑树转换成链表- 6.
MIN_TREEIFY_CAPACITY
:最小树形化阈值,当哈希表中的容量>MIN_TREEIFY_CAPACITY
时,才允许将链表转换成红黑树,否则则直接扩容,而不是树形化,为了防止前期阶段频繁扩容和树化过程冲突- 4.HashMap的属性
- 1.
table
- 1.
HashMap
采用Node
类型的数组来存储映射值,每一个键值对组成了一个Node
实体- 2.
Node
类是一个静态内部类,也是一个单向的链表结构,其具有Next
指针,可以指向下一个Node
实体- 3.
size
:此映射中包含的键-值映射的数量- 4.
modCount
:修改的次数- 5.
threshold
:扩容阈值 =table
容量 * 负载因子- 6.
loadFactor
:负载因子- 5.HashMap的构造方法
- 1.
HashMap()
无参构造:构造一个空的HashMap
,具有默认初始容量16
和默认负载因子0.75
- 2.
HashMap(int initialCapacity)
有参构造:构造一个空的HashMap
,具有指定的初始容量和默认负载因子0.75
- 3.
HashMap(int initialCapacity, float loadFactor)
有参构造
- 1.首先判断初始化容量是否大于
MAXIMUM_CAPACITY
- 2.如果大于直接将容量改成
MAXIMUM_CAPACITY
- 3.然后设置负载因子,并通过
tableSizeFor(int cap)
方法获取任意初始化容量的2的次幂
作为集合容量- 4.
HashMap(Map<? extends K, ? extends V> m)
有参构造
- 1.构造一个新的
HashMap
,使用默认负载因子0.75
- 2.然后调用
putMapEntries(m, false)
方法将集合的元素全部加入其中- 6.tableSizeFor(int cap)方法
- 1.为了保证容量必须是
2
的整数幂,HashMap
使用了方法tableSizeFor(int cap)
- 2.分别经过
1,2,4,8,16
为右移再进行或运算,最后+1
可以将一个任意的int
类型数据转换为2的整数次幂- 3.具体步骤可以参考地址链接
- 7.resize方法
- 1.实际的扩容方法,扩容大小为原来的两倍
- 2.扩容时针对链表,
JAVA7
时还需要重新计算hash
位,,但是JAVA8
做了优化,且规避JAVA8
版本以下的多线程成环问题- 3.通过
(e.hash & oldCap) == 0
来判断是否需要移位; 如果为真则在原位不动, 否则则需要移动到当前hash槽位 + oldCap的位置
- 4.具体信息可查看上述注释
- 8.put(K key, V value)方法
- 1.
put(K key, V value)
方法底层实际调用的是putVal()
方法添加元素,并且通过哈希值(n - 1) & hash
添加元素,所以是无序的- 2.
putVal()
方法中binCount >= TREEIFY_THRESHOLD - 1
说明只有链表长度大于8
才会树化(treeifyBin
)- 3.并且
treeifyBin
方法内会判断,只有数组长度大于等于64
才会真去树化,否则会先扩容- 4.具体信息可查看上述注释
- 9.treeifyBin(Node<K,V>[] tab, int hash)方法
- 1.先根据
hash
计算出当前链表所在table
数组中的位置,然后将其数据结构从单向链表Node
转为双向链表TreeNode
- 2.如果双向链表
TreeNode
的头节点hd
不为null
,则调用treeify()
方法对TreeNode
双向链表进行树型化操作- 10.treeify(Node<K,V>[] tab)方法
- 1.遍历
TreeNode
双向链表,确定待插入节点x
在其父节点的左边还是右边,然后将其插入节点到红黑树中- 2.插入节点之后树结构发生变化,需要通过变色和旋转操作维护红黑树的平衡
- 3.因为调整了红黑树,
root
节点可能发生了变化,所以需要把最新的root
节点放到双向链表的头部,并插⼊到table
中- 11.hash(Object key)方法
- 1.右移
16
位是为了让高16
位也参与运算,可以更好的均匀散列,减少碰撞,进一步降低hash
冲突的几率- 2.异或运算是为了更好保留两组
32
位二进制数中各自的特征- 12.多线程的使用场景中,使用线程安全的ConcurrentHashMap
2.LinkedHashMap
- 1.LinkedHashMap继承了HashMap类并且实现了Map接口
- 2.LinkedHashMap类再JDK1.4版本发布,线程不安全,效率高(线程安全和效率冲突)
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> { // 实际存储数据的Entry类型的节点,使用泛型<K,V>修饰 static class Entry<K,V> extends HashMap.Node<K,V> { // 前后指针,用于维护双向链表 Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } private static final long serialVersionUID = 3801124242820219131L; // 双向链表的头部:插入顺序时,是最先插入的entry;访问顺序时,是最近最少访问的entry transient LinkedHashMap.Entry<K,V> head; // 双向链表的尾部:插入顺序时,是最后插入的entry;访问顺序时,是最近刚访问的entry transient LinkedHashMap.Entry<K,V> tail; // true表示按照访问顺序迭代,false时表示按照插入顺序 final boolean accessOrder;
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; // 原尾节点为null,表明p是第一个节点 if (last == null) // head指向p head = p; // 否则,将p和原尾结点关联 else { p.before = last; last.after = p; } } private void transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst) { LinkedHashMap.Entry<K,V> b = dst.before = src.before; LinkedHashMap.Entry<K,V> a = dst.after = src.after; if (b == null) head = dst; else b.after = dst; if (a == null) tail = dst; else a.before = dst; } void reinitialize() { super.reinitialize(); head = tail = null; } Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); // 将p放到链表末尾 linkNodeLast(p); return p; } Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) { LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; LinkedHashMap.Entry<K,V> t = new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next); transferLinks(q, t); return t; } TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) { TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next); // 将p放到链表末尾 linkNodeLast(p); return p; } TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) { LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p; TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next); transferLinks(q, t); return t; } void afterNodeRemoval(Node<K,V> e) { // unlink LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; // 主动断开p与其他entry的关联 p.before = p.after = null; // p是头节点 if (b == null) // 则head指向其后继节点 head = a; else // 否则,前驱节点指向后继节点 b.after = a; // p是尾结点 if (a == null) // tail指向前驱节点 tail = b; else // 否则,后继节点指向前驱节点 a.before = b; } void afterNodeInsertion(boolean evict) { // possibly remove eldest // 声明一个变量用于保存头节点 LinkedHashMap.Entry<K,V> first; // 头节点不为null,且需要删除最老的节点,则删除头结点 if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; // 删除节点 removeNode(hash(key), key, null, false, true); } } protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; } void afterNodeAccess(Node<K,V> e) { // move node to last // 声明一个Entry类型的指针,用于保存尾节点 LinkedHashMap.Entry<K,V> last; // 如果查找为访问顺序 且 找到的不是尾节点,因为尾节点不需要移动 if (accessOrder && (last = tail) != e) { // 声明三个变量,分别用于保存当前节点,当前节点前指针和后指针 LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; // 首先断开与后继节点的关联 p.after = null; // 前驱节点为空,说明p在头部 if (b == null) // 直接将head指向后继节点 head = a; // 否则 else // 否则,前驱节点指向后继节点 b.after = a; // 如果后继节点不为空 if (a != null) // 后继节点指向前驱节点 a.before = b; else // 这里的代码多余,如果后继节点为null,那p就是尾节点,根本进入不了if last = b; if (last == null) // 这里是因为上一步导致的多余判断 head = p; else { // 将p移到末尾 p.before = last; last.after = p; } tail = p; // 更新tail引用 ++modCount; } } public V get(Object key) { // 声明一个Node节点用于保存查询的结果 Node<K,V> e; // 如果没有找到 if ((e = getNode(hash(key), key)) == null) // 返回null return null; // 否则如果是访问顺序 if (accessOrder) // 调用afterNodeAccess方法对顺序进行调整 afterNodeAccess(e); // 返回结果 return e.value; }
// 创建一个指定了初始化容量和loadFactor的、基于插入顺序的LinkedHashMap // 对应HashMap的public HashMap(int initialCapacity, float loadFactor) public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } // 创建一个指定了初始化容量的、基于插入顺序的LinkedHashMap // 对应HashMap的public HashMap(int initialCapacity) public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } // 创建一个基于插入顺序的LinkedHashMap,容量和loadFactor使用默认值 // 对应HashMap的public HashMap() public LinkedHashMap() { super(); accessOrder = false; } // 基于已有的map,创建一个基于插入顺序的LinkedHashMap,容量和loadFactor使用默认值 public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); } // 指定初始化容量、loadFactor和顺序的LinkedHashMap,accessOrder为true表示访问顺序 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
- 1.
LinkedHashMap
继承HashMap
,拥有HashMap
的所有特性,同时增加了自己的特性(有序性
)- 2.LinkedHashMap内部类Entry
- 1.实际存储数据的
Entry
类型的节点,继承HashMap
的Node
类,并使用泛型<K,V>
修饰- 2.
Entry
类中定义了before
和after
和两个指针,用来构建双向链表- 3.HashMap内部类
Node
的next
指针用于维护各个桶中的链表,before
,after
用于维护LinkedHashMap
的双向链表,各自分离
- 3.LinkedHaspMap的属性
- 1.
head
:双向链表的头部,插入顺序时,是最先插入的entry
;访问顺序时,是最近最少访问的entry
- 2.
tail
:双向链表的尾部,插入顺序时,是最后插入的entry
;访问顺序时,是最近刚访问的entry
- 3.
accessOrder
:访问顺序,true
表示按照访问顺序迭代,false
时表示按照插入顺序,默认为插入顺序- 4.LinkedHaspMap的构造方法
- 1.
LinkedHashMap()
无参构造:调用父类HashMap
的构造方法,默认按插入顺序访问- 2.
LinkedHashMap(int initialCapacity)
有参构造:调用父类HashMap
的构造方法,默认按插入顺序访问- 3.
LinkedHashMap(int initialCapacity, float loadFactor)
有参构造:调用父类HashMap
的构造方法,默认按插入顺序访问- 4.
LinkedHashMap(Map<? extends K, ? extends V> m)
有参构造:调用父类HashMap
的构造方法,默认按插入顺序访问- 5.
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
有参构造:调用父类HashMap
的构造方法,并且可以设置访问顺序- 3.get()方法
- 1.
LinkedHashMap
中查找方法几乎都进行了重写,为了支持访问顺序,一旦通过查找方法访问了entry
,entry
的顺序应该发生变化- 2.
get()
方法中根据key
的hash
值去查找,如果没有则返回null
,如果找到则判断顺序,如果是按访问顺序则调用afterNodeAccess()
方法法调整该entry
的位置,否则直接返回对应key
的value
- 4.afterNodeAccess()方法
- 1.该方法在
HashMap
的putVal()
方法中出现,但是在HashMap
中是空方法,LinkedHashMap
重写了该方法- 2.
afterNodeAccess()
方法的作用是在LinkedHashMap
使用访问顺序时,将刚访问过的entry
移到双向链表末尾
- 1.如果
entry
本身就在末尾,则不用移动- 2.如果
entry
处于双向链表的头部,则只需要断开与后继节点的关联,然后将其移到末尾- 3.如果
entry
处于双向链表的中部,则需要先将前驱节点与后继节点连上,然后将其移到末尾- 5.afterNodeRemoval(Node<K,V> e)方法
- 1.实现
LinkedHashMap
只需要重写afterNodeAccess
和afterNodeRemoval
方法,这也是为什么LinkedHashMap
没有重写put()
方法- 2.其入参
evict
是驱逐的意思,put() -> putVal()
时固定为true
,即调用afterNodeInsertion()
方法时evict
固定为true
- 3.因此是否会删除最老的
entry
,由removeEldestEntry()
方法决定,removeEldestEntry()
方法总是返回false
,因此LinkedHashMap
就算使用访问顺序,也只是让最老的entry
位于头部,并不会删除- 4.所以在使用
LinkedHashMap
实现LRU
时,一般都需要重写removeEldestEntry()
方法,让其在某种情况下返回true
,实现过期元素的清理- 6.newNode()和 newTreeNode<>()方法
- 1.
HashMap
的putVal()
方法中新增entry
是通过newNode()
和newTreeNode()
完成节点的创建- 2.
LinkedHashMap
重写了这两个创建节点的方法并在这两个方法中完成了entry
加入双向链表的逻辑- 7.
LinkedHashMap
没有对应的线程安全的替代类,只能通过Collections.synchronizedMap()
将其转为线程安全
3.TreeMap
- 1.TreeMap继承了AbstractMap抽象类实现了NavigableMap接口,说明拥有更强的元素搜索能力
- 2.TreeMap类实现了Cloneable接口,说明TreeMap可以被克隆
- 3.TreeMap类实现了Serializable接口,说明TreeMap可以被序列化
- 4.TreeMap类在JDK1.2版本发布,线程不安全,但是效率高(线程安全和效率冲突)
- 5.TreeMap基于红黑树实现,根据键进行排序(默认比较器为升序),且可以根据自定义的比较器进行排序
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable { // 自定义的比较器 private final Comparator<? super K> comparator; // 红黑树的根节点 private transient Entry<K,V> root; // 集合元素数量 private transient int size = 0; // 修改记录 private transient int modCount = 0; // 无参构造方法:comparator属性置为null // 使用key的自然顺序来维持TreeMap的顺序,这里要求key必须实现Comparable接口 public TreeMap() { comparator = null; } //带有比较器的构造方法:初始化comparator属性; public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } //带有map的构造方法: //同样比较器comparator为空,使用key的自然顺序排序 public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } //带有SortedMap的构造方法: //根据SortedMap的比较器来来维持TreeMap的顺序 public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } } }
// 内部类Entry,用泛型<K,V>修饰 static final class Entry<K,V> implements Map.Entry<K,V> { K key; // 键 V value; // 值 Entry<K,V> left; // 左节点 Entry<K,V> right; // 左节点 Entry<K,V> parent; // 父节点 boolean color = BLACK; // 颜色,ture为黑色,false为红色 Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return valEquals(key,e.getKey()) && valEquals(value,e.getValue()); } public int hashCode() { int keyHash = (key==null ? 0 : key.hashCode()); int valueHash = (value==null ? 0 : value.hashCode()); return keyHash ^ valueHash; } public String toString() { return key + "=" + value; } }
public V put(K key, V value) { // 声明一个变量t保存根节点 Entry<K,V> t = root; // 如果根节点为null,则创建第一个节点,根节点 if (t == null) { // 对传入的元素进行比较,若TreeMap没定义Comparator,则验证传入的元素是否实现了Comparable接口 compare(key, key); // 根节点赋值 root = new Entry<>(key, value, null); // 长度为1: size = 1; // 修改次数+1 modCount++; // 直接返回:此时根节点默认为黑色 return null; } //如果根节点不为null: int cmp; // 声明parent用于保存父节点 Entry<K,V> parent; // 将TreeMap中自定义比较器comparator赋值给cpr Comparator<? super K> cpr = comparator; // 判断TreeMap中自定义比较器comparator是否为null: if (cpr != null) { // do while循环,查找新插入节点的父节点: do { // 记录上次循环的节点t,首先将根节点赋值给parent: parent = t; //利用自定义比较器的比较方法:传入的key跟当前遍历节点比较: cmp = cpr.compare(key, t.key); //判断结果小于0,处于父节点的左边 if (cmp < 0) t = t.left; else if (cmp > 0) //判断结果大于0,处于父节点的右边 t = t.right; else //判断结果等于0,为当前节点,覆盖原有节点处的value return t.setValue(value); // 只有当t为null,也就是找到了新节点的parent了 } while (t != null); // 没有自定义比较器 } else { // 如果key为null if (key == null) // TreeMap不允许插入key为null,抛空指针异常 throw new NullPointerException(); // 将key转换为Comparable对象,若key没有实现Comparable接口,此处会报错 Comparable<? super K> k = (Comparable<? super K>) key; // 同上:寻找新增节点的父节点: do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } //构造新增节点对象: Entry<K,V> e = new Entry<>(key, value, parent); // 根据之前的比较结果,判断新增节点是在父节点的左边还是右边 if (cmp < 0) // 如果新节点key的值小于父节点key的值,则插在父节点的左侧 parent.left = e; else // 如果新节点key的值大于父节点key的值,则插在父节点的右侧 parent.right = e; // 核心方法:插入新的节点后,为了保持红黑树平衡,对红黑树进行调整 fixAfterInsertion(e); size++; modCount++; return null; } ... // 对插入的元素比较:若TreeMap没有自定义比较器,则调用调用默认自然顺序比较,要求元素必须实现Comparable接口 // 若自定义比较器,则用自定义比较器对元素进行比较 final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); } // 核心方法:维护TreeMap平衡的处理逻辑 private void fixAfterInsertion(Entry<K,V> x) { // 首先将新插入节点的颜色设置为红色 x.color = RED // TreeMap是否平衡的重要判断,当不在满足循环条件时,代表树已经平衡 // x不为null,不是根节点,父节点是红色(三者均满足才进行维护) while (x != null && x != root && x.parent.color == RED) { // 节点x的父节点 是 x祖父节点的左孩子 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // 获取x节点的叔叔节点y Entry<K,V> y = rightOf(parentOf(parentOf(x))); // 叔叔节点y是红色 if (colorOf(y) == RED) { // 将x的父节点设置黑色 setColor(parentOf(x), BLACK); // 将x的叔叔节点y设置成黑色 setColor(y, BLACK); // 将x的祖父节点设置成红色 setColor(parentOf(parentOf(x)), RED); // 将x节点的祖父节点设置成x(进入了下一次判断) x = parentOf(parentOf(x)); } else { // 叔叔节点y不为红色 // x为其父节点的右孩子 if (x == rightOf(parentOf(x))) { x = parentOf(x); // 左旋 rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); // 右旋 rotateRight(parentOf(parentOf(x))); } } else { // 节点x的父节点 是x祖父节点的右孩子 // 获取x节点的叔叔节点y Entry<K,V> y = leftOf(parentOf(parentOf(x))); // 判断叔叔节点y是否为红色 if (colorOf(y) == RED) { setColor(parentOf(x), BLACK);12 setColor(y, BLACK);5 setColor(parentOf(parentOf(x)), RED);10 x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); // 左旋 rotateLeft(parentOf(parentOf(x))); } } } root.color = BLACK; } ... // 获取节点的颜色 private static <K,V> boolean colorOf(java.util.TreeMap.Entry<K,V> p) { // 节点为null,则默认为黑色 return (p == null ? BLACK : p.color); } // 获取p节点的父节点 private static <K,V> Entry<K,V> parentOfEntry<K,V> p) { return (p == null ? null: p.parent); } // 对节点进行着色,TreeMap使用了boolean类型来代表颜色(true为红色,false为黑色) private static <K,V> void setColor(Entry<K,V> p, boolean c){ if (p != null) p.color = c; } // 获取左子节点 private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) { return (p == null) ? null: p.left; } // 获取右子节点 private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) > { return (p == null) ? null: p.right; } // 左旋 private void rotateLeft(Entry<K,V> p) { if (p != null) { java.util.TreeMap.Entry<K,V> r = p.right; p.right = r.left; if (r.left != null) r.left.parent = p; r.parent = p.parent; if (p.parent == null) root = r; else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r; r.left = p; p.parent = r; } } // 右旋 private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left; p.left = l.right; if (l.right != null) l.right.parent = p; l.parent = p.parent; if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l; l.right = p; p.parent = l; } }
public interface SortedMap<K,V> extends Map<K,V> { // 返回元素比较器。如果是自然顺序,则返回null Comparator<? super K> comparator(); // 返回从fromKey到toKey的集合:含头不含尾 java.util.SortedMap<K,V> subMap(K fromKey, K toKey); // 返回从头到toKey的集合:不包含toKey java.util.SortedMap<K,V> headMap(K toKey); // 返回从fromKey到结尾的集合:包含fromKey java.util.SortedMap<K,V> tailMap(K fromKey); // 返回集合中的第一个元素 K firstKey(); // 返回集合中的最后一个元素 K lastKey(); // 返回集合中所有key的集合 Set<K> keySet(); // 返回集合中所有value的集合 Collection<V> values(); // 返回集合中的元素映射 Set<Map.Entry<K, V>> entrySet(); }
public interface NavigableMap<K,V> extends SortedMap<K,V> { // 返回小于key的第一个元素 Map.Entry<K,V> lowerEntry(K key); // 返回小于key的第一个键 K lowerKey(K key); // 返回小于等于key的第一个元素 Map.Entry<K,V> floorEntry(K key); // 返回小于等于key的第一个键 K floorKey(K key); // 返回大于或者等于key的第一个元素 Map.Entry<K,V> ceilingEntry(K key); // 返回大于或者等于key的第一个键 K ceilingKey(K key); // 返回大于key的第一个元素 Map.Entry<K,V> higherEntry(K key); // 返回大于key的第一个键 K higherKey(K key); // 返回集合中第一个元素 Map.Entry<K,V> firstEntry(); // 返回集合中最后一个元素 Map.Entry<K,V> lastEntry(); // 返回集合中第一个元素,并从集合中删除 Map.Entry<K,V> pollFirstEntry(); // 返回集合中最后一个元素,并从集合中删除 Map.Entry<K,V> pollLastEntry(); // 返回倒序的Map集合 java.util.NavigableMap<K,V> descendingMap(); NavigableSet<K> navigableKeySet(); // 返回Map集合中倒序的Key组成的Set集合 NavigableSet<K> descendingKeySet(); java.util.NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive); java.util.NavigableMap<K,V> headMap(K toKey, boolean inclusive); java.util.NavigableMap<K,V> tailMap(K fromKey, boolean inclusive); SortedMap<K,V> subMap(K fromKey, K toKey); SortedMap<K,V> headMap(K toKey); SortedMap<K,V> tailMap(K fromKey); }
- 1.TreeMap内部类Entry
- 1.
TreeMap
基于红黑树实现,其内部也有一个Entry
内部类,并使用泛型<K,V>
修饰- 2.
Entry
内部类提供了left
代表左子节点,right
代表右子节点,parent
代表父子节点,color
代表节点的颜色,默认为黑色- 3.通过上述参数创建一个红黑树
- 2.NavigableMap和SortedMap接口
- 1.
NavigableMap接口
继承于SortedMap
接口,SortedMap
接口中定义了方法Comparator<? super K> comparator()
,返回一个比较器,用于集合排序- 2.
NavigableMap
接口在SortedMap
接口的基础上进行了增强,主要增加了对集合内元素的搜索获取操作,TreeMap
实现了NavigableMap
接口,说明可以通过Comparator
比较器对集合进行排序- 3.TreeMap排序原理
- 1.使用元素自然排序,需要区分两种情况:一种是Jdk定义的对象,一种是自己定义的对象
- 1.自然顺序比较中,需要让被比较的元素实现Comparable接口,否则在向集合里添加元素时报:"java.lang.ClassCastException: com.jiaboyan.collection.map.SortedTest cannot be cast to java.lang.Comparable"异常;
- 2.因为调用put()方法时会将传入的元素转化成Comparable类型对象,所以当你传入的元素没有实现Comparable接口时,就无法转换会报错
public class SortedTest implements Comparable<SortedTest> { private int age; public SortedTest(int age){ this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //自定义对象,实现compareTo(T o)方法: public int compareTo(SortedTest sortedTest) { int num = this.age - sortedTest.getAge(); //为0时候,两者相同: if(num==0){ return 0; //大于0时,传入的参数小: }else if(num>0){ return 1; //小于0时,传入的参数大: }else{ return -1; } } } public class TreeMapTest { public static void main(String[] agrs){ //自然顺序比较 naturalSort(); } //自然排序顺序: public static void naturalSort(){ //第一种情况:Integer对象 TreeMap<Integer,String> treeMapFirst = new TreeMap<Integer, String>(); treeMapFirst.put(1,"jiabon"); treeMapFirst.put(6,"jiabn"); treeMapFirst.put(3,"jiabon"); treeMapFirst.put(10,"jiabon"); System.out.println(treeMapFirst.toString()); //第二种情况:SortedTest对象 TreeMap<SortedTest,String> treeMapSecond = new TreeMap<SortedTest, String>(); treeMapSecond.put(new SortedTest(10),"joyan"); treeMapSecond.put(new SortedTest(1),"jioyan"); treeMapSecond.put(new SortedTest(13),"jioyan"); treeMapSecond.put(new SortedTest(4),"jian"); System.out.println(treeMapSecond.toString()); } }
- 2.使用自定义比较器排序,需要在创建TreeMap对象时,将自定义比较器对象传入到TreeMap构造方法中
- 1.自定义比较器对象,需要实现Comparator接口,并实现比较方法compare(T o1,T o2);使用自定义比较器排序则被比较的对象无需再实现Comparable接口
public class SortedTest { private int age; public SortedTest(int age){ this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class SortedTestComparator implements Comparator<SortedTest> { //自定义比较器:实现compare(T o1,T o2)方法: public int compare(SortedTest sortedTest1, SortedTest sortedTest2) { int num = sortedTest1.getAge() - sortedTest2.getAge(); if(num==0){//为0时候,两者相同: return 0; }else if(num>0){//大于0时,后面的参数小: return 1; }else{//小于0时,前面的参数小: return -1; } } } public class TreeMapTest { public static void main(String[] agrs){ //自定义顺序比较 customSort(); } //自定义排序顺序: public static void customSort(){ TreeMap<SortedTest,String> treeMap = new TreeMap<SortedTest, String>(new SortedTestComparator()); treeMap.put(new SortedTest(10),"hello"); treeMap.put(new SortedTest(21),"my"); treeMap.put(new SortedTest(15),"name"); treeMap.put(new SortedTest(7),"JON"); System.out.println(treeMap.toString()); } }
- 4.红黑树必须满几点要求:
- 1.树中每个节点必须是有颜色的,要么红色,要么黑色
- 2.树中的根节点必须是黑色的
- 3.树中的叶节点必须是黑色的
- 4.树中任意一个节点如果是红色的,那么它的两个子节点一点是黑色的
- 5.任意节点到叶节点(树最下面一个节点)的每一条路径所包含的黑色节点数目一定相同
- 5.左旋:对节点进行向左旋转处理
- 1.对节点
X
进行向左进行旋转,将其变成一个左子节点- 2.如果
Y
的的左子节点不为null
,则将其赋值X
的右子节点- 6.右旋:对节点进行向右旋转处理
- 1.对节点
Y
进行向右进行旋转,将其变成一个右子节点- 2.如果
X
节点的右子节点不为null
,则赋值给Y
的左子节点
4.Hashtable
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { private transient Entry<?,?>[] table; private transient int count; private int threshold; private float loadFactor; private transient int modCount = 0; public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } public Hashtable() { this(11, 0.75f); } public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); } }
public synchronized V put(K key, V value) { // Make sure the value is not null // 如果值为空抛出空指针异常 if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; // 获取键的hash值 int hash = key.hashCode(); // 取模运算获取下标 int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; // 如果当前下标有值,则往下遍历链表 for(; entry != null ; entry = entry.next) { // 如果当前内容相同,则覆盖 if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } // 直到找到没有值的位置,插入数据 addEntry(hash, key, value, index); return null; } private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; // 如果元素个数大于扩容阈值 if (count >= threshold) { // Rehash the table if the threshold is exceeded // 扩容 rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; } protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code // 扩容2倍 int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; // 遍历旧table,将其数据重哈希后放入新table for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
- 1.Hashtable继承自Dictionary字典抽象类,实现了Map接口和Cloneable以及Serializable接口,其内部使用Entry类保存数据
- 2.Hashtable和HashMap类似,主要区别在于Hashtable为了实现线程安全,几乎所有的方法上都加上了synchronized锁,从而导致Hashtable的效率十分低下
- 3.Hashtable不允许键或值为null,因为put方法中对value的值进行了判断,如果为null则抛出异常;且会调用key的hashCode()方法,如果key为null则会出现空指针异常;而HashMap中是调用Hash()方法,其中对key为null的情况将其赋值为0
- 4.HashMap底层数组长度必须为2的幂,这样可以减少hash冲突,默认为16;而HashTable底层数组长度可以为任意值,这就造成了hash算法散射不均匀,容易造成hash冲突,默认为11
- 5.HashMap的hash算法通过非常规设计,将底层table长度设计为2的幂,使用位与运算代替取模运算,减少运算消耗;而HashTable的hash算法首先使得hash值小于整型数最大值,再通过取模进行散射运算
- 5.HashMap是由数组+链表形成,在JDK1.8之后链表长度大于8时转化为红黑树;而HashTable一直都是数组+链表
- 6.HashTable继承自Dictionary类;而HashMap继承自AbstractMap类
5.Properties
- 1.Properties继承于Hashtable,表示一个持久的的属性集,且使用泛型<Object,Object>修饰
- 2.Properties 类位于 java.util.Properties ,一般用于项目配置文件
- 1.数据库的配置:
jdbc.properties
- 2.系统参数配置:
system.properties
- 3.
SpringBoot
配置文件:application.properties
- 3.Properties继承于Hashtable,所以拥有Hashtable的特性,同时还提供了其特有的属性和方法
- 4.Properties提供setProperty(String key, String value)和getProperty(String key)方法,强制使用String类型的数据进行存储
- 5.Propertie提供一系列load方法用于加载输入流中的数据
- 6.Propertie提供一系列store方法用于将数据写入到输出流中
public class Properties extends Hashtable<Object,Object> { private static final long serialVersionUID = 4112578634029874840L; protected Properties defaults; public Properties() { this(null); } public Properties(Properties defaults) { this.defaults = defaults; } public synchronized void load(Reader reader) throws IOException { load0(new LineReader(reader)); } public synchronized void load(InputStream inStream) throws IOException { load0(new LineReader(inStream)); } private void load0 (LineReader lr) throws IOException { char[] convtBuf = new char[1024]; int limit; int keyLen; int valueStart; char c; boolean hasSep; boolean precedingBackslash; while ((limit = lr.readLine()) >= 0) { c = 0; keyLen = 0; valueStart = limit; hasSep = false; //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); precedingBackslash = false; while (keyLen < limit) { c = lr.lineBuf[keyLen]; //need check if escaped. if ((c == '=' || c == ':') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break; } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { valueStart = keyLen + 1; break; } if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } keyLen++; } while (valueStart < limit) { c = lr.lineBuf[valueStart]; if (c != ' ' && c != '\t' && c != '\f') { if (!hasSep && (c == '=' || c == ':')) { hasSep = true; } else { break; } } valueStart++; } String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); put(key, value); } } public synchronized Object setProperty(String key, String value) { return put(key, value); } public String getProperty(String key) { Object oval = super.get(key); String sval = (oval instanceof String) ? (String)oval : null; return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; }
2.线程安全的集合
1.List
1.CopyOnWriteArrayList
- 很多应用场景中读操作可能远远大于写操作
- 由于读操作根本不会修改原有的数据,因此每次读取都进行加锁操作其实是一种资源浪费
- 读操作是线程安全的,所有应该允许多个线程同时访问List的内部数据
- 写时复制(Copy On Write,COW)思想是计算机程序设计领域的一种优化策略
- 其核心思想是:如果有多个访问器(Accessor)访问一个资源(如内存或者磁盘上的数据存储),它们会共同获取相同的指针指向相同的资源,只要有一个修改器(Mutator)需要修改该资源,系统就会复制一份专用副本(Private Copy)给该修改器,而其他访问器所见到的最初资源仍然保持不变,修改的过程对其他访问器都是透明的(Transparently)。
- COW的主要优点是如果没有修改器去修改资源,就不会创建副本,因此多个访问器可以共享同一份资源。
1.CopyOnWriteArrayList的原理
- CopyOnWrite(写时复制)就是在修改器对一块内存进行修改时,不直接在原有内存块上进行写操作,而是将内存复制一份,在新的内存中进行写操作,写完之后,再将原来的指针(或者引用)指向新的内存,原来的内存被回收
- CopyOnWriteArrayList是写时复制思想的一种典型实现,其含有一个指向操作内存的内部指针array,而可变操作(add、set等)是在array数组的副本上进行的。当元素需要被修改或者增加时,并不直接在array指向的原有数组上操作,而是首先对array进行一次复制,将修改的内容写入复制的副本中。写完之后,再将内部指针array指向新的副本,这样就可以确保修改操作不会影响访问器的读取操作
- CopyOnWriteArrayList是一个满足CopyOnWrite思想并使用Array数组存储数据的线程安全List
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** 对所有的修改器方法进行保护,访问器方法并不需要保护 */ final transient ReentrantLock lock = new ReentrantLock(); /** 内部对象数组,通过 getArray/setArray方法访问 */ private transient volatile Object[] array; /** *获取内部对象数组 */ final Object[] getArray() { return array; } /** *设置内部对象数组 */ final void setArray(Object[] a) { array = a; } // 省略其他代码 }
2.CopyOnWriteArrayList的读取操作
- 访问器的读取操作没有任何同步控制和锁操作,理由是内部数组array不会发生修改,只会被另一个array替换,因此可以保证数据安全
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; } public CopyOnWriteArrayList() { setArray(new Object[0]); } public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); } public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } ... private E get(Object[] a, int index) { return (E) a[index]; } public E get(int index) { return get(getArray(), index); } }
2.CopyOnWriteArrayList的写入操作
- CopyOnWriteArrayList的写入操作add()方法在执行时加了独占锁以确保只能有一个线程进行写入操作,避免多线程写的时候会复制出多个副本
public boolean add(E e) { final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { Object[] elements = getArray(); int len = elements.length; //复制新数组 Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { //释放 lock.unlock(); } }
- 从add()操作可以看出,在每次进行添加操作时,CopyOnWriteArrayList底层都是重新复制一份数组,再往新的数组中添加新元素,待添加完了,再将新的array引用指向新的数组。当add()操作完成后,array的引用就已经指向另一个存储空间了
- 既然每次添加元素的时候都会重新复制一份新的数组,那就带来了一个问题,就是增加了内存的开销,如果容器的写操作比较频繁,那么其开销就比较大。所以,在实际应用的时候,CopyOnWriteArrayList并不适合进行添加操作。但是在并发场景下,迭代操作比较频繁,CopyOnWriteArrayList就是一个不错的选择。
3.CopyOnWriteArrayList的迭代器实现
- CopyOnWriteArray有自己的迭代器,该迭代器不会检查修改状态,也无须检查状态。
- 因为被迭代的array数组可以说是只读的,不会有其他线程能够修改它。
static final class COWIterator<E> implements ListIterator<E> { //对象数组的快照snapshot /** Snapshot of the array */ private final Object[] snapshot; /** Index of element to be returned by subsequent call to next. */ private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } @SuppressWarnings("unchecked") public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } @SuppressWarnings("unchecked") public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } ... }
- 迭代器的快照成员会在构造迭代器的时候使用CopyOnWriteArrayList的array成员去初始化
CopyOnWriteArrayList的优点
- CopyOnWriteArrayList有一个显著的优点,那就是读取、遍历操作不需要同步,速度会非常快。所以,CopyOnWriteArrayList适用于读操作多、写操作相对较少的场景(读多写少)
2.CopyOnWriteArrayList和ReentrantReadWriteLock的比较
- CopyOnWriteArrayList和ReentrantReadWriteLock读写锁的思想非常类似,即读读共享、写写互斥、读写互斥、写读互斥。但是前者相比后者的更进一步:为了将读取的性能发挥到极致,CopyOnWriteArrayList读取是完全不用加锁的,而且写入也不会阻塞读取操作,只有写入和写入之间需要进行同步等待,读操作的性能得到大幅度提升。
2.Set
1.CopyOnWriteArraySet
2.ConcurrentSkipListSet
3.Map
1.ConcurrentHashMap
- ConcurrentHashMap是一个常用的高并发容器类,也是一种线程安全的哈希表
- Java 7以及之前版本中的ConcurrentHashMap使用Segment(分段锁)技术将数据分成一段一段存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问
- Java 8对其内部的存储结构进行了优化,使之在性能上有进一步的提升。
- ConcurrentHashMap和同步容器HashTable的主要区别在锁的类型和粒度上:HashTable实现同步是利用synchronized关键字进行锁定的,其实是针对整张哈希表进行锁定的,即每次锁住整张表让线程独占,虽然解决了线程安全问题,但是造成了巨大的资源浪费。
1.HashMap和HashTable的问题
2.ConcurrentSkipListMap
- 基础容器HashMap是线程不安全的,在多线程环境下,使用HashMap进行put操作时,可能会引起死循环,导致CPU利用率飙升,甚至接近100%,所以在高并发情况下是不能使用HashMap的
- 于是JDK提供了一个线程安全的Map——HashTable,HashTable虽然线程安全,但效率低下。HashTable和HashMap的实现原理几乎一样,区别有两点
- 1.HashTable不允许key和value为null。
- 2.HashTable使用synchronized来保证线程安全,包含get()/put()在内的所有相关需要进行同步执行的方法都加上了synchronized关键字,对这个Hash表进行锁定。
- HashTable线程安全策略的代价非常大,这相当于给整个哈希表加了一把大锁。当一个线程访问HashTable的同步方法时,其他访问HashTable同步方法的线程就会进入阻塞或轮询状态。若有一个线程在调用put()方法添加元素,则其他线程不但不能调用put()方法添加元素,而且不能调用get()方法来获取元素,相当于将所有的操作串行化。所以,HashTable的效率非常低下。
4.Queue
5.BlockingQueue
- 多线程环境中,通过BlockingQueue(阻塞队列)可以很容易地实现多线程之间的数据共享和通信,比如在经典的“生产者”和“消费者”模型中,通过BlockingQueue可以完成一个高性能的实现版本。
BlockingQueue的特点
- 阻塞队列与普通队列(ArrayDeque等)之间的最大不同点在于阻塞队列提供了阻塞式的添加和删除方法。
1.阻塞添加
- 阻塞添加是指当阻塞队列元素已满时,队列会阻塞添加元素的线程,直到队列元素不满时,才重新唤醒线程执行元素添加操作。
2.阻塞删除
- 阻塞删除是指在队列元素为空时,删除队列元素的线程将被阻塞,直到队列不为空时,才重新唤醒删除线程,再执行删除操作。