LinkedHashMap是HashMap的子类,它的大部分实现与HashMap相同,两者最大的区别在于,HashMap的对哈希表进行迭代时是无序的,而 LinkedHashMap对哈希表迭代是有序的,LinkedHashMap默认的规则是,迭代输出的结果保持和插入key-value pair的顺序一致(当然具体迭代规则可以修改)。LinkedHashMap除了像HashMap一样用数组、单链表和红黑树来组织数据外,还额外维护了一个 双向链表,每次向linkedHashMap插入键值对,除了将其插入到哈希表的对应位置之外,还要将其插入到双向循环链表的尾部。它是由 数组 + 一个单项链表+一个双向链表组成。在原HashMap的数据结构基础上加一个双向链表
LinkedHashMap比HashMap多了一个双向链表的维护,在数据结构而言它要复杂一些,阅读源码起来比较轻松一些,因为大多都由HashMap实现了…
阅读源码的时候我们会发现多态是无处不在的~子类用父类的方法,子类重写了父类的部分方法即可达到不一样的效果!
比如:LinkedHashMap并没有重写put方法,而put方法内部的newNode()方法重写了。LinkedHashMap调用父类的put方法,里面回调的是重写后的newNode(),从而达到目的!
LinkedHashMap可以设置两种遍历顺序:
//双向链表的头节点
transient LinkedHashMap.Entry<K,V> head;
//双向链表的尾节点
transient LinkedHashMap.Entry<K,V> tail;
//排序的规则,false按插入顺序排序,true访问顺序排序
final boolean accessOrder;
//所以说accessOrder的作用就是控制访问顺序,设置为true后每次访问一个元素,就将该元素所在的Node变成最后一个节点,
改变该元素在LinkedHashMap中的存储顺序。
//LinkedHashMap的构造方法,都是通过调用父类的构造方法来实现,大部分accessOrder默认为false
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
上面是LinkedHashMap的构造方法,通过传入初始化参数和代码看出,LinkedHashMap的构造方法和父类的构造方法,是一一对应的。也是通过super()关键字来调用父类的构造方法来进行初始化,唯一的不同是最后一个构造方法,提供了AccessOrder参数,用来指定LinkedHashMap的排序方式,accessOrder =false -> 插入顺序进行排序 , accessOrder = true -> 访问顺序进行排序。
这个比较重要
private static class Entry<K,V> extends HashMap.Entry<K,V> {
//定义Entry类型的两个变量,或者称之为前后的两个指针
Entry<K,V> before, after;
//构造方法与HashMap的没有区别,也是调用父类的Entry构造方法
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
//删除
private void remove() {
before.after = after;
after.before = before;
}
//插入节点到指定的节点之前
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
//方法重写,HashMap中为空
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
//方法重写 ,HashMap中方法为空
void recordRemoval(HashMap<K,V> m) {
remove();
}
}
到这里已经可以知道,相对比HashMap,LinkedHashMap内部不光是使用HashMap中的哈希表来存储Entry对象,还另外维护了一个LinkedHashMapEntry,这些LinkedHashMapEntry内部又保存了前驱跟后继的引用,可以确定这是个双向链表。而这个LinkedHashMapEntry提供了对象的增加删除方法都是去更改节点的前驱后继指向。
LinkedHashMap并没有重写父类的put()方法,说明调用put方法时实际上调用的是父类的put方法。HashMap的put这里就不多说了中有这样一个方法
LinkedHashMap 重写了2个方法
ode<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);
linkNodeLast(p);
return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//用临时变量last记录尾节点tail
LinkedHashMap.Entry<K,V> last = tail;
//将尾节点设为当前插入的节点p
tail = p;
//如果原先尾节点为null,表示当前链表为空
if (last == null)
//头结点也为当前插入节点
head = p;
else {
//原始链表不为空,那么将当前节点的上节点指向原始尾节点
p.before = last;
//原始尾节点的下一个节点指向当前插入节点
last.after = p;
}
}
//把当前节点放到双向链表的尾部
void afterNodeAccess(HashMap.Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//当 accessOrder = true 并且当前节点不等于尾节点tail。这里将last节点赋值为tail节点
if (accessOrder && (last = tail) != e) {
//记录当前节点的上一个节点b和下一个节点a
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//释放当前节点和后一个节点的关系
p.after = null;
//如果当前节点的前一个节点为null
if (b == null)
//头节点=当前节点的下一个节点
head = a;
else
//否则b的后节点指向a
b.after = a;
//如果a != null
if (a != null)
//a的前一个节点指向b
a.before = b;
else
//b设为尾节点
last = b;
//如果尾节点为null
if (last == null)
//头节点设为p
head = p;
else {
//否则将p放到双向链表的最后
p.before = last;
last.after = p;
}
//将尾节点设为p
tail = p;
//LinkedHashMap对象操作次数+1,用于快速失败校验
++modCount;
}
}
通过重写put方法,来维护一个双向链表,使得存取有序。
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key); //调用父类的getEntry()方法
if (e == null)
return null;
e.recordAccess(this); //判断排序方式,如果accessOrder = true , 删除当前e节点
return e.value;
}
相比于 HashMap 的 get 方法,这里多出了第 5,6行代码,当 accessOrder = true 时,即表示按照最近访问的迭代顺序,会将访问过的元素放在链表后面。
public TreeMap(): 自然排序
public TreeMap(Comparator<? super K> comparator): 使用的是比较器排序
之前已经学习过HashMap和LinkedHashMap了,HashMap不保证数据有序,LinkedHashMap保证数据可以保持插入顺序,而如果我们希望Map可以保持key的大小顺序的时候,我们就需要利用TreeMap了。public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
// 如果该节点存在,则替换值直接返回
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
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)
parent.left = e;
else
parent.right = e;
// 红黑树平衡调整
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
// 按照二叉树搜索的方式进行搜索,搜到返回
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
for(Entry<Integer, String> entry : tmap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
for(Iterator<Map.Entry<String, String>> it = tmap.entrySet().iterator() ; tmap.hasNext(); ) {
Entry<Integer, String> entry = it.next();
System.out.println(entry.getKey() + ": " + entry.getValue());
}
successor
这个是过的后继的重点。static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.right != null) {
// 有右子树的节点,后继节点就是右子树的“最左节点”
// 因为“最左子树”是右子树的最小节点
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
// 如果右子树为空,则寻找当前节点所在左子树的第一个祖先节点
// 因为左子树找完了,根据LDR该D了
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
// 保证左子树
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
- HashSet 实现了 Set 接口,不允许插入重复的元素,允许包含 null 元素,且不保证元素迭代顺序,特别是不保证该顺序恒久不变
- HashSet 的代码十分简单,去掉注释后的代码不到两百行。HashSet 底层是通过 HashMap 来实现的。
- HashSet是根据hashCode来决定存储位置的,是通过HashMap实现的,所以对象必须实现hashCode()方法,存储的数据无序不能重复,可以存储null,但是只能存一个。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable{
//序列化ID
static final long serialVersionUID = -5024744406713321676L;
//HashSet 底层用 HashMap 来存放数据
//Key值由外部传入,Value则由 HashSet 内部来维护
private transient HashMap<E,Object> map;
//HashMap 中所有键值对都共享同一个值
//即所有存入 HashMap 的键值对都是使用这个对象作为值
private static final Object PRESENT = new Object();
//无参构造函数,HashMap 使用默认的初始化大小和装载因子
public HashSet() {
map = new HashMap<>();
}
//使用默认的装载因子,并以此来计算 HashMap 的初始化大小
//+1 是为了弥补精度损失
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
//为 HashMap 自定义初始化大小和装载因子
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
//为 HashMap 自定义初始化大小
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
//此构造函数为包访问权限,只用于对 LinkedHashSet 的支持
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
//将对 HashSet 的迭代转换为对 HashMap 的 Key 值的迭代
public Iterator<E> iterator() {
return map.keySet().iterator();
}
//获取集合中的元素数量
public int size() {
return map.size();
}
//判断集合是否为空
public boolean isEmpty() {
return map.isEmpty();
}
//判断集合是否包含指定元素
public boolean contains(Object o) {
return map.containsKey(o);
}
//如果 HashSet 中不包含元素 e,则添加该元素,并返回 true
//如果 HashSet 中包含元素 e,则不会影响 HashSet ,并返回 false
//该方法将向 HashSet 添加元素 e 的操作转换为向 HashMap 添加键值对
//如果 HashMap 中包含 key 值与 e 相等的结点(hashCode() 方法返回值相等,通过 equals() 方法比较也返回 true)
//则新添加的结点的 value 会覆盖原有数据,但 key 不会有所改变
//因此如果向 HashSet 添加一个已存在的元素时,元素不会被存入 HashMap 中
//从而实现了 HashSet 元素不重复的特征
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//移除集合中的元素 o
//如果集合不包含元素 o,则返回 false
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
//清空集合中的元素
public void clear() {
map.clear();
}
@SuppressWarnings("unchecked")
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());
// Write out size
s.writeInt(map.size());
// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}
/**
* Reconstitute the HashSet instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
//为了并行遍历数据源中的元素而设计的迭代器
public Spliterator<E> spliterator() {
return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}
}
想要理解LinkedHashSet,则需要对 HashMap 、HashSet 和 LinkedHashMap 的源码有所了解,因为 LinkedHashSet 的内部实现都是来自于这三个容器类,其内部源码十分简单,简单到它只有一个成员变量、四个构造函数、一个 Set 接口的方法。
LinkedHashSet的所有源码
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
//序列化ID
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);
}
}
LinkedHashSet 继承于 HashSet,而 LinkedHashSet 调用的父类构造函数均是
private transient HashMap map;
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
即 LinkedHashSet 底层是依靠 LinkedHashMap 来实现数据存取的,而 LinkedHashMap 继承于 HashMap,在内部自己维护了一条双向链表用于保存元素的插入顺序。
因此使得 LinkedHashSet 也具有了存取有序,元素唯一的特点。
TreeSet是根据二叉树实现的,也就是TreeMap, 放入数据不能重复且不能为null,可以重写compareTo()方法来确定元素大小,从而进行升序排序。TJava中的TreeSet是基于红黑树实现的Set集合。它继承自AbstractSet类,实现了NavigableSet接口,提供了按自然顺序或自定义比较器排序的元素迭代器。
TreeSet提供了一些常用的方法,例如add()、remove()、contains()、size()等方法。这些方法的时间复杂度都是O(log n),其中n是集合中元素的数量。
除了基本的集合操作,TreeSet还提供了一些高级的API,例如descendingSet()、tailSet()、subSet()等方法,它们返回的是一个新的TreeSet对象,但是按照不同的顺序进行排序。
需要注意的是,TreeSet中的元素必须实现Comparable接口或者提供一个Comparator对象来进行比较。如果元素没有实现Comparable接口,那么默认情况下会使用元素的自然顺序进行排序。如果需要自定义排序规则,则需要提供一个Comparator对象来实现比较逻辑。
常用方法:
TreeSet():默认构造函数,创建一个空的TreeSet集合。
TreeSet(Collection extends E> c):构造函数,创建一个包含指定集合元素的TreeSet集合。
add(E e):添加一个元素到TreeSet集合中。如果该元素已经存在,则不进行任何操作;否则,将该元素插入到合适的位置以保持有序性。
remove(Object o):删除指定元素,如果该元素存在,则删除并返回true;否则,返回false。
contains(Object o):检查TreeSet集合中是否包含指定元素,如果存在则返回true;否则,返回false。
first():返回TreeSet集合中的最小元素(按自然顺序排列)。如果该集合为空,则返回null。
last():返回TreeSet集合中的最大元素(按自然顺序排列)。如果该集合为空,则返回null。
headSet(E toElement):返回一个视图,包含TreeSet集合中小于指定元素的子集。该方法可以用于截取子集。
tailSet(E fromElement):返回一个视图,包含TreeSet集合中大于等于指定元素的子集。该方法可以用于截取子集。
subSet(E fromElement, E toElement):返回一个视图,包含TreeSet集合中在指定范围内的子集。该方法可以用于截取子集。
iterator():返回一个迭代器,用于遍历TreeSet集合中的元素。迭代器支持add操作,但每次迭代只能修改一次集合。
size():返回TreeSet集合中元素的数量。
isEmpty():检查TreeSet集合是否为空,如果为空则返回true;否则,返回false。
clear():清空TreeSet集合中的所有元素。
Iterator迭代器应对多线程并发修改的fail_fast机制是指在迭代过程中,如果集合的结构发生了改变(例如添加、删除元素),则迭代器会抛出ConcurrentModificationException异常。
fail_fast机制是一种避免多线程并发修改时出现不可预知结果的机制。当迭代器检测到集合结构发生改变时,它会立即抛出ConcurrentModificationException异常,而不是继续迭代下去。
这种机制可以保证数据的一致性和正确性,因为在多线程并发修改的情况下,可能会出现数据不一致的情况,例如一个线程正在遍历集合,而另一个线程在同时修改集合,这样就会导致迭代器遍历到不正确的数据。
需要注意的是,fail_fast机制并不是绝对安全的,它只能在一定程度上避免多线程并发修改时出现不可预知的结果。如果需要更加安全的数据操作,可以考虑使用线程安全的集合类,例如CopyOnWriteArrayList、ConcurrentHashMap等。