Java集合类Collection包括ArrayDeque,ArrayList,LinkedList,HashSet,LinkedHashSet和Map接口HashMap,LinkedHashMap

本文主要是介绍Java集合类Collection包括ArrayDeque,ArrayList,LinkedList,HashSet,LinkedHashSet和Map接口HashMap,LinkedHashMap,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Collection

在这里插入图片描述
Collection是单列集合类顶级接口,没有直接实现类,只有更具体细分的接口,如List和Set。

collections可以是有序的,无须的,允许重复的,不允许重复的。

Queue

Queue是Collection的子接口

public interface Queue<E> extends Collection<E> {// 队列中插入元素e,成功返回true,空间不够抛异常(IllegalStateException)boolean add(E e);// 队列中插入元素e,成功返回true,对于有长度限制的queue,offer优于add,因为add插入失败只能抛异常boolean offer(E e);// 获取并删除队列头,队列为空的时候抛异常E remove();// 获取并删除队列头,队列为空的时候返回nullE poll();// 获取不删除队列头,队列为空的时候抛异常E element();// 获取不删除队列头,队列为空的时候返回nullE peek();
}
Deque

Deque is short for “Double Ended QUEue”,是Queue的子接口。

First Element (Head)Last Element (Tail)
Throws exceptionSpecial valueThrows exceptionSpecial value
InsertaddFirst(e)offerFirst(e)addLast(e)offerLast(e)
RemoveremoveFirst()pollFirst()removeLast()pollLast()
ExaminegetFirst()peekFirst()getLast()peekLast()
ArrayDeque

在这里插入图片描述

特性
  1. 数组实现双端队列,
  2. int类型的head和tail标记队列头尾,转圈循环使用,头尾碰撞后扩容一倍
  3. 自动扩容,a power of 2
  4. 线程不安全
  5. 不能添加null元素,会报空指针
  6. 可用作栈,比Stack快;可用作队列,比LinkedList快
  7. 插入和删除操作时有两种处理方式:Throw exception和return Special value
字段
transient Object[] elements; // 存储元素的数组,a power of 2,数组大小就是deque的容量
transient int head; // deque的头元素角标,开始的时候tail和head值相等
transient int tail; // deque的下一个新元素的角标
private static final int MIN_INITIAL_CAPACITY = 8;
添加元素
  • 添加元素的操作由两个方法实现的:addFirst(E e)addLast(E e)
方法调用的方法
addaddLast
addFirstaddFirst
addLastaddLast
pushaddFirst
offeraddLast
offerFirstaddFirst
offerLastaddLast
  • addLast
public void addLast(E e) {if (e == null)throw new NullPointerException();elements[tail] = e;// 改变tail指向的位置(tail只有右移和跳转到头部两个动作),并判断tail和head是否碰撞,碰撞代表数组已满// 1、& (elements.length - 1)保证了结果在0 到 elements.length - 1范围内// 2、(tail + 1) & (elements.length - 1):tail后移一位或跳转到头部0角标(跳转必定从0开始)// 3、tail == head:tail和head碰撞,数组已满,触发扩容操作doubleCapacity()if ( (tail = (tail + 1) & (elements.length - 1)) == head)doubleCapacity();
}
  • addFirst
public void addFirst(E e) {if (e == null)throw new NullPointerException();// 改变head指向的位置,判断碰撞(head只有左移和跳转到尾部两个动作)elements[head = (head - 1) & (elements.length - 1)] = e;if (head == tail)doubleCapacity();
}
  • 总结

    添加元素操作实际就是

    1. 在头或尾添加元素;
    2. head值减1或跳转到尾部或者tail值加1或跳转到头部
删除元素
  • 删除操作由两个方法实现:pollFirstpollLast
方法调用的方法
removepollFirst
removeFirstpollFirst
removeLastpollLast
removeFirstOccurrence需要从头到尾遍历然后再依次补位复制
removeLastOccurrence需要从尾到头遍历然后再依次补位复制
pollpollFirst
pollFirstpollFirst
pollLastpollLast
  • pollFirst
public E pollFirst() {int h = head;E result = (E) elements[h];if (result == null)return null;elements[h] = null;     // Must null out slot//head右移一位或跳转到头部(head值为0)head = (h + 1) & (elements.length - 1);return result;
}
  • pollLast
public E pollLast() {// tail左移1位或跳转到尾部(tail值为elements.length - 1)int t = (tail - 1) & (elements.length - 1);E result = (E) elements[t];if (result == null)return null;elements[t] = null;tail = t;return result;
}
  • 总结

    删除元素操作:与添加元素的操作相反,

    1. 头或尾移除元素
    2. head值加1或跳转到头部或者tail值减1或跳转到尾部
查看首尾元素

元素不存在时get方法会抛异常

public E getFirst() {E result = (E) elements[head];if (result == null)//不允许null值存在throw new NoSuchElementException();return result;
}public E getLast() {E result = (E) elements[(tail - 1) & (elements.length - 1)];if (result == null)//不允许null值存在throw new NoSuchElementException();return result;
}

元素不存在时peek方法返回null,不抛异常

public E peekFirst() {return (E) elements[head];
}public E peekLast() {return (E) elements[(tail - 1) & (elements.length - 1)];
}
扩容
private void doubleCapacity() {assert head == tail;int p = head;int n = elements.length;int r = n - p; // number of elements to the right of pint newCapacity = n << 1;//扩容一倍if (newCapacity < 0)// 超出int值范围throw new IllegalStateException("Sorry, deque too big");Object[] a = new Object[newCapacity];System.arraycopy(elements, p, a, 0, r);System.arraycopy(elements, 0, a, r, p);// 拷贝到头部elements = a;head = 0;// 重置头标记位tail = n;// 重置尾标记位
}
其他方法

size

// size方法很巧妙
public int size() {return (tail - head) & (elements.length - 1);
}

isEmpty

public boolean isEmpty() {return head == tail;
}
List

有序集合接口,元素可以重复。

接口方法
boolean add(E e); // 添加(append)元素到集合尾部
void add(int index, E element);// 添加元素到指定位置, 原位置及后边元素不删除依次后移
E remove(int index);// 删除指定位置元素, 原位置后边的元素依次前移,返回被删除元素
boolean remove(Object o);// 删除指定元素(第一个), 后面元素依次前移
int indexOf(Object o);// 返回指定元素所在位置,不存在则返回-1
boolean contains(Object o);// 是否包含指定元素
E get(int index);// 获取指定位置的元素
E set(int index, E element);// 替换指定位置的元素为指定值
int size();// 返回集合中存储的元素个数
ArrayList

在这里插入图片描述

数据结构是数组,线程不安全。不指定容量时初始化为空数组,初次add时初始化为默认容量10。继续add可触发扩容,扩容条件为size+1 > elementData.length, 扩容后数组大小为原大小1.5倍(int newCapacity = oldCapacity + (oldCapacity >> 1);)。

重要字段
// 初始值 懒加载
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 默认大小
private static final int DEFAULT_CAPACITY = 10;
// 实际存储单元
transient Object[] elementData;

PS:注意add(int index, E element)方法,是将自index起至数组尾部的值后移动一位,空出的位置插入element值,时间复杂度为O(n)

添加元素

添加元素分两种情况:

  1. 尾端添加(append),直接添加即可,size++;
  2. 指定位置(index)添加,需要原index~size-1元素右移,size++。
// 直接放到末尾 时间复杂度O(1)
public boolean add(E e) {ensureCapacityInternal(size + 1);  // 先保证容量足够,不够的话扩容(后边说)elementData[size++] = e;return true;
}
// 放到指定角标位置其后元素右移一位,时间复杂度O(n)
public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1);  // Increments modCount!!System.arraycopy(elementData, index, elementData, index + 1, size - index);elementData[index] = element;size++;
}

批量添加:addAll(Collection<? extends E> c) 和addAll(int index, Collection<? extends E> c),和add同理不过是+n个和右移n位的区别,贴出代码

public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew);  // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);//添加numNew个元素size += numNew;return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew);  // Increments modCountint numMoved = size - index;if (numMoved > 0)System.arraycopy(elementData, index, elementData, index + numNew, numMoved);//右移numNew位System.arraycopy(a, 0, elementData, index, numNew);size += numNew;return numNew != 0;
}
扩容

添加元素操作可能触发扩容操作

// 调用链 ensureCapacityInternal(int minCapacity)-->ensureExplicitCapacity(int minCapacity)-->grow(int minCapacity)private void grow(int minCapacity) {int oldCapacity = elementData.length;//原大小int newCapacity = oldCapacity + (oldCapacity >> 1);//原大小的1.5倍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);//按newCapacity大小创建新数组,并将老数组中元素拷贝进去
}

注意扩容后elementData内存地址已改变,即已经不是原数组了

public static <T> T[] copyOf(T[] original, int newLength) {return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {// copy是新创建的数组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;
}
删除元素

删除分几种情况:

  1. 删指定位置的元素,返回被删除元素内容;
  2. 删指定内容的元素,返回true或false;
  3. 删除指定角标区间的元素,无返回值;
  4. JDK8之后添加了按条件删除,有元素被删除返回true否则false。

基本逻辑是定位到元素,删除,后方元素依次左移,size–。

//删除指定位置的元素
public E remove(int index) {rangeCheck(index);//检查角标越界modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;//需要左移元素的个数if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index, numMoved);//左移elementData[--size] = null; // size--,并将空出的位置置空方便垃圾回收return oldValue;//返回老元素内容
}
// 删除指定内容的元素,分null和非null两种情况,遍历-匹配-删除
public boolean remove(Object o) {if (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;
}

按条件删除,JDK8的新特性,大概搂了一眼,需要传入删除条件Predicate对象,遍历后将需要删除的数据记录到BitSet(位图?)中,然后根据BitSet进行删除并左移,具体机制先挖个坑,后边再说TODO。

public boolean removeIf(Predicate<? super E> filter) {Objects.requireNonNull(filter);// figure out which elements are to be removed// any exception thrown from the filter predicate at this stage// will leave the collection unmodifiedint removeCount = 0;final BitSet removeSet = new BitSet(size);final int expectedModCount = modCount;final int size = this.size;for (int i=0; modCount == expectedModCount && i < size; i++) {final E element = (E) elementData[i];if (filter.test(element)) {removeSet.set(i);removeCount++;}}if (modCount != expectedModCount) {throw new ConcurrentModificationException();}// shift surviving elements left over the spaces left by removed elementsfinal boolean anyToRemove = removeCount > 0;if (anyToRemove) {final int newSize = size - removeCount;for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {i = removeSet.nextClearBit(i);elementData[j] = elementData[i];}for (int k=newSize; k < size; k++) {elementData[k] = null;  // Let gc do its work}this.size = newSize;if (modCount != expectedModCount) {throw new ConcurrentModificationException();}modCount++;}return anyToRemove;
}

按角标区间删除没什么意思,不分析了

protected void removeRange(int fromIndex, int toIndex) {modCount++;int numMoved = size - toIndex;System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);int newSize = size - (toIndex-fromIndex);for (int i = newSize; i < size; i++) {elementData[i] = null;}size = newSize;
}
获取元素
// 先检查角标越界,然后直接用角标在数组取值
public E get(int index) {rangeCheck(index);return elementData(index);
}
E elementData(int index) {return (E) elementData[index];
}
判断元素是否存在
public boolean contains(Object o) {return indexOf(o) >= 0;
}
// 分null和非null两种情况从前向后依次遍历匹配,没什么好讲的
public int indexOf(Object o) {if (o == null) {for (int i = 0; i < size; i++)if (elementData[i]==null)return i;} else {for (int i = 0; i < size; i++)if (o.equals(elementData[i]))return i;}return -1;
}
// 判断是否为空
public boolean isEmpty() {return size == 0;
}
设置set元素
// 替换老元素,将老元素返回
public E set(int index, E element) {rangeCheck(index);E oldValue = elementData(index);elementData[index] = element;return oldValue;
}
特性
  1. 数据结构是数组

  2. 线程不安全,fail-fast;

  3. 操作时间效率如下,get set为常量时间,指定位置add和remove为O(n)

    方法时间复杂度
    boolean add(E e);O(1)
    void add(int index, E element);O(n) 插入为O(1),后移后边数据为O(n)
    E remove(int index);O(n) 移除为O(1),前移后边数据为O(n)
    boolean remove(Object o);O(n)
    int indexOf(Object o);O(n)
    boolean contains(Object o);O(n)
    E get(int index);O(1)
    E set(int index, E element);O(1)
    int size();O(1)
Vector

在这里插入图片描述

Vector和ArrayList的数据结构完全一样,行为方法也基本一致只是在方法上都套了synchronized以保证线程安全,不做过多赘述。

Stack

Stack继承了Vector,提供了栈结构常用的几个方法peek pop push search empty。实现了FIFO。synchronized保证线程安全。

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;}
}

获取指定位置的Node对象

// 这是个基础方法,按照角标增删改查的的方法都会用到此方法
Node<E> node(int index) {// 如果index小于size的一半,从前向后遍历,否则从后向前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;}
}
添加元素

添加元素的方法有很多,但最终都是调用的链表的添加节点方法,无非:头部添加(linkFirst)、尾部添加(linkLast)和中间添加(linkBefore)三种情况。汇总如下:

方法调用的方法异常&返回值
add(E e)linkLast(e)return true|false; 不抛异常
add(int index, E element)linkLast(element)或linkBefore(element, node(index))void,会抛异常
addFirst(E e)linkFirst(e)void,不抛异常
addLast(E e)linkLast(E e)void,不抛异常
offer(E e)add(E e)–>linkLast(e)return true|false; 不抛异常
offerFirst(E e)addFirst(E e)–>linkFirst(e)return true|false; 不抛异常
offerLast(E e)addLast(E e)–>linkLast(E e)return true|false; 不抛异常
  • 头部添加
private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);first = newNode;if (f == null)last = newNode;elsef.prev = newNode;size++;modCount++;
}
  • 尾部添加
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;elsel.next = newNode;size++;modCount++;
}
  • 指定位置添加
public void add(int index, E element) {checkPositionIndex(index);//检查角标越界if (index == size)//加在最后linkLast(element);else//加在中间,先通过node(index)方法找到目标节点,然后再目标节点前添加元素elementlinkBefore(element, node(index));
}
// 指定元素前添加,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;elsepred.next = newNode;size++;modCount++;
}
删除元素

对应的删除元素方法,最终调用的是链表节点的delink方法:头部删除(unlinkFirst)、尾部删除(unlinkLast)和中间指定节点删除(unlink)

方法调用的方法异常&返回值
poll()unlinkFirst(Node f)不抛异常,返回被删除值
pollFirst()unlinkFirst(Node f)不抛异常,返回被删除值
pollLast()unlinkLast(Node l)不抛异常,返回被删除值
pop()removeFirst()–>unlinkFirst(Node f)会抛异常(不存在的元素),返回被删除值
remove()removeFirst()–>unlinkFirst(Node f)会抛异常(不存在的元素),返回被删除值
removeFirst()unlinkFirst(Node f)会抛异常(不存在的元素),返回被删除值
removeLast()unlinkLast(Node l)会抛异常(不存在的元素),返回被删除值
remove(int index)unlink(Node x)会抛异常(越界),返回被删除值
remove(Object o)独立方法,从前向后遍历删除不抛异常,返回true|false
removeFirstOccurrence(Object o)remove(Object o)不抛异常,返回true|false
removeLastOccurrence(Object o)独立方法,从后向前遍历删除不抛异常,返回true|false
  • 删除头部元素
// 1.元素置空以便GC
// 2.first指向头元素的下一个元素,处理好双向指针
// 3.size--
private E unlinkFirst(Node<E> f) {final E element = f.item;final Node<E> next = f.next;f.item = null;f.next = null; // help GCfirst = next;if (next == null)last = null;elsenext.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 GClast = prev;if (prev == null)first = null;elseprev.next = null;size--;modCount++;return element;
}
  • 删除指定元素

一般搭配遍历链表的代码使用,或按index计数遍历,或按元素值匹配遍历,找到要删除的节点后unlink(Node x)此节点,unlink时将本节点剔除,前后节点相连即可。

// 传入需要删除的Node
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 {prev.next = next;x.prev = null; // 置空以GC}// 处理后置节点if (next == null) {last = prev;} else {next.prev = prev;x.next = null; // 置空以GC}x.item = null;// 置空以GCsize--;modCount++;return element;
}
查看元素

即peek方法,查看头或尾,只查看不删除

public E peek() {final Node<E> f = first;return (f == null) ? null : f.item;
}
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 boolean contains(Object o) {return indexOf(o) != -1;
}
public int indexOf(Object o) {int index = 0;// 就是分null和非null两种情况,从前向后遍历,存在就返回index值,否则返回-1if (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;
}
设置set元素
// 设置index元素值,返回原值
public E set(int index, E element) {checkElementIndex(index);Node<E> x = node(index);E oldVal = x.item;x.item = element;return oldVal;
}
特性

LinkedList底层是一个双向链表,实现了List和Deque可以作为一个双向链表使用,也可以用作栈。

线程不安全

Map

key-value集合的顶层接口,代替java.util.Dictionary。基本操作有:

  1. put:添加key-value;
  2. get:通过key获取对应value;
  3. containsKey:判断是否包含key;
  4. containsValue:判断是否包含value;
  5. remove:通过key删除key-value;
  6. size:key-value个数;
  7. isEmpty:是否为空;
  8. clear:清空key-value对;
  9. keySet:获取所有key的集合;
  10. entrySet:获取所有key-value集合;
boolean containsKey(Object key);
boolean containsValue(Object value);
Set<K> keySet();
Set<Map.Entry<K, V>> entrySet();
V get(Object key);
V put(K key, V value);
void putAll(Map<? extends K, ? extends V> m);
V remove(Object key);
void clear();	
int size();
Collection<V> values();
boolean isEmpty();

Entry:代表一个key-value,entrySet方法会返回map中所有键值对集合,集合中元素就是Entry对象

interface Entry<K,V> {K getKey();V getValue();V setValue(V value);
}
HashMap

在这里插入图片描述

实现了Map接口;

允许null key和null value;

不保证元素顺序,随着时间的迁移顺序也可能会变;

重要字段
//默认容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认装载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表树化阈值
static final int TREEIFY_THRESHOLD = 8;
//树链表化阈值
static final int UNTREEIFY_THRESHOLD = 6;
//table大小超过此值才有可能树化, 否则resize
static final int MIN_TREEIFY_CAPACITY = 64;
//第一次使用的时候才初始化, 大小为2^n, 允许为0
transient Node<K,V>[] table;
//包含了所有key-value的集合,key-value以Map.Entry的形式表示,遍历用
transient Set<Map.Entry<K,V>> entrySet;
//大小
transient int size;
//修改次数
transient int modCount;
//超过这个值就进行resize
int threshold;
//装载因子
final float loadFactor;
Node

JDK1.8中的HashMap是通过数组+链表+红黑树实现的, 具体table中的数据类型如下
Node用以实现(双向)链表
TreeNode用以实现红黑树, TreeNode既是树也是一个双向链表, 在conaintValue方法时, 就用了链表的方式去遍历
在这里插入图片描述

构造方法

保证table大小为2^n

public HashMap(int initialCapacity, float loadFactor) {this.loadFactor = loadFactor;//装载因子this.threshold = tableSizeFor(initialCapacity);//
}
//确保大小为2^n
static final int tableSizeFor(int cap) {int n = cap - 1;//保证低位为1n |= n >>> 1;//保证所有位都为1n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
put添加key-value
public V put(K key, V value) {// 最终调用putVal方法return putVal(hash(key), key, value, false, true);
}
  • hash方法
static final int hash(Object key) {int h;// (key == null):如果key为null,hash值为0// (h = key.hashCode()) ^ (h >>> 16):高位和低位异或,为什么采用这种方式:// 最终hash值要参与到脚标定位,定位方式为:tab[i = (n - 1) & hash], 一般hashMap大小都小于2^16, 这样能让高位hash也参与进来, 减少hash冲突return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • putVal

  • 方法总体逻辑

  1. 如果table为空,则初始化
  2. hash定位到的桶是空桶,通过key-value构建node直接放入桶中
  3. 桶中不空即hash冲突,两种情况:
    1. 同key冲突,通过onlyIfAbsent和原value是否为null判断是否替换原值;
    2. 非同key冲突,链表挂在尾部,树放在合适位置。PS:桶中单节点的当做长度为1的链表处理
  4. 递增size值,判断是否需要扩容,需要的进入扩容逻辑;
  5. afterNodeInsertion后续处理,HashMap为空操作。
/*hash – hash for key,key的hash值key – the keyvalue – the value to putonlyIfAbsent – if true, don't change existing valueevict – if false, the table is in creation mode.
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)//空map, 懒加载方式n = (tab = resize()).length;// i = (n - 1) & hash:计算脚标,将"取模"运算转换为"减"和"按位与"提高效率if ((p = tab[i = (n - 1) & hash]) == null)// 如果当前位置为空, 直接放到当前位置tab[i] = newNode(hash, key, value, null);else {// hash冲突,如果没有同key冲突,只需要添加新节点即可,有的话需要判断是否替换原值Node<K,V> e; K k;// e:同key冲突的原node,如果e为null,说明没有同key冲突if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))// 如果同key冲突, 进入到是否替换原值的逻辑e = p;// p是原值else if (p instanceof TreeNode)// 当前节点是树e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 链表hash冲突for (int binCount = 0; ; ++binCount) {//binCount用来给链表长度计数,判断是否需要转换为树if ((e = p.next) == null) {// 已经遍历到链表尾部p.next = newNode(hash, key, value, null);// 挂在链表尾部if (binCount >= TREEIFY_THRESHOLD - 1)// 大于指定长度,将当前位置的链表转化为树treeifyBin(tab, hash);break;}// 同key冲突,跳出循环进入到是否替换原值的逻辑if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))break;p = e;// 向前推进一个节点}}if (e != null) { // 同key冲突, 判断是否替换原值V oldValue = e.value;// onlyIfAbsent为false 或 原值为null则替换原值。put方法onlyIfAbsent为false默认替换原值,如果不想替换可以用putIfAbsent方法if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();// 扩容afterNodeInsertion(evict);return null;
}
resize扩容

resize负责初始化和扩容,如果table是空的, 初始化。

扩容逻辑:

  • table不空, 分三种情况:

    1. 当前位置存放单个元素, 通过hash值和新容量, 重新计算脚标位置: 脚标index可能保持不变, 也可能index += oldCap, 取决于新容量值高位和同样位置对应的hash值是0还是1

    2. 当前位置存放的是红黑树, 拆分成两棵树, 或者两个链表。

      • 先将树转化为两个链表, 通过next和prev两个字段进行转化, 转化规则见3
      • 判断转化后的链表是否符合"树化"条件, 链表长大于6,如果符合重新转化为树, 不符合直接将链表挂在节点上

      PS: HashMap.TreeNode继承自HashMap.Node的next属性和自身的prev属性用来拆分树

    3. 当前位置存放的是链表, 通过hash值和新容量,

      • 拆分成两个链表, 原链表相对位置不变;
      • index可能保持不变, 也可能index += oldCap;
      • index值同1规则
/*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.* @return the table*/
final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;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; // double threshold 容量翻倍} else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);}threshold = newThr;Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //创建新table数组table = newTab;if (oldTab != null) {//原table不为nullfor (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {//遍历table中所有不为空的元素oldTab[j] = null;//置为nullif (e.next == null)//当前位置没有hash冲突, 只有一个元素/*位置可能不变或后移位数为原容量值, 决定于新容量高位对应的hash值是0还是1,之前容量:16(10000), e.hash:0101 0111, 则(扩容后容量为32:100000)扩容前:e.hash:0101 0111 & (10000 - 1) = 0111 = 7(十进制), 扩容后:e.hash:0101 0111 & (100000 - 1) = 1 0111 = 23(十进制) 后移16位之前容量:16, e.hash:1010 0101, 扩容前:1010 0101 & (10000 - 1) = 0101 = 5(十进制)扩容前脚标1010 0101 & (100000 - 1) = 0101 = 5(十进制) 位置不变*/newTab[e.hash & (newCap - 1)] = e;//重新计算脚标值else if (e instanceof TreeNode)//树((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//将树拆分???TODO 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)将链表随机拆分为2段减少hash冲突, 拆分原则:元素hash值最高位为1的拆出去放在table后半部分,脚标为原index + oldCap;元素hash值最高位为0的拆出去放在table前半部分,脚标为原index */if ((e.hash & oldCap) == 0) {//链表放在新table的前半部分if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;} else {//链表放在新table的后半部分if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {//如果原链表在table的前半部分, index保持不变loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {//如果原链表在table的后半部分, index += oldCaphiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;
}
remove删除元素
// 按key删除
public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
}
// 需要同时匹配key-value删除
public boolean remove(Object key, Object value) {return removeNode(hash(key), key, value, true, true) != null;
}

EntrySet、KeySet和HashIterator的删除都是调用的removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {public final boolean remove(Object o) {if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>) o;Object key = e.getKey();Object value = e.getValue();return removeNode(hash(key), key, value, true, true) != null;}return false;}
}
final class KeySet extends AbstractSet<K> {public final boolean remove(Object key) {return removeNode(hash(key), key, null, false, true) != null;}
}
abstract class HashIterator {public final void remove() {Node<K,V> p = current;if (p == null)throw new IllegalStateException();if (modCount != expectedModCount)throw new ConcurrentModificationException();current = null;K key = p.key;removeNode(hash(key), key, null, false, false);expectedModCount = modCount;}
}
  • removeNode
/** 删除的逻辑很简单,只是细节需要注意处理* 逻辑:*	1.如果是空table 或 hash(key)定位到的桶中元素为空,直接返回null*	2.找到要被删除的目标node,两种可能:1)桶中元素就是目标node即链表头(为方便处理单节点当做长度为1的特殊链表)或树的根节点;2)桶中元素不是目标node即链表中或树的非root节点*	3.删除目标node*/
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;// p:桶中元素或链表中目标node的前一个节点// 1、(tab = table) != null && (n = tab.length) > 0:table不为空进入if,为空直接返回null// 2、(p = tab[index = (n - 1) & hash]) != null:hash(key)定位到的桶元素不为空进入if,为空返回nullif ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) {// node:要被删除的node(目标node)Node<K,V> node = null, e; K k; V v;// if-else的目的:找到目标node// 1、如果桶中第一个节点元素就是目标node,可能目标node就是链表头结点 或 目标root是树的根节点if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))node = p;// 2.1、如果桶中第一个节点不是目标node,且目标node.next为null,则目标node为null。// 2.2、如果桶中第一个节点不是目标node,且目标node.next不是null,则桶中元素是链表头结点或红黑树根节点,需要去链表和树上寻找目标node// 注意,此时TreeNode被当做链表,链表头是root节点,但具体树上节点是什么顺序链接的还不清楚else if ((e = p.next) != null) {// 2.2.1、如果桶中元素是TreeNode类型,则去树上查找匹配节点if (p instanceof TreeNode)node = ((TreeNode<K,V>)p).getTreeNode(hash, key);// 2.2.3、目标node如果桶中元素不是TreeNode类型,且桶中第一个元素不是目标node,则目标node必在此桶的链表上的头后部分else {// 从第二个节点遍历链表,匹配keydo {if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {node = e;break;}// p = e:非常非常重要,要保证p节点和目标node同步向前,以便后边删除时直接将p指向目标node的next(即目标node被删除)p = e;} while ((e = e.next) != null);}}// 通过上边if-else,已经找到需要删除的node节点// 1、node != null:如果目标node为null,说明未找到,不进入if直接返回null// 2、(!matchValue || (v = node.value) == value || (value != null && value.equals(v))):不比较value值,或者比较value值但value值匹配// 如果1&2条件都满足,即目标node不为null,且满足删除条件(value值匹配或不需要匹配),则进入if做删除操作if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) {// 目标node是树上节点,去树上删除节点,可能是root节点if (node instanceof TreeNode)((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);// 目标node就是桶中node,直接让桶指向目标node的下一个节点(可能为null即链表长度为1的情况)else if (node == p)tab[index] = node.next;// 目标node是链表上的某一个节点(非头结点),直接删除目标node(目标node的前节点p指向目标node的next即可)elsep.next = node.next;// 剩下的就简单了++modCount;--size;afterNodeRemoval(node);return node;//返目标node}}return null;
}
get通过key获取value
public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}
  • getNode

时间复杂度O(1)

final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// hash定位到的桶中元素// 如果table为空不进入if// 如果hash定位到的桶中元素为空不进入ifif ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {// 如果桶中元素key值匹配,直接返回if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))return first;// 桶中元素key值不匹配if ((e = first.next) != null) {// 去红黑树上查找if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);do {// 遍历链表if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}
containsKey判断是否包含key
public boolean containsKey(Object key) {// 就是调用getNode方法return getNode(hash(key), key) != null;
}
containsValue判断是否包含value

以数组+链表的方式遍历整个map,时间复杂度O(n)

public boolean containsValue(Object value) {Node<K,V>[] tab; V v;if ((tab = table) != null && size > 0) {// 遍历每个桶for (int i = 0; i < tab.length; ++i) {// 以链表的方式遍历桶下元素for (Node<K,V> e = tab[i]; e != null; e = e.next) {if ((v = e.value) == value || (value != null && value.equals(v)))return true;}}}return false;
}
replace替换
public boolean replace(K key, V oldValue, V newValue) {Node<K,V> e; V v;// (e = getNode(hash(key), key)) != null:获取到元素不为空// ((v = e.value) == oldValue || (v != null && v.equals(oldValue))):原值和期望值匹配,都为null或者匹配if ((e = getNode(hash(key), key)) != null && ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {// 替换e.value = newValue;afterNodeAccess(e);return true;// 返回替换成功}return false;
}public V replace(K key, V value) {Node<K,V> e;// 获取到元素不为空,直接替换if ((e = getNode(hash(key), key)) != null) {V oldValue = e.value;e.value = value;afterNodeAccess(e);return oldValue;}return null;
}
遍历

HashMap有多种遍历方式,但实现方式大同小异,我们以entrySet()方式举例讲解。

调用entrySet方法会返回一个EntrySet对象,EntrySet对象实现了Iterable接口
在这里插入图片描述

public Set<Map.Entry<K,V>> entrySet() {Set<Map.Entry<K,V>> es;return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {public final int size()                 { return size; }public final Iterator<Map.Entry<K,V>> iterator() {return new EntryIterator();}
}
final class EntryIterator extends HashIteratorimplements Iterator<Map.Entry<K,V>> {public final Map.Entry<K,V> next() { return nextNode(); }
}

HashIterator具体实现迭代器

遍历顺序:找到第一个不为空的桶,以链表的方式遍历这个桶下所有元素,找到下一个桶继续……

迭代器方式map的实现逻辑

  1. 构造迭代器时先将next指向第一个不为空的桶中的元素
  2. 获取下一个元素:
    1. 将next赋值给e;
    2. 以链表的方式获取e的下一个节点,将next指向e.next,
      1. 如果next为空,说明当前链表遍历完毕,再次将next指向下一个不为空的桶中元素
      2. 如果next不为空继续向下执行
    3. 将e返回
abstract class HashIterator {Node<K,V> next;        // next entry to returnNode<K,V> current;     // current entryint expectedModCount;  // for fast-failint index;             // current slotHashIterator() {// 构造迭代器时就锁定modCountexpectedModCount = modCount;Node<K,V>[] t = table;current = next = null;index = 0;if (t != null && size > 0) { // advance to first entry// index < t.length:保证遍历所有桶// (next = t[index++]) == null:next指向第一个不为空的桶中元素,注意此时current也指向这个元素do {} while (index < t.length && (next = t[index++]) == null);}}public final boolean hasNext() {return next != null;}// 遍历下一个节点,注意不一定是下一个桶还可能是链表上的下一个节点或树上的下一个节点(此时树按照链表处理)final Node<K,V> nextNode() {Node<K,V>[] t;Node<K,V> e = next;// 迭代过程中不能改变map大小如put remove操作if (modCount != expectedModCount)throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();// 按链表的方式遍历一个桶上的每一个节点// (next = (current = e).next) == null:在这里将next指向链表上的下一个节点,同时判断当前链表是否已经遍历完毕(==null),如果完毕进入if将next指向下一个不为空的桶,下次调用next()即nextNode()时再回到if条件保证先遍历链表// (t = table) != nullif ((next = (current = e).next) == null && (t = table) != null) {do {} while (index < t.length && (next = t[index++]) == null);}return e;}
}

可见:HashMap的遍历是无序的,因为不管怎么样的插入顺序以及怎样修改,访问顺序都是从前向后按桶访问,然后从头到尾访问链表

LinkedHashMap

在这里插入图片描述

LinkedHashMap就是有顺序的HashMap,底层数据结构和HashMap一样,HashMap所有的属性方法LinkedHashMap都有。

重要字段

  • LinkedHashMap.Entry

LinkedHashMap的基础元素是LinkedHashMap.Entry,它继承了HashMap.Node,并在其基础上添加了before和after属性用以构建额外的有序双向链表以保证顺序,对HashMap的原数据结构没有影响,需要维护顺序时会维护此双向链表的顺序。
在这里插入图片描述

  • 数据结构示意图
    在这里插入图片描述

上图中红线就是befort和after,去掉红线就是HashMap的数据机构(红黑树没有表现)

// 有序链表头
transient LinkedHashMap.Entry<K,V> head;
// 有序链表尾
transient LinkedHashMap.Entry<K,V> tail;
// 顺序,true是访问顺序,最近访问的在链表尾部,false是插入顺序,最近插入的在链表尾
final boolean accessOrder;
插入操作
  • 插入有序

LinkedHashMap重写了newNode方法,put创建新node时,会调用linkNodeLast
在这里插入图片描述

LinkedHashMap.newNode

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);linkNodeLast(p);return p;
}
// 将新插入的数据链入有序链表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null)head = p;else {p.before = last;last.after = p;}
}

以上可见,LinkedHashMap插入key-value时就保存了插入顺序

  • afterNodeInsertion

    putVal最后一段代码调用

        ++modCount;if (++size > threshold)resize();// 插入后要整理有序链表的顺序afterNodeInsertion(evict);return null;
    }
    
// 此方法中有可能要删除最老的元素即head
void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;// evict默认是true// (first = head) != null:如果head就是null,则不需要删除// 需要自己去实现removeEldestEntry方法,根据一定条件决定是否删除最老元素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;
}
  • 自己实现一个只能存储5个元素的map
class MyLinkedHashMap extends LinkedHashMap{@Overrideprotected boolean removeEldestEntry(Map.Entry eldest) {// key-value多于5对时删除老元素return size() > 5;}
}
@Test
public void TestMyLinkedHashMap() {Map<Integer, Integer> map = new MyLinkedHashMap();for (int i = 0; i < 30; i++) {map.put(i, i);}System.out.println(map);// {25=25, 26=26, 27=27, 28=28, 29=29}
}

实现一个LRU

class MyLinkedHashMap extends LinkedHashMap{MyLinkedHashMap(){}MyLinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder){super(initialCapacity, loadFactor, accessOrder);}@Overrideprotected boolean removeEldestEntry(Map.Entry eldest) {return size() > 5;}
}
@Test
public void testLru(){Map<Integer, Integer> map = new MyLinkedHashMap(16, 0.75f, true);for (int i = 0; i < 30; i++) {map.get(2);//2=2应该不会被删掉,key为2的键值对一直被访问,我们构造的map链表顺序为按访问顺序排序,所以每次访问2都会被排到尾部map.put(i, i);}System.out.println(map);// {26=26, 27=27, 28=28, 2=2, 29=29}
}
遍历有序

LinkedHashMap实现了自己的迭代器LinkedEntryIterator

public Set<Map.Entry<K,V>> entrySet() {Set<Map.Entry<K,V>> es;return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {public final int size()                 { return size; }public final Iterator<Map.Entry<K,V>> iterator() {return new LinkedEntryIterator();}
}
final class LinkedEntryIterator extends LinkedHashIteratorimplements Iterator<Map.Entry<K,V>> {public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class LinkedHashIterator {LinkedHashMap.Entry<K,V> next;LinkedHashMap.Entry<K,V> current;int expectedModCount;LinkedHashIterator() {next = head;expectedModCount = modCount;current = null;}public final boolean hasNext() {return next != null;}final LinkedHashMap.Entry<K,V> nextNode() {// 可见遍历时是通过put时linkNodeLast方法构建的链表顺序遍历的LinkedHashMap.Entry<K,V> e = next;/*为了使代码紧凑,中间modCount等检测代码删掉了*/current = e;next = e.after;return e;}
}
删除元素

删除主题逻辑就是HashMap.remove,在删除完成后,会调用afterNodeRemoval(node)在HashMap中此方法不做任何操作,LinkedHashMap重写了此方法,用于维护有序链表,将目标节点出链

void afterNodeRemoval(Node<K,V> e) { // unlink// 逻辑很简单,就是双向链表的节点删除操作,处理一下被删节点是头或尾的极值情况LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.before = p.after = null;if (b == null)head = a;elseb.after = a;if (a == null)tail = b;elsea.before = b;
}
afterNodeAccess
// 把最近访问的节点放到尾部即变成最新节点
void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.Entry<K,V> last;// 如果accessOrder为true即按访问顺序排序,且刚刚访问的不是尾结点(是尾结点代表已经是最新的了)则把当前节点移动到尾部(最新)if (accessOrder && (last = tail) != e) {LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.after = null;//尾结点的后驱节点置null// 这里执行双向链表节点删除操作// 如果前驱节点是空,说明是头结点,将头结点指向p的后驱节点, 因为p要挪到尾部了if (b == null)head = a;else// 否则将p的前驱的后驱指向p的后驱,即将p左至右单向出链b.after = a;// 如果p的后驱不是null说明p不是尾结点,将p的后驱的前驱指向p的前驱节点,即将p右至左单向出链if (a != null)a.before = b;else// 否则说明p已经是尾结点,last指向p的前驱节点last = b;// 这里将p放到尾部// 只有一个节点p,head指向pif (last == null)head = p;else {// 将p放到尾部p.before = last;last.after = p;}tail = p;// tail指向p       ++modCount;}
}
get

get方法简单易懂,如果按读顺序排序,读后调整顺序

public V get(Object key) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return null;if (accessOrder)afterNodeAccess(e);return e.value;
}
Set

无序集合,且顺序会变化

元素不可重复,允许null值

基本操作add, remove, contains and size时间复杂度为O(1)

底层是HashMap

// 不存在就添加,并返回true;存在返回false
boolean add(E e);
boolean addAll(Collection<? extends E> c);
void clear();
boolean contains(Object o);
boolean containsAll(Collection<?> c);
boolean isEmpty();
// 返回迭代器
Iterator<E> iterator();
// 存在则删除并返回true,否则返回false
boolean remove(Object o);
boolean removeAll(Collection<?> c);
// 删除除c之外的所有元素
boolean retainAll(Collection<?> c);
int size();
Object[] toArray();
<T> T[] toArray(T[] a);
HashSet
重要字段
// 集合元素就是存储在map的key中
private transient HashMap<E,Object> map;
// map的所有value都指向PRESENT
private static final Object PRESENT = new Object();
构造方法
public HashSet() {map = new HashMap<>();
}
// 由于是hash算法,HashSet也有装载因子
public HashSet(int initialCapacity, float loadFactor) {map = new HashMap<>(initialCapacity, loadFactor);
}
// 注意此构造函数没有被public修饰,只给LinkedHashSet用,会创建一个LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
添加元素
public boolean add(E e) {return map.put(e, PRESENT)==null;
}
清空集合
public void clear() {map.clear();
}
是否包含元素
public boolean contains(Object o) {return map.containsKey(o);
}
是否为空
public boolean isEmpty() {return map.isEmpty();
}
迭代器
public Iterator<E> iterator() {return map.keySet().iterator();
}
删除
public boolean remove(Object o) {return map.remove(o)==PRESENT;
}
size
public int size() {return map.size();
}
LinkedHashSet

在这里插入图片描述

LinkedHashSet继承了HashSet,底层是LinkedHashMap,是一个有序不重复集合,顺序只有插入顺序,没有读取顺序选项。

构造函数

注意所有构造函数都调用了HashSet的三个参数的构造函数,底层是LinkedHashMap

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);
}

HashSet三个参数的构造函数,底层就是LinkedHashMap

HashSet(int initialCapacity, float loadFactor, boolean dummy) {map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

由于LinkedHashMap已经做过分析,这里就重复分析了

这篇关于Java集合类Collection包括ArrayDeque,ArrayList,LinkedList,HashSet,LinkedHashSet和Map接口HashMap,LinkedHashMap的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/424589

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定