集合Collection

TreeMap

put

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

fixAfterInsertion

//平衡红黑树
private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;
​
    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK;
}

putAll

public void putAll(Map<? extends K, ? extends V> map) {
    int mapSize = map.size();
    if (size==0 && mapSize!=0 && map instanceof SortedMap) {
        Comparator<?> c = ((SortedMap<?,?>)map).comparator();
        if (c == comparator || (c != null && c.equals(comparator))) {
            ++modCount;
            try {
                buildFromSorted(mapSize, map.entrySet().iterator(),
                                null, null);
            } catch (java.io.IOException cannotHappen) {
            } catch (ClassNotFoundException cannotHappen) {
            }
            return;
        }
    }
    super.putAll(map);
}

buildFromSorted

//将一个有序数据源转化为红黑树
private void buildFromSorted(int size, Iterator<?> it,
                             java.io.ObjectInputStream str,
                             V defaultVal)
    throws  java.io.IOException, ClassNotFoundException {
    this.size = size;
    root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
                           it, str, defaultVal);
}
/**
* level: 当前树层级,初始调用时为0。
lo: 当前子树要处理的元素起始索引(实际是顺序处理,非真实索引)。
hi: 当前子树要处理的元素结束索引。
redLevel: 应该将节点标记为红色的层级(在红黑树中用于保持树的平衡)。
it: 用于遍历元素的迭代器(如果非空)。
str: 用于从对象流中读取元素的输入流(如果迭代器为空)。
defaultVal: 当迭代器仅提供键时使用的默认值。
*/
 private final Entry<K,V> buildFromSorted(int level, int lo, int hi,int redLevel,Iterator<?> it,java.io.ObjectInputStream str,V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {
        //如果hi < lo,说明没有元素需要处理,返回null
        if (hi < lo) return null;
        //找到中间元素,以此为中心建树
        int mid = (lo + hi) >>> 1;
        
        Entry<K,V> left  = null;
        if (lo < mid)
            //左子结点
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
                                   it, str, defaultVal);
​
        // extract key and/or value from iterator or stream
        K key;
        V value;
     //如果迭代器不为空
        if (it != null) {
            //默认值为空,提供键和真实值
            if (defaultVal==null) {
                Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next();
                key = (K)entry.getKey();
                value = (V)entry.getValue();
               // 不为空,值设为默认值
            } else {
                key = (K)it.next();
                value = defaultVal;
            }
         //迭代器为空   
        } else { // use stream
            key = (K) str.readObject();
            value = (defaultVal != null ? defaultVal : (V) str.readObject());
        }
        //构建中间值
        Entry<K,V> middle =  new Entry<>(key, value, null);
​
        // 如果当前层级level等于redLevel,则将中间节点标记为红色
        if (level == redLevel)
            middle.color = RED;
        
        if (left != null) {
            middle.left = left;
            left.parent = middle;
        }
        //构建右子节点
        if (mid < hi) {
            Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
                                               it, str, defaultVal);
            middle.right = right;
            right.parent = middle;
        }
​
        return middle;
    }
​

getCeilingEntry/getFloorEntry

//getCeilingEntry 查找不小于给定键的最小键的条目,包括等于给定键的情况。
final Entry<K,V> getCeilingEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else if (cmp > 0) {
            if (p.right != null) {
                p = p.right;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } else
            return p;
    }
    return null;
}
​
final Entry<K,V> getFloorEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp > 0) {
            if (p.right != null)
                p = p.right;
            else
                return p;
        } else if (cmp < 0) {
            if (p.left != null) {
                p = p.left;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.left) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } else
            return p;
​
    }
    return null;
}
​
​
//getHigherEntry 查找大于给定键的最小键的条目,不包括等于给定键的情况
final Entry<K,V> getHigherEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else {
            if (p.right != null) {
                p = p.right;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        }
    }
    return null;
}
​
​
final Entry<K,V> getLowerEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp > 0) {
            if (p.right != null)
                p = p.right;
            else
                return p;
        } else {
            if (p.left != null) {
                p = p.left;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.left) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        }
    }
    return null;
}

TreeSet

TreeSet基于TreeMap

public TreeSet() {
    this(new TreeMap<E,Object>());
}
​
​
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}
​
​
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}
​
​
public TreeSet(SortedSet<E> s) {
    this(s.comparator());
    addAll(s);
}

add/remove

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

LinkedHashMap

LinkedHashMap中的构造器使用了HashMap的构造器

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}
​
/**
 * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
 * with the specified initial capacity and a default load factor (0.75).
 *
 * @param  initialCapacity the initial capacity
 * @throws IllegalArgumentException if the initial capacity is negative
 */
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}
​
/**
 * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
 * with the default initial capacity (16) and load factor (0.75).
 */
public LinkedHashMap() {
    super();
    accessOrder = false;
}
​
/**
 * Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with
 * the same mappings as the specified map.  The <tt>LinkedHashMap</tt>
 * instance is created with a default load factor (0.75) and an initial
 * capacity sufficient to hold the mappings in the specified map.
 *
 * @param  m the map whose mappings are to be placed in this map
 * @throws NullPointerException if the specified map is null
 */
public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}
​
/**
 * Constructs an empty <tt>LinkedHashMap</tt> instance with the
 * specified initial capacity, load factor and ordering mode.
 *
 * @param  initialCapacity the initial capacity
 * @param  loadFactor      the load factor
 * @param  accessOrder     the ordering mode - <tt>true</tt> for
 *         access-order, <tt>false</tt> for insertion-order
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 */
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}
transient LinkedHashMap.Entry<K,V> head;
​
​
transient LinkedHashMap.Entry<K,V> tail;
​
//这个变量是一个布尔值,用来指示LinkedHashMap应该使用哪种顺序来迭代其元素。如果accessOrder为true,则元素按照它们被访问的顺序来迭代(最近访问的元素首先被迭代),这称为访问顺序。如果accessOrder为false,则元素按照它们被插入的顺序来迭代,这称为插入顺序。这个属性是在LinkedHashMap对象被创建时确定的,之后不会改变。默认都是插入顺序
final boolean accessOrder;

put方法直接使用的HashMap中的put方法,但在LinkedHashMap类中,重写了afterNodeAccess(e)和 afterNodeInsertion(evict)方法,解决了LinkedHashMap中的after、before节点的问题

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);
    }
}
​
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

直接使用的是HashMap中的put方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //如果数组为空,进行resize()初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //(n - 1) & hash计算出该键值对应该放在哪个桶里,获取数组的索引位置
    //如果计算的位置Node不存在,直接创建节点插入
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        //如果已经存在的key与传入的Key一模一样,进行覆盖
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //如果 index 位置元素已经存在,且是红黑树
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        //如果是链表的情况
        else {
            //对链表进行遍历,统计链表长度
            for (int binCount = 0; ; ++binCount) {
                //如果节点链表的next为空
                if ((e = p.next) == null) {
                    //找到节点链表中next为空的节点,创建新的节点插入
                    p.next = newNode(hash, key, value, null);
                    //如果插入后,节点的链表中的个数超过8,转化为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //树化
                        treeifyBin(tab, hash);
                    break;
                }
                //判断节点链表中的key和传入的key是否一样
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    //如果一样,退出
                    break;
                p = e;
            }
        }
        //如果存在相同key的节点e不为空
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            //onlyIfAbsent表示是否在oldValue为null 的情况下更新键值对的值
            if (!onlyIfAbsent || oldValue == null)
                //设置新的值
                e.value = value;
            afterNodeAccess(e);
           //返回旧的结果
            return oldValue;
        }
    }
    ++modCount;
    //当前大小大于临界大小、扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

HashSet

public HashSet() {
    map = new HashMap<>();
}
​
​
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}
​
​
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
​
​
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}
​
​
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
​
​
public Iterator<E> iterator() {
    return map.keySet().iterator();
}
​
​
public int size() {
    return map.size();
}
​
​
public boolean isEmpty() {
    return map.isEmpty();
}
​
​
public boolean contains(Object o) {
    return map.containsKey(o);
}
​
​
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
​
​
public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

HashMap与HashSet的不同

1.接口和实现

  • HashSet实现了Set接口,他是一个不包含重复元素的集合

  • HashMap实现了Map接口,它是一个存储键值对的集合,每个键都映射到最多一个值

2.存储内容

  • HashSet存储的是对象本身,即它存储的是一个元素的集合,这些元素是唯一的。

  • HashMap存储的是键值对(Key-Value Pair),键是唯一的,但值可以重复。

3.用途

  • HashSet主要用于需要快速查找、插入和删除元素,且元素唯一性要求较高的场景。

  • HashMap主要用于存储键值对,并通过键来快速访问值。

4.内部实现细节

  • 尽管HashSet内部使用HashMap来存储元素,但它只使用了HashMap的键部分,而忽略了值部分。在HashSet中,所有的值都被设置为一个固定的哑元素(PRESENT),这个元素在HashSet的实现中没有任何实际意义,只是用来填充HashMap的值部分。

  • HashMap则完整地使用了其键值对结构,允许用户存储和访问键值对。

HashTable

//它是一个32位整数,其中最高位(即符号位)是0,其余31位都是1。
//hash & 0x7FFFFFFF相当于取模
int index = (hash & 0x7FFFFFFF) % tab.length;

该类中的方法常用synchronized来修饰,线程安全

public Hashtable(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load: "+loadFactor);
​
    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
    table = new Entry<?,?>[initialCapacity];
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
​
​
public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
}
​
​
public Hashtable() {
    this(11, 0.75f);
}
​
​
public Hashtable(Map<? extends K, ? extends V> t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}

put方法(头插法)

public synchronized V put(K key, V value) {
    //如果value、key为空的话,会抛出异常
    if (value == null) {
        throw new NullPointerException();
    }
​
    
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    //抑制因泛型擦除而引起的类型转换警告
    @SuppressWarnings("unchecked")
    //先找到对应的桶
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    //进行遍历,寻找是否有与添加的元素键值相同的,如果相同,覆盖掉原来的值,返回原来的值
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }
    //将添加的元素放在最前面
    addEntry(hash, key, value, index);
    return null;
}

addEntry

private void addEntry(int hash, K key, V value, int index) {
    modCount++;
​
    Entry<?,?> tab[] = table;
    
    if (count >= threshold) {
        // 先扩容,后插入
        rehash();
​
        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }
​
    // Creates the new entry.
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>) tab[index];
    //头插法
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

rehash方法

protected void rehash() {
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table;
​
    // 扩容至原来的两倍再加1
    int newCapacity = (oldCapacity << 1) + 1;
    
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    //创建一个新容量数组
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
    //修改次数+1
    modCount++;
    //扩容阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    //将table指向新创建的数组
    table = newMap;
    //倒着遍历,只是一种可能的优化手段。在某些哈希表的实现中,特别是当哈希表的加载因子较高时,后插入的元素更有可能导致哈希冲突,并且这些元素更有可能位于数组的末尾。因此,从后往前遍历可以减少在重新哈希过程中对新插入元素的移动。
    for (int i = oldCapacity ; i-- > 0 ;) {
        //遍历当前桶内的元素
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;
            old = old.next;
            //重新获取索引值
            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            // 将e插入到新链表的头部 
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

remove方法

public synchronized V remove(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>)tab[index];
    //进行查找
    for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            modCount++;
            if (prev != null) {
                prev.next = e.next;
            } else {
                tab[index] = e.next;
            }
            count--;
            V oldValue = e.value;
            //虽然e已经从链表中移除,但它仍然是一个有效的对象,在Java的内存中占用空间。如果不将e.value设置为null,那么即使这个键值对不再被HashMap管理,其值对象仍然会被垃圾回收器视为可达(因为e对象本身仍然存在),从而可能导致内存泄漏。将e.value设置为null有助于垃圾回收器识别这个值对象不再被使用,进而可以在未来的某个时间点回收它占用的内存。
            e.value = null;
            return oldValue;
        }
    }
    return null;
}

HashMap和HashTable的区别

  1. HashMap非线程安全,适合单线程环境,而HashTable是线程安全但效率低,推荐使用ConcurrentHashMap替代。

  2. HashTable不允许空键值,HashMap则可以

  3. 在结构上,hashTable一直是数组和链表结构,而hashMap在1.8时改为了数组和链表及红黑树

  4. 在遍历上,HashTable使用Enumeration,HashMap使用Iterator。

  5. 在容量上,hashTable默认为11,扩容为2n+1,而hashMap默认为16,扩容为2倍。如添加指定长度,table会直接使用,而map总会扩充为2的n次幂。

  6. 在散列算法上,上HashTable会使用key对hashCode对长度取模,hashMap会做一些扰动来达到更好的分布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值