TreeSet/HashSet(TreeMap/HashMap) 区别

本文详细对比了Java集合框架中的TreeSet和HashSet,它们分别基于TreeMap(红黑树实现)和HashMap(Node数组实现)。内容包括构造函数、add方法、get方法、remove方法、Iterator的实现以及两者的区别。TreeSet支持自动排序,而HashSet的元素是无序的。插入自定义类对象时,TreeSet需要实现Comparable接口,HashSet则需重写equals和hashCode方法。两者在处理null值和扩容机制上也有所不同。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、构造函数

TreeSet的无参构造函数如下

	public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

也就是说底层存储数据的是一个TreeMap,TreeMap是由红黑树实现的。
HashSet的无参构造函数如下

	public HashSet() {
        map = new HashMap<>();
    }

也就是说底层存储数据的是一个HashMap,由Node<K,V>数组实现。

2、add方法

TreeSet的add方法如下

	private static final Object PRESENT = new Object();
	public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

可以看到是在TreeMap中插入一个key为add对象,value为一个Object的键值对,所有的TreeSet中的TreeMap的value都是同一个Object
TreeMap的put方法:

public V put(K key, V value) {
        Entry<K,V> t = root;
        //如果原来的TreeMap为空,
        if (t == null) {
            compare(key, key); // 构造函数中没有初始化comparator的,key为null时会抛异常;否则要看comparator的实现

            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) {
        	//构造函数中初始化了comparator的,要看具体实现来看能不能用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 {
        	//构造函数中没有初始化comparator的,如果put的键是null会报错
            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方法来将红黑树重新平衡,具体方法就是变色和旋转,简单的说明点此

HashSet的add方法如下

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

可以看到是在HashMap中插入一个key为add对象,value为一个Object的键值对,同样的所有的HashSet中的HashMap的value都是同一个Object

HashMap的put方法如下

	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
	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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            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))))
                e = p;
            else if (p instanceof TreeNode)
                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);
                        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;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

可以看到在插入第一个元素之前HashMap的table属性是null,因此我们会调用resize方法来对Map进行初始化。

	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 threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = 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;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        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;
    }

可以看到由原map获取到的oldTab,oldCap,oldThr不是null就是0,因此在接下去的流程中会给newCap,newThr两个变量赋初始值,而后创建一个newCap大小的Node数组newTab,这样完成了HashMap的初始化。

插入值的过程首先判断该元素hash值对应Node数组的位置是否有值,如果没有,直接新建一个Entry存入相应的位置。如果有值,那么要判断这个值的类型,这里有两种情况:红黑树和链表。然后在红黑树或链表中找key相同的Node,如果找到了,那么会用新值替换原有Node的value。如果没有找到那么新建一个node插入链表的末端或者红黑树合适的位置上。如果链表的长度在插入新元素后达到7,那么调用treeifyBin方法,依据Node数组的长度是否达到64来决定是将链表转换成红黑树还是调用resize方法来扩大数组。如果对应索引是红黑树,那么还会通过moveRootToFront方法确保根节点是与Node数组相连的节点。

另外有afterNodeAccessafterNodeInsertion两个空方法以供重写来在插入完成后进行一些操作。

3、get方法

对于Set来说其实是没有相对应的get方法的,因此此处主要来看对应Map类的get方法。
TreeMap的get方法如下

	public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
    
	final Entry<K,V> getEntry(Object key) {
        // 如果自定义了comparator那么调用getEntryUsingComparator
        if (comparator != null)
            return getEntryUsingComparator(key);
        // 如果没有自定义comparator且key为null,则抛异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        // 红黑树二分查找,找到返回对应Entry,否则返回null
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }
	//使用自定义的comparator二分查找,找到返回对应Entry,否则返回null
	final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

简单来说就是使用初始化的comparator或者key对象类型对应的Comparable实现依靠红黑树的二分查找来查找对应的Entry,并将Entry的value值返回。

HashMap的get方法如下

	public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            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;
    }

如果map为空则直接返回null,否则通过hash值找到数组中对应的索引,而后判断所以中对应的Node是否就是目标,如果不是则遍历链表或在红黑树中二叉查找目标,如果找到则将目标Node的value返回,否则返回null。

4、remove方法

TreeSet的remove方法如下

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

实际上是调用TreeMap的remove方法

	public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

首先调用getEntry方法获取到key对应的Entry,具体见1.3节。然后调用deleteEntry方法来删除Entry。

	private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

我们看到如果要删除的Entry有两个子节点,那么会通过successor找到继任节点,并将继任节点的值复制到要删除的Entry节点,而后之前寻找到的Entry就不用删除了,而是对继任节点进行操作,继任节点代替目标节点。

	static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

可以看到如果目标节点有右子节点,那么返回比目标节点大的最小节点,否则返回目标节点的父节点中第一个非左子节点的节点。而由于此处目标节点必然有右子节点,所以此处的返回是比目标节点大的最小节点。

之后获取目标节点的子节点作为替代节点,左子节点优先。

如果目标节点是叶节点,那么判断目标节点是否是根节点,如果是根节点,那么这个红黑树置空;否则判断目标节点是否是黑色节点来判断是否需要通过fixAfterDeletion方法再平衡(如果目标节点是黑色节点,那么移除之后必然会导致“从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点”这条不满足),之后将目标节点断开。

如果目标节点有子节点那么替代节点替代目标节点的位置,目标节点与树的连接全部断开。如果目标节点是黑色的那么调用fixAfterDeletion方法再平衡。

HashSet的remove方法和TreeSet没有什么不同,只不过它调用的是HashMap的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<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;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

可以看到,大体是先调用类似于getNode方法的流程查找目标Node,如果没找到直接返回null,如果找到了分情况处理:如果目标是在红黑树中,调removeTreeNode方法移除并再平衡,如果目标是链表的首位,那么数组将指向目标后面的元素,否则只要将目标节点从链表中删除即可。最后调用afterNodeRemoval方法来做删除后处理。
我们来详细看下removeTreeNode方法。

	final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                return;
            int index = (n - 1) & hash;
            TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
            TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
            if (pred == null)
                tab[index] = first = succ;
            else
                pred.next = succ;
            if (succ != null)
                succ.prev = pred;
            if (first == null)
                return;
            if (root.parent != null)
                root = root.root();
            if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
                tab[index] = first.untreeify(map);  // too small
                return;
            }
            TreeNode<K,V> p = this, pl = left, pr = right, replacement;
            if (pl != null && pr != null) {
                TreeNode<K,V> s = pr, sl;
                while ((sl = s.left) != null) // find successor
                    s = sl;
                boolean c = s.red; s.red = p.red; p.red = c; // swap colors
                TreeNode<K,V> sr = s.right;
                TreeNode<K,V> pp = p.parent;
                if (s == pr) { // p was s's direct parent
                    p.parent = s;
                    s.right = p;
                }
                else {
                    TreeNode<K,V> sp = s.parent;
                    if ((p.parent = sp) != null) {
                        if (s == sp.left)
                            sp.left = p;
                        else
                            sp.right = p;
                    }
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                p.left = null;
                if ((p.right = sr) != null)
                    sr.parent = p;
                if ((s.left = pl) != null)
                    pl.parent = s;
                if ((s.parent = pp) == null)
                    root = s;
                else if (p == pp.left)
                    pp.left = s;
                else
                    pp.right = s;
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
            }
            else if (pl != null)
                replacement = pl;
            else if (pr != null)
                replacement = pr;
            else
                replacement = p;
            if (replacement != p) {
                TreeNode<K,V> pp = replacement.parent = p.parent;
                if (pp == null)
                    root = replacement;
                else if (p == pp.left)
                    pp.left = replacement;
                else
                    pp.right = replacement;
                p.left = p.right = p.parent = null;
            }

            TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

            if (replacement == p) {  // detach
                TreeNode<K,V> pp = p.parent;
                p.parent = null;
                if (pp != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                }
            }
            if (movable)
                moveRootToFront(tab, r);
        }

我们看到首先将目标的前后两个节点和当前红黑树和数组相连的节点取出,将目标节点前后两个节点相连,完成链表结构上的删除。然后获取红黑树的根节点,之后判断该红黑树根节点的左子节点的左子节点和根节点的右子节点是否存在,如果有一个不存在(红黑树大小可能在2-6之间)则调用untreeify方法将红黑树转换成链表。 而后通过连接的断开和续接来将目标节点从红黑树中移除,之后根据目标节点是否是黑色来判断是否调用balanceDeletion方法来再平衡。最后依据movable变量来看是否要调用moveRootToFront方法将红黑树的根节点移到链表的头上。

5、Iterator

TreeSet的Iterator来源于其TreeMap的KeySet的Iterator。

 	public Iterator<E> iterator() {
        if (m instanceof TreeMap)
            return ((TreeMap<E,?>)m).keyIterator();
        else
            eturn ((TreeMap.NavigableSubMap<E,?>)m).keyIterator();
    }

	Iterator<K> keyIterator() {
        return new KeyIterator(getFirstEntry());
    }

可以看到是新建了一个KeyIterator对象,构造函数的参数是红黑树的第一个Entry。

	abstract class PrivateEntryIterator<T> implements Iterator<T> {
        Entry<K,V> next;
        Entry<K,V> lastReturned;
        int expectedModCount;

        PrivateEntryIterator(Entry<K,V> first) {
            expectedModCount = modCount;
            lastReturned = null;
            next = first;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            next = successor(e);
            lastReturned = e;
            return e;
        }

        final Entry<K,V> prevEntry() {
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            next = predecessor(e);
            lastReturned = e;
            return e;
        }

        public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            // deleted entries are replaced by their successors
            if (lastReturned.left != null && lastReturned.right != null)
                next = lastReturned;
            deleteEntry(lastReturned);
            expectedModCount = modCount;
            lastReturned = null;
        }
    }

可以看到这个Iterator类有nextEntryprevEntry两个方法,因为TreeSet还有另外一个获取逆序迭代器的descendingIterator方法。我们选nextEntry方法详细看看。
可以看到nextEntry返回的是迭代器next属性对应的Entry,而后通过successor方法更新next对应的Entry。
successor方法在之前我们有详细讲过,他会返回红黑树结构下比目标节点大的最小节点,也就实现了迭代器的有序遍历。

HashSet的Iterator来源于其HashMap的KeySet的Iterator。

 	public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

	public final Iterator<K> iterator() { 
		return new KeyIterator(); 
	}

final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; }
}

可以看到这个Iterator对象的hasNext,nextNode,remove方法都是继承自HashIterator类的,自身只实现了next方法。

abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                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;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

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

可以看到构造函数会遍历table数组找到第一个不为null的节点作为初始的next值,如果找不到next为null。
nextNode方法中,由于每个hash索引都保留了链表的级联结构关系,所以找当前节点的next属性作为下一个next节点的值,如果没有下个节点则继续在table数组中找下一个非null的节点直到table数组完全遍历。

6、总结

1.TreeSet底层结构是TreeMap,也就是红黑树,能够通过compareTo方法的比较实现自动排序,通过equals方法来判断重复键以及取值。HashSet底层是HashMap,key是无序的,通过hashCode方法来寻找hash索引,而后通过equals方法来判断重复键以及取值。
特别要注意:HashSet中要插入自定义类的对象(HashMap已自定义类为键),必须要重写hashCode方法和equals方法,且当两个对象equals方法的结果为true时,hashCode方法的结果必须相同,否则两个对象的hash值不同,将无法查找(除非你的需求是同一个对象才是同一个键)。TreeSet中要插入自定义类的对象(TreeMap已自定义类为键),那么自定义类需要实现Comparable接口。

2.TreeSet默认不可以接受null值,HashSet可以,如果在构造Set时提供了Comparator,那么看Comparator的实现来确定null的处理。

3.TreeSet没有扩容机制,HashSet的扩容机制如下,当HashMap中的元素数量超过数组总长度*负载因子(默认0.75)时触发,数组总长度翻倍,原数组中的链表和红黑树重新散列,这时若红黑树拆分后的大小小于UNTREEIFY_THRESHOLD(默认6),则会重构成链表。
另外需要注意的是当链表转换成红黑树时,如果数组总长度小于MIN_TREEIFY_CAPACITY(默认64),那么也会触发扩容进行散列。

### TreesetTreemapHashMap HashSet 的用法与区别 #### 数据结构基础 在 Java 中,`TreeSet` `TreeMap` 是基于红黑树的数据结构实现的集合映射容器,而 `HashSet` `HashMap` 则分别依赖哈希表机制来存储数据。以下是它们的主要特性差异。 --- #### **1. TreeSet** - **定义**: `TreeSet` 是一种有序的 Set 集合,它通过底层的 `TreeMap` 来实现[^4]。 - **特性**: - 自动按照元素的自然顺序排序(升序),或者可以通过自定义 Comparator 排序。 - 不允许存储 null 值,因为无法对其进行比较操作。 - 查找、插入删除的时间复杂度均为 \(O(\log n)\)[^3]。 - **适用场景**: 当需要对集合中的元素保持某种特定顺序时使用。 ```java // 使用示例 TreeSet<Integer> treeSet = new TreeSet<>(); treeSet.add(5); treeSet.add(2); System.out.println(treeSet); // 输出: [2, 5] ``` --- #### **2. TreeMap** - **定义**: `TreeMap` 是一种键值对形式的 Map 容器,其内部同样采用红黑树实现[^2]。 - **特性**: - 键会自动按照自然顺序排列,也可以提供自定义 Comparator 进行排序。 - 不支持 null 键,但允许多个 null 值。 - 插入、查找删除的操作时间复杂度为 \(O(\log n)\)[^3]。 - **适用场景**: 对于需要按键排序的场景非常有用。 ```java // 使用示例 TreeMap<String, Integer> treeMap = new TreeMap<>(); treeMap.put("Apple", 1); treeMap.put("Banana", 2); System.out.println(treeMap.keySet()); // 输出: [Apple, Banana] ``` --- #### **3. HashMap** - **定义**: `HashMap` 是基于哈希表实现的一种高效 Map 结构[^2]。 - **特性**: - 存储的是无序的键值对。 - 支持一个 null 键以及多个 null 值。 - 平均情况下,插入、查找删除的时间复杂度为 \(O(1)\),但在极端情况(大量冲突)下可能退化到 \(O(n)\)[^3]。 - **适用场景**: 主要用于快速访问数据而不关心顺序的情况。 ```java // 使用示例 HashMap<String, Integer> hashMap = new HashMap<>(); hashMap.put("Apple", 1); hashMap.put(null, 2); // 允许 null 键 System.out.println(hashMap.get("Apple")); // 输出: 1 ``` --- #### **4. HashSet** - **定义**: `HashSet` 是一种基于 `HashMap` 实现的 Set 集合[^1]。 - **特性**: - 底层利用 `HashMap` 的 key 来存储唯一元素。 - 不保证任何迭代顺序。 - 支持一个 null 元素。 - 插入、查找删除的时间复杂度为 \(O(1)\)[^3]。 - **适用场景**: 如果只需要存储唯一的对象且无需考虑顺序,则适合使用 `HashSet`。 ```java // 使用示例 HashSet<Integer> hashSet = new HashSet<>(); hashSet.add(1); hashSet.add(2); System.out.println(hashSet.contains(1)); // 输出: true ``` --- #### 总结对比表格 | 特性 | TreeSet | TreeMap | HashMap | HashSet | |---------------------|----------------------------|------------------------------|--------------------------------|--------------------------------| | **底层实现** | TreeMap | 红黑树 | 哈希表 | HashMap (key-only) | | **是否有序** | 按照指定顺序 | 按键排序 | 无序 | 无序 | | **null 支持** | 不支持 null 元素 | 支持单个 null 键 | 支持单个 null 键 | 支持单个 null 元素 | | **时间复杂度** | \(O(\log n)\) | \(O(\log n)\) | \(O(1)\) | \(O(1)\) | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值