集合简介
Collection 接口
Collection
,它是除Map
外所有其他集合类的根接口。
package java.util;
public interface Collection<E> extends Iterable<E>
Iterable 接口
package java.lang;
public interface Iterable<T> {
// 获取迭代器
Iterator<T> iterator();
// 遍历元素
default void forEach(Consumer<? super T> action) {...}
// 获取可分割迭代器
default Spliterator<T> spliterator() {...}
}
Iterator 接口
Java访问集合总是通过统一的方式——迭代器(Iterator)来实现。
package java.util;
public interface Iterator<E>
例子:
List<Number> list = new ArrayList<>();
list.add(1);
list.add(2);
Iterator<Number> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
iterator.remove();
}
System.out.println(list);//[]
Spliterator接口
本质:与Iterator
差不多,只是Spliterator
可以拆分成多份去遍历,用于并行遍历。
方法签名为Spliterator spliterator()
,该方法返回容器的可拆分迭代器。从名字来看该方法跟iterator()
方法有点像,我们知道Iterator
是用来迭代容器的,Spliterator
也有类似作用,但二者有如下不同:
Spliterator
既可以像Iterator
那样逐个迭代,也可以批量迭代。批量迭代可以降低迭代的开销。Spliterator
是可拆分的,一个Spliterator
可以通过调用Spliterator trySplit()
方法来尝试分成两个。一个是this
,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。
可通过(多次)调用Spliterator.trySplit()
方法来分解负载,以便多线程处理。
参考:原理jdk8中Spliterator的作用 - facelessvoidwang - 博客园 (cnblogs.com)
使用Spliterator:
import java.util.ArrayList;
import java.util.Spliterator;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
}
// 数据平均分配到四个线程
Spliterator<Integer> spliterator01 = list.spliterator(); //01中有20个元素
Spliterator<Integer> spliterator02 = spliterator01.trySplit(); //01中有10个元素,02中有10个元素
Spliterator<Integer> spliterator03 = spliterator01.trySplit(); //01中有5个元素,02中有10个元素,03中有5个元素
Spliterator<Integer> spliterator04 = spliterator02.trySplit(); //01中有5个元素,02中有5个元素,03中有5个元素,04中有5个元素
MyThread t01 = new MyThread(spliterator01);
MyThread t02 = new MyThread(spliterator02);
MyThread t03 = new MyThread(spliterator03);
MyThread t04 = new MyThread(spliterator04);
t01.start();
t02.start();
t03.start();
t04.start();
}
}
//处理线程
class MyThread extends Thread {
// 数据源
private Spliterator<Integer> spliterator;
public MyThread(Spliterator<Integer> spliterator) {
this.spliterator = spliterator;
}
@Override
public void run() {
spliterator.forEachRemaining(x -> System.out.println(Thread.currentThread().getName() + " === " + x));
}
}
输出:
Thread-2 === 10
Thread-2 === 11
Thread-2 === 12
Thread-2 === 13
Thread-2 === 14
Thread-3 === 0
Thread-3 === 1
Thread-3 === 2
Thread-3 === 3
Thread-3 === 4
Thread-0 === 15
Thread-0 === 16
Thread-0 === 17
Thread-0 === 18
Thread-0 === 19
Thread-1 === 5
Thread-1 === 6
Thread-1 === 7
Thread-1 === 8
Thread-1 === 9
遗留类
由于Java的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:
Hashtable
:一种线程安全的Map
实现;Vector
:一种线程安全的List
实现;Stack
:基于Vector
实现的LIFO
的栈。
还有一小部分接口是遗留接口,也不应该继续使用:
Enumeration
:已被Iterator
取代。
List
源码
public interface List<E> extends Collection<E>
常用方法:
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();//转为数组,会新分配数组
<T> T[] toArray(T[] a);//转为数组,如果a容量不够,则会新分配数组
// Modification Operations
boolean add(E e);
boolean remove(Object o);//删除某个元素
// Bulk Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);//从指定位置开始插入
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);//保留c中的元素,删除其他的
default void replaceAll(UnaryOperator<E> operator) {}//对所有元素执行替换操作
default void sort(Comparator<? super E> c) {}//排序
void clear();
// 比较与哈希
boolean equals(Object o);
int hashCode();
// 位置访问操作
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
// 查找操作
int indexOf(Object o);
int lastIndexOf(Object o);
// List Iterators
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
// View
List<E> subList(int fromIndex, int toIndex);
default Spliterator<E> spliterator() {}//获取可分割迭代器
List和Array转换
【List 转 Array】
(1)调用toArray()
方法直接返回一个Object[]
数组:
List<Integer> list = new ArrayList<>();
Object[] array1 = list.toArray();//ok
//因为ArrayList内部数组是Object[]类型,所以toArray()返回值本身是Object[]类型
Integer[] array2 = (Integer[]) list.toArray();//error: ClassCastException
这种方法会丢失类型信息,所以实际应用很少。
(2)给toArray(T[])
传入一个类型相同的Array
,List
内部自动把元素复制到传入的Array
中:
List<Integer> list = new ArrayList<>();
Integer[] array2 = list.toArray(new Integer[0]);//ok,根据入参数组决定数组类型
Number[] array3 = list.toArray(new Number[0]);//ok
【Array 转 List】
Integer[] array = {1, 2, 3};
List<Integer> list1 = Arrays.asList(array);
List<String> list2 = Arrays.asList("1", "2");
Map
源码
public interface Map<K,V>
常用方法:
// Query Operations
int size();//键值对数量,如果超过 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);//获取value,不存在则返回 null
// Modification Operations
V put(K key, V value);//更新KV对,返回旧 value
V remove(Object key);//删除KV对,返回 value
// Bulk Operations
void putAll(Map<? extends K, ? extends V> m);
void clear();
// Views
Set<K> keySet();//返回键的视图,它和原 map 相互影响
Collection<V> values();//返回值的视图,它和原 map 相互影响
Set<Map.Entry<K, V>> entrySet();//返回KV视图,它和原 map 相互影响
// Comparison and hashing
boolean equals(Object o);
int hashCode();
// 默认实现的方法
default V getOrDefault(Object key, V defaultValue) {}
default void forEach(BiConsumer<? super K, ? super V> action) {}
//所有元素执行替换操作
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {}
//如果键不存在或值为null(更新value,返回null);如果键存在(返回旧值)
default V putIfAbsent(K key, V value) {}
//当(key,value)存在时,删除对应键值对
default boolean remove(Object key, Object value) {}
//当(key,oldValue)存在时,更新值
default boolean replace(K key, V oldValue, V newValue) {}
//如果key存在,更新值,返回旧值
default V replace(K key, V value) {}
//大意:如果key不存在,则根据key计算出newValue,插入该键值对
//如果key不存在或值为null,且mappingFunction(key)结果不为null,则插入新键值对。
//返回当前value(本来存在的 或者 计算出来的)
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {}
//大意:如果key存在,则根据(key,oldValue)计算出newValue,更新该键值对
//(1)如果key存在,且oldValue不为null,则计算 newValue = mappingFunction(key, oldValue)
//(1.1)如果newValue不为null则更新值,返回newValue
//(1.2)如果newValue为null则删除值,返回null
//(2)如果key不存在,或oldValue为null,则返回null
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {}
//大意:根据(key,oldValue)计算出newValue,更新该键值对
//得到(key, oldValue),计算 newValue = remappingFunction(key, oldValue)
//(1)如果 newValue 为 null,则删除该 key,返回 null
//(2)如果 newValue 不为 null,更新键值对,返回 newValue
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {}
//大意:合并新旧value(merge是特殊的compute)
//找到oldValue,计算 newValue = (oldValue == null) ? value : remappingFunction(oldValue, value)
//如果newValue为null,则删除key;如果newValue不为null,则更新key
//返回newValue
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {}
Map.Entry<K, V> 接口:
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
//返回比较器,比较的类型是Map.Entry<K, V>,比较 key 值
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {}
//返回比较器,比较的类型是Map.Entry<K, V>,比较 value 值
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {}
//返回比较器,比较的类型是Map.Entry<K, V>,比较 key 值,比较规则由入参决定
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {}
//返回比较器,比较的类型是Map.Entry<K, V>,比较 value 值,比较规则由入参决定
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
遍历Map
Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 456);
map.put("banana", 789);
//遍历 key
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
//遍历 key 和 value
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
HashMap查找key原理
根据key的hashCode确定KV对存储位置(即数组下标),但是这个位置下面可能有多个KV对,遍历这多个KV对,key与每个键用equals()比较,为true则找到KV对。
EnumMap
如果Map
的key是enum
类型,推荐使用EnumMap
,既保证速度,也不浪费空间(因为它的key范围是固定的,所以内部数组是紧凑的,并且根据 key 直接定位到内部数组的索引,并不需要计算 hashCode())
import java.time.DayOfWeek;
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
map.put(DayOfWeek.MONDAY, "星期一");
map.put(DayOfWeek.TUESDAY, "星期二");
map.put(DayOfWeek.WEDNESDAY, "星期三");
map.put(DayOfWeek.THURSDAY, "星期四");
map.put(DayOfWeek.FRIDAY, "星期五");
map.put(DayOfWeek.SATURDAY, "星期六");
map.put(DayOfWeek.SUNDAY, "星期日");
System.out.println(map);
System.out.println(map.get(DayOfWeek.MONDAY));
}
}
TreeMap
(1)TreeMap:
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
NavigableMap:
public interface NavigableMap<K,V> extends SortedMap<K,V>
SortedMap:
public interface SortedMap<K,V> extends Map<K,V>
(2)关于Key的要求:
使用TreeMap
时,放入的Key必须实现Comparable
接口。
如果作为Key的class没有实现Comparable
接口,那么,必须在创建TreeMap
时同时指定一个自定义排序算法:
public class Main {
public static void main(String[] args) {
Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
});
map.put(new Person("Tom"), 1);
map.put(new Person("Bob"), 2);
map.put(new Person("Lily"), 3);
for (Person key : map.keySet()) {
System.out.println(key);
}
// {Person: Bob}, {Person: Lily}, {Person: Tom}
System.out.println(map.get(new Person("Bob"))); // 2
}
}
class Person {
public String name;
Person(String name) {
this.name = name;
}
public String toString() {
return "{Person: " + name + "}";
}
}
使用Properties
因为配置文件非常常用,所以Java集合库提供了一个Properties
来表示一组“配置”。由于历史遗留原因,Properties
内部本质上是一个Hashtable
,但我们只需要用到Properties
自身关于读写配置的接口。
例子:
读取的配置文件 setting.properties:
# setting.properties
last_open_file=/data/hello.txt
auto_save_interval=60是
代码:
//1.创建 Properties 实例
Properties props = new Properties();
//2.读取配置文件
props.load(new InputStreamReader(new FileInputStream("src/setting.properties"), StandardCharsets.UTF_8));
//3.获取配置
String filepath = props.getProperty("last_open_file");//不存在返回null
String interval = props.getProperty("auto_save_interval", "120");//提供默认值
System.out.println(interval);
//4.写入配置
props.setProperty("language", "java语言");
//5.写入配置文件
props.store(new OutputStreamWriter(new FileOutputStream("src/save.properties"), StandardCharsets.UTF_8), "这是写入的properties注释");
写入的配置文件 save.properties:
#\u8FD9\u662F\u5199\u5165\u7684properties\u6CE8\u91CA
#Tue Jan 04 13:41:35 CST 2022
auto_save_interval=60是
last_open_file=/data/hello.txt
language=java语言
Set
例子
Set<String> set = new HashSet<>();
set.add("one");//true
set.add("two");//true
set.add("two");//false,添加失败,因为元素已存在
boolean isExist = set.contains("one");//true,元素存在
set.remove("three");//false,删除失败,因为元素不存在
int size = set.size();//2,一共两个元素
源码
public interface Set<E> extends Collection<E>
常用方法:
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean add(E e);//增加成功返回true;如果已存在返回false
boolean remove(Object o);
// Bulk Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);//只要有变化就返回true
boolean retainAll(Collection<?> c);//保留c中的元素,删除其他的
boolean removeAll(Collection<?> c);
void clear();
// Comparison and hashing
boolean equals(Object o);
int hashCode();
最常用的Set
实现类是HashSet
,实际上,HashSet
仅仅是对HashMap
的一个简单封装。
有序Set
Set
接口并不保证有序,而SortedSet
接口则保证元素是有序的:
HashSet
是无序的,因为它实现了Set
接口,并没有实现SortedSet
接口;TreeSet
是有序的,因为它实现了SortedSet
接口。
Queue
队列操作:队尾插入,队头获取。
源码
public interface Queue<E> extends Collection<E>
常用方法:
boolean add(E e);//插入元素,成功返回true,失败抛异常(可能超过了队列的容量)
boolean offer(E e);//插入元素,成功返回true,失败返回false
E remove();//删除队头元素并返回,队列为空时抛异常
E poll();//删除队头元素并返回,队列为空时返回null
E element();//查看队头元素,队列为空时抛异常
E peek();//查看队头元素,队列为空时返回null
PriorityQueue
(1)PriorityQueue
和Queue
的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue
调用remove()
或poll()
方法,返回的总是优先级最高的元素。
(2)使用方式1:元素实现Comparable
//元素实现Comparable
Queue<String> queue = new PriorityQueue<>();
queue.offer("b");
queue.offer("a");
System.out.println(queue);//[a, b]
(3)使用方式2:实例化PriorityQueue时,提供一个Comparator对象
//实例化PriorityQueue时,提供一个Comparator对象
Queue<User> queue = new PriorityQueue<>(new UserComparator());
queue.offer(new User("A1", "Bob"));
queue.offer(new User("A2", "Alice"));
queue.offer(new User("V1", "Boss"));
System.out.println(queue.poll()); // Boss/V1
System.out.println(queue.poll()); // Bob/A1
System.out.println(queue.poll()); // Alice/A2
System.out.println(queue.poll()); // null,因为队列为空
Deque
(1)允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue)。
public interface Deque<E> extends Queue<E>
(2)常用方法:
失败抛异常 | 失败返回false或null | |
---|---|---|
插入队首 | void addFirst(E e) | boolean offerFirst(E e) |
插入队尾 | void addLast(E e) | boolean offLast(E e) |
取队首元素并删除 | E removeFirst() | E pollFirst() |
取队尾元素并删除 | E removeLast() | E pollLast() |
查看队首元素 | E getFirst() | E peekFirst() |
查看队尾元素 | E getLast() | E peekLast() |
boolean removeFirstOccurance(Object o);
boolean removeLastOccurrence(Object o);
// *** Stack methods ***
void push(E e);//入栈,等于 addFirst(e)
E pop();//出栈,等于 removeFirst()
(3)例子:
Deque<String> deque = new LinkedList<>();
deque.offerLast("A"); // A
deque.offerLast("B"); // A <- B
deque.offerFirst("C"); // C <- A <- B
System.out.println(deque.pollFirst()); // C, 剩下A <- B
System.out.println(deque.pollLast()); // B, 剩下A
System.out.println(deque.pollFirst()); // A
System.out.println(deque.pollFirst()); // null
Collections工具类
创建集合
(1)创建空集合:
- 创建空List:
List emptyList()
- 创建空Map:
Map emptyMap()
- 创建空Set:
Set emptySet()
要注意到返回的空集合是不可变集合,无法向其中添加或删除元素。
(2)创建一个单元素集合:
- 创建一个元素的List:
List singletonList(T o)
- 创建一个元素的Map:
Map singletonMap(K key, V value)
- 创建一个元素的Set:
Set singleton(T o)
要注意到返回的单元素集合也是不可变集合,无法向其中添加或删除元素。
(3)把可变集合封装成不可变集合:
- 封装成不可变List:
List unmodifiableList(List list)
- 封装成不可变Set:
Set unmodifiableSet(Set set)
- 封装成不可变Map:
Map unmodifiableMap(Map m)
这种封装实际上是通过创建一个代理对象,拦截掉所有修改方法实现的。
排序
public static <T extends Comparable<? super T>> void sort(List<T> list) {}
洗牌
public static void shuffle(List<?> list) {}
CheckedList
如果列表没有泛型信息,则可以放入不同类型的元素:
List list = new ArrayList<>();
list.add(1);
list.add("two");
System.out.println(list);//[1, two]
使用CheckedList,如果插入的元素类型错误,将立即导致ClassCastException:
List list = new ArrayList<>();
list.add(1);
List tsafelist = Collections.checkedList(list, Integer.class);
tsafelist.add("");//异常 ClassCastException