Java集合总结

集合类图

在这里插入图片描述

集合的原理,区别

1、ArrayList和LikedList的区别?
  • ArrayList的底层实现是Object数组。当我们new
  	// 初始化数组的长度
    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};
	
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 初始化的数组
    transient Object[] elementData; // non-private to simplify nested class access

	// list中存在多少个元素
    private int size;
    
    // 创建一个默认大小为 10 的list集合
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

	// 扩容机制,每次都是以当前容量大小的1.5倍进行扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }
  • LinkedList的底层实现是 Node节点,将数据通过前后指针链接起来
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;


    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

	// 当add进行数据添加的时候,每次都是将数据添加在Node节点的最后一个位置
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    /**
     * Links e as last element.
     */
    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++;
    }
    
    // Node 节点
    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;
        }
    }
2、hashSet和LinkedHashSet的区别?

hashSet的底层实现就是HashMap
LinkedHashset的底层实现其实就是 LinkedHashMap
所以这2个的区别其实就是 HashMap 与 LinkedHashMap 的区别

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    
	// new HashSe的时候其实就是创建一个 大小为16,负载因子为0.75的 HashMap
    public HashSet() {
        map = new HashMap<>();
    }
	// 添加元素的时候,其实就是将传入的对象作为key进行存储,value其实是 new Object() 对象
	    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
3、HashSet与TreeSet的区别?

HashSet 的底层是 HashMap
TreeSet 的底层是 TreeMap
所以它们的区别其实就是 HashMap 与 TreeMap的区别

TreeMap的底层实现,是基于红黑树实现的

	// TreeMap属性
    private final Comparator<? super K> comparator;
    private transient TreeMap.Entry<K, V> root;
    private transient int size = 0;
    private transient int modCount = 0;
    private transient TreeMap<K, V>.EntrySet entrySet;
    private transient TreeMap.KeySet<K> navigableKeySet;
    private transient NavigableMap<K, V> descendingMap;
    private static final Object UNBOUNDED = new Object();
    private static final boolean RED = false;
    private static final boolean BLACK = true;

	// TreeMap构造器
	    public TreeMap() {
        this.comparator = null;
    }

    public TreeMap(Comparator<? super K> var1) {
        this.comparator = var1;
    }

    public TreeMap(Map<? extends K, ? extends V> var1) {
        this.comparator = null;
        this.putAll(var1);
    }

    public TreeMap(SortedMap<K, ? extends V> var1) {
        this.comparator = var1.comparator();

        try {
            this.buildFromSorted(var1.size(), var1.entrySet().iterator(), (ObjectInputStream)null, (Object)null);
        } catch (IOException var3) {
        } catch (ClassNotFoundException var4) {
        }

    }


	// 存储数据的Entry
	    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key; 
        V value;
        Entry<K,V> left; // 左节点
        Entry<K,V> right; // 右节点
        Entry<K,V> parent;  // 父节点
        boolean color = BLACK; // 节点颜色

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        /**
         * Returns the key.
         *
         * @return the key
         */
        public K getKey() {
            return key;
        }

        /**
         * Returns the value associated with the key.
         *
         * @return the value associated with the key
         */
        public V getValue() {
            return value;
        }

        /**
         * Replaces the value currently associated with the key with the given
         * value.
         *
         * @return the value associated with the key before this method was
         *         called
         */
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }

TreeMap.put()添加元素的时候,会对key进行排序。默认是正序排序。

    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     *
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code key}.)
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map uses natural ordering, or its comparator
     *         does not permit null keys
     */
    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;
    }
4、HashMap与ConcurrentHashMap的区别?

HashMap不是线程安全的
ConcurrentHashMap是线程安全的

在7中ConcurrentHashMap是通过分段锁(segment来实现并发的)
在8中通过数组+cas+红黑树来实现并发

下面是java8中 ConcurrentHashMap 的底层实现

1、Node节点用来存储数据,hash,key都是 final;val 和 next 都是用 volatile 修饰。
所以在高并发时,可以保证内存数据可见性。

    /**
     * Key-value entry.  This class is never exported out as a
     * user-mutable Map.Entry (i.e., one supporting setValue; see
     * MapEntry below), but can be used for read-only traversals used
     * in bulk tasks.  Subclasses of Node with a negative hash field
     * are special, and contain null keys and values (but are never
     * exported).  Otherwise, keys and vals are never null.
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }

下面的代码是往 concurrentHashMap 中存放数据的逻辑

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

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    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)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
5、HashMap 与 LinkedHashMap 区别
  • HashMap 是无序的,如果我们希望有顺序的去存储 key-value类型的数据,就需要用到 LinkedHashMap、

  • LinkedHashMap 继承了 HashMap,当调用 LinkedHashMap.put() 的时候,实际上是调用了HashMap.put(); 保存的数据都是在Node节点中。但是由于 LinkedHashMap.Entry 同时也继承了 HashMap.Node; 此时Entry 除了存储的数据之外,还有了前后指针 before,after

    /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

在调用HashMap的时候,最后会将数据插入至前一个数据的节点上

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

LinkedHashMap 在添加数据的时候,下面标红的几行都是继承了HashMap,此时就会在LinkedHashMap 的尾部节点上添加数据,链式的存数据。
在这里插入图片描述

6、ConcurrentHashMap

在这里插入图片描述
jdk1.8 ConcurrentHashMap 与 jdk1.8 HashMap的结构大体是一致的

jdk1.7 采用的是 分段锁(segmengt)的机制,一个分段 segmengt 就是一个 数组,segmengt继承了 ReentrantLock,实现了加锁。

但是在jdk1.8,摒弃了分段锁(segmengt),通过使用 CAS + Synchronized 的方式来保证并发安全性。

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
             // 计算出的 f 就是经过位与运算计算出的值,如果为空,表示数据可以尝试写入,
             // 写入的时候,利用 CAS 尝试写入,失败则自旋保证成功。
            }

concurrentHashMap在添加元素的时候,多线程之间,以volatile的方式读取sizeCtl属性,
来判断ConcurrentHashMap当前所处的状态。通过cas设置sizeCtl属性,告知其他线程ConcurrentHashMap的状态变更。

扩容:
未初始化:
sizeCtl=0:表示没有指定初始容量。
sizeCtl>0:表示初始容量。

初始化中:
sizeCtl=-1,标记作用,告知其他线程,正在初始化

正常状态:
sizeCtl=0.75n ,扩容阈值

扩容中:
sizeCtl < 0 : 表示有其他线程正在执行扩容
sizeCtl = (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2 :表示此时只有一个线程在执行扩容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值