java 集合 List Set Map

集合分类:

分类特点线程安全
ListArrayList底层数据结构是数组,查询快,增删慢, 元素可以重复非线程安全,效率高
Vector底层数据结构是数组,查询快,增删慢, 元素可以重复线程安全,效率低
LinkedList底层数据结构是双向链表,查询慢,增删快, 元素可以重复非线程安全,效率高
SetHashSet底层数据结构是哈希表,存储数据使用的是HashMap,元素不可以重复
TreeSet底层数据结构是二叉树,元素不可以重复
LinkedHashSet元素不可以重复, 有序,存、取顺序一致
MapHashMapkey和value可以为null,key 不能重复,value可以重复,无序非线程安全
LinkedHashMapkey和value可以为null,key 不能重复,value可以重复,有序非线程安全
Hashtablevalue不可以为null(存在疑问,记得key不可以为null,通过看1.8.0_60源码发现验证的是value不为null),key 不能重复,value可以重复,无序线程安全
TreeMapkey 不能重复,value可以重复,无序(元素顺序与添加顺序不一致),
默认会根据key进行排序
非线程安全
IdentityHashMapkey如果为null会默认使用Object对象,
判断key是否相等用的是== 比较的是内存地址
线程不安全
ConcurrentHashMapkey和value都不可以为null,key 不能重复,value可以重复线程安全

List

类关系图

«interface» Iterable «interface» Collection «interface» List «abstract» AbstractList Vector «interface» RandomAccess ArrayList «abstract» AbstractCollection «abstract» AbstractSequentialList LinkedList «interface» Deque «interface» Queue

ArrayList

ArrayList变量

// 默认数组的长度为10
private static final int DEFAULT_CAPACITY = 10;
// 共享空数组 无默认数组长度
private static final Object[] EMPTY_ELEMENTDATA = {};
// 共享空数组 使用默认数组的长度
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 记录当前数组的实际长度
private int size;
// 存放实际数据的数组
transient Object[] elementData;
// 验证集合最大长度最大int值-8 实际集合最大长度为int最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造方法

  1. 无参构造
    会创建一个空数组
// 共享空数组 使用默认数组的长度
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存放数据数组
transient Object[] elementData;
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  1. 有参构造 参数是int类型
// 共享空数组 无默认数组长度
private static final Object[] EMPTY_ELEMENTDATA = {};
// 初始化容量数据类型为int 所以数组最大长度为int的最大值 2^31-1
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
    	// 如果初始化容量大于0 则创建指定长度的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    	// 如果初始化容量为0 则创建一个空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
    	// 如果初始化容量小于0 则直接抛异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
  1. 有参构造 参数是Collection接口
public ArrayList(Collection<? extends E> c) {
	// c.toArray() 将集合转成数组
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 验证集合对象是否是Object集合,如果不是继续先后执行
        if (elementData.getClass() != Object[].class)
        	// 将elementData复制成一个新的数组重新赋值给elementData
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 如果集合长度为0 则创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

// Arrays.copyOf方法
// original-原数组
// newLength -新数组的长度
// newType -新数组的数据类型
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    // 先验证新数组的数据类型是否是Object 如果是则创建Object类型的数组,否则创建指定类型的数组
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    // 调用系统拷贝 将原数据拷贝到新数组里面
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

add方法 添加元素

// 添加元素 e需要添加的元素
public boolean add(E e) {
	// 数组扩容
    ensureCapacityInternal(size + 1);
    // 将元素添加到elementData数组中,并且集合长度+1
    elementData[size++] = e;
    return true;
}

// minCapacity = 当前集合长度+1
private void ensureCapacityInternal(int minCapacity) {
	// 如果elementData数组是默认有长度的数组 重新计算minCapacity
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    	// DEFAULTCAPACITY_EMPTY_ELEMENTDATA 默认数组长度为10
    	// 如果minCapacity比默认值10小 则赋值默认值10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
	// 通过上面计算得到minCapacity,为目前数组的最小长度
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
	// 记录当前集合参数次数
    modCount++;
	// 如果最小数组长度比当前实际存储数据的数组长度大则进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // oldCapacity 存储实际数据的数组的长度
    int oldCapacity = elementData.length;
    // 新的容量为现在容量扩大一半 
    // newCapacity = oldCapacity + oldCapacity/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
    	// 如果新的容量小于最小容量 则新的容量=最小容量
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    	// 如果新的容量 大于最大集合的大小 MAX_ARRAY_SIZE = int最大长度-8
        newCapacity = hugeCapacity(minCapacity);
        
    // 通过上面计算出最新数组长度 并进行扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}


private static int hugeCapacity(int minCapacity) {
	// 验证最小容量是否小于0
    if (minCapacity < 0) 
        throw new OutOfMemoryError();
    // 如果最小容量大于最大集合值 则返回最大int值,否则返回最大集合大小
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

size方法

public int size() {
	// 直接返回集合的size变量
    return size;
}

get方法

public E get(int index) {
	// 验证下标是否正确
    rangeCheck(index);
	// 直接获取数组指定的下标元素返回
    return elementData(index);
}

remove方法

  1. 通过下标删除
public E remove(int index) {
    // 检查下标是否正确
    rangeCheck(index);
	// 记录操作次数+1
    modCount++;
    // 获取当前下标的元素
    E oldValue = elementData(index);
	// numMoved 获取需要移动的元素的个数
	// 比如size=10, index=3 要移除下标为3的元素
	// numMoved = 10 - 3 - 1 = 6;
    int numMoved = size - index - 1;
    if (numMoved > 0)
    	// 将elementData数组index+1位置(包含index+1)以后numMoved个元素向前移动一位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 设置原最后一个元素的下标内容为null,并且集合长度-1 
    elementData[--size] = null;
	// 返回删除的元素
    return oldValue;
}

// 检查下标是否正确
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  1. 删除指定对象
public boolean remove(Object o) {
    if (o == null) {
    	// 如果为null 循环数组删除第一个元素为null的对象,然后结束循环
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
        	// 如果两个对象相等删除 默认比较的是内存地址,如果需要比起其他需要重写equals
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

private void fastRemove(int index) {
	// 记录操作次数
    modCount++;
    // 获取需要移动的元素个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
    	// 移动元素
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null;
}

总结

  1. ArrayList默认长度我10
  2. 无参构造创建的时候数组长度为0,当第一次添加数据时才会进行扩容到10
  3. 每次扩容原容量的1.5倍, 计算公式 :新容量 = 原容量 + 原容量/2
  4. 数据结构:数组

LinkedList

LinkedList变量

// 集合长度
transient int size = 0;
// 指向链表第一个节点
transient Node<E> first;
// 指向链表最后一个节点
transient Node<E> last;

// 节点定义
private static class Node<E> {
    E item; // 当前元素
    Node<E> next; // 下一个节点
    Node<E> prev; // 上一个节点

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

构造方法

  1. 无参构造
public LinkedList() {
}
  1. 有参构造 参数是Collection接口
// 创建时添加元素
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

// 在指定的位置添加 集合
public boolean addAll(int index, Collection<? extends E> c) {
	// 验证下标是否正确
    checkPositionIndex(index);
	// 集合转成数组
    Object[] a = c.toArray();
    // 需要添加数组的长度
    int numNew = a.length;
    if (numNew == 0)
    	// 如果长度为0 则不添加数据
        return false;
	// 定义 pred-上一个节点,succ-当前index对应的节点
    Node<E> pred, succ;
    if (index == size) {
    	// 如果增加的下标和集合长度一样 上一个节点指向链表的最后一个接点,当前index的节点为null
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }

    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        // 创建节点
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
        	// 如果没有上一个节点 说明是第一个节点
            first = newNode;
        else
        	// 上一个节点的下一个节点指向当前新加的节点
            pred.next = newNode;
        //上一个节点指向重新赋值指向当前新加的节点 进行下一轮循环
        pred = newNode;
    }

    if (succ == null) {
    	// 如果当前index下的接口为null 那么链表最后一个节点就是pred
        last = pred;
    } else {
    	// 如果当前index下的接口不为null 那么上一个节点的下一个节点指向当前index节点
    	// 当前index节点的上一个节点指向上一个接点
        pred.next = succ;
        succ.prev = pred;
    }
    // 到此LinkList 添加数据完成

	// 集合长度 size = 原长度+新添加的集合的长度
    size += numNew;
    // 记录操作次数
    modCount++;
    return true;
}

// 验证下标是否正确
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

add方法 添加元素

public boolean add(E e) {
	// 向链表最后添加元素
    linkLast(e);
    return true;
}

void linkLast(E e) {
	// l指向最后一个节点
    final Node<E> l = last;
    // 创建节点
    final Node<E> newNode = new Node<>(l, e, null);
    // 最后节点指针指向当前新的几点
    last = newNode;
    if (l == null)
    	// 如果之前没有最后一个节点 说明现在是第一次添加数据 第一个节点指针指向当前新的节点
        first = newNode;
    else
    	// 如果之前已经有最后一个几点  之前最后一个几点下一个节点指向当前节点
        l.next = newNode;
    // 集合长度加1
    size++;
    // 记录操作次数
    modCount++;
}

size方法

// 因为有单独记录集合长度的属性 直接返回就好
public int size() {
    return size;
}

get 方法

public E get(int index) {
	// 检查下标index是否正确
    checkElementIndex(index);
    return node(index).item;
}
// 获取指定下标的节点
Node<E> node(int index) {
	// index < (size >> 1) 验证当前index是否在集合前一半内
	// 在前一半 从集合的首部开始往后遍历链表
	// 在后一半 从集合的尾部开始往前遍历链表
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
    
// 检查下标index是否正确
private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 检查下标index是否正确
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

remove方法

  1. 第一种删除 根据下标删除
public E remove(int index) {
	// 检查小标是否有效
    checkElementIndex(index);
    // node(index) 获取指定下标的节点
    return unlink(node(index));
}
// 解除链表的关联
E unlink(Node<E> x) {
	// 获取节点的元素数据
    final E element = x.item;
    // 下一个节点
    final Node<E> next = x.next;
    // 上一个节点
    final Node<E> prev = x.prev;
	
	// 下面开始处理链表节点之间的关联 start
    if (prev == null) {
    	// 如果当前节点上一个节点为null 则将链表第一个节点指向当前节点的下一个节点
        first = next;
    } else {
    	// 上一个节点的下一个节点指向 当前节点的下一个节点
        prev.next = next;
        // 把当前节点的上一个节点置空
        x.prev = null;
    }
    
    if (next == null) {
    	// 如果当前节点下一个节点为null 则将链表最后一个节点指向当前节点的上一个节点
        last = prev;
    } else {
    	// 当前节点的下一个节点的上一个节点 指向当前几点的上一个节点
        next.prev = prev;
        // 把当前节点的下一个节点置空
        x.next = null;
    }
	// 开始处理链表节点之间的关联 end
	
	// 把当前节点的元素置空 这样当前节点上的全部属性都置为空了
    x.item = null;
    // 集合长度-1
    size--;
    // 记录集合变化次数
    modCount++;
    return element;
}
  1. 第二种删除 删除指定对象
public boolean remove(Object o) {
    if (o == null) {
    	// 如果传入对象为null 则遍历整个链表 找到第一个元素为null的节点删除然后退出循环
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
    	// 遍历整个链表 找到第一个元素与当前对象相同的的节点删除然后退出循环
    	// 注意比较对象是否相等 默认使用Object的,比较内存地址。如果需要比较其他需要重写equals
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

总结

  1. 数据结构:双向链表
  2. 通过下标获取数据会验证当下标是集合前半还是后半。在前半,从首位开始往前遍历;在后半,从尾部开始往前遍历。

Vector

Vector变量

// 指定集合扩容扩容量。如果为0 则每次扩容一倍,大于0在每次增加指定数量
protected int capacityIncrement;

// 扩容代码
// int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
// capacityIncrement 大于0 每次在原容量基础增加指定容量
// capacityIncrement 小于等于0 每次在原容量基础增加一倍容量                       
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

构造方法

// initialCapacity 初始集合容量  默认10
// capacityIncrement 指定集合扩容每次在原容量基础上增加多少  默认是增加1倍
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

总结

  1. Vector和ArrayList非常相似。不同
    不同:Vector可以指定扩容量 ArrayList不可以指定
    不同:Vector线程安全 ArrayList非线程安全
  2. Vector方法使用synchronized修饰保证线程安全,所以效率比较低

面试题

  1. 你知道的List都有哪些
    ArrayList LinkedList Vector
  2. List 特点
    有序,查询快,增删慢
  3. ArrayList LinkedList 底层数据结构
    ArrayList是数组, LinkedList是双向链表
  4. ArrayList默认大小是多少,是如何扩容的
    jdk1.7默认是10;jkd1.7之后默认是0,在第一次添加数据的时候会验证,如果是默认数组并在容量小于10会把容量扩充到10。
    每次扩容原来的1.5倍
  5. List是线程安全的吗?如果要线程安全要怎么做?
    List 中的Vector是线程安全的,如果是其他线程需要使用工具类Collections.synchronizedList(new ArrayList())中的方法。
    Collections使用synchronized关键字保证线程安全
  6. Arrays.asList方法后的List可以扩容吗?
    Arrays.asList方法后是final数组,不可以add元素,也不可以扩容
// Arrays.asList 创建的集合
 private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
    	// 用final 修饰
        private final E[] a;
        
        ArrayList(E[] array) {
        	// Objects.requireNonNull(array); 验证array是否为null
            a = Objects.requireNonNull(array);
        }
}
  1. List 和数组直间的转换
    List -> 数组:用List中的toArray方法。
    数组 -> List:用Arrays.asList(array)方法(如果想创建可以改变的List 可以用List的构造函数,例如ArrayList的 new ArrayList(Arrays.asList(array)))

Set

类关系图

«interface» Iterable «interface» Collection «interface» Set «abstract» AbstractSet TreeSet «interface» NavigableSet «interface» SortedSet LinkedHashSet HashSet «abstract» AbstractCollection

HashSet

HashSet 变量

// 存储数据
private transient HashMap<E,Object> map;
// 存储数据中的value值
private static final Object PRESENT = new Object();

构造方法

  1. 无参构造
    创建一个空的HashMap 数据全部使用默认值创建HashMap
public HashSet() {
	// 创建一个空的HashMap
    map = new HashMap<>();
}
  1. 指定初始容量
    initialCapacity 指定HashMap的初始容量,使用默认的负载因子
 public HashSet(int initialCapacity) {
 	// 指定HashMap的初始容量
    map = new HashMap<>(initialCapacity);
}
  1. 指定初始容量 和 负载因子
    创建HashMap的时候一般不建议修改负载因子,所以该方法尽量不用
// initialCapacity  初始容量大小
// loadFactor	负载因子大小
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
  1. 指定初始容量 负载因子 和 boolean值(LinkedHashSet使用)
    创建一个LinkedHashMap 而不是HashMap,
    最后一个参数dummy 只是为了区分其他构造函数(多态的使用),没有其他实际意义
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
  1. 构造函数 直接填写元素 参数是Collection接口
public HashSet(Collection<? extends E> c) {
	// Math.max((int) (c.size()/.75f) + 1, 16)
	// c.size()/.75f 计算出初始HashMap容量多少合适,
	// +1是因为c/.75f计算结果需要取整,不+1可能存在存入数据会触发扩容。为了保证把c全部存储而不触发扩容
	// Math.max((int) (c.size()/.75f) + 1, 16) 因为HashMap的默认长度是16,所以会进行一次比较取最大值,
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    // addAll 是AbstractCollection类中的方法
    // addAll会循环调用add方法 因为HashSet已经重写了add方法所以会调用自己的add方法
    addAll(c);
}

// 添加元素方法
public boolean add(E e) {
	// 在map里面存入数据 value为默认值Object 
	// 应为map的put方法存入成功会返回null,所以比较是否==null验证是否存入成功
    return map.put(e, PRESENT)==null;
}

// class AbstractCollection
public abstract class AbstractCollection<E> implements Collection<E> {
	public boolean addAll(Collection<? extends E> c) {
	    boolean modified = false;
	    for (E e : c)
	    	// 循环调用add 方法,
	    	// 因为HashSet已经重写了add方法所以会调用HashSet的add方法
	        if (add(e))
	            modified = true;
	    return modified;
	}
}

add方法 添加元素

public boolean add(E e) {
	// 在map里面存入数据 value为默认值Object 
	// 应为map的put方法存入成功会返回null,所以比较是否==null验证是否存入成功
    return map.put(e, PRESENT)==null;
}

size方法

会调用Map的size方法 返回Map的大小

public int size() {
    return map.size();
}

remove方法

删除会调用map的remove方法,并且比较删除的key对应的value是否为默认的Object

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

总结

  1. HashSet 是用HashMap存储数据,不过也可以通过构造函数指定LinkedHashMap存储数据
  2. HashSet中Map中的value为固定值Object
  3. HashSet没有get方法,因为没有下标指针。查询数据只能循环遍历
  4. HashSet 中的大部分方法都是直接调用的是Map里面对应的方法

TreeSet

TreeSet变量

// 存储实际数据 NavigableMap是接口 继承了SortedMap接口
private transient NavigableMap<E,Object> m;
// 存储数据中固定value值
private static final Object PRESENT = new Object();

构造方法

  1. 无参构造
    创建一个TreeMap
public TreeSet() {
	// 调用有参构造 参数是一个NavigableMap接口 
	// TreeMap实现了NavigableMap接口
    this(new TreeMap<E,Object>());
}
  1. 有参构造 参数是一个NavigableMap接口
TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}
  1. 有参构造 参数是一个Comparator接口
    传入一个比较器,调用TreeMap 比较器构造方法传入一个比较器
public TreeSet(Comparator<? super E> comparator) {
	// 调用TreeMap 比较器构造方法
    this(new TreeMap<>(comparator));
}
  1. 有参构造 传入一个Collection接口
public TreeSet(Collection<? extends E> c) {
	// 先调用无参构造创建一个TreeMap
    this();
    // 添加数据
    addAll(c);
}
  1. 有参构造 参数是一个SortedSet接口
public TreeSet(SortedSet<E> s) {
	// 先调用有参构造 参数是一个Comparator接口比较器 创建一个TreeMap
	// s.comparator() 获取比较器
    this(s.comparator());
    // // 添加数据
    addAll(s);
}

addAll方法

public  boolean addAll(Collection<? extends E> c) {
   // 如果存储实际数据的m为空 并且 传入的数据c不为空 并且 传入的数据c实现了SortedSet接口 并且存储实际数据的m是TreeMap
    if (m.size()==0 && c.size() > 0 &&
        c instanceof SortedSet &&
        m instanceof TreeMap) {
        // 满足条件 执行
        // 因为上面验证了c instanceof SortedSet 所以下面可以进行强转
        SortedSet<? extends E> set = (SortedSet<? extends E>) c;
        // 因为上面验证了m instanceof TreeMap 所以下面可以进行强转
        TreeMap<E,Object> map = (TreeMap<E, Object>) m;
        // 获取c的比较器
        Comparator<?> cc = set.comparator();
        // 获取m的比较器
        Comparator<? super E> mc = map.comparator();
        // 如果比较器内存地址相等 或者 c的比较器等于m的比较器
        if (cc==mc || (cc != null && cc.equals(mc))) {
        	// 满足条件执行 调用TreeMap的方法存入数据并排序
        	// TreeMap使用的是红黑树排序
            map.addAllForTreeSet(set, PRESENT);
            return true;
        }
    }
	// 如果不满足上面的情况会调用父类的addAll
	// 父类是AbstractCollection 通过源码可以看出实现调用的还是add()方法
    return super.addAll(c);
}

public abstract class AbstractCollection<E> implements Collection<E> {
	public boolean addAll(Collection<? extends E> c) {
	    boolean modified = false;
	    for (E e : c)
	        if (add(e))
	            modified = true;
	    return modified;
	}
}

add方法

add方法会调用TreeMap的put方法

public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}

size方法

size方法会调用TreeMap的size方法

public int size() {
    return m.size();
}

remove方法

remove方法会调用TreeMap的remove方法

public boolean remove(Object o) {
    return m.remove(o)==PRESENT;
}

总结

  1. TreeSet是用TreeMap存储数据
  2. 在创建的时候可以自己指定比较器,来指定排序顺序。默认是根据自然顺序排序的
  3. TreeSet的操作一般都会调用TreeMap中的方法来处理数据

LinkedHashSet

LinkedHashSet类的定义

// 继承了HashSet
public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
}

构造方法

构造方法都是调用父类HashSet三个参数的构造方法,
父类三个参数的构造方法会创建LinkedHashMap

initialCapacity : 初始化容量大小
loadFactor : 负载因子大小

public LinkedHashSet(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor, true);
}

public LinkedHashSet(int initialCapacity) {
    super(initialCapacity, .75f, true);
}

public LinkedHashSet() {
    super(16, .75f, true);
}

// 父类构造方法
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
	HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
}

总结

  1. LinkedHashSet继承了HashSet
  2. LinkedHashSet的创建都是调用的父类三参构造方法 创建的是LinkedHashMap
  3. LinkedHashSet的操作都会调用父类类方法

Map

类关系图

«interface» Map HashMap «abstract» Dictionary Hashtable «abstract» AbstractMap LinkedHashMap TreeMap «interface» NavigableMap «interface» SortedMap IdentityHashMap ConcurrentHashMap «interface» ConcurrentMap

HashMap

HashMap变量

// 默认HashMap容量 16 2^4次方
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量 2^30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载因子  一般使用默认的不进行修改
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 指定当链表长度大于多少后变成红黑树 默认8
static final int TREEIFY_THRESHOLD = 8;
// 当长度小于6的时候从红黑树转换成链表
static final int UNTREEIFY_THRESHOLD = 6;
// 最小树形化参数  当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)
// 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64;

// 
transient Node<K,V>[] table;
// 
transient Set<Map.Entry<K,V>> entrySet;
// 表示HashMap中存放key-value的数量(为链表和树中的key-value的总和)
transient int size;
// 
transient int modCount;
// threshold表示阀值。当HashMap的size大于threshold时会执行resize(扩容)操作。 
// threshold = capacit(容量)*loadFactor(加载因子)
int threshold;
// 加载因子  一般为默认值0.75f DEFAULT_LOAD_FACTOR
final float loadFactor;

构造方法

  1. 无参构造
public HashMap() {
	// 指定加载因子为默认值 0.75f
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}
  1. 指定初始容量和加载因子
    指定的初始容量是map扩容前可以存放的数据,
public HashMap(int initialCapacity, float loadFactor) {
	// 验证初始容量是否小于0
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    // 初始容量不能大于最大容量,如果大于设置为系统默认最大容量为初始容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 验证负载因子是否正确
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    // 设置负载因子
    this.loadFactor = loadFactor;
    // 设置阀值 目前的阀值是没有经过负载因子计算。在第一次put数据的时候会重新根据负载因子计算
    // 计算公式 阀值 = 容量 * 负载因子
    this.threshold = tableSizeFor(initialCapacity);
}
// 重新计算阀值 保证结果为2的幂次方
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
  1. 指定初始容量
public HashMap(int initialCapacity) {
	// 加载因子为默认值 0.75f
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
  1. 指定Map直接存入数据
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) {
        	// 如果table为null说明还没有数据 重新计算扩容阀值threshold
        	// (float)s / loadFactor) 计算新map的容量 +1.0F 是为了保证在put数据的时候一定不会再进行扩容
            float ft = ((float)s / loadFactor) + 1.0F;
            // ft和默认最大容量对比
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
            	// 如果新的容量大于当前容量则重新计算赋值
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
        	// 如果之前已经有数据 并且新的容量大于之前的容量则进行扩容处理
            resize();
        // 循环新增数据 put到当前map里面
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

// 计算key的hash值
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

// 存入数据
// hash 当前key的hash
// key 新key
// value 新value
// onlyIfAbsent 当为true时如果key存在重复时不替换value值(实际在替换的时候会验证 onlyIfAbsent为fals或旧value为null 才替换)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    // tab 存储数据的节点数组
    // p 存在hash冲突时,和数组下标中的链表中的元素相等的节点
    // n 当前存储数据数组的长度
    // i 根据当前key的hash和存储数据数组长度计算的 hash冲突存在的下标
    Node<K,V>[] tab; Node<K,V> p; int n, i;

	// 如果table为空或者长度为0,即没有元素,那么使用resize()方法扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
        
    // 计算插入存储的数组索引小标i(i = (n - 1) & hash),此处计算方法同 1.7 中的indexFor()方法
    if ((p = tab[i = (n - 1) & hash]) == null)
    	// 如果数组指定下标元素为空,即不存在hash冲突,则直接创建新的节点插入到数组
        tab[i] = newNode(hash, key, value, null);
    else {
    	// 存在hash冲突,解决冲突存储数据
    	// e 已经存在的数据的节点信息 当key不相等时为null
        Node<K,V> e; K k;
        // 判断tab[i] 下的链表第一个元素与当前元素是否相等 hash相等 并且key相等,相等用新的value覆盖旧的value
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // p instanceof TreeNode 验证插入的数据结构是红黑树还是链表
        else if (p instanceof TreeNode)
        	// 红黑树
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
        	// 链表
        	// 循环遍历tab[i] 下的链表判断是否在链表上存在重复的key,如果存在则直接跳出循环
        	// 如果遍历完还是不存在重复的key则直接在链表后面添加新的节点
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                	// 当前已经是最后一个节点 并且没有相等的key 则添加新的节点
                    p.next = newNode(hash, key, value, null);
                    // 验证节点是否大于等于8 如果大于等于则将链表转成红黑树
                    // binCount从0开始 所以当binCount=7的时候就代表链表长度已经到8了
                    // 当链表已经有8个节点了,此时再新链上第9个节点,在成功添加了这个新节点之后,立马做链表转红黑树。
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                    	// 将链表转成红黑树
                        treeifyBin(tab, hash);
                    break;
                }
                // 验证链表下的元素key是否有和新的key想等的,相等直接跳出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果e不为null
        if (e != null) {
            V oldValue = e.value;
            // 如果需要替换value或这旧value为null 则将新value替换旧value
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            // 替换完成直接返回 无需在进行其他操作
            return oldValue;
        }
    }
    // 记录修改次数
    ++modCount;
    // 新key 在当前链表中不存在 新增数据
    // 数据长度+1 并且验证是否需要扩容。 当数据长度>阀值的时候会进行扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

// 创建新节点
// hash-key的hash值
// key
// value
// next 下一个节点
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}


扩容

该方法有2中使用情况:1.初始化哈希表;2.当前数组容量过小,需要扩容

final Node<K,V>[] resize() {
	// oldTab 目前节点数据
    Node<K,V>[] oldTab = table;
    // oldCap 目前节点数组长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // oldThr 目前的扩容阀值
    int oldThr = threshold;
    // 新的容量 新的扩容阀值
    int newCap, newThr = 0;
    // 1.如果原数组有容量(用在当数组存在数据的时候 进行处理)
    // 2.如果原数组容量为0,验证原阀值是否大于0,如果阀值大于0则指定新的容量为原阀值的值。(用在 在创建map的时候指定了容量并且是第一次扩容 )
    // 3.如果使用无参构造创建的map,第一次扩容时用。新容量=默认容量16, 新阀值=默认容量*默认加载因子 结果取整
    if (oldCap > 0) {
    	// 1.如果原数组容量大于等于定义的最大容量则不在进行扩容,并且指定threshold(阀值)为int最大值
    	// 2.如果不满足1, 
    	// newCap = oldCap << 1 新数组容量=原数组容量了2倍(oldCap << 1  相当于 oldCap *2)
    	// 如果新数组容量 小于定义的最大容量 并且 原数容量大于等于默认数组容量 新的阀值等于原阀值的2倍
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; 
    }
    else if (oldThr > 0) 
        newCap = oldThr;
    else {
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 如果新的阀值为0,则重新计算阀值 阀值=新的容量*加载因子,同时验证如果新的容量小于指定的最大容量,并且阀值也小于指定的最大容量 则指定新的阀值为计算之后的阀值,否则为最大int值
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 指定阀值变量为最新计算出来的阀值
    threshold = newThr;
    // 创建新的节点数组 长度为新的容量
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    // 将新的节点数组 赋值给table变量
    table = newTab;
    if (oldTab != null) {
    	// 将老的数据复制到新的数组里面去
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

remove

public V remove(Object key) {
	// 要删除的节点信息
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}
// 删除节点
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        // node 要删除的节点信息
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
           	// 如果删除的是链表第一个
            node = p;
        else if ((e = p.next) != null) {
        	// 如果删除的是不是第一个元素,则循环链表匹配节点
            if (p instanceof TreeNode)
            	// 红黑树查询节点
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
            	// 链表查询节点
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        // 如果查询到删除节点 执行删除
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
            	// 红黑树删除
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            // 记录修改次数
            ++modCount;
            // 存储数据大小-1
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

总结

  1. 默认长度为16

  2. map最大长度为2^30 (1 << 30) = 1,073,741,824

  3. 创建HashMap对象 长度一定的是2次幂,在指定长度后会进行tableSizeFor处理,保证结果为2次幂
    为什么长度结果是2次幂值
    ​ 1)、&位运算速度快,至少比%取模运算块
    ​ 2)、能保证 索引值 肯定在 capacity 中,不会超出数组长度
    ​ 3)、putVal时用到(n - 1) & hash,当n为2次幂时,会满足一个公式:(n - 1) & hash = hash % n

  4. 当链表长度大于等于8的时候将链表转为红黑树

ConcurrentHashMap

属性

static final int MOVED     = -1; // 表示正在转移
static final int TREEBIN   = -2; // 表示已经转换成树

put方法

public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
	// key和value不可以为null
    if (key == null || value == null) throw new NullPointerException();
    // 获取key的hash
    int hash = spread(key.hashCode());
    int binCount = 0;// 记录当前节点下的链表长度
    
    // 自旋 for (Node<K,V>[] tab = table;;)一直循环 直到执行了break方法 跳出循环
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            // 第一次put的时候table没有初始化,则初始化table
            tab = initTable();
			
			// tabAt(tab, i = (n - 1) & hash)) 通过哈希计算出一个表中的位置因为n是数组的长度,所以(n-1)&hash肯定不会出现数组越界
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
        	// casTabAt 如果这个位置没有元素的话,则通过cas的方式尝试添加,注意这个时候是没有加锁的
        	// new Node<K,V>(hash, key, value, null) 创建一个Node添加到数组中区,null表示的是下一个节点为空
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }

		/*
         * 如果检测到某个节点的hash值是MOVED,则表示正在进行数组扩张的数据复制阶段,
         * 则当前线程也会参与去复制,通过允许多线程复制的功能,一次来减少数组的复制所带来的性能损失
         */
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);

		/*
         * 如果在这个位置有元素的话,就采用synchronized的方式加锁,
         *     如果是链表的话(hash大于0),就对这个链表的所有元素进行遍历,
         *         如果找到了key和key的hash值都一样的节点,则把它的值替换到
         *         如果没找到的话,则添加在链表的最后面
         *  否则,是树的话,则调用putTreeVal方法添加到树中去
         *  
         *  在添加完之后,会对该节点上关联的的数目进行判断,
         *  如果在8个以上的话,则会调用treeifyBin方法,来尝试转化为树,或者是扩容
         */
        else {
            V oldVal = null;
            synchronized (f) {
                 // 再次取出要存储的位置的元素,跟前面取出来的比较
                if (tabAt(tab, i) == f) {
                	//  取出来的元素的hash值大于0,当转换为树之后,hash值为-2
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
							// 要存的元素的hash,key跟要存储的位置的节点的相同的时候,替换掉该节点的value即可
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                // 当使用putIfAbsent的时候,只有在这个key没有设置值得时候才设置
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            
                            if ((e = e.next) == null) {
                            	// 如果和第一个不是同样的hash,同样的key的时候,则判断该节点的下一个节点是否为空,
                            	// 为空的话把这个要加入的节点设置为当前节点的下一个节点
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
					
					// 表示已经转化成红黑树类型了 则往红黑树里面插入
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                	// 当在同一个节点的数目达到8个的时候,则扩张数组或将给节点的数据转为tree
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

// 用在计算hash时进行安位与计算消除负hash
static final int HASH_BITS = 0x7fffffff;
// 计算key的hash
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

第一次put数据初始化

/**
 * 用来控制表初始化和扩容的,默认值为0,当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75
 * 当为负的时候,说明表正在初始化或扩张,
 *     -1表示初始化
 *     -(1+n) n:表示活动的扩张线程
 */
private transient volatile int sizeCtl;
private static final sun.misc.Unsafe U;
    
 // 初始化数组table
 private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
     // 第一次put的时候,table还没被初始化,进入while
    while ((tab = table) == null || tab.length == 0) {
        // sizeCtl初始值为0,当小于0的时候表示在别的线程在初始化表或扩展表
        if ((sc = sizeCtl) < 0)
        	// Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间
        	// 因为其他线程在初始化表 当前线程就不需要在初始化
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
        	// 使用CAS比较保证线程安全。 SIZECTL:表示当前对象的内存偏移量,sc表示期望值,-1表示要替换的值,设定为-1表示要初始化表了
            try {
                if ((tab = table) == null || tab.length == 0) {
                	//  指定了大小的时候就创建指定大小的Node数组,否则创建指定大小(16)的Node数组
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

总结

  1. ConcurrentHashMap key的hash计算和HashMap key的hash计算不一同
  2. key和value不可以为null

LinkedHashMap

总结

TreeMap

总结

Hashtable

总结

IdentityHashMap

总结

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值