Hashset源码分析

一、概述:

● 基于HashMap实现,不存在重复元素
● 无序无索引但存在null值
● 底层数据结构:数组+链表(红黑树)
● 扩容机制:初始通量为16,加载因子:0.75,当存储元素容量达到:16*0.75时,扩容为:原容量的2倍。

二、底层数据结构图示

在这里插入图片描述

三、boolean add(E e)添加元素:

// 虚拟值占据位置,标识该对象在映射中存在
private static final Object PRESENT = new Object();
// 
private transient HashMap<E,Object> map;

public boolean add(E e) {		//接受参数
return map.put(e, PRESENT)==null;	//调用 HashMap 的put 方法添加元素
}
public V put(K key, V value) {
    // 调用hash方法计算出哈希值找到对应存储的位置
    return putVal(hash(key), key, value, false, true);//调用putVal方法
}

static final int hash(Object key) {
    int h;
// 获取key的哈希码,将其进行异或运算
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
transient Node<K,V>[] table;

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)	//如果为真,则条用resize()方法扩容
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 根据计算得到的哈希值和数组长度,找到存储位置的索引 i。如果该位置为空,则直接创建新节点并插入。
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 若当前节点 p 的哈希值与给定哈希值相等,并且键 key 和当前节点的键相等(或引用相等),则将当前节点赋值给变量 e。
            e = p;
        else if (p instanceof TreeNode)
            // 若当前节点是红黑树节点,则调用红黑树的插入方法 putTreeVal。
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 遍历链表,查找与给定键相等的节点
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //如果链表长度超过阈值 TREEIFY_THRESHOLD - 1,即链表长度达到树化阈值的上限,将链表转化为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 如果大小超过负载因子阈值,调用 resize() 方法进行扩容。
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

● 说明:向HashSet对象中添加元素时,首先通过hash函数计算除hash码值,根据计算得到的哈希值和数组长度,找到存储位置的索引 i。如果该位置为空,则直接创建新节点并插入。如果该位置已经存在节点,判断当前节点的哈希值与给定哈希值是否相等,并且键 key 和当前节点的键是否相等(或引用相等),若相等则将当前节点赋值给变量。如果该条件不满足,则判断该节点是不是红黑树的叶子节点,若是则插入,如果不是则遍历链表,查找与给定键相等的节点,判断链表长度超过阈值 (size>8),即链表长度达到树化阈值的上限,将链表转化为红黑树.如果未找到与给定键相等的节点,在链表末尾添加新节点。

四、boolean remove(Object o) 移除元素

private static final Object PRESENT = new Object();

public boolean remove(Object o) {
// 	调用HashMap 的 remove 方法
	return map.remove(o)==PRESENT;
}

● 说明:map.remove(o) 用于移除键为 o 的映射,如果 remove() 返回 PRESENT,即已定义的常量对象,在 HashSet 中表示元素存在且成功移除,则 remove() 方法返回 true。如果 remove() 返回其他值,表示元素不存在或者移除失败,则 remove() 方法返回 false。

public V remove(Object key) {
Node<K,V> e;
// 计算key 的hash值,并调用removeNode方法移除节点
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}

● 说明:首先,根据传入的 key 计算哈希值,并确定对应的桶(bucket)索引值。然后,调用 removeNode() 方法进行节点的删除操作。removeNode() 方法用于删除指定桶中的节点,并返回被删除的节点。如果 removeNode() 返回的节点 e 为 null,则表示指定键对应的节点不存在,即要删除的元素不存在,此时返回 null。如果 removeNode() 返回的节点 e 不为 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;
    // 判断table是否为空,若不为空,根据hash值计算出对应桶的索引,并获得桶的头节点p
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        //判断 p 节点是否为要删除的节点,如果是,则将p 节点赋值给 node
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            // 如果节点p 不匹配,则遍历红黑树或链表,找到要删除匹配的节点,并赋值给 node
            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)))) {
            // 如果节点为 红黑树节点,调用removeTreeNode方法进行移除
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 如果 node 是链表头节点,将下一个节点作为新的头节点
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            // 调用该方法进行删除后的处理
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

● 说明:判断table是否为空,若不为空,根据hash值计算出对应桶的索引,并获得桶的头节点p,然后判断 p 节点是否为要删除的节点,如果是,则将p 节点赋值给 node。如果节点p 不匹配,则遍历红黑树或链表,找到要删除匹配的节点,并赋值给 node。如果节点为 红黑树节点,调用removeTreeNode方法进行移除。如果 node 是链表头节点,将下一个节点作为新的头节点。并返回node节点。

五、void clear() 移除所有元素

public void clear() {
    map.clear();
}

public void clear() {
    Node<K,V>[] tab;
    // 修改程序计数器
    modCount++;
    if ((tab = table) != null && size > 0) {
        // 如果table 不为空,并且长度大于0,则将长度置为0,表示该对象不在拥有任何元素
        size = 0;
        for (int i = 0; i < tab.length; ++i)
        // 通过循环遍历将每个桶都置为 null。
            tab[i] = null;
    }
}

说明:首先,声明一个 Node<K,V> 类型的数组变量 tab 用于存储哈希表。同时,增加修改计数器 modCount 的值来标识哈希表的修改。判断哈希表 table 是否不为 null,且当前哈希表中存在元素(size > 0)。如果满足条件,则将 size 设置为 0,表示哈希表中不再有任何元素。然后,使用循环遍历整个哈希表数组 tab,将每个桶置为空(即将桶中的引用设置为 null)。完成清空操作后,HashMap 中的键值对都被彻底移除。但不会释放数组和节点占用的内存空间,只是将桶中的引用置为 null,以便后续的垃圾回收。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值