一、HashSet
1.定义
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable
2.基本属性
private transient HashMap map;
//定义一个Object对象作为HashMap的value
// 定义一个虚拟的Object PRESENT是向map中插入key-value对应的value
// 因为HashSet中只需要用到key,而HashMap是key-value键值对;
// 所以,向map中添加键值对时,键值对的值固定是PRESENT
private static final Object PRESENT = new Object();
3.构造函数
// 默认构造函数 底层创建一个HashMap
public HashSet() {
// 调用HashMap的默认构造函数,创建map
map = new HashMap();
}
// 带集合的构造函数
public HashSet(Collection extends E> c) {
// 创建map。
// 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),
// 从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?
// 首先,说明(c.size()/.75f) + 1
// 因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
// 当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
// 就需要将HashMap的容量翻倍。
// 所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
// 接下来,说明为什么是 16 。
// HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
// HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
// 所以,这里指定为16是从性能考虑。避免重复计算。
map = new HashMap(Math.max((int) (c.size()/.75f) + 1, 16));
// 将集合(c)中的全部元素添加到HashSet中
addAll(c);
}
// 指定HashSet初始容量和加载因子的构造函数
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap(initialCapacity, loadFactor);
}
// 指定HashSet初始容量的构造函数
public HashSet(int initialCapacity) {
map = new HashMap(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap(initialCapacity, loadFactor);
}
4.方法
- 返回对HashSet中元素进行迭代的迭代器,返回元素的顺序并不是特定的
- 底层调用HashMap的keySet返回所有的key,这点反应了HashSet中的所有元素都是保存在HashMap的key中,value则是使用的PRESENT对象,该对象为static final
public Iterator
iterator() { // 实际上返回的是HashMap的“key集合的迭代器” return map.keySet().iterator(); }
- 返回此 set 中的元素的数量(set 的容量),底层调用HashMap的size方法,返回HashMap容器的大小
public int size() { return map.size();
- 调用HashMap的isEmpty()来判断HaspSet是否为空
- HashMap为null,对应的HashSet也为空
public boolean isEmpty() { return map.isEmpty(); }
- 调用HashMap的containsKey判断是否包含指定的key
- HashSet的所有元素就是通过HashMap的key来保存的
- contains(),判断某元素是否存在于HashSet()中,即(o==null ? e==null : o.equals(e)),存在返回true,否则返回false
- 底层调用containsKey判断HashMap的key值是否为空,即判断hashset中是否存在指定元素(key)
public boolean contains(Object o) { return map.containsKey(o); } public boolean containsKey(Object key) { return getNode(hash(key), key) != null; } // key和 hash(key) final Node
getNode(int hash, Object key) { Node [] tab; Node first, e; int n; K k; //判断数组不为空 长度不为0 找到数组位置 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { /* 判断第一个节点hash等于传进来的hash(key) && 第一节点的key=传进来的key || first是数组某个位置上的Node节点 */ 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 )first).getTreeNode(hash, key); /*如果不是树 ,遍历链表每个元素 有则返回 从第二个才开始遍历 e是first.next的节点 第二节点 判断 hash值相同 key相同或者key.equels相同 */ do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
- hashset只是不允许重复的元素加入,而不是不允许元素连成链表
- 当两个hashcode相同但key不相等的entry插入时,仍然会连成一个链表,长度超过8时依然会和hashmap一样扩展成红黑树
- 当两个hashcode相同且key相等的entry插入时,会发生value的替换,只因所有entry的value一样,让人以为相同元素没有插入,事实是将value替换成和原来相同的值,所以和没有插入时一样的
- 当add方法发生冲突时,如果key相同,则替换value(value值相同),如果key不同,则连成链表
- add()如果此 set 中尚未包含指定元素,则添加指定元素
- 如果此Set没有包含满足(e==null ? e2==null : e.equals(e2)) 的e2时,则将e2添加到Set中,否则不添加且返回false
- 由于底层使用HashMap的put方法将key = e,value=PRESENT构建成key-value键值对,当此e存在于HashMap的key中,则value将会覆盖原有value,但是key保持不变,所以如果将一个已经存在的e元素添加中HashSet中,新添加的元素是不会保存到HashMap中,所以这就满足了HashSet中元素不会重复的特性
public boolean add(E e) { return map.put(e, PRESENT)==null; } 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
[] tab; Node p; int n, i; // 拿到Node数组 如果数组为空 那么扩容 if ((tab = table) == null || (n = tab.length) == 0) //扩容看resize() n为新扩容的大小 n = (tab = resize()).length; // 在新扩容(可能不需要扩容)的实际位置 如果数组位置为空 直接赋值 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { //如果已经存在值 //对e做赋值的过程 Node e; K k; //e为Node;hash相同;key也相同就赋值 //这里针对的是存在数组位置上的 //Node就和你插入的key相同 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //树的处理 else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value); //p是什么 //某个位置上的Node(已经存在) 而且是存在值的(可能next为空) else { //开始循环 循环的是链表 for (int binCount = 0; ; ++binCount) { //如果是链表末尾,也就是没有next链表 if ((e = p.next) == null) { //在末尾创建新节点并且赋值 p.next = newNode(hash, key, value, null); /*如果链表长度>=7 那么就创建treeifyBin 在treeifyBin中 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); 在判断数组长度<64 时 只会扩容 */ if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //在循环过程中找到了相同的key 和 hash if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; //循环过程中 直接覆盖掉p p永远是e的next节点 p = e; } } // 对e赋值完成 //判断是否是覆盖操作,对接到上一个break之前 //如果是覆盖的话 // existing mapping for key if (e != null) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; //判断accessOrder才会执行 afterNodeAccess(e); //覆盖操作 返回老的值 return oldValue; } } //设置修改频次 ++modCount; //如果达到扩容的阈值 那么会扩容 上面的老值就肯定不需要扩容了 if (++size > threshold) resize(); //判断evict才会执行 afterNodeInsertion(evict); return null; }
- remove如果指定元素存在于此 set 中,则将其移除。底层使用HashMap的remove方法删除指定的Entry
public boolean remove(Object o) { return map.remove(o)==PRESENT; }
- clear从此 set 中移除所有元素。底层调用HashMap的clear方法清除所有的Entry
public void clear() { map.clear(); }
- clone返回此 HashSet 实例的浅表副本:并没有复制这些元素本身
public Object clone() { try { HashSet
newSet = (HashSet ) super.clone(); newSet.map = (HashMap ) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(); } }
二、TreeSet
1.定义
public class TreeSet extends AbstractSet
implements NavigableSet, Cloneable, java.io.Serializable
2.TreeSet中的变量
private transient NavigableMap m;
//PRESENT会被当做Map的value与key构建成键值对
private static final Object PRESENT = new Object();
3.构造方法
//默认构造方法,根据其元素的自然顺序进行排序
public TreeSet() {
this(new TreeMap());
}
//构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。
public TreeSet(Comparator super E> comparator) {
this(new TreeMap<>(comparator));
}
//构造一个新的空 TreeSet,它根据指定比较器进行排序。
public TreeSet(Collection extends E> c) {
this();
addAll(c);
}
//构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。
public TreeSet(SortedSet s) {
this(s.comparator());
addAll(s);
}
TreeSet(NavigableMap m) {
this.m = m;
}
4.方法
- add:将指定的元素添加到此 set(如果该元素尚未存在于 set 中)
public boolean add(E e) { return m.put(e, PRESENT)==null; } public V put(K key, V value) { Entry
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 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; //如果key的比较返回值相等,直接更新值(一般compareto相等时equals方法也相等) 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 e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; //插入后进行红黑树调整 fixAfterInsertion(e); size++; modCount++; return null; }
- get:获取元素
public V get(Object key) { Entry
p = getEntry(key); return (p==null ? null : p.value); }
- ceiling:返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null
public E ceiling(E e) { return m.ceilingKey(e); }
- clear:移除此 set 中的所有元素
public void clear() { m.clear(); }
- clone:返回 TreeSet 实例的浅表副本。属于浅拷贝
public Object clone() { TreeSet
clone = null; try { clone = (TreeSet ) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } clone.m = new TreeMap<>(m); return clone; }
- comparator:返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null
public Comparator super E> comparator() { return m.comparator(); }
- contains:如果此 set 包含指定的元素,则返回 true
public boolean contains(Object o) { return m.containsKey(o); }
- descendingIterator:返回在此 set 元素上按降序进行迭代的迭代器
public Iterator
descendingIterator() { return m.descendingKeySet().iterator(); }
- descendingSet:返回此 set 中所包含元素的逆序视图
public NavigableSet
descendingSet() { return new TreeSet<>(m.descendingMap()); }
- first:返回此 set 中当前第一个(最低)元素
public E first() { return m.firstKey(); }
- floor:返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null
public E floor(E e) { return m.floorKey(e); }
- headSet:返回此 set 的部分视图,其元素严格小于 toElement
public SortedSet
headSet(E toElement) { return headSet(toElement, false); }
- higher:返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null
public E higher(E e) { return m.higherKey(e); }
- isEmpty:如果此 set 不包含任何元素,则返回 true
public boolean isEmpty() { return m.isEmpty(); }
- iterator:返回在此 set 中的元素上按升序进行迭代的迭代器
public Iterator
iterator() { return m.navigableKeySet().iterator(); }
- last:返回此 set 中当前最后一个(最高)元素
public E last() { return m.lastKey(); }
- lower:返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null
public E lower(E e) { return m.lowerKey(e); }
- pollFirst:获取并移除第一个(最低)元素;如果此 set 为空,则返回 null
public E pollFirst() { Map.Entry
e = m.pollFirstEntry(); return (e == null) ? null : e.getKey(); }
- pollLast:获取并移除最后一个(最高)元素;如果此 set 为空,则返回 null
public E pollLast() { Map.Entry
e = m.pollLastEntry(); return (e == null) ? null : e.getKey(); }
- remove:将指定的元素从 set 中移除(如果该元素存在于此 set 中)
public boolean remove(Object o) { return m.remove(o)==PRESENT; }
- size:返回 set 中的元素数(set 的容量)
public int size() { return m.size(); }
- subSet:返回此 set 的部分视图
/** * 返回此 set 的部分视图,其元素范围从 fromElement 到 toElement。 */ public NavigableSet
subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { return new TreeSet<>(m.subMap(fromElement, fromInclusive, toElement, toInclusive)); } /** * 返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。 */ public SortedSet subSet(E fromElement, E toElement) { return subSet(fromElement, true, toElement, false); }
- tailSet:返回此 set 的部分视图
/** * 返回此 set 的部分视图, *其元素大于(或等于,如果 inclusive 为 true)fromElement。 */ public NavigableSet
tailSet(E fromElement, boolean inclusive) { return new TreeSet<>(m.tailMap(fromElement, inclusive)); } /** * 返回此 set 的部分视图,其元素大于等于 fromElement。 */ public SortedSet tailSet(E fromElement) { return tailSet(fromElement, true); }
三、LinkedHashSet
1.定义
2.构造函数
//Constructor - 1
public LinkedHashSet(int initialCapacity, float loadFactor)
{
super(initialCapacity, loadFactor, true); //Calling super class constructor
}
//Constructor - 2
public LinkedHashSet(int initialCapacity)
{
super(initialCapacity, .75f, true); //Calling super class constructor
}
//Constructor - 3
public LinkedHashSet()
{
super(16, .75f, true); //Calling super class constructor
}
//Constructor - 4
public LinkedHashSet(Collection extends E> c)
{
super(Math.max(2*c.size(), 11), .75f, true); //Calling super class constructor
addAll(c);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy)
{
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
3.LinkedHashSet如何维护插入顺序(具体参见Java基础汇总(十六)——LinkedHashMap)
参见文章:Java基础汇总(十六)——LinkedHashMap_我爱豆子的博客-CSDN博客
private static class Entry extends HashMap.Entry
{
// These fields comprise the doubly linked list used for iteration.
Entry before, after;
Entry(int hash, K key, V value, HashMap.Entry next) {
super(hash, key, value, next);
}
}
附:
访问级别:
private | 默认 | protected | public | |
---|---|---|---|---|
同一类的成员 | √ | √ | √ | √ |
同一个包的其他类(包括子类) | × | √ | √ | √ |
不同包的子类 | × | × | √ | √ |
不同包的非子类 | × | × |
其中:以上访问级别只适用于类和类的成员,不适用于局部变量。
成员变量、成员方法、构造方法都可以使用上面的四种访问级别
参考文章:
Java-Tutorial/Java集合详解7:HashSet,TreeSet与LinkedHashSet.md at master · h2pl/Java-Tutorial · GitHub
深入源码分析HashSet_不能说的秘密go的博客-CSDN博客
HashMap详解_多啦哀梦的博客-CSDN博客
一文搞懂Java的 构造方法 和 访问权限_Designer 小郑的博客-CSDN博客_构造方法的访问权限