集合分类:
分类 | 特点 | 线程安全 | |
List | ArrayList | 底层数据结构是数组,查询快,增删慢, 元素可以重复 | 非线程安全,效率高 |
Vector | 底层数据结构是数组,查询快,增删慢, 元素可以重复 | 线程安全,效率低 | |
LinkedList | 底层数据结构是双向链表,查询慢,增删快, 元素可以重复 | 非线程安全,效率高 | |
Set | HashSet | 底层数据结构是哈希表,存储数据使用的是HashMap,元素不可以重复 | |
TreeSet | 底层数据结构是二叉树,元素不可以重复 | ||
LinkedHashSet | 元素不可以重复, 有序,存、取顺序一致 | ||
Map | HashMap | key和value可以为null,key 不能重复,value可以重复,无序 | 非线程安全 |
LinkedHashMap | key和value可以为null,key 不能重复,value可以重复,有序 | 非线程安全 | |
Hashtable | value不可以为null(存在疑问,记得key不可以为null,通过看1.8.0_60源码发现验证的是value不为null),key 不能重复,value可以重复,无序 | 线程安全 | |
TreeMap | key 不能重复,value可以重复,无序(元素顺序与添加顺序不一致), 默认会根据key进行排序 | 非线程安全 | |
IdentityHashMap | key如果为null会默认使用Object对象, 判断key是否相等用的是== 比较的是内存地址 | 线程不安全 | |
ConcurrentHashMap | key和value都不可以为null,key 不能重复,value可以重复 | 线程安全 |
List
类关系图
ArrayList
ArrayList变量
// 默认数组的长度为10
private static final int DEFAULT_CAPACITY = 10;
// 共享空数组 无默认数组长度
private static final Object[] EMPTY_ELEMENTDATA = {};
// 共享空数组 使用默认数组的长度
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 记录当前数组的实际长度
private int size;
// 存放实际数据的数组
transient Object[] elementData;
// 验证集合最大长度最大int值-8 实际集合最大长度为int最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造方法
- 无参构造
会创建一个空数组
// 共享空数组 使用默认数组的长度
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存放数据数组
transient Object[] elementData;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 有参构造 参数是int类型
// 共享空数组 无默认数组长度
private static final Object[] EMPTY_ELEMENTDATA = {};
// 初始化容量数据类型为int 所以数组最大长度为int的最大值 2^31-1
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果初始化容量大于0 则创建指定长度的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果初始化容量为0 则创建一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果初始化容量小于0 则直接抛异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- 有参构造 参数是Collection接口
public ArrayList(Collection<? extends E> c) {
// c.toArray() 将集合转成数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 验证集合对象是否是Object集合,如果不是继续先后执行
if (elementData.getClass() != Object[].class)
// 将elementData复制成一个新的数组重新赋值给elementData
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果集合长度为0 则创建空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
// Arrays.copyOf方法
// original-原数组
// newLength -新数组的长度
// newType -新数组的数据类型
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
// 先验证新数组的数据类型是否是Object 如果是则创建Object类型的数组,否则创建指定类型的数组
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 调用系统拷贝 将原数据拷贝到新数组里面
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
add方法 添加元素
// 添加元素 e需要添加的元素
public boolean add(E e) {
// 数组扩容
ensureCapacityInternal(size + 1);
// 将元素添加到elementData数组中,并且集合长度+1
elementData[size++] = e;
return true;
}
// minCapacity = 当前集合长度+1
private void ensureCapacityInternal(int minCapacity) {
// 如果elementData数组是默认有长度的数组 重新计算minCapacity
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA 默认数组长度为10
// 如果minCapacity比默认值10小 则赋值默认值10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 通过上面计算得到minCapacity,为目前数组的最小长度
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// 记录当前集合参数次数
modCount++;
// 如果最小数组长度比当前实际存储数据的数组长度大则进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// oldCapacity 存储实际数据的数组的长度
int oldCapacity = elementData.length;
// 新的容量为现在容量扩大一半
// newCapacity = oldCapacity + oldCapacity/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
// 如果新的容量小于最小容量 则新的容量=最小容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
// 如果新的容量 大于最大集合的大小 MAX_ARRAY_SIZE = int最大长度-8
newCapacity = hugeCapacity(minCapacity);
// 通过上面计算出最新数组长度 并进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 验证最小容量是否小于0
if (minCapacity < 0)
throw new OutOfMemoryError();
// 如果最小容量大于最大集合值 则返回最大int值,否则返回最大集合大小
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
size方法
public int size() {
// 直接返回集合的size变量
return size;
}
get方法
public E get(int index) {
// 验证下标是否正确
rangeCheck(index);
// 直接获取数组指定的下标元素返回
return elementData(index);
}
remove方法
- 通过下标删除
public E remove(int index) {
// 检查下标是否正确
rangeCheck(index);
// 记录操作次数+1
modCount++;
// 获取当前下标的元素
E oldValue = elementData(index);
// numMoved 获取需要移动的元素的个数
// 比如size=10, index=3 要移除下标为3的元素
// numMoved = 10 - 3 - 1 = 6;
int numMoved = size - index - 1;
if (numMoved > 0)
// 将elementData数组index+1位置(包含index+1)以后numMoved个元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 设置原最后一个元素的下标内容为null,并且集合长度-1
elementData[--size] = null;
// 返回删除的元素
return oldValue;
}
// 检查下标是否正确
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- 删除指定对象
public boolean remove(Object o) {
if (o == null) {
// 如果为null 循环数组删除第一个元素为null的对象,然后结束循环
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
// 如果两个对象相等删除 默认比较的是内存地址,如果需要比起其他需要重写equals
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
// 记录操作次数
modCount++;
// 获取需要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 移动元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
}
总结
- ArrayList默认长度我10
- 无参构造创建的时候数组长度为0,当第一次添加数据时才会进行扩容到10
- 每次扩容原容量的1.5倍, 计算公式 :新容量 = 原容量 + 原容量/2
- 数据结构:数组
LinkedList
LinkedList变量
// 集合长度
transient int size = 0;
// 指向链表第一个节点
transient Node<E> first;
// 指向链表最后一个节点
transient Node<E> last;
// 节点定义
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;
}
}
构造方法
- 无参构造
public LinkedList() {
}
- 有参构造 参数是Collection接口
// 创建时添加元素
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
// 在指定的位置添加 集合
public boolean addAll(int index, Collection<? extends E> c) {
// 验证下标是否正确
checkPositionIndex(index);
// 集合转成数组
Object[] a = c.toArray();
// 需要添加数组的长度
int numNew = a.length;
if (numNew == 0)
// 如果长度为0 则不添加数据
return false;
// 定义 pred-上一个节点,succ-当前index对应的节点
Node<E> pred, succ;
if (index == size) {
// 如果增加的下标和集合长度一样 上一个节点指向链表的最后一个接点,当前index的节点为null
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 创建节点
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
// 如果没有上一个节点 说明是第一个节点
first = newNode;
else
// 上一个节点的下一个节点指向当前新加的节点
pred.next = newNode;
//上一个节点指向重新赋值指向当前新加的节点 进行下一轮循环
pred = newNode;
}
if (succ == null) {
// 如果当前index下的接口为null 那么链表最后一个节点就是pred
last = pred;
} else {
// 如果当前index下的接口不为null 那么上一个节点的下一个节点指向当前index节点
// 当前index节点的上一个节点指向上一个接点
pred.next = succ;
succ.prev = pred;
}
// 到此LinkList 添加数据完成
// 集合长度 size = 原长度+新添加的集合的长度
size += numNew;
// 记录操作次数
modCount++;
return true;
}
// 验证下标是否正确
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
add方法 添加元素
public boolean add(E e) {
// 向链表最后添加元素
linkLast(e);
return true;
}
void linkLast(E e) {
// l指向最后一个节点
final Node<E> l = last;
// 创建节点
final Node<E> newNode = new Node<>(l, e, null);
// 最后节点指针指向当前新的几点
last = newNode;
if (l == null)
// 如果之前没有最后一个节点 说明现在是第一次添加数据 第一个节点指针指向当前新的节点
first = newNode;
else
// 如果之前已经有最后一个几点 之前最后一个几点下一个节点指向当前节点
l.next = newNode;
// 集合长度加1
size++;
// 记录操作次数
modCount++;
}
size方法
// 因为有单独记录集合长度的属性 直接返回就好
public int size() {
return size;
}
get 方法
public E get(int index) {
// 检查下标index是否正确
checkElementIndex(index);
return node(index).item;
}
// 获取指定下标的节点
Node<E> node(int index) {
// index < (size >> 1) 验证当前index是否在集合前一半内
// 在前一半 从集合的首部开始往后遍历链表
// 在后一半 从集合的尾部开始往前遍历链表
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 检查下标index是否正确
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 检查下标index是否正确
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
remove方法
- 第一种删除 根据下标删除
public E remove(int index) {
// 检查小标是否有效
checkElementIndex(index);
// node(index) 获取指定下标的节点
return unlink(node(index));
}
// 解除链表的关联
E unlink(Node<E> x) {
// 获取节点的元素数据
final E element = x.item;
// 下一个节点
final Node<E> next = x.next;
// 上一个节点
final Node<E> prev = x.prev;
// 下面开始处理链表节点之间的关联 start
if (prev == null) {
// 如果当前节点上一个节点为null 则将链表第一个节点指向当前节点的下一个节点
first = next;
} else {
// 上一个节点的下一个节点指向 当前节点的下一个节点
prev.next = next;
// 把当前节点的上一个节点置空
x.prev = null;
}
if (next == null) {
// 如果当前节点下一个节点为null 则将链表最后一个节点指向当前节点的上一个节点
last = prev;
} else {
// 当前节点的下一个节点的上一个节点 指向当前几点的上一个节点
next.prev = prev;
// 把当前节点的下一个节点置空
x.next = null;
}
// 开始处理链表节点之间的关联 end
// 把当前节点的元素置空 这样当前节点上的全部属性都置为空了
x.item = null;
// 集合长度-1
size--;
// 记录集合变化次数
modCount++;
return element;
}
- 第二种删除 删除指定对象
public boolean remove(Object o) {
if (o == null) {
// 如果传入对象为null 则遍历整个链表 找到第一个元素为null的节点删除然后退出循环
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 遍历整个链表 找到第一个元素与当前对象相同的的节点删除然后退出循环
// 注意比较对象是否相等 默认使用Object的,比较内存地址。如果需要比较其他需要重写equals
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
总结
- 数据结构:双向链表
- 通过下标获取数据会验证当下标是集合前半还是后半。在前半,从首位开始往前遍历;在后半,从尾部开始往前遍历。
Vector
Vector变量
// 指定集合扩容扩容量。如果为0 则每次扩容一倍,大于0在每次增加指定数量
protected int capacityIncrement;
// 扩容代码
// int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
// capacityIncrement 大于0 每次在原容量基础增加指定容量
// capacityIncrement 小于等于0 每次在原容量基础增加一倍容量
private void grow(int minCapacity) {
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);
}
构造方法
// initialCapacity 初始集合容量 默认10
// capacityIncrement 指定集合扩容每次在原容量基础上增加多少 默认是增加1倍
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
总结
- Vector和ArrayList非常相似。不同
不同:Vector可以指定扩容量 ArrayList不可以指定
不同:Vector线程安全 ArrayList非线程安全 - Vector方法使用synchronized修饰保证线程安全,所以效率比较低
面试题
- 你知道的List都有哪些
ArrayList LinkedList Vector - List 特点
有序,查询快,增删慢 - ArrayList LinkedList 底层数据结构
ArrayList是数组, LinkedList是双向链表 - ArrayList默认大小是多少,是如何扩容的
jdk1.7默认是10;jkd1.7之后默认是0,在第一次添加数据的时候会验证,如果是默认数组并在容量小于10会把容量扩充到10。
每次扩容原来的1.5倍 - List是线程安全的吗?如果要线程安全要怎么做?
List 中的Vector是线程安全的,如果是其他线程需要使用工具类Collections.synchronizedList(new ArrayList())中的方法。
Collections使用synchronized关键字保证线程安全 - Arrays.asList方法后的List可以扩容吗?
Arrays.asList方法后是final数组,不可以add元素,也不可以扩容
// Arrays.asList 创建的集合
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
// 用final 修饰
private final E[] a;
ArrayList(E[] array) {
// Objects.requireNonNull(array); 验证array是否为null
a = Objects.requireNonNull(array);
}
}
- List 和数组直间的转换
List -> 数组:用List中的toArray方法。
数组 -> List:用Arrays.asList(array)方法(如果想创建可以改变的List 可以用List的构造函数,例如ArrayList的 new ArrayList(Arrays.asList(array)))
Set
类关系图
HashSet
HashSet 变量
// 存储数据
private transient HashMap<E,Object> map;
// 存储数据中的value值
private static final Object PRESENT = new Object();
构造方法
- 无参构造
创建一个空的HashMap 数据全部使用默认值创建HashMap
public HashSet() {
// 创建一个空的HashMap
map = new HashMap<>();
}
- 指定初始容量
initialCapacity 指定HashMap的初始容量,使用默认的负载因子
public HashSet(int initialCapacity) {
// 指定HashMap的初始容量
map = new HashMap<>(initialCapacity);
}
- 指定初始容量 和 负载因子
创建HashMap的时候一般不建议修改负载因子,所以该方法尽量不用
// initialCapacity 初始容量大小
// loadFactor 负载因子大小
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
- 指定初始容量 负载因子 和 boolean值(LinkedHashSet使用)
创建一个LinkedHashMap 而不是HashMap,
最后一个参数dummy 只是为了区分其他构造函数(多态的使用),没有其他实际意义
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
- 构造函数 直接填写元素 参数是Collection接口
public HashSet(Collection<? extends E> c) {
// Math.max((int) (c.size()/.75f) + 1, 16)
// c.size()/.75f 计算出初始HashMap容量多少合适,
// +1是因为c/.75f计算结果需要取整,不+1可能存在存入数据会触发扩容。为了保证把c全部存储而不触发扩容
// Math.max((int) (c.size()/.75f) + 1, 16) 因为HashMap的默认长度是16,所以会进行一次比较取最大值,
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
// addAll 是AbstractCollection类中的方法
// addAll会循环调用add方法 因为HashSet已经重写了add方法所以会调用自己的add方法
addAll(c);
}
// 添加元素方法
public boolean add(E e) {
// 在map里面存入数据 value为默认值Object
// 应为map的put方法存入成功会返回null,所以比较是否==null验证是否存入成功
return map.put(e, PRESENT)==null;
}
// class AbstractCollection
public abstract class AbstractCollection<E> implements Collection<E> {
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
// 循环调用add 方法,
// 因为HashSet已经重写了add方法所以会调用HashSet的add方法
if (add(e))
modified = true;
return modified;
}
}
add方法 添加元素
public boolean add(E e) {
// 在map里面存入数据 value为默认值Object
// 应为map的put方法存入成功会返回null,所以比较是否==null验证是否存入成功
return map.put(e, PRESENT)==null;
}
size方法
会调用Map的size方法 返回Map的大小
public int size() {
return map.size();
}
remove方法
删除会调用map的remove方法,并且比较删除的key对应的value是否为默认的Object
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
总结
- HashSet 是用HashMap存储数据,不过也可以通过构造函数指定LinkedHashMap存储数据
- HashSet中Map中的value为固定值Object
- HashSet没有get方法,因为没有下标指针。查询数据只能循环遍历
- HashSet 中的大部分方法都是直接调用的是Map里面对应的方法
TreeSet
TreeSet变量
// 存储实际数据 NavigableMap是接口 继承了SortedMap接口
private transient NavigableMap<E,Object> m;
// 存储数据中固定value值
private static final Object PRESENT = new Object();
构造方法
- 无参构造
创建一个TreeMap
public TreeSet() {
// 调用有参构造 参数是一个NavigableMap接口
// TreeMap实现了NavigableMap接口
this(new TreeMap<E,Object>());
}
- 有参构造 参数是一个NavigableMap接口
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
- 有参构造 参数是一个Comparator接口
传入一个比较器,调用TreeMap 比较器构造方法传入一个比较器
public TreeSet(Comparator<? super E> comparator) {
// 调用TreeMap 比较器构造方法
this(new TreeMap<>(comparator));
}
- 有参构造 传入一个Collection接口
public TreeSet(Collection<? extends E> c) {
// 先调用无参构造创建一个TreeMap
this();
// 添加数据
addAll(c);
}
- 有参构造 参数是一个SortedSet接口
public TreeSet(SortedSet<E> s) {
// 先调用有参构造 参数是一个Comparator接口比较器 创建一个TreeMap
// s.comparator() 获取比较器
this(s.comparator());
// // 添加数据
addAll(s);
}
addAll方法
public boolean addAll(Collection<? extends E> c) {
// 如果存储实际数据的m为空 并且 传入的数据c不为空 并且 传入的数据c实现了SortedSet接口 并且存储实际数据的m是TreeMap
if (m.size()==0 && c.size() > 0 &&
c instanceof SortedSet &&
m instanceof TreeMap) {
// 满足条件 执行
// 因为上面验证了c instanceof SortedSet 所以下面可以进行强转
SortedSet<? extends E> set = (SortedSet<? extends E>) c;
// 因为上面验证了m instanceof TreeMap 所以下面可以进行强转
TreeMap<E,Object> map = (TreeMap<E, Object>) m;
// 获取c的比较器
Comparator<?> cc = set.comparator();
// 获取m的比较器
Comparator<? super E> mc = map.comparator();
// 如果比较器内存地址相等 或者 c的比较器等于m的比较器
if (cc==mc || (cc != null && cc.equals(mc))) {
// 满足条件执行 调用TreeMap的方法存入数据并排序
// TreeMap使用的是红黑树排序
map.addAllForTreeSet(set, PRESENT);
return true;
}
}
// 如果不满足上面的情况会调用父类的addAll
// 父类是AbstractCollection 通过源码可以看出实现调用的还是add()方法
return super.addAll(c);
}
public abstract class AbstractCollection<E> implements Collection<E> {
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
}
add方法
add方法会调用TreeMap的put方法
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
size方法
size方法会调用TreeMap的size方法
public int size() {
return m.size();
}
remove方法
remove方法会调用TreeMap的remove方法
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
总结
- TreeSet是用TreeMap存储数据
- 在创建的时候可以自己指定比较器,来指定排序顺序。默认是根据自然顺序排序的
- TreeSet的操作一般都会调用TreeMap中的方法来处理数据
LinkedHashSet
LinkedHashSet类的定义
// 继承了HashSet
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
}
构造方法
构造方法都是调用父类HashSet三个参数的构造方法,
父类三个参数的构造方法会创建LinkedHashMap
initialCapacity : 初始化容量大小
loadFactor : 负载因子大小
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 class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
}
总结
- LinkedHashSet继承了HashSet
- LinkedHashSet的创建都是调用的父类三参构造方法 创建的是LinkedHashMap
- LinkedHashSet的操作都会调用父类类方法
Map
类关系图
HashMap
HashMap变量
// 默认HashMap容量 16 2^4次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量 2^30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载因子 一般使用默认的不进行修改
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 指定当链表长度大于多少后变成红黑树 默认8
static final int TREEIFY_THRESHOLD = 8;
// 当长度小于6的时候从红黑树转换成链表
static final int UNTREEIFY_THRESHOLD = 6;
// 最小树形化参数 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)
// 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64;
//
transient Node<K,V>[] table;
//
transient Set<Map.Entry<K,V>> entrySet;
// 表示HashMap中存放key-value的数量(为链表和树中的key-value的总和)
transient int size;
//
transient int modCount;
// threshold表示阀值。当HashMap的size大于threshold时会执行resize(扩容)操作。
// threshold = capacit(容量)*loadFactor(加载因子)
int threshold;
// 加载因子 一般为默认值0.75f DEFAULT_LOAD_FACTOR
final float loadFactor;
构造方法
- 无参构造
public HashMap() {
// 指定加载因子为默认值 0.75f
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
- 指定初始容量和加载因子
指定的初始容量是map扩容前可以存放的数据,
public HashMap(int initialCapacity, float loadFactor) {
// 验证初始容量是否小于0
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;
// 设置阀值 目前的阀值是没有经过负载因子计算。在第一次put数据的时候会重新根据负载因子计算
// 计算公式 阀值 = 容量 * 负载因子
this.threshold = tableSizeFor(initialCapacity);
}
// 重新计算阀值 保证结果为2的幂次方
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;
}
- 指定初始容量
public HashMap(int initialCapacity) {
// 加载因子为默认值 0.75f
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
- 指定Map直接存入数据
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) {
// 如果table为null说明还没有数据 重新计算扩容阀值threshold
// (float)s / loadFactor) 计算新map的容量 +1.0F 是为了保证在put数据的时候一定不会再进行扩容
float ft = ((float)s / loadFactor) + 1.0F;
// ft和默认最大容量对比
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
// 如果新的容量大于当前容量则重新计算赋值
threshold = tableSizeFor(t);
}
else if (s > threshold)
// 如果之前已经有数据 并且新的容量大于之前的容量则进行扩容处理
resize();
// 循环新增数据 put到当前map里面
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 计算key的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 存入数据
// hash 当前key的hash
// key 新key
// value 新value
// onlyIfAbsent 当为true时如果key存在重复时不替换value值(实际在替换的时候会验证 onlyIfAbsent为fals或旧value为null 才替换)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
// tab 存储数据的节点数组
// p 存在hash冲突时,和数组下标中的链表中的元素相等的节点
// n 当前存储数据数组的长度
// i 根据当前key的hash和存储数据数组长度计算的 hash冲突存在的下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果table为空或者长度为0,即没有元素,那么使用resize()方法扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 计算插入存储的数组索引小标i(i = (n - 1) & hash),此处计算方法同 1.7 中的indexFor()方法
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果数组指定下标元素为空,即不存在hash冲突,则直接创建新的节点插入到数组
tab[i] = newNode(hash, key, value, null);
else {
// 存在hash冲突,解决冲突存储数据
// e 已经存在的数据的节点信息 当key不相等时为null
Node<K,V> e; K k;
// 判断tab[i] 下的链表第一个元素与当前元素是否相等 hash相等 并且key相等,相等用新的value覆盖旧的value
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// p instanceof TreeNode 验证插入的数据结构是红黑树还是链表
else if (p instanceof TreeNode)
// 红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 链表
// 循环遍历tab[i] 下的链表判断是否在链表上存在重复的key,如果存在则直接跳出循环
// 如果遍历完还是不存在重复的key则直接在链表后面添加新的节点
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 当前已经是最后一个节点 并且没有相等的key 则添加新的节点
p.next = newNode(hash, key, value, null);
// 验证节点是否大于等于8 如果大于等于则将链表转成红黑树
// binCount从0开始 所以当binCount=7的时候就代表链表长度已经到8了
// 当链表已经有8个节点了,此时再新链上第9个节点,在成功添加了这个新节点之后,立马做链表转红黑树。
if (binCount >= TREEIFY_THRESHOLD - 1)
// 将链表转成红黑树
treeifyBin(tab, hash);
break;
}
// 验证链表下的元素key是否有和新的key想等的,相等直接跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果e不为null
if (e != null) {
V oldValue = e.value;
// 如果需要替换value或这旧value为null 则将新value替换旧value
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
// 替换完成直接返回 无需在进行其他操作
return oldValue;
}
}
// 记录修改次数
++modCount;
// 新key 在当前链表中不存在 新增数据
// 数据长度+1 并且验证是否需要扩容。 当数据长度>阀值的时候会进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
// 创建新节点
// hash-key的hash值
// key
// value
// next 下一个节点
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
扩容
该方法有2中使用情况:1.初始化哈希表;2.当前数组容量过小,需要扩容
final Node<K,V>[] resize() {
// oldTab 目前节点数据
Node<K,V>[] oldTab = table;
// oldCap 目前节点数组长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// oldThr 目前的扩容阀值
int oldThr = threshold;
// 新的容量 新的扩容阀值
int newCap, newThr = 0;
// 1.如果原数组有容量(用在当数组存在数据的时候 进行处理)
// 2.如果原数组容量为0,验证原阀值是否大于0,如果阀值大于0则指定新的容量为原阀值的值。(用在 在创建map的时候指定了容量并且是第一次扩容 )
// 3.如果使用无参构造创建的map,第一次扩容时用。新容量=默认容量16, 新阀值=默认容量*默认加载因子 结果取整
if (oldCap > 0) {
// 1.如果原数组容量大于等于定义的最大容量则不在进行扩容,并且指定threshold(阀值)为int最大值
// 2.如果不满足1,
// newCap = oldCap << 1 新数组容量=原数组容量了2倍(oldCap << 1 相当于 oldCap *2)
// 如果新数组容量 小于定义的最大容量 并且 原数容量大于等于默认数组容量 新的阀值等于原阀值的2倍
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 如果新的阀值为0,则重新计算阀值 阀值=新的容量*加载因子,同时验证如果新的容量小于指定的最大容量,并且阀值也小于指定的最大容量 则指定新的阀值为计算之后的阀值,否则为最大int值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 指定阀值变量为最新计算出来的阀值
threshold = newThr;
// 创建新的节点数组 长度为新的容量
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 将新的节点数组 赋值给table变量
table = newTab;
if (oldTab != null) {
// 将老的数据复制到新的数组里面去
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
remove
public V remove(Object key) {
// 要删除的节点信息
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
// 删除节点
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
// node 要删除的节点信息
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 如果删除的是链表第一个
node = p;
else if ((e = p.next) != null) {
// 如果删除的是不是第一个元素,则循环链表匹配节点
if (p instanceof TreeNode)
// 红黑树查询节点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
// 链表查询节点
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
// 如果查询到删除节点 执行删除
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
// 红黑树删除
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
// 记录修改次数
++modCount;
// 存储数据大小-1
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
总结
-
默认长度为16
-
map最大长度为2^30 (1 << 30) = 1,073,741,824
-
创建HashMap对象 长度一定的是2次幂,在指定长度后会进行tableSizeFor处理,保证结果为2次幂
为什么长度结果是2次幂值
1)、&位运算速度快,至少比%取模运算块
2)、能保证 索引值 肯定在 capacity 中,不会超出数组长度
3)、putVal时用到(n - 1) & hash,当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n -
当链表长度大于等于8的时候将链表转为红黑树
ConcurrentHashMap
属性
static final int MOVED = -1; // 表示正在转移
static final int TREEBIN = -2; // 表示已经转换成树
put方法
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key和value不可以为null
if (key == null || value == null) throw new NullPointerException();
// 获取key的hash
int hash = spread(key.hashCode());
int binCount = 0;// 记录当前节点下的链表长度
// 自旋 for (Node<K,V>[] tab = table;;)一直循环 直到执行了break方法 跳出循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
// 第一次put的时候table没有初始化,则初始化table
tab = initTable();
// tabAt(tab, i = (n - 1) & hash)) 通过哈希计算出一个表中的位置因为n是数组的长度,所以(n-1)&hash肯定不会出现数组越界
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// casTabAt 如果这个位置没有元素的话,则通过cas的方式尝试添加,注意这个时候是没有加锁的
// new Node<K,V>(hash, key, value, null) 创建一个Node添加到数组中区,null表示的是下一个节点为空
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
/*
* 如果检测到某个节点的hash值是MOVED,则表示正在进行数组扩张的数据复制阶段,
* 则当前线程也会参与去复制,通过允许多线程复制的功能,一次来减少数组的复制所带来的性能损失
*/
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
/*
* 如果在这个位置有元素的话,就采用synchronized的方式加锁,
* 如果是链表的话(hash大于0),就对这个链表的所有元素进行遍历,
* 如果找到了key和key的hash值都一样的节点,则把它的值替换到
* 如果没找到的话,则添加在链表的最后面
* 否则,是树的话,则调用putTreeVal方法添加到树中去
*
* 在添加完之后,会对该节点上关联的的数目进行判断,
* 如果在8个以上的话,则会调用treeifyBin方法,来尝试转化为树,或者是扩容
*/
else {
V oldVal = null;
synchronized (f) {
// 再次取出要存储的位置的元素,跟前面取出来的比较
if (tabAt(tab, i) == f) {
// 取出来的元素的hash值大于0,当转换为树之后,hash值为-2
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 要存的元素的hash,key跟要存储的位置的节点的相同的时候,替换掉该节点的value即可
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
// 当使用putIfAbsent的时候,只有在这个key没有设置值得时候才设置
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
// 如果和第一个不是同样的hash,同样的key的时候,则判断该节点的下一个节点是否为空,
// 为空的话把这个要加入的节点设置为当前节点的下一个节点
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 表示已经转化成红黑树类型了 则往红黑树里面插入
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
// 当在同一个节点的数目达到8个的时候,则扩张数组或将给节点的数据转为tree
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
// 用在计算hash时进行安位与计算消除负hash
static final int HASH_BITS = 0x7fffffff;
// 计算key的hash
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
第一次put数据初始化
/**
* 用来控制表初始化和扩容的,默认值为0,当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75
* 当为负的时候,说明表正在初始化或扩张,
* -1表示初始化
* -(1+n) n:表示活动的扩张线程
*/
private transient volatile int sizeCtl;
private static final sun.misc.Unsafe U;
// 初始化数组table
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 第一次put的时候,table还没被初始化,进入while
while ((tab = table) == null || tab.length == 0) {
// sizeCtl初始值为0,当小于0的时候表示在别的线程在初始化表或扩展表
if ((sc = sizeCtl) < 0)
// Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间
// 因为其他线程在初始化表 当前线程就不需要在初始化
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
// 使用CAS比较保证线程安全。 SIZECTL:表示当前对象的内存偏移量,sc表示期望值,-1表示要替换的值,设定为-1表示要初始化表了
try {
if ((tab = table) == null || tab.length == 0) {
// 指定了大小的时候就创建指定大小的Node数组,否则创建指定大小(16)的Node数组
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
总结
- ConcurrentHashMap key的hash计算和HashMap key的hash计算不一同
- key和value不可以为null