Java集合简介
- 数组存在的限制:
- 数值初始化后大小不可变;
- 数组只能按索引顺序存取;
Collection
:包含在Java
标准库自带的java.util
中,是除了Map
外其他所有集合类的根接口;java.util
包所提供的集合的三种主要类型:
List
:一种有序列表的集合;Set
:一种无重复元素的集合;Map
:一种通过键值(key-value
)对查找的映射表集合;
- 集合的设计特点
- 实现了接口和实现类相分离;
- 支持泛型,可以限制在一个集合中只能放入同一种数据类型的元素;
- 集合总是通过统一方式——**迭代器(
Iterator
)**实现,其最明显的好处在于无需知道集合内部元素是按何种方式存储;
- 遗留的集合类
Hashtable
:一种线程安全的Map
实现;Vector
:一种线程安全的List
实现;Stack
:基于Vector
实现的FIFO
的栈;Enumeration<E>
:已被Iterator<E>
取代;
List
-
定义:一种有序链表,其内部按放入元素的先后顺序存放,每个元素可通过索引确定自己的位置,索引同数组均从
0
开始; -
List<E>
接口主要用到的接口方法:
方法 | 功能 |
---|---|
void add(E e) |
末尾添加元素 |
void add(int index, E e) |
指定索引添加元素 |
int remove(int index) |
删除指定索引元素 |
int remove(Object e) |
删除某元素 |
E get(int index) |
获取指定索引元素 |
int size() |
获取链表大小(即所含元素个数) |
boolean contains(Object obj) |
判断List 是否包含某个指定元素 |
int indexOf(Object obj) |
返回某个元素的索引,若不存在则返回-1 |
ArrayList
和LinkedList
的区别
ArrayList |
LinkedList |
|
---|---|---|
获取指定元素 | 速度很快 | 需要从头开始查找元素 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/删除 | 需要移动数据 | 无需移动元素 |
内存占用 | 少 | 较大 |
List
常见操作
-
创建
// 1. ArrayList List<T> list = new ArrayList<T>(); // 2. LinkedList List<T> list = new LinkedList<T>(); // 3. of(),不接受null List<T> list = List.of(T t1, T t2, ...);
-
遍历
import java.util.List; public class Main{ public static void main(String[] args) throws Exception{ List<String> list = List.of("apple", "pear", "banana"); for(int i = 0; i < list.size(); i++){ String s = list.get(i); System.out.println(s); } } }
import java.util.Iterator; import java.util.List; public class Main{ public static void main(String[] args) throws Exception{ List<String> list = List.of("apple", "pear", "banana"); for(Iterator<String> it = list.iterator(); it.hasNext();){ String s = it.next(); System.out.println(s); } } }
import java.util.List; public class Main{ public static void main(String[] args) throws Exception{ List<String> list = List.of("apple", "pear", "banana"); for(String s:list){ System.out.println(s); } } }
Iterator
对象的两个方法
方法 | 功能 |
---|---|
boolean hasNext() |
判断是否有下一个元素 |
E next() |
返回下一个元素 |
List
转换Array
的三种方法:
-
调用
toArray()
方法直接返回一个Object[]
数组,但是会导致丢失类型信息;import java.util.List public class Main{ public static void main(String[] args) throws Exception{ List<String> list = List.of("Apple", "Huawei", "Xiaomi"); Object[] array = list.toArray(); for(Object obj : array){ System.out.println(obj) } } }
-
给
toArray(T[])
传入一个类型相同的Array
,List
内部自动将元素复制到传入的Array
中;import java.util.List; public class Main{ public static void main(String[] args) throws Exception{ List<Integer> list = List.of(12, 34, 56, 78); Integer[] array = list.toArray(new Integer[4]); for(Integer n; array){ System.out.println(n); } } }
-
通过
List
接口定义的T[] toArray(IntFunction<T[]> generator)
方法;Integer[] array = list.toArray(Integer[]::new);
Array
转换为List
的方法
-
利用
List.of(T...)
;Integer[] array = {1, 3, 4}; List<Integer> list = List.of(array);
equals方法
equals()
方法所要满足的条件:
- 自反性(
Reflexive
):对非null
的x
而言,x.equals(x)
必须为true
; - 对称性(
Symmetric
):对非null
的x
和y
而言,x.eauals(y)
和y.equals(x)
返回的结果相同; - 传递性(
Transitive
):对非null
的x
、y
和z
而言,若x.equals(y)
的true
,y.equals(z)
也为true
,则x.equals(z)
也必须为true
; - 一致性(
Consistent
):对非null
的x
和y
而言,只要x
和y
状态不变,则x.equals(y)
总是一致返回true
或false
; null
间的比较:x.equals(null)
永远返回false
;
- 编写
equals()
的步骤:
- 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
instanceof
判断传入的待比较的Object
是否为当前类型,若是则继续比较,否则返回false
;- 对引用类型用
Objects.equals()
比较,对基本类型直接用==
比较;
- 注意: 使用
Objects.equals()
比较两个引用类型是否相等的目的是省去了判断null
的麻烦,两个引用类型均为null
时他们也相等;
Map
-
通过一个键去查询对应值,用
List
实现效率极低,平均需要扫描一半元素才能确定,使用Map
这种键值(key-value
)映射表,能高效通过key
查找value
; -
常用方法
方法 | 功能 |
---|---|
boolean containsKey(K key) |
查询某个key 是否存在 |
void put(K key, V value) |
将key 和value 做映射并放入Map |
V get(K key) |
通过key 获取对应value ,若key 不存在则返回null |
-
注意:
Map
中不存在重复的key
,因为将不同value
放入相同的key
,只会讲原有key-value
映射对中的value
替换掉;但是不同key
可以对应相同value
; -
常见操作
-
创建
import java.util.Map; import java.util.HashMap; public class Main{ public void main(String[] args) throws Exception{ Map<String,Integer> map = new HashMap<>(); map.put("Apple", "10000"); map.put("Huawei", "5000"); System.out.println(map.get("Apple")); } }
-
遍历
// 只遍历key import java.util.Map; import java.util.HashMap; public class Main{ public void main(String[] args) throws Exception{ Map<String,Integer> map = new HashMap<>(); map.put("Apple", "10000"); map.put("Huawei", "5000"); for(String key : map.keySet()){ Integer value = map.get(key); System.out.println(key + ":" + value); } } }
// 同时遍历key和value import java.util.Map; import java.util.HashMap; public class Main{ public void main(String[] args) throws Exception{ Map<String,Integer> map = new HashMap<>(); map.put("Apple", "10000"); map.put("Huawei", "5000"); for(Map.Entry<String, Integer> entry : map.entrySet()){ String key = entry.getKey(); Integer value = map.getValue(); System.out.println(key + ":" + value); } } }
- 注意: 遍历
Map
时,输出的key
是无序的;
equals和hashCode
hashCode()
方法需要遵循的规范:
- 若两个对象相等,则两个对象的
hashCode()
必须相等; - 若两个对象不相等,则两个对象的
hashCode()
尽量不相等;
- 正确使用
Map
的原则:
- 作为
key
的对象必须正确覆写equals()
方法,相等的两个key
实例调用equals()
必须返回true
; - 作为
key
的对象还必须覆写hashCode()
方法;
- 编写
equals()
和hashCode()
方法必须遵循的原则:
equals()
用于比较的每个字段都必须在hashCode()
中用于计算;equals()
中未使用的字符,hashCode()
中绝对不能出现;
EnumMap
- 相关定义
针对enum
类型,可使用EnumMap
,它在内部以一个非常紧凑的数组存储value
,且根据enum
类型的key
直接定位到内部数组的索引,不需要计算hashCode()
,效率高且没有额外浪费空间;
import java.time.DayOfWeek;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception{
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.SUNDAY));
}
}
TreeMap
Map
继承树
HashMap
是一种空间换时间的映射表,内部key
无序,而SortedMap
在内部会对key
进行排序,作为接口其实现类是TreeMap
;
import java.util.*;
public class Main{
public static void main(String[] args) throws Exception{
Map<String, Integer> map = new TreeMap<>();
map.put("Apple", "10000");
map.put("Xiaomi", "3000");
map.put("Huawei", "5000");
for(String key : map.keySet()){
System.out.print(key); // Apple, Huawei, Xiaomi
}
}
}
- 作为
SortedMap
的key
必须实现Comparable
接口,或者传入Comparator
,同时严格按照compare()
规范实心比较逻辑;
Properties
- 读取配置文件的步骤:
-
创建
Properties
实例; -
调用
load()
读取文件; -
调用
getProperty()
获取配置,若key
不存在,则返回null
;String f = "setting.properties"; // 创建实例 Properties props = new Properties(); // 读取文件 props.load(new java.io.FileInputStream(f)); // 获取配置 String filePath = props.getProperty("属性名");
- 写入配置文件
Properties props = new Properties();
props.setProperty("url", "https://cunyu1943.github.io");
props.setProperty("author", "村雨遥");
props.store(new FileOutputStream("./home/cunyu/setting.properties"), "写入的properties注释")
Set
- 定义:用于存储不重复的元素集合;
- 放入
HashSet
的元素与作为HashMap
的key
要求相同; - 放入
TreeSet
的元素与作为TreeMap
的key
要求相同;
- 常用方法:
方法 | 功能 |
---|---|
boolean add(E e) |
将元素添加入Set<E> |
boolean remove(Object e) |
将元素从Set<E> 删除 |
boolean contains(Object e) |
判断是否包含某元素 |
int size() |
Set<E> 中的元素个数 |
Set
继承树
Set
接口不保证有序,而SortedSet
保证元素有序;
HashSet
无序,只实现了Set
接口,未实现SortedSet
接口;TreeSet
有序,因为实现了SortedSet
接口;
Queue
- 定义:一个先进先出(
FIFO:First In First Out
)的有序表,与List
的区别在于List
可在任意位置添加/删除元素,而Queue
只有两个操作;
- 将元素添加到队列末尾;
- 从队列头部取出元素;
- 常用方法
方法 | 功能 |
---|---|
int size() |
获取队列长度 |
boolean add(E e)/boolean offer(E e) |
添加元素到队尾 |
E remove()/E poll() |
获取队首元素并从队列中删除 |
E element()/E peek() |
获取队首元素但不从队列中删除 |
- 获取或删除元素失败时不同方法的应对策略
功能 | throw Exception |
返回false 或null |
---|---|---|
添加元素到队尾 | add(E e) |
boolean offer(E e) |
取队首元素并删除 | E remove() |
E poll() |
取队首元素但不删除 | E element() |
E peek() |
- 注意: 避免将
null
加入队列,否则poll()
方法返回null
时,难以确定是取到了null
元素还是队列为空;
PriorityQueue
Queue
与PriortyQueue
的区别:
PriorityQueue
的出队顺序与元素的优先级有关,无论调用remove()
或poll()
方法,返回的总是优先级最高的元素;- 使用
PriorityQueue
需要给每个元素定义“优先级”;
-
PriortyQueue
默认按元素比较的顺序排序(需实现Comparable
接口),也可以通过Comparator
自定义排序算法(元素不必实现Comparable
接口); -
举例
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<User> queue = new PriorityQueue<>(new UserComparator());
// 添加3个元素到队列:
queue.offer(new User("Tim", "A1"));
queue.offer(new User("Park", "A2"));
queue.offer(new User("Manu", "V1"));
System.out.println(queue.poll()); // Manu/V1
System.out.println(queue.poll()); // Tim/A1
System.out.println(queue.poll()); // Park/A2
System.out.println(queue.poll()); // null,因为队列为空
}
}
class UserComparator implements Comparator<User> {
public int compare(User u1, User u2) {
if (u1.number.charAt(0) == u2.number.charAt(0)) {
// 如果两人的号都是A开头或者都是V开头,比较号的大小:
return u1.number.compareTo(u2.number);
}
if (u1.number.charAt(0) == 'V') {
// u1的号码是V开头,优先级高:
return -1;
} else {
return 1;
}
}
}
class User {
public final String name;
public final String number;
public User(String name, String number) {
this.name = name;
this.number = number;
}
public String toString() {
return name + "/" + number;
}
}
Deque
-
定义:允许两头都进出的队列叫做双端队列(
Double Ended Queue
),即Deque
; -
Deque
是一个接口,其实现类有ArrayDeque
和LinkedList
; -
Queue
和Deque
出入队方法比较:
Queue |
Deque |
|
---|---|---|
添加元素到队尾 | add(E e)/offer(E e) |
addLast(E e)/offerLast(E e) |
取队首元素并删除 | E remove()/E poll() |
E removeFirst()/E pollFirst() |
取队首元素但不删除 | E element()/E peek() |
E getFirst()/E peekFirst() |
添加元素到队首 | addFirst(E e)/offerFirst(E e) |
|
取队尾元素并删除 | E removeLast()/E pollLast() |
|
取队尾元素但不删除 | E getLast()/E peekLast() |
- 举例
import java.util.Deque;
import java.util.LinkedList;
public class Main{
public static void main(String[] args){
Deque<String> deque = new LinkedList<>();
deque.offerLast("A"); // A
deque.offerLast("B"); // B->A
deque.offerFirst("C"); // B->A->C
System.out.println(deque.pollFirst()); // C, B -> A
System.out.println(deque.pollLast()); // B, A
System.out.println(deque.pollFirst()); // A
System.out.println(deque.pollFirst()); // null
}
}
- 抽象编程的一个原则:尽量持有接口,而非具体实现类;
Stack
-
定义:栈,一种后进先出(
LIFO:Last In First Out
)的数据结构,只能不断往stack
中压入(push
)元素,最后进去的元素最先弹出(pop
); -
出入栈的操作
方法 | 功能 |
---|---|
push (E) |
将元素压栈 |
pop (E) |
将栈顶元素“弹出” |
peek (E) |
取栈顶元素但不弹出 |
- 利用
Deque
实现Stack
功能
方法 | 功能 |
---|---|
push (E)/addFirst(E) |
将元素压栈 |
pop(E)/removeFirst() |
将栈顶元素“弹出” |
peek(E)/peekFirst(E) |
取栈顶元素但不弹出 |
Stack
的作用
-
JVM
在处理Java
方法调用时会通过栈来维护方法调用的层次; -
利用栈对整数进行进制转换;
import java.util.Deque; import java.util.LinkedList; // 十进制转十六进制 public class Main{ public static void main(String[] args){ int intNum = 12500; Main demo = new Main(); String hex = demo.toHex(intNum); if(hex.equalsIgnoreCase("30D4")){ System.out.println("Success"); }else{ System.out.println("Failed"); } } public String toHex(int n){ Deque<Character> stack = new LinkedList<>(); String alpha = "0123456789ABCDE"; int remainder = 0; while(n>0){ remainder = n % 16; if(remainder < 16){ stack.push(alpha.charAt(remainder)); } n /= 16; } String result = ""; while(stack.isEmpty() != true){ result += stack.pop(); } return result; } }
Iterator
-
迭代器:通过
Iterator
对象遍历集合的模式,使用迭代器的好处在于调用方总是以统一方式遍历各种集合类型,而无需关系其内部存储结构; -
当要在一个编写的集合类中使用
for each
循环时,需要满足以下条件:
- 集合类实现
Iterable
接口,且接口要求返回一个Iterator
对象; - 用
Iterator
对象迭代集合内部数据;
- 示例
// Iterator
import java.util.*;
public class Main{
public static void main(String[] args) throws Exception{
ReverseList<String> rlist = new ReverseList<>();
rlist.add("Apple");
rlist.add("Orange");
rlist.add("Pear");
for(String s : rlist){
System.out.println(s);
}
}
}
class ReverseList<T> implements Iterable<T>{
private List<T> list = new ArrayList<>();
public void add(T t){
list.add(t);
}
@override
public Iterator<T> iterator(){
return new ReverseIterator(list.size());
}
class ReverseIterator implements Iterators<T>{
int index;
ReverseIterator(int index){
this.index = index;
}
@override
public boolean hasNext(){
return index > 0;
}
@override
public T next(){
index--;
return ReverseList.this.list.get(index);
}
}
}
Iterator
是一种抽象数据访问模型,使用它的好处有如下几点:
- 对任何集合均采用同一种访问模式;
- 调用者对集合内部结构一无所知;
- 集合类返回的
Iterator
对象知道如何迭代;
Java
提供标准迭代器模型,即集合类实现java.util.Iterable
接口,返回java.util.Iterator
实例;
Collections
- 创建空集合
方法 | 功能 |
---|---|
List<T> emptyList() |
创建空List |
Map<K,V> emptyMap() |
创建空Map |
Set<T> emptySet() |
创建空Set |
of() |
创建对应空集合 |
- 创建单元素集合
方法 | 功能 |
---|---|
List<T> singletonList(T o) |
创建单元素的List |
Map<K,V> singletonMap(K key, V value) |
创建单元素的Map |
Set<T> singleton(T o) |
创建单元素的Set |
of(T o) |
创建对应单元素集合 |
- 排序
// 排序会改变List元素位置,因此传入的List必须可变
import java.util.*;
public class Main{
public static void main(String[] args) throws Exception{
List<String> list = new ArrayList<>();
list.add("Huawei");
list.add("Vivo");
list.add("OnePlus");
// 排序前
System.out.println(list);
Collection.sort(list);
// 排序后
System.out.println(list);
}
}
- 洗牌:将传入的有序
List
,随机打乱List
内部元素顺序;
import java.util.*;
public class Main{
public static void main(String[] args) throws Exception{
List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; i++){
list.add(i);
}
// 洗牌前
System.out.println(list);
Collections.shuffle(list);
// 洗牌后
System.out.prinln(list);
}
}
- 不可变集合,封装时是通过创建一个代理对象,拦截所有修改方案,此时对原始可变
List
进行修改仍然可以,但是会对封装后的不可变List
造成影响;
方法 | 功能 |
---|---|
List<T> unmodifiableList(List<? extends T> list) |
封装为不可变List |
Set<T> unmodifiableSet(Set<? extends T> set) |
封装为不可变Set |
Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> map) |
封装为不可变Map |
- 线程安全集合
方法 | 功能 |
---|---|
List<T> synchronizedList(List<T> list) |
变为线程安全的List |
Set<T> synchronizedSet(Set<T> set) |
变为线程安全的Set |
Map<K, V> synchronizedMap(Map<K, V> map) |
变为线程安全的Map |