@[TOC]
HashMap底层类继承实现图
首先来看一下HashMap底层类的继承图
HashMap最高层的接口是Map接口。总的来说,HashMap继承了抽象AbstractMap实现类,并且实现了Serializable接口和Cloneable接口,这两个接口的具体功能请查看我关于ArrayList实现类的源码解析。
HashMap类的基本特点
- 以Key-Value的格式存储,key值不可以重复,value值可以重复
- 存储的key值和value值可以为null,但null值当做key键存储只能有一个,null值作为是value值可以有多个。
- HashMap是线程不安全实现类。与HashTable有很大区别,HashTable是线程安全实现类。key和value值都不可以重复,且key值不能为null值。
- HashMap类的初始化容量为16,每一次达到扩容后,扩容的容量是原来的两倍。
- java1.8之前的底层数据结构是数组和链表。数组是HashMap的主体,jdk1.8以后的 HashMap 在为了解决哈希冲突而进行了改变,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于64,那么会优先选择数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
- 使用2的幂次方作为哈希表的大小。因为在jdk7的HashMap类是基于数组和链表实现的,jdk8之后添加了红黑树,在HashMap类中,当某个key-value需要存储到数组里面时,必须要有对应的数组下标index,而且这个数组下标是不可以越界的,在存储过程中,首先由key得到hashCode值,hashCode是一个数值,它通过hashCode&(table.length-1)运算可以得到一个数组下标,这种运算方法是与运算,比取余运算速度快,因为是通过位运算进行的。在这里就可以知道为什么必须要使用2的幂次方作为哈希表的大小了,因为计算数组下标的运算方法限制:位运算
HashMap类底层数据结构解析
类的主要结构代码
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
这里没什么好说的,该说的上面已经提及。
类的主要变量
/**
* 默认的初始容量,必须是2的幂次方
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/*
* 最大容量,如果一个更高的值由任何一个带参数的构造函数隐式指定时使用。
* 必须是 2 <= 1<<30 的幂。
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
// 构造函数中未指定时使用的负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 树形阈值,该值必须要大于2并且应当至少为8
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 取消阈值, 最多 6 以在移除时进行收缩检测。
*/
static final int UNTREEIFY_THRESHOLD = 6;
// 最小树形容量,可进行树化的最下容量,
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table;
// 存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值(容量*填充因子) 当实际大小超过临界值时,会进行扩容
int threshold;
// 加载因子
final float loadFactor;
加载因子:
loadFactor 加载因子是控制数组存放数据的疏密程度,loadFactor 的值的范围是0到1,当loadFactor的值越接近于 1,那么 数组中存放的数据也就越多,存放的密度也就约密,而且链表的长度也会随之增加,loadFactor 的值越小,越接近于0,,数组中存放的数据也就越少,存放的密度也就越稀疏。
如果loadFactor 的值太大,当程序中需要查找元素时会导致查找元素效率低,如果loadFactor的值太小导致数组存放数据的利用率低,这样子存放的数据会很分散。loadFactor 的默认值为 0.75f,这个是官方给出的一个比较好值
HashMap类给定的默认容量为 16,负载因子为 0.75f。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,这是非常消耗性能的。
临界值threshold:
threshold = capacity * loadFactor,当 size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,临界值threshold是衡量数组是否需要扩增的一个标准。
Node节点类源码
static class Node<K,V> implements Map.Entry<K,V> {
// 存储hash值
final int hash;
// HashMap的key值
final K key;
// HashMao的value值
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; }
// 重写toString方法
public final String toString() { return key + "=" + value; }
// 获取hashCode值
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
// 重写Object类的equals方法
public final boolean equals(Object o) {
// 判断是当前的对象
if (o == this)
return true;
// 判断是否是Map.Entry
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
// 调用Object方法判断key值与value值是否相等
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
树节点类源码解析
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 父节点
TreeNode<K,V> left; // 左节点
TreeNode<K,V> right; // 右节点
TreeNode<K,V> prev; // 前一个节点,需要在删除时取消链接
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
/**
* 返回包含此节点的树的根。
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
}
HashMap类的hash源码对比
在JDK1.7和JDK1.7之前的HashMap类的底层是数组和链表,两者结合在一起就是链表散列。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,也就是调用HashMap类的hash方法,使用这个方法也就是实现扰动,可以防止一些实现比较差的hashCode方法从而产生哈希碰撞。通过 (table.length - 1) & hash 判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
拉链法:
将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
jdk1.8的HashMap类的hash源码:
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
jdk1.7的HashMap类的hash源码:
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
代码分析: 整体来说,JDK7的HashMap类的hash方法实现的整体性能差,而JDK8实现的HashMap类的hash方法性能比较好,因为JDK7的HashMap类的hash方法扰动了四次。
HashMap类的构造方法
默认构造方法:
// 构造一个具有初始默认容量的空HashMap对象。
public HashMap() {
// 适用于其他字段
this.loadFactor = DEFAULT_LOAD_FACTOR; defaulted
}
指定容量大小的构造方法:
// 构造一个具有指定初始容量和默认加载因子 (0.75) 的空HashMap,
// initialCapacity为指定的容量大小数值
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
指定容量大小和加载因子的构造函数:
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);
}
包含一个Map的构造函数:
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
putMapEntries()方法
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
// 获取table大小
int s = m.size();
if (s > 0) {
// 判断table是否已经初始化
if (table == null) {
// 如果没有初始化,重新计算容量负载因子。s为table的大小
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
// 如果超出最大的阈值,则重新计算并设置阈值。
if (t > threshold)
threshold = tableSizeFor(t);
}
// 如果已经初始化table,而且table的大小已经超出阈值,则扩容
else if (s > threshold)
resize();
// 遍历每一个元素,然后调用putVal()方法存储
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);
}
}
}
HashMap类添加元素
HashMap类添加元素首先需要根据hash值计算数组下标,如果该位置没有元素,那就直接插入,如果定位到的数组中有元素,那就需要和该元素比较hash值和key值,如果key值相等,那就直接覆盖,如果key值不相等,那就需要判断该节点是否为树节点或者是链表节点,如果是树节点,那就直接调用树节点的插入元素的方法e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果是链表节点,那就需要遍历链表节点并比较是否是相同的元素,如果实现相同的元素,那就执行值覆盖,返回旧值,如果链表中不存在相同的元素,那就把添加进的元素插入链表的尾部。
注意: 使用者只能使用HashMap提供的put()方法,而不能使用putVal()方法。putVal()方法只能内部使用。
// 将指定的值与此映射中的指定键相关联。如果映射先前包含键的映射,则替换旧值
public V put(K key, V value) {
// 传入key的hash值,key值,value值
return putVal(hash(key), key, value, false, true);
}
/**
* @param key的hash值
* @param key的值
* @param 要存放的value值
* @param onlyIfAbsent 如果为真,则不改变已存在的值。
* @param evict 如果为 false,则表处于创建模式。
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 判断table是否初始化完成,没有初始化完成则调用扩容函数
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// (n - 1) & hash 确定元素存放在哪个桶中,桶为空,
// 新生成结点放入桶中(此时,这个结点是放在数组中)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 如果桶中已经存在了其他的元素,不为空
else {
Node<K,V> e; K k;
// 分别比较hash值,key值是否相等而且不为空,如果符合,则取出这个节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 使用 e来存储 p
e = p;
//如果hash值不相等,也就是key不相等;则判断节点是否为树节点
else if (p instanceof TreeNode)
// 调用红黑树插入值的方法将元素数据存放进树节点中。
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 上面的底层数据结构都不是,那就是链表的数据结构了。
else {
// 遍历到链表最后的节点
for (int binCount = 0; ; ++binCount) {
// 为空则是最后的节点。
if ((e = p.next) == null) {
// 在节点尾部插入数据
p.next = newNode(hash, key, value, null);
// 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法
// 这个方法会根据 HashMap 数组来决定是否转换为红黑树。
// 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只能对数组扩容。
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 判断新添加的元素是否存在于链表中,也就是判断hash值和key值是否相等且不为空。
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 直接跳出,因为已经存在了该值。
break;
p = e;
}
}
// e!=null 说明存在旧值的key与要插入的key"相等"
if (e != null) {
V oldValue = e.value;
// 进行值覆盖,然后返回旧值
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果是由于新添加的元素导致容量达到最大值,则执行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
JDK8与JDK7的HashMap类的put方法对比
put(K key, V value)方法是将指定的key, value对添加到HashMap里。首先判断table是否为空,然后会对table做一次查找,看是否包含该元素,如果已经包含则执行值覆盖,然后直接返回,查找过程类似于getEntry()方法;如果没有找到,则会通过addEntry(int hash, K key, V value, int bucketIndex)方法插入新的entry,插入方式为头插法。
public V put(K key, V value)
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
// 查找数组的位置
int i = indexFor(hash, table.length);
// 遍历数组存储的链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 先遍历
Object k;
// 如果值相等,那就直接覆盖,然后返回旧值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 如果元素不存在,那就执行插入
addEntry(hash, key, value, i); // 再插入
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
// 判断负载因子如果超过阈值,且传入的桶数组不为空
if ((size >= threshold) && (null != table[bucketIndex])) {
//自动扩容
resize(2 * table.length);
// 重新计算哈希
hash = (null != key) ? hash(key) : 0;
bucketIndex = hash & (table.length-1);
}
//在冲突链表头部插入新的entry
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
HashMap类获取元素
public V get(Object key) {
Node<K,V> e;
// 查找数组元素,如果查找为空,则返回null,如果不为空,那就返回对应的值。
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
// 查找元素
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 判断table不为空且长度大于0,然后根据hash值计算数组下标
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 找出数组下标,判断第一个节点是否就是要寻找的节点元素。
if (first.hash == hash && // always check first node
// 检查对应的key值是否相等
((k = first.key) == key || (key != null && key.equals(k))))
// 确定第一个就是要找的,直接返回
return first;
// 第一个不是要找的,判断节点结构
if ((e = first.next) != null) {
// 如果节点是树节点
if (first instanceof TreeNode)
// 返回树节点查找元素的方法
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
// 如果是链表节点,那就一个个循环遍历并比较对应的key值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 找到直接返回
return e;
} while ((e = e.next) != null);
}
}
// 元素找不到返回为空
return null;
}
HashMap类删除元素
// 根据key值删除对应的映射
public V remove(Object key) {
Node<K,V> e;
// 删除元素,如果删除的元素找不到,则返回null,如果成功删除值,那就返回要删除对应的值。
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;
// 判断table是否不为空且长度大于0,根据hash值计算数组下标
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
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节点存储要删除的元素
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
// 如果数组中存储的是链表
do {
// 遍历并判断每一个节点是否是要删掉的元素,查找对于的key值。
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;
// 删除长度
--size;
afterNodeRemoval(node);
// 返回删除的节点数据
return node;
}
}
// 删除不成功
return null;
}
HashMap类的扩容流程
开辟内存空间: HashMap的扩容需要数组扩容,但是数组占用的是连续的内存空间,所以一旦需要扩容就需要重新开辟内存空间。
创建2的幂次方大小的数组: 因为HashMap计算数组下标的方法是通过位运算实现的,所以数组的大小必须是2的幂次方。
遍历数组并转移数据: 创建完数组后,就遍历旧数组上的每一个位置,如果数组位置上是一个链表,那么就把这个链表上的元素全部转移到新的数组上。
JDK7和JDK8扩容机制的不同对比
JDK7及其之前的java版本中HashMap的底层是链表加数组的结构,那么扩容的过程中,需要遍历链表上的每一个元素,然后按照每一个元素的hashCode值进行计算出新数组的下标,
JDK8之后的HashMap类的底层数据结构是数组和链表加红黑树,至于什么时候使用红黑树进行优化上文已经提及。在JDK8中会使用到一个双向链表来维护红黑树中的元素,首先jdk8在转移某个位置上的元素时,会判断这个位置是不是一个红黑树,如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。
扩容代码分析(如有纰漏,还望指正):
final Node<K,V>[] resize() {
// 保存旧的位桶数组 Node<K,V> table
Node<K,V>[] oldTab = table;
// 判断旧的是否为空,为空则赋值为零,不为空则赋值为旧的数组长度。
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 保存旧的HashMap的负载因子
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 超过最大值,重新调整后赋值
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);
}
// 计算新的resize上限
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"})
// 创建新的位桶数组对象,newCap为新的长度。
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 遍历bucket,把每一个bucket都移动到新的buckets中
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 {
// 这块是处理链表的情况,
// 需要将此链表拆成两个链表,放到新的数组中,并且保留原来的先后顺序
// loHead、loTail 对应一条链表,hiHead、hiTail 对应另一条链表。
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;
}
// 原索引+oldCap
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 原索引+oldCap放到bucket里
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
HashSet源码
首先来看一下HashSet的类继承构造图,有些东西前文已经有过描述,现在这里不做过多的解析。
HashSet类的底层是支持可序列化的,同时也支持可复制克隆的,HashSet类底层具体的存储是通过HashMap类完成的,包括去重机制。
底层数据结构
类变量: 其中HashMap是对应于存储HashSet的值,Object类是用于填充HashMap中的key-value格式的value值。
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
构造方法: HashSet类的构造方法是对应于HashMap类的构造方法的,HashMap类中构造方法有什么用,在HashSet类中就实现了什么作用。
// 第一种
public HashSet() {
map = new HashMap<>();
}
// 第二种
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
// 第三种
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
// 第四种
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet添加元素
首先由源码中可以看出HashSet类添加的值是填充到HashMap的key值,至于HashMap类对应的key-value格式的,则是由一个Object对象填充。当HashMap类添加元素成功,那就会返回null值,这个时候可以通过判断是否返回null值从而判断HashSet的元素是否添加成功。
// HashSet类的添加元素
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// 调用HashMap类的添加元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
HashSet删除元素
当HashSet类需要删元素时,调用HashMap类的删除元素,一旦HashMap删除元素成功,那就会返回key-value值中的value值,由于HashSet传递给HashMap的value值都是Object对象,那就说明value值都是相等的。HashSet从而可以判断返回的value值是否是Object对象即可。
// HashSet类删除元素
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
// HashMap类删除元素
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
HashSet去重机制
HashSet的去重机制是通过HashMap完成的,前文中已经有谈到,HashMap是支持Key-Value格式的,那么HashSet是一个个值的,那么它存储到HashMap中的值是只有Key值的。HashMap保存的所有的值中,value值都是相等的,HashMap类是通过重写hashCode函数和equals函数从而判断是否是同一个对象元素,所以HashSet的去重机制是通过上述两个方法实现的。只是在HashMap的基础上添加了一点修改。
@[TOC]
什么是链表?
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
单向链表
单向链表是通过每一个结点的指针指向下一个结点从而连接起来的结构,最后一个节点的next指针指向null值。单向链表的构造图如下所示:
单向循环链表
单向循环链表与单向链表类似,但是有一点不同的是单向循环链表的最后一个节点的next指针指向的不是null值,而是指向链表头部节点,这样子就形成了一个链表环。单向循环链表构造图如下所示:
双向链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继(next指针)和直接前驱(pre指针),pre指向前一个节点,next指向后一个节点。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。但是第一个节点head的pre指向null,最后一个节点的tail指向null。双向链表的构造图如下所示:
如果上面这幅图不够清楚,那么请看下面这幅图的构造,head结点的pre指针指向null值,而tail结点的next指针指向null值。
双向循环链表
双向循环链表和双向链表的不同在于,head节点的pre指针指向最后一个节点,最后一个节点的next指针指向第一个节点,也形成一个“环”。双向循环链表构造如图所示:
LinkedList集合
LinkedList类是List接口的实现类,但是它的实现方式和ArrayList类的实现方式是完全不同的,ArrayList类的底层是通过一个动态的Object[]数组实现的,ArrayList类支持随机访问,但是它的增加元素和删出元素的速度慢,而LinkedList类的底层是通过链表来实现的,它不支持随机访问,但是删除和插入元素的操作快。LinkedList是一个非线程安全的集合,只适合在单线程的环境下使用。LinkedList类实现了Deque接口、Queue接口,它还是一个队列,可以当成双端队列来使用。
LinkedList的类继承实现图
LinkedList类是List接口,Queue接口,Deque接口的实现类,它同时还继承了AbstractSequentialList的双向链表,所以LinkedList类可以当做是堆栈,队列或是双端队列进行使用。
LinkedList类实现了Serialieable接口,这表明LinkedList类是支持序列化和反序列化的,它能够通过序列化传输。LinkedList类同时还实现了Cloneable接口,覆盖重写了clone()函数,即支持复制克隆操作。
变量定义
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;
}
节点的定义
LinkedList源码中的节点代码,以及头节点、尾节点和存储元素变量, LinkedList类的底层是双向链表,双向链表是由一个一个节点单元连接在一起形成的,每一个节点中包含3个组成部分,上一个节点的内存地址、存储的数据、下一个节点的内存地址。如下所示:Node< E >类是LinkedList类中的静态内部类,表示节点对象,其中 item用于存储数据,next存储下一个节点的内存地址,prev存储上一个节点的内存地址。
// 定义静态内部类,链表节点对象类
private static class Node<E> {
// 定义泛型变量,LinkedList类每一个节点数据都是使用它存储的。
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() {
}
第二种构造方法: 将一个指定的集合添加到LinkedList中,先完成初始化,在调用添加操作
// 将一个指定的集合添加到LinkedList中,先完成初始化,在调用添加操作
public LinkedList(Collection<? extends E> c) {
this(); // 初始化列表
addAll(c); // 执行添加集合元素的操作
}
添加元素
添加元素有四个版本,添加头部元素,添加尾部元素,在指定位置添加元素,将一个集合转变成一个链表。其中addFirst(E e)方法是添加头部元素,因为有头指针指向链表的头部,只需要调整几个相关的引用即可。add(E e)是添加元素到尾部,具体与添加头部元素类似。add(int index, E element)方法是在指定下表处插入元素,需要先通过线性查找找到具体位置,然后修改相关引用完成插入操作。
添加头部元素
/**
* 在此列表的开头插入指定元素。
*/
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
// 将f节点指向头节点
final Node<E> f = first;
// 新建一个newNode节点,并以f节点作为后继节点
final Node<E> newNode = new Node<>(null, e, f);
// 将头节点指向新创建的节点
first = newNode;
// 如果头节点为空
if (f == null)
// 将尾节点指向新创建的节点
last = newNode;
else
// 如果头结点不为空,f节点的前驱节点指向新创建的节点
f.prev = newNode;
size++;
modCount++;
}
如图所示:
添加尾部元素
// 添加元素进尾部链表中
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 将指定元素添加进此链表的尾部。
*/
public void addLast(E e) {
linkLast(e);
}
// 将数据添加到链表的尾部
void linkLast(E e) {
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;
size++;
modCount++;
}
如图所示:
在指定位置添加元素
/**
* index: 指定位置
* element: 添加的元素
*/
public void add(int index, E element) {
// 检测index是否越界
checkPositionIndex(index);
// 如果添加的位置index等于链表长度,说明在链表的尾部添加元素
if (index == size)
linkLast(element);
else
// 在查找出index之后的元素插入新的元素
linkBefore(element, node(index));
}
如图所示:
添加集合元素
添加集合元素首先是检测当前链表中的长度,然后将集合转换成数组,如果传入的插入位置等于链表长度,那就说明在链表尾部添加元素。否则就是以集合为基础构建一个新的链表。
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
// 检测index是否越界
checkPositionIndex(index);
// 集合转变数组
Object[] a = c.toArray();
int numNew = a.length;
// 如果长度为零说明集合为空,直接返回false
if (numNew == 0)
return false;
Node<E> pred, succ;
// 如果index等于链表长度,从链表尾部进行添加元素
if (index == size) {
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
// 否则后继节点的next指针指向新创建的节点
pred.next = newNode;
// pred节点移动到下一个节点。
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
删除元素
删除元素有四种方法,一种是删除第一个元素removeFirst(),一种是删除最后一个元素removeLast() ,一种是删除指定元素remove(Object o),一种是删除指定位置的元素remove(int index)。
删除第一个元素
public E removeFirst() {
// 获取头部指针
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// 获取头部存储的元素
final E element = f.item;
// next指向链表的头部的下一个节点
final Node<E> next = f.next;
// 然后将链表头节点的元素和指针赋值null,有效帮助垃圾回收
f.item = null;
f.next = null; // help GC
// 将头部指针归位
first = next;
// 如果链表只有一个元素,那尾部指针也为空
if (next == null)
last = null;
else
// 如果链表不只有一个元素,那将第二个节点的前驱节点指向null,因为已经删除了头结点了
next.prev = null;
// 链表长度减1
size--;
modCount++;
// 返回保存的旧的节点
return element;
}
删除最后一个元素
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
// 删除最后一个节点,与删除第一个节点类似
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
删除指定元素
删除指定的元素需要先查找到该元素,如果该元素查找不到,说明不在链表中,那就直接返回false,如果链表为空,那也直接返回false,否则删除成功,则返回true。
public boolean remove(Object o) {
if (o == null) {
// 判断要删除的节点是否为null值,这是一个特殊的案例
for (Node<E> x = first; x != null; x = x.next) {
// 使用 == 比较的引用地址
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 使用for循环查找到要删除的元素
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
// equals比较的是值
unlink(x);
return true;
}
}
}
return false;
}
第一种情况: 删除的元素位于头结点
第二种情况:删除的元素位于尾部
第三种情况: 删除的元素的位置链表中间
删除指定位置元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
unlink()方法
E unlink(Node<E> x) {
// 保存要删除的节点
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 如果要删除的节点是头节点,那就将头节点直接指向下一个节点
if (prev == null) {
first = next;
} else {
// 如果删的不是头节点,删除节点的上一个节点的next指针指向删除节点的下一个节点
prev.next = next;
x.prev = null;
}
// 要删除的节点是尾节点
if (next == null) {
// last尾节点往前移动
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
查找元素
get(index)方法
LinkedList类的get(index)方法的作用是获取到链表中指定位置(index)的元素。首先会调用checkElementIndex(index)方法进行判断index是否越界,不越界,那就从链表中找到指定位置的元素并返回。
// 获取链表中指定位置的元素
public E get(int index) {
// 检测位置是否越界
checkElementIndex(index);
return node(index).item;
}
// 判断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;
}
// 返回指定位置的元素,为了效率问题,
// 如果说指定的位置大于链表长度的一半,那就从链表的尾部进行遍历,
// 否则从链表的头部进行遍历。
Node<E> node(int 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;
}
}
getFirst()方法
获取链表的第一个元素,首先获取到链表的头节点,然后判断头结点是否为空,不为空返回节点数据,为空则抛出异常。
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
getLast()方法
获取链表的最后一个元素,首先获取到链表的尾节点,然后判断尾结点是否为空,不为空返回节点数据,为空则抛出异常。
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
元素赋值
元素赋值是改变指定位置的元素,首先需要检测传入的指定位置是否越界,不越界那就查找到该元素,然后从新赋值并且返回旧的值。
public E set(int index, E element) {
// 检测元素越界
checkElementIndex(index);
// 查找到指定位置的元素
Node<E> x = node(index);
// 保存旧的值
E oldVal = x.item;
// 赋新值
x.item = element;
return oldVal;
}
clear()方法清空链表
为了让GC更快可以回收放置的元素,需要将node之间的引用关系赋空。
/**
* 删除列表中所有的元素。调用这个函数后,列表将为空。
*/
public void clear() {
// 清除节点之间的所有"非不要"的链接,如果丢弃的节点超过一代,
// 则有助于分代GC垃圾回收。 即使存在可到达的迭代,也肯定会释放内存
for (Node<E> x = first; x != null; ) {
// 遍历每一个节点,将所有的节点的值全部赋值为null值,帮助java虚拟机进行垃圾回收
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
Queue方法
/**
* 返回头部元素但不删除
*/
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* 检索但不删除此列表的头部(第一个元素)
*/
public E element() {
return getFirst();
}
/**
* 返回头部元素并删除
*/
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
/**
* 返回并删除头部元素
*/
public E remove() {
return removeFirst();
}
/**
* 添加指定元素作为此列表的尾部(最后一个元素)
*/
public boolean offer(E e) {
return add(e);
}
Deque方法
/**
* 在此列表的前面插入指定的元素。
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
/**
* 在此列表的末尾插入指定的元素。
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
/**
* 检索但不删除此列表的第一个元素,如果此列表为空,则返回 null。
*/
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
*检索但不删除此列表中的最后一个元素,如果此列表为空,则返回null
*/
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
/**
* 检索并删除此列表的第一个元素,如果此列表为空,则返回null。
*/
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
/**
* 检索并删除此列表的最后一个元素,如果此列表为空,则返回 null。
*/
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
/**
* 将元素推送到此列表表示的堆栈上。换句话说,在这个列表的前面插入元素。
*/
public void push(E e) {
addFirst(e);
}
/**
* 从此列表表示的堆栈中弹出一个元素。换句话说,删除并返回此列表的第一个元素。
*/
public E pop() {
return removeFirst();
}
/**
* 删除此列表中第一次出现的指定元素(从头到尾遍历列表时)。如果列表不包含该元素,则它不变。
*/
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
/**
* 删除此列表中指定元素的最后一次出现(从头到尾遍历列表时)。如果列表不包含该元素,则它不变。
*/
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
源码解析
public class LinkedList<E>extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// 保存LinkedList类中已经存放的元素个数
transient int size = 0;
// 定义头节点
transient Node<E> first;
// 定义尾节点
transient Node<E> last;
//构造方法,创建一个空的列表
public LinkedList() {
}
//将一个指定的集合元素添加到LinkedList类中,首先完成类的初始化,构造出一个空的列表,然后再调用添加操作将集合元素添加进列表里。
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//插入头节点
private void linkFirst(E e) {
// 将头节点赋值给f节点
final Node<E> f = first;
// new 一个新的节点,此节点的data = e , pre = null , next - > f
final Node<E> newNode = new Node<>(null, e, f);
first = newNode; //将新创建的节点地址复制给first
// f == null,表示此时LinkedList为空
if (f == null)
// 将新创建的节点赋值给last
last = newNode;
else
//否则f.前驱指向newNode
f.prev = newNode;
// 保存的集合元素加1
size++;
modCount++;
}
//插入尾节点
void linkLast(E e) {
// 将last(尾)节点赋值给l节点
final Node<E> l = last;
// 创建一个新的节点,节点元素数据是传入的要添加进尾部的数据
final Node<E> newNode = new Node<>(l, e, null);
// 尾节点执行新创建的节点
last = newNode;
// 如果尾节点为空,说明这个时候的链表为空
if (l == null)
// 将头结点指向新创建的节点
first = newNode;
else
// 如果尾节点不为空,将l节点
l.next = newNode;
size++;
modCount++;
}
//在succ节点前插入e节点,并修改各个节点之间的前驱后继
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//删除头节点
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
//删除尾节点
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//删除指定节点
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next; //获取指定节点的前驱
final Node<E> prev = x.prev; //获取指定节点的后继
if (prev == null) {
first = next; //如果前驱为null, 说明此节点为头节点
} else {
prev.next = next; //前驱结点的后继节点指向当前节点的后继节点
x.prev = null; //当前节点的前驱置空
}
if (next == null) { //如果当前节点的后继节点为null ,说明此节点为尾节点
last = prev;
} else {
next.prev = prev; //当前节点的后继节点的前驱指向当前节点的前驱节点
x.next = null; //当前节点的后继置空
}
x.item = null; //当前节点的元素设置为null ,等待垃圾回收
size--;
modCount++;
return element;
}
//获取LinkedList中的第一个节点信息
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//获取LinkedList中的最后一个节点信息
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//删除头节点
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//删除尾节点
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//将添加的元素设置为LinkedList的头节点
public void addFirst(E e) {
linkFirst(e);
}
//将添加的元素设置为LinkedList的尾节点
public void addLast(E e) {
linkLast(e);
}
//判断LinkedList是否包含指定的元素
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//返回List中元素的数量
public int size() {
return size;
}
//在LinkedList的尾部添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
//删除指定的元素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//将集合中的元素添加到List中
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//将集合中的元素全部插入到List中,并从指定的位置开始
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray(); //将集合转化为数组
int numNew = a.length; //获取集合中元素的数量
if (numNew == 0) //集合中没有元素,返回false
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index); //获取位置为index的结点元素,并赋值给succ
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) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
//删除List中所有的元素
public void clear() {
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
//获取指定位置的元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//将节点防止在指定的位置
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
//将节点放置在指定的位置
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
//删除指定位置的元素
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//判断索引是否合法
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//判断位置是否合法
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//索引溢出信息
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
//检查节点是否合法
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//检查位置是否合法
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//返回指定位置的节点信息
//LinkedList无法随机访问,只能通过遍历的方式找到相应的节点
//为了提高效率,当前位置首先和元素数量的中间位置开始判断,小于中间位置,
//从头节点开始遍历,大于中间位置从尾节点开始遍历
Node<E> node(int 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;
}
}
//返回第一次出现指定元素的位置
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
//返回最后一次出现元素的位置
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
//弹出第一个元素的值
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//获取第一个元素
public E element() {
return getFirst();
}
//弹出第一元素,并删除
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//删除第一个元素
public E remove() {
return removeFirst();
}
//添加到尾部
public boolean offer(E e) {
return add(e);
}
//添加到头部
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
//插入到最后一个元素
public boolean offerLast(E e) {
addLast(e);
return true;
}
//队列操作
//尝试弹出第一个元素,但是不删除元素
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//队列操作
//尝试弹出最后一个元素,不删除
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
//弹出第一个元素,并删除
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//弹出最后一个元素,并删除
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//如队列,添加到头部
public void push(E e) {
addFirst(e);
}
//出队列删除第一个节点
public E pop() {
return removeFirst();
}
//删除指定元素第一次出现的位置
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//删除指定元素最后一次出现的位置
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
//遍历方法
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
//内部类,实现ListIterator接口
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned = null;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
//静态内部类,创建节点
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;
}
}
/**
* @since 1.6
*/
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
/**
* Adapter to provide descending iterators via ListItr.previous
*/
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
@SuppressWarnings("unchecked")
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
// 继承了Cloneable接口,重写了clone()方法,支持复制克隆操作
public Object clone() {
LinkedList<E> clone = superClone();
// Put clone into "virgin" state
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
// 遍历元素,将元素添加进由克隆后产生的链表中
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
// 将链表转换成Object数组
public Object[] toArray() {
// 创建与链表长度相等的数组
Object[] result = new Object[size];
int i = 0;
// 遍历元素并添加元素
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
// 将链表转换成泛型数组
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
// 序列化标记
private static final long serialVersionUID = 876323262645176354L;
//将对象写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
//从输入流中将对象读出
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
}
无序性
什么是无序性?无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的,根据哈希值不同,可以改变元素存储的位置,及数组索引对应的元素映射也会改变。
不可重复性
什么是不可重复性?不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()方法和 HashCode()方法。因为假如存储一个对象,使用equals()方法判断,返回false,那hashCode()方法可能返回相同的哈希值,那么程序就会判别这是一个不同的元素,存储进集合元素里,那就违反了Set的不能存储相同元素的原则。
List
ArrayList实现类
Vector实现类
LinkedList实现类
Queue
Deque接口
PriorityQueue实现类
ArrayDeque实现类
Set
HashSet实现类
TreeSet实现类
LinkedHashSet实现类
HashMap实现类
LinkedHashMap实现类
SortedMap 接口和 TreeMap 实现类
WeakHashMap 实现类
IdentityHashMap 实现类
EnumMap实现类
备注:个人总结,可能有纰漏,还请各位大佬指正。
文章参考: