Collection 接口
Collection 接口提供了一系列用于操作和管理集合的方法,包括添加、删除、查询、遍历等。它是所有集合类的根接口,包括 List、Set、Queue 等。
![![[Collection UML.png]]](https://i-blog.csdnimg.cn/direct/7d064196b6634b1ea2153504ce1badc1.png)
Collection 接口常见方法
-  
add(E element):向集合中添加元素。 -  
addAll(Collection col):将 col 中的所有元素添加到集合中 -  
boolean remove(Object obj):通过元素的equals方法判断是否是要删除的那个元素,只删除找到的第一个元素 -  
boolean removeAll(Collection col):取两集合差集 -  
boolean retain(Collection col):把交集的结果存在当前的集合中,不影响col -  
boolean contains(Object obj):判断集合中是否包含指定的元素。 -  
boolean containsAll(Collection col):调用元素的equals方法来比较的。用两个两个集合的元素逐一比较 -  
size():返回集合中的元素个数。 -  
isEmpty():判断集合是否为空。 -  
clear():清空集合中的所有元素。 -  
iterator():返回用于遍历集合的迭代器。 -  
hashCode(): 获取集合对象的哈希值 -  
Object[] toArray():转换成对象数组 
Set 接口
-  
Set接口是Collection的子接口,Set集合中的元素是无序,同时不可重复的,可以存放null元素 -  
Set接口的实现类有HashSet、treeSet、LinkedHashSet 
Set 接口是一个不包含重复元素的集合。Set 接口的主要特点是:
-  
无序性:
Set中的元素是没有顺序的,除非使用特定的实现类(如LinkedHashSet)。无序性不等于随机性,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。 -  
不可重复性:
Set不允许重复的元素。指添加、重写的元素按照equals()判断时,返回 false,需要同时重写equals()方法和hashCode()方法。 
Set 接口常用方法
![![[Set Methods.png]]](https://i-blog.csdnimg.cn/direct/dd66a6f9c72347cf80d39c3779a7d2d6.png)
Set 常见实现类
Set 接口在 Java 中有多种实现类,其中最常用的是 HashSet、LinkedHashSet 和 TreeSet。Set 的三个实现类都线程不安全的
HashSet
HashSet 是 Java 中实现 Set 接口的一个常用类。它基于哈希表实现,不允许包含重复元素,并且不保留元素的插入顺序。HashSet 允许存储 null 元素。
HashSet主要特点
-  
不允许重复元素:
HashSet内部使用哈希表来存储元素,利用哈希值来快速定位元素位置,因此不会存储重复的元素。 -  
无序集合:
HashSet不保持元素的插入顺序,即无法按照添加顺序或元素值的顺序访问元素。 -  
允许存储 null 元素:
HashSet可以存储 null 元素,但只能存储一个 null,因为重复元素不被允许。 
HashSet 四种构造方法
HashSet():构建一个空的 HashSet 对象,其初始容量为默认值 16 ,负载因子为默认值 0.75
Set<String> set = new HashSet<>(); 
public HashSet(){
	map = new HashMap<>();
}
 
HashSet 的构造方法中可以看出,底层实际是实现了HashMap
-  
HashSet(Collection <? extends E> col):创建一个包含指定集合 col中的元素的新 HashSet 对象。将其初始化为给定集合中的元素。 -  
HashSet(int initCapacity):创建一个空的HashSet对象,同时指定初始容量initCapacity,负载因子为默认值 0.75 -  
HashSet(int initCapacity,float loadFactor):创建一个空的HashSet对象,指定初始容量initCapacity和负载因子loadFactor 
负载因子:比如说当前的容器容量是 16,负载因子是 0.75, 16\*0.75 = 12 ,也就是说,当容量达到了12的时候就会进行扩容操作。简单来说相当于扩容机制的一个阈值,当超过这个阈值的时候就会触发扩容。
HashSet 使用示例
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
    public static void main(String[] args) {
        // 创建一个 HashSet
        Set<String> names = new HashSet<>();
        // 添加元素
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        // 尝试添加重复元素
        names.add("Alice");  // 不会被添加,因为不允许重复元素
        // 检查集合是否包含指定元素
        System.out.println(names.contains("Alice"));  // 输出: true
        // 遍历集合中的所有元素
        for (String name : names) {
            System.out.println(name);
        }
        // 移除元素
        names.remove("Bob");
        System.out.println(names);  // 输出: [Alice, Charlie] (顺序不确定)
        // 清空集合
        names.clear();
        System.out.println(names.isEmpty());  // 输出: true
    }
}
 
HashSet 扩容机制
resize() 数组扩容方法:
-  
判断 table 表是否为null,如果为null,则为table表进行容量开辟
newCap = DEFAULT_INITIAL_CAPACITY;
默认的初始值为 DEFAULT_INITIAL_CAPACITY(16);newThr = (int)(DEFAULT_LOAD_FACTOR (0.75)* DEFAULT_INITIAL_CAPACITY);
扩容阈值为:newThr= 16 * 0.75 = 12;当集合容量达到12时再次调用 resize() 方法进行扩容
 -  
第二步:当进行第二次扩容,以及之后每一次扩容的时候,每次到达扩容阈值的时候,容量扩容到原先的两倍
newCap = oldCap << 1,新的扩容阈值为:newThr = newCap * 0.75 = 24,以此类推(12,24,36,48.....) 
HashSet 添加数据底层源码
添加元素,调用 map.put() 方法
 public boolean add(E e) {
	 return map.put(e, PRESENT)==null;
 }
 
首先进行添加元素时,要先通过计算其 hash 值来确认要添加到数组位置索引
public V put(K key, V value) {
   return putVal(hash(key), key, value, false, true);
} 
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
 
- 计算出 key(传进来的元素)的 hashCode 值
 - 将计算出的 hashCode 值再无符号右移16位得到最终的hash值
 
putVal() 方法的源码:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent ,boolean evict) {
//这里定义的都是一些辅助变量
Node<K,V>[] tab; 
Node<K,V> p; 
int n, i;
 
- 第一次添加元素时先判断 table 表是否为 null,
 
 //如果为null将通过resize()方法扩容给table赋初始容量(16)
//接下来每一次都是当集合容量达到扩容阈值时调用resize()方法进行扩容
 if ((tab = table) == null || (n = tab.length) == 0)
	n = (tab = resize()).length;
 
- 每一次向集合中添加元素的时候,会调用该元素的 hashCode() 方法得到一个地址值,接着将得到的地址值放进 tab 数组中进行查询,若当前位置为 null 直接将元素添加到当前位置。
 
 if ((p = tab[i = (n - 1) & hash]) == null)
	tab[i] = newNode(hash, key, value, null);
 
- 如果当前位置已经存放元素,那么会先判断当前传进来的对象和已有对象是否是同一对象,或者调用equals方法进行比较,如果满足其一,新的元素将会覆盖原先对象的值
 
 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);
 //经过比较当前传入元素与当前元素所处tab数组位置处的元素不是同一对象,
 //则与当前位置对象next所以指的对象一一比较
 //如果p.next==null就直接将当前元素添加去。
	 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;
 }
 
LinkedHashSet
LinkedHashSet 是 Java 中的一个实现了 Set 接口的类,它是 HashSet 的子类。与 HashSet 不同,LinkedHashSet 保留了元素的插入顺序,因此可以按照插入顺序迭代访问元素。它基于 HashTable 实现,同时使用链表来维护元素的插入顺序。
LinkedHashSet 主要特点
-  
不允许重复元素:与
HashSet一样,在LinkedHashSet中不能存储重复的元素。 -  
有序集合:
LinkedHashSet保留了元素插入的顺序,因此迭代遍历LinkedHashSet可以按照插入顺序访问元素。 -  
允许存储 null 元素:
LinkedHashSet可以存储一个 null 元素,但只能存储一个 null,重复的 null 元素不被允许 
LinkedHashSet 的四种构造方法
LinkedHashSet():创建一个具有默认容量(16),负载因子(0.75)的新的空连接散列集。
Set<String> set = new LinkedList<>(); 
 
-  
LinkedHashSet(Collection <? extends E> col):创建一个包含指定集合 col中的元素的新 LinkedHashSet 对象。将其初始化为给定集合中的元素。 -  
LinkedHashSet(int initCapacity):创建一个空的 LinkedHashSet 对象,同时指定初始容量 initCapacity,负载因子为默认值 0.75 -  
LinkedHashSet(int initCapacity, float loadFactor):创建一个空的LinkedHashSet对象,指定初始容量 initCapacity 和负载因子 loadFactor 
LinkedHashSet 使用示例
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetExample {
    public static void main(String[] args) {
        // 创建一个 LinkedHashSet
        Set<String> names = new LinkedHashSet<>();
        // 添加元素
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        // 尝试添加重复元素
        names.add("Alice");  // 不会被添加,因为不允许重复元素
        // 检查集合是否包含指定元素
        System.out.println(names.contains("Alice"));  // 输出: true
        // 遍历集合中的所有元素
        for (String name : names) {
            System.out.println(name);
        }
        // 移除元素
        names.remove("Bob");
        System.out.println(names);  // 输出: [Alice, Charlie] (按插入顺序)
        // 清空集合
        names.clear();
        System.out.println(names.isEmpty());  // 输出: true
    }
}
 
LinkedHashSet 底层机制
-  
底层扩容机制与
HashSet扩容机制相同 -  
LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表它根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序, 其遍历顺序和插入顺序一致。 -  
LinkedHashSet中有 head 和 tail, 分别指向链表的头和尾。每一个节点有before 和 after 属性在添加一个元素时,先求 hashCode 值,再求索引,确定该元素在table表中的位置,然后将添加的元素加入到双向链表(如果该元素已经存在,则不添加)
p.next= newElement;newElement.pre = p; 
添加元素底层源码
 public boolean add(E e) {
         return map.put(e, PRESENT)==null;
      }
 
LinkedHashSet 的底层大多实现原理与 HashSet 相同,同时实现了 LinkedHashMap
table[] 数组的类型为 HashMap $Node [],且数组里每一个结点的类型为 LinkedHashMap $Entry
当传进元素时,会先将元素创建为 Node<K,V> ,然后将Node<K,V>里的K-V封装到数据类型为 Entry<k,v> 的 entrySet<Entry<k,v>> 集合中去
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
       LinkedHashMap.Entry<K,V> p =
       new LinkedHashMap.Entry<K,V>(hash, key, value, e);
       linkNodeLast(p);
       return p;
}
// LinkedHashMap 的静态内部类 Entry 继承自 HashMap 的静态内部类 Node
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);
    }
}
//底层Node是没有任何直接遍历方法,因此会将Node<k,v>实现Entry<k,v>接口,
//通过Entry<k,v>里的getKey()和getValue()方法来获取元素 
static class Node<K,V> implements Map.Entry<K,V> {
    
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    
    Node(int hash, K key, V value, Node<K,V> next) {
   	 this.hash = hash;
   	 this.key = key;
   	 this.value = value;
   	 this.next = next;
    }
    
    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
   	 LinkedHashMap.Entry<K,V> last = tail;
   	 tail = p;
   	 if (last == null)
   	 head = p;
   	 else {
   	 p.before = last;
   	 last.after = p;
    }
}
 
TreeSet
TreeSet 是 Java 中的一个实现了 SortedSet 接口的类,它基于红黑树(Red-Black Tree) 的数据结构实现。与 HashSet 和 LinkedHashSet 不同,TreeSet 是有序的集合,可以保持元素的自然排序(例如,数字按升序,字符串按字典序)
SortedSet接口是 Java 集合框架中的一种有序集合,它继承自Set接口,并添加了一些与集合元素排序相关的方法。SortedSet保证集合中的元素按照特定的顺序排列,并且不允许出现重复的元素。
SortedSet接口的常用实现类是TreeSet,它基于红黑树数据结构实现了有序集合,可以自动对元素进行排序。
TreeSet 相较于 HashSet 性能较差
TreeSet 主要特点
-  
不允许重复元素:与其他
Set实现类一样,TreeSet中不能存储重复的元素。 -  
有序集合:
TreeSet会根据元素的自然顺序进行排序。如果元素不具备自然顺序,则需要在创建TreeSet时提供一个Comparator对象来指定排序规则。 -  
支持高效的查找和遍历:由于
TreeSet内部使用红黑树,它的插入、删除和查找操作的时间复杂度都是 O(log n) -  
不是线程安全的
 -  
JDK8 以后,集合中的元素不可以是 null(如果为空,则会抛出异常
java.lang.NullPointerException) 
TreeSet 的四种构造方法
TreeSet():创建一个空的TreeSet,它按照元素的自然顺序进行排序。
Set<Stirng> set = new TreeSet<>();
TreeSet<String> set = new TreeSet<>();
 
TreeSet(Comparator<? super E> comparator):创建一个空的TreeSet,并使用指定的比较器对元素进行排序。比较器可以自定义,用于指定元素的排序规则。
TreeSet<String> set = new TreeSet<>(new MyComparator());
 
-  
TreeSet(Collection <? extends E> col):创建一个 TreeSet,并将指定集合 col 中的元素添加到 TreeSet 中。元素将按照自然顺序进行排序。 -  
TreeSet(Sorted <E> sortedSet):创建一个 TreeSet,并使用指定排序集合的比较器对元素进行排序。这样可以将一个已经排序好的集合转换为 TreeSet。 
SortedSet<String> sortedSet = new TreeSet<>();
sortedSet.add("apple");
sortedSet.add("banana");
sortedSet.add("orange");
TreeSet<String> treeSet = new TreeSet<>(sortedSet);
 
在使用自定义对象作为 TreeSet 元素时,需要确保对象实现了 Comparable 接口或传入了合适的比较器。否则可能会抛出 ClassCastException 异常。
TreeSet 使用示例
import java.util.TreeSet;
import java.util.Set;
public class TreeSetExample {
    public static void main(String[] args) {
        // 创建一个 TreeSet
        Set<String> names = new TreeSet<>();
        // 添加元素
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        // 尝试添加重复元素
        names.add("Alice");  // 不会被添加,因为不允许重复元素
        // 检查集合是否包含指定元素
        System.out.println(names.contains("Alice"));  // 输出: true
        // 遍历集合中的所有元素
        for (String name : names) {
            System.out.println(name);
        }
        // 移除元素
        names.remove("Bob");
        System.out.println(names);  // 输出: [Alice, Charlie] (按字母顺序)
        // 清空集合
        names.clear();
        System.out.println(names.isEmpty());  // 输出: true
    }
}
 
TreeSet 底层实现机制
-  
TreeSet底层使用的是红黑树实现,对于元素之间排序,如果不指定自定义的外部比较器 ——Comparator,那么插入的对象必须实现内部比较器——Comparable接口,元素按照实现此接口的compareTo()方法去排序。 -  
TreeSet判断两个对象不相等的方式:两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0 -  
在不使用默认排序的情况下,可以重写 compare() 方法来实现自定义排序
 
compare() 底层源码
 ![![[compare Source Code.png]]](https://i-blog.csdnimg.cn/direct/ee30609c6f55443ab60f96f4c4be814f.png)
TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).compareTo((String)o2);
            }
// String、Integer等类均已实现Comparable接口,无需另外实现
 
compareTo()方法
compareTo 方法定义在 Comparable 接口中,用于比较一个对象与另一个对象的顺序。
int compareTo(T object)
@Override 
public int compareTo(Person o) { 
	if (this.age > o.getAge()) {
		 return 1; 
	} 
	if (this.age < o.getAge()) {
		 return -1; 
		 
	} 
	return 0; 
}
 
- 如果返回值 < 0,则表示当前对象小于与其比较的对象。
 - 如果返回值 == 0,则表示当前对象等于与其比较的对象。
 - 如果返回值 > 0,则表示当前对象大于与其比较的对象
 
在实际使用中,当需要对自定义对象进行排序时,通常实现 Comparable 接口,并在其中重写 compareTo 方法。该方法的具体实现根据业务需求来决定。
添加元素
public boolean add(E e) {
        return m.put(e, PRESENT)==null;
 }
 
- 创建一个 
Entry<K,V>类型的 root(根)结点,之后每次添加的子结点类型都为 Entry<K,V> 
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;
	}
 
- 每次添加元素的时候,都会调用 
compare()方法判断当前添加的元素与集合中已有元素是否为同一元素
如果不是则直接添加,同时根据compare()方法返回值来判断添加的位置 
	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);
	}
 
-  
传进来的子节点先与根结点进行判断:
- 如果大于根结点,则让结点与根结点的子结点进行比较
 - 如果传入元素小于任意子结点的左右结点其中一个结点,则让该结点作为该元素的双亲结点
 
 -  
传入元素与双亲结点进行比较,如果大于双亲结点添加到右子树,如果小于双亲结点,则添加到左子树,否则直接返回值
 
	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>类型,然后根据判断放进双亲结点的子节点中
	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;
}
 
Comparable 和 Comparator的区别
Comparable 接口和 Comparator 接口都是 Java 中用于排序的接口,它们在实现类对象之间比较大小、排序等方面发挥了重要作用:
-  
Comparable 接口出自 java.lang 包 它有一个
compareTo(Object obj)方法用来排序 -  
Comparator 接口 出自 java.util 包它有一个
compare(Object obj1, Object obj2)方法用来排序 
一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo()方法或compare()方法,当我们需要对某一个集合实现两种排序方式:
比如一个 song 对象中的歌名和歌手名分别采用一种排序方法的话,可以:
- 重写
compareTo()方法 - 使用自制的 
Comparator方法 - 以两个 
Comparator来实现歌名排序和歌星名排序,使用两个参数版的Collections.sort() 
HashSet、LinkedHashSet 和 TreeSet 三者的异同
-  
HashSet、LinkedHashSet和TreeSet都是Set接口的实现类,都能保证元素唯一,并且都不是线程安全的。 -  
HashSet、LinkedHashSet和TreeSet的主要区别在于底层数据结构不同:-  
HashSet的底层数据结构是哈希表(基于HashMap实现)。 -  
LinkedHashSet的底层数据结构是链表和哈希表,元素的插入和取出顺序满足 FIFO。 -  
TreeSet底层数据结构是红黑树,元素是有序的,排序的方式有自然排序和定制排序。 
 -  
 -  
底层数据结构不同又导致这三者的应用场景不同:
-  
HashSet用于不需要保证元素插入和取出顺序的场景 -  
LinkedHashSet用于保证元素的插入和取出顺序满足 FIFO 的场景 -  
TreeSet用于支持对元素自定义排序规则的场景。 
 -  
 
总结
Set 接口是 Java 集合框架中的一个重要接口,它提供了存储唯一元素的能力。通过不同的实现类,可以满足不同的需求场景。在使用 Set 接口时,需要注意元素的唯一性是基于 hashCode() 和 equals() 方法的,因此必须确保这两个方法被正确实现。同时,也需要根据实际需求选择合适的实现类。










![[windows][apache]Apache代理安装](https://i-blog.csdnimg.cn/direct/08ef6ff53e7d4f7d9c08a53fc8555b4d.png)








![[godot] 采用状态机时,如何处理攻击时移动?如“冲撞”](https://i-blog.csdnimg.cn/direct/088bc82883cf4497845ed800eb1f29dc.png)