Skip to content

[!hint] 为什么要引入集合 ? 因为数组的长度无法改变,所以我们用到了集合

❤ Collection

java
public interface Collection<E> extends Iterable<E>
  • add("hello"); 添加元素
  • remove("hello"); 删除元素
  • clear(); 清空集合中的元素
  • 判断
    • contains("hello"); 判断集合中是否有指定元素
    • containAll(集合B) 判断集合 B 的元素,集合 A 里是否都有
    • isEmpty(); 判断集合是否为空
  • size(); 判断集合中元素的个数
  • 交集,差集
    • Boolean retainAll(集合2) 返回集合和集合 2 的交集,如果集合因调用而更改,则返回 true
    • removeAll(集合2) 集合 2 中的元素,集合中都没有,如果集合因调用而更改,则返回 true

[!hint] 创建 Collection 对象并使用

java
public class collection {  
    public static void main(String[] args) {  
        Collection\<String> c1 = new ArrayList\<>();  
  
        String s1 = "绿坦";  
        String s2 = "kite";  
  
        c1.add(s1);  
        c1.add(s2);  
  
        System.out.println(c1);  
    }  
}

---
[绿坦, kite]

[!hint] Collection 集合的遍历

[!quote] Iterator 迭代器

  • next() 返回迭代中的下一个元素
  • hasNext() 判断 next() 之后是否还有元素
java
public static void main(String[] args) {  
	Collection\<String> c1 = new ArrayList<>();  
  
	String s1 = "绿坦";  
	String s2 = "kite";  
  
	c1.add(s1);  
	c1.add(s2);  
  
	Iterator\<String> i1 = c1.iterator();  
  
	while (i1.hasNext()) {  
		String s = i1.next();  
		System.out.println(s);  
	}  
}

--
绿坦
kite

[!quote] 增强 for【原理也是一个迭代器】

java
int arr[] = {1, 2, 3};  
for (int i : arr) {  
    System.out.println(i);  
}

--
1
2
3

💛 List

[!quote] List List 集合的特点就是索引

  • 静态方法
    • 【增】
      • List.of(……) 向集合中一次性添加元素,生成一个不可变集合(并且集合中不允许有 null 值)
  • 非静态方法
    • 【增】
      • add(索引,数值); 在集合中指定位置添加元素
    • 【删】
      • remove(值) 删除指定值,如果没有返回 null
    • 【改】
      • set(索引,数值); 修改指定索引处的元素
      • List subList(索引1,索引2) 截取列表中的一部分,生成新集合,不影响原集合
    • 【查】
      • get(索引); 返回指定元素
java
List<String> list = new ArrayList<>();  
  
list.add(0, "李白");  

list.set(0, "杜甫");  
String s1 = list.get(0);  

System.out.println(s1);

---
杜甫

[!quote] ListIterator 迭代器 ListIterator 可以实现双向遍历

public interface ListIterator\<E> extends Iterator\<E>
  • 从后往前遍历
    • hasPrevious() 判断 previous() 是否遍历完成
    • previous() 从后向前一个一个遍历
java
public static void main(String[] args) {  
	List\<String> list = new ArrayList<>();  

	list.add("李白");  
	list.add("杜甫");  
	list.add("王维");  

	ListIterator lstitrt = list.listIterator();  

	// 从前往后遍历
	while (lstitrt.hasNext()) {  
		System.out.print(lstitrt.next());  
	}  

	System.out.println("");  
	System.out.println(" ");  

	// 从后往前遍历
	while (lstitrt.hasPrevious()) {  
		System.out.print(lstitrt.previous());  
	}  
}  

——
李白杜甫王维
王维杜甫李白
java
List\<String> list = new ArrayList<>();  

list.add("李白");  
list.add("杜甫");  
list.add("王维");  

ListIterator lstitrt = list.listIterator();  

while (lstitrt.hasPrevious()) {  
	System.out.print(lstitrt.previous());  
}  

——
NULL

如果不使用 next() 方法,直接使用 previous() 方法,会导致什么也不打印

因为实现遍历的背后原理是指针,没有向后遍历的基础,向前遍历指针指向集合的最开始,所以不会有结果

如果一定要在迭代时改变集合元素,请使用 ListIterator 的 `add` 方法

以下代码会报出异常,这是由于在迭代时使用 List 集合的 add 方法改变集合:

java
public static void main(String[] args) {  
    List\<String> list = new ArrayList<>();  
  
    list.add("李白");  
    list.add("杜甫");  
  
    ListIterator\<String> lstitrt = list.listIterator();  
  
    while (lstitrt.hasNext()) {  
        String s = lstitrt.next();  
        if (s.equals("杜甫")) {  
            list.add("王安石");  
        }  
    }  
}

——
Exception in thread "main" java.util.ConcurrentModificationException

💙 ArrayList

[!quote] ArrayList ArrayList 是基于动态数组实现的 List

💙 LinkedList

[!quote] LinkedList LinkedList 基于双向链表实现的 List

    • addFirst(e) 在该列表的开头插入 e 元素
    • addLast(e) 在该列表的末尾插入 e 元素
    • removeFirst() 删除并返回第一个元素
    • removeLast() 删除并返回最后一个元素
    • getFirst() 返回此列表中的第一个元素
    • getLast() 返回此列表中的最后一个元素

💙 Vector

[!quote] Vector Vector 是一个线程安全集合,不过我们需要线程安全时也不常使用它,我们一般使用 synchronizedList() 方法

public class Vector\<E>
extends AbstractList\<E>
implements List\<E>, RandomAccess, Cloneable, Serializable
  • 使用 synchronizedList() 方法
List<String> list=Collections.synchronizedList(new ArrayList<>());

💛 Set

[!quote] Set

  • 不能有重复元素
  • 可以有 NULL
  • 无序
  • 没有索引
public interface Set\<E> 
extends Collection\<E>
  • 静态方法
    • Set.of(……) 生成一个不可变 Set 集合
  • 非静态方法
    • 【增】
      • add(E e) 添加元素,如果元素重复,则忽略该操作
    • 【删】
      • remove(E e) 删除元素
      • clear() 清空集合
    • 【查】
      • size() 返回集合的元素数
      • contains(E e) 判断集合是否有指定元素

💙 HashSet

[!quote] HashSetHashSet 的底层数据结构是哈希表

Hash 容器 :Hash 容器可以根据 hashcode 定位到相应的对象【hashcode 不同,两个对象不同】,Hash 容器有:HashSet,HashMap,HashTable,ConcurrentHashMap

哈希值 hashcode :每一个对象都有属于自己的 hashcode ,是用于区分不同对象的整型变量

  • 默认由 Object 类中的本地方法 hashcode() 生成,所以它不是内存地址
  • 如果两个对象的 equals() 方法返回相同的值,那这两个对象的 hashcode 必须相同,要不然 HashMap 怎么跟据 key 找值
  • 但是两个对象的 hashcode 相同,不一定相等,也就是哈希冲突,需要通过 equals() 做进一步判断

哈希冲突 :不同的输入值经过 hashcode() 处理后产生了相同的 hashcode哈希值】,哈希冲突是不可避免的,因为哈希函数将无限的输入映射到有限的输出

java
// "重地" 的 Unicode 编码是 37325 和 22320,计算哈希码得到 37325 * 31 + 22320 = 1179395。
// "通话" 的 Unicode 编码是 36890 和 35805,计算哈希码得到 36890 * 31 + 35805 = 1179395。
System.out.println("重地".hashCode());  //1179395  
System.out.println("通话".hashCode());  //1179395

[!hint]+ 为什么 HashSet 可以保证元素的唯一性 ? 700

[!hint]+ 比较对象时,需要重写 hashcode()equals() 方法

  • 没有重写时
java
public static void main(String[] args) {  
	HashSet\<students> hs = new HashSet\<students>();  
  
	students s1 = new students(12, "吴彦祖");  
	students s2 = new students(12, "陈冠希");  
	students s3 = new students(12, "吴彦祖");  
	students s4 = new students(23, "刘德华");  
  
	hs.add(s1);  
	hs.add(s2);  
	hs.add(s3);  
	hs.add(s4);  
  
	for (students s : hs) {  
		System.out.println(s.getAge() + s.getName());  
	}  
}


23刘德华
12吴彦祖
12吴彦祖  //打印了两个吴彦祖,说明哈希表认为s1和s3是不同对象
12陈冠希
  • 重写后
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class students {  
	private int age;  
	private String name;  
  
	@Override  
	public boolean equals(Object o) {  
		if (this == o) return true;  
		if (o == null || getClass() != o.getClass()) return false;  
		students students = (students) o;  
  
		if (age != students.age) return false;  
		return name != null ? name.equals(students.name) : students.name == null;  
	}  
  
	@Override  
	public int hashCode() {  
		int result = age;  
		result = 31 * result + (name != null ? name.hashCode() : 0);  
		return result;  
	}  
}

// 测试类
public class collection {  
	public static void main(String[] args) {  
		HashSet\<students> hs = new HashSet\<students>();  
  
		students s1 = new students(12, "吴彦祖");  
		students s2 = new students(12, "陈冠希");  
		students s3 = new students(12, "吴彦祖");  
		students s4 = new students(23, "刘德华");  
  
		hs.add(s1);  
		hs.add(s2);  
		hs.add(s3);  
		hs.add(s4);  
  
		for (students s : hs) {  
			System.out.println(s.getAge() + s.getName());  
		}  
	}  
}


12吴彦祖
12陈冠希
23刘德华

💚 LinkedHashSet

[!quote] LinkedHashSet LinkedHashSet 底层是哈希表和链表,可以预测迭代顺序

public class LinkedHashSet\<E> extends HashSet\<E>
implements Set\<E>, Cloneable, Serializable

💙 TreeSet

[!quote] TreeSet TreeSet 是一种有序集合,要求集合中的元素必须是可比较的,所以 TreeSet 中的元素必须实现 Comparable 接口或者在创建 TreeSet 时提供一个 Comparator 比较器对象

public class TreeSet\<E> extends AbstractSet\<E>
implements NavigableSet\<E>,Cloneable,Serializable
  • TreeSet() 无参构造,元素按照自然排序
  • TreeSet(Comparator c) 带参构造,根据指定的比较器进行排序

  • 实现 Comparable 接口的无参构造

    • 元素为 Integer
      java
      public static void main(String[] args) {  
          TreeSet<Integer> ts = new TreeSet<Integer>();  //这里的元素用了Integer,而不是int
                                                  //是因为Integer实现了Comparable接口,int没有
          ts.add(190);  
          ts.add(33);  
          ts.add(77);  
        
          for (Integer i : ts) {  
              System.out.println(i);  
          }  
      }
      
      
      33
      77
      190
    • 元素为 students,return 0;
      java
      public class students implements Comparable<students>{  
          private int age;  
          private String name;  
        
          public students(int age, String name) {  
              this.age = age;  
              this.name = name;  
          }  
        
          public int getAge() {return age;}  
          public void setAge(int age) {this.age = age;}  
          public String getName() {return name;}  
          public void setName(String name) {this.name = name;}  
        
          @Override  
          public int compareTo(students o) {  
              return 0;  //首先添加s1,添加s2的时候返回0(表示s2的元素和s1相同),不存储
          }  
      }
      java
      public class collection {  
          public static void main(String[] args) {  
              TreeSet<students> ts = new TreeSet<students>();  
        
              students s1 = new students(12, "吴彦祖");  
              students s2 = new students(23, "陈冠希");  
              students s3 = new students(42, "刘德华");  
              students s4 = new students(12, "吴彦祖");  
        
              ts.add(s1);  
              ts.add(s2);  
              ts.add(s3);  
              ts.add(s4);  
        
              for (students s : ts) {  
                  System.out.println(s.getAge() + s.getName());  
              }  
          }  
      }
      
      
      12吴彦祖
    • 元素为 students,return 1;
      java
      public class students implements Comparable<students>{  
          private int age;  
          private String name;  
        
          public students(int age, String name) {  
              this.age = age;  
              this.name = name;  
          }  
        
          public int getAge() {return age;}  
          public void setAge(int age) {this.age = age;}  
          public String getName() {return name;}  
          public void setName(String name) {this.name = name;}  
        
          @Override  
          public int compareTo(students o) {  
              return 1;  //首先存s1,存s2时返回1(表示s2大于s1),s2存在s1之后
          }  
      }
      
      
      12吴彦祖
      23陈冠希
      42刘德华
      12吴彦祖
    • 元素是 students,灵活 return;
      java
      public class students implements Comparable<students>{  
      	private int age;  
      	private String name;  
        
      	public students(int age, String name) {  
      		this.age = age;  
      		this.name = name;  
      	}  
        
      	public int getAge() {return age;}  
      	public void setAge(int age) {this.age = age;}  
      	public String getName() {return name;}  
      	public void setName(String name) {this.name = name;}  
        
      	@Override  
      	public int compareTo(students o) {  
      		int i = this.age - o.age;  
      		return i;
      	}  
      }
      
      
      12吴彦祖
      23陈冠希
      42刘德华
  • 提供 Comparator 比较器对象的带参构造

    java
    public class collection {  
        public static void main(String[] args) {  
            TreeSet<students> ts = new TreeSet<students>(new Comparator<students>() {  
                @Override  
                public int compare(students o1, students o2) {  
                    int sum = o1.getAge() - o2.getAge();  
                    return sum;  
                }  
            });                        //此处为匿名内部类,是Comparator接口的实现类对象
      
            students s1 = new students(12, "吴彦祖");  
            students s2 = new students(23, "陈冠希");  
            students s3 = new students(42, "刘德华");  
            students s4 = new students(12, "吴彦祖");  
      
            ts.add(s1);  
            ts.add(s2);  
            ts.add(s3);  
            ts.add(s4);  
      
            for (students s : ts) {  
                System.out.println(s.getAge() + s.getName());  
            }  
        }  
    }
    
    
    12吴彦祖
    23陈冠希
    42刘德华

❤ Map

[!quote] Map Map 集合 不能有重复的 key,每个 key 最多映射到一个值

  • 给 Map 添加键值对时,key 可以是 null,不会报错,但是最好不要

静态方法

  • 【增】
    • Map.of(……) 生成一个不可变的 map 集合【最多只能有 10 个键值对】
    • Map.ofEntries 传递无限个 Entry 对象
java
Map<String, Integer> map = Map.of("one", 1, "two", 2, "three", 3);

非静态方法

  • 【增】
    • put(K key,V value) 插入元素,如果 key 存在,则覆盖
    • putAll(Map集合) 将另一个 map 集合添加到当前集合,如果 key 重复则覆盖
    • putIfAbsent(K key, V value) 如果 key 存在则不做任何处理,否则插入
    • merge(key, value, 冲突处理方法) 添加键值对,如果集合中存在相同 key,则会覆盖旧值,也可以自定义冲突处理方法
    • T compute(Key, BiFunction函数式接口) 无论 key 是否存在,都对该 key 的 value 进行计算
      • 如果这个 key 存在,则将 Key,和该值作为函数式接口的入参;
      • 如果这个 key 不存在,则将 Key,和 null 值作为函数式接口的入参;
    • T computeIfAbsent(Key, Function函数式接口) 只有当指定的键不存在时,才会执行函数式接口生成新的值
    • T computeIfPresent(Key, BiFunction函数式接口) 只有当指定的键存在时,才会执行函数式接口生成新的值。BiFunction 返回 null,则删除该键值对;BiFunction 返回 T,则将该键的值设为 T
  • 【删】
    • remove(Object key) 根据键删除键值对元素
    • remove(key, value) 只有当键值对条件都满足时,才删除
    • clear() 移除所有键值对元素
  • 【查】
    • size() 返回集合的长度
    • get(Object key) 根据键获取值
    • getOrDefault(key, 默认值) 如果找不到键值对,则返回默认值
    • keySet() 返回一个所有键的 Set 集合
    • values() 返回一个所有值的 Collection 集合
    • entrySet() 返回一个 Set 集合 Set<Map.Entry<String, Integer>>,其中每个 Map.Entry 表示一个键值对
    • foreach((key, value) -> {……}) Java 8 新增的遍历方法
    • 判断
      • boolean containsKey(Object key) 判断集合是否包含指定的键
      • boolean containsValue(Object value) 判断集合是否包含指定的值
      • boolean isEmpty() 判断集合是否为空

[!quote] Map.Entry<K,V>

  • getKey() 返回 K
  • getValue() 返回 V

java
Map<Integer, String> m = new HashMap<Integer, String>();  
m.put(12, "吴彦祖");  
m.put(23, "彭于晏");  
m.put(12, "郭富城");  

System.out.println(m.get(12));  
System.out.println(m.get(34));  
System.out.println(m.keySet());  
System.out.println(m.values());  
System.out.println(m.entrySet());  

---
郭富城
null
[23, 12]
[彭于晏, 郭富城]
[23=彭于晏, 12=郭富城]

compute

java
Map<String, Integer> map = new HashMap<>();
map.put("apple", 2);

// 无论键 "apple" 是否存在,都会对它重新计算
map.compute(
	"apple", 
	(key, value) -> value == null ? 1 : value + 3
);

System.out.println(map.get("apple")); // 输出 5

computeIfAbsent

java
Map<String, List<Integer>> map = new HashMap<>();  
map.put("a", new ArrayList<>(List.of(1, 2, 3)));

// 返回的集合的引用,所以直接add,可以影响原map
map.computeIfAbsent("a", k -> new ArrayList<>()).add(5);

---
[1, 2, 3, 5]

💛 HashMap

java
HashMap<Integer, String> m = new HashMap<Integer, String>();  
m.put(12, "吴彦祖");  
m.put(23, "彭于晏");  
m.put(34, "郭富城");  
  
System.out.println(m);


{34=郭富城, 23=彭于晏, 12=吴彦祖}

NOTE

关于 HashMap 的实现,JDK 8 和以前的版本有着很大的不同。当多个 Entry 被 Hash 到同一个数组下标位置时,为了提升插入和查找的效率,HashMap 会把 Entry 的链表转化为红黑树。

💙 LinkedHashMap

LinkedHashMap 为键值对维护了一张链表,记录了从最旧到最新的键值对


  • LinkedHashMap() 新旧策略是按照插入的顺序排
  • LinkedHashMap(初始容量,扩容因子,排序策略) 初始容量一般为 16,扩容因子一般为 0.75
    • 排序策略
      • true 访问顺序
      • false 插入顺序

💛 WeakHashMap

WeakHashMap 是弱引用 HashMap,如果该集合中的 key 不再被引用了,再下一次垃圾回收时,将会删除该键值对


构造函数

  • WeakHashMap()
  • WeakHashMap(Map集合) 将一个 Map 集合转成 WeakHashMap

💛 TreeMap

[!quote] TreeMap TreeMap 可以对 key 进行排序

java
public class TreeMap<K,V> extends AbstractMap<K,V>
implements NavigableMap<K,V>,Cloneable,Serializable

💛 线程安全集合

[!quote] 内部同步 和 外部同步

  • 内部同步:在类或对象的实现内部进行的同步。通常,这是通过在方法上使用 synchronized 关键字或者在代码块上使用 synchronized 锁来实现的
    • 使用简单,不需要编写额外的同步代码
    • 由于方法都被加了 synchronized ,会导致性能损失
  • 外部同步:使用者需要自己管理对共享资源的访问,以保证线程安全
    • 灵活性高,可以更加细粒度
    • 使用复杂,容易出错

提供线程安全的方法

  • HashTable 内部同步,不推荐
  • Collections.synchronizedMap() 外部同步,不推荐
    • 每次对 Map 的操作都需要同步,这会导致显著的性能开销
    • 提供的是粗粒度的锁,任何时候只有一个线程可以访问 Map 的任何部分
  • ConcurrentHashMap 特殊的内部同步,推荐

💙 HashTable

[!quote] HashTable

  • HashTable 不允许键和值为 null
  • 迭代器是弱一致的

💙 Collections.synchronizedMap

java
Map<Object,Object> map=Collections.synchronizedMap(new HashMap<>());
// 现在这个map集合就是线程安全集合了

💙 ConcurrentHashMap

[!quote] ConcurrentHashMapConcurrentHashMap 中用的是分段锁将数据分成多个段,每个段可以独立地被锁定,不同的线程可以同时访问不同段的数据,从而提高了并发性能

  • 由于是分段锁,细粒度更高,提高了并行度
  • 由于是内部同步,使用起来就像使用 HashMap 一样,简单方便
  • 性能高 :无锁设计
  • 迭代器是弱一致的 :迭代器在迭代过程中可以容忍集合的并发修改【不会抛异常】,但不会保证迭代器能够实时反映这些修改
  • ConcurrentHashMap 也允许键和值为 null

❤ Collections 工具

java
public class Collections
extends Object
  • 【排序】
    • sort() 将指定的列表升序排列
    • reverse() 反转指定列表中的元素顺序
    • shuffle() 把集合里的元素顺序重新随机排列
  • 【空集合】
    • emptyMap() 返回一个不可变的空 Map
java
public static void main(String[] args) {  
    List list = new ArrayList();  
    list.add(30);  
    list.add(10);  
    list.add(100);  
    list.add(70);  
  
    System.out.println(list);  
      
    Collections.sort(list);  
    System.out.println(list);  
      
    Collections.reverse(list);  
    System.out.println(list);  
      
    Collections.shuffle(list);  
    System.out.println(list);  
}


[30, 10, 100, 70]
[10, 30, 70, 100]
[100, 70, 30, 10]
[30, 70, 10, 100]