Java笔记07 - 集合
java标准库中被使用最多的类型
Java集合简介
- 集合是为了处理一组类似的数据
- 如果一个Java对象可以在内部持有若干个其他Java对象, 并对外提供访问接口, 就是Java中的集合
Collection
除Map外, 其他集合类的根接口
java.util
主要提供以下三种类型的集合List
: 一种序列表的集合.Set
: 保证没有重复元素的集合.Map
: 通过键值(key-value)查找的映射表集合.
- 集合的设计特点:
- 实现了接口和继承类相分离
- 支持泛型
- 通过迭代器(Iterator)访问
- 尽量不适用遗留接口
使用List
- List: 是一种有序链表
- List和数组几乎完全相同:
- 按照元素的先后顺序存放
- 每个元素都可以通过索引确定自己的位置
- 索引从0开始
ArrayList
内部使用数组来存储元素- 添加的使用, 向后挪一位
- 继续添加, 没空闲的时候, 就再造一个更大的数组, 然后把这个数组复制过去
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()
- 末尾添加一个元素:
LinkedList
通过链表也实现了List
接口- 在
LinkedList
中, 每个元素都指向下一个元素
List特点
- 可以添加重复元素
- 可以添加
null
创建List
- 可以通过
List.of()
进行创建, 根据给定元素快速创建List
- 不接受
null
遍历List
- 使用
Iterator
- 由List实例调用
iterator()
创建 hasNext()
是否含有下一个元素E next()
返回下一个元素
List<String> list = List.of("apple", "pear", "banana");
for (Iterator<String> it = list.iterator(); it.hasNext();) {
String s = it.next();
System.out.println(s);
}
Iterator
遍历List
永远都有最高效的方式- Java中的
for each
使用的就是Iterator
遍历 - 只要实现了
Iterable
接口的集合都可以直接使用for each
进行遍历
List和Array互换
- List to Array
toArray()
方法直接返回一个Object[]
数组: 造成信息丢失toArray(T[])
传入一个相同类型的Array, List自动把元素复制到传入的Array
中- 可以传入向上兼容的类型.
- 如果传入的数组元素比之前多了, 会再创建一个数组进行存储.
Number[] arr = list.toArray(Number[]::new);
: 函数式写法
- Array to List
- 使用
List.of(T...)
List.of
生成的List
是只读的.- 调用
add()
,remove()
方法会抛出错误.
- 使用
编写equals方法
boolean contains(Object o)
方法判断List
是否包含某个指定元素.int indexOf(Object o)
方法可以返回某个元素的索引
编写equals
- 编写规则:
- 自反性: 非null的x. x.equals(x)返回true
- 对称性: 非null的x, y. x.equals(y)为true, 那么y.equals(x)也必须为true
- 传递行: 非null的x, y, z. x.equals(y)为true, y.equals(z)为true, x.equals(z)也必须为true
- 一致性: 非null的x, y, 只要x和y的状态不变, x.equals(y)总是一致返回true或者false
- 对null的比较: x.equals(null)总是返回false
- 编写步骤:
- 先确定实例相等逻辑
- 用
instanceof
判断传入的实例, 是否为当前的类型 - 对与引用类型, 使用
Objects.equals()
进行判断, 基本类型, 使用==
Objects.equals
判断两个null
, 他们相等.
使用Map
- 高效通过
key
快速查找value
(元素) Map<K, V>
是一种键-值
映射表- 调用
put(K key, V value)
方法时, 即将key
和value
做了映射关系 - 调用
V get(K key)
时, 就可以通过key, 获取value
key
, 不存在, 返回nullboolean containsKey(K key)
方法, 判断key是否存在- 不存在重复的key, 如果放入同样的key, 会把之前的key替换掉, 存一个新的value
- value是可以重复的
遍历Map
.keySet()
获取key
的集合, 在用for each
遍历集合.entrySet()
同时遍历key
和value
- Map不能保证顺序. 遍历MAP, 应该假设输出的key是无序的
编写equals和hashCode
- hashMap内部使用空间换时间的方法
- 作为
key
的对象, 必须正确覆写equals
方法 - 对
key
对象, 进行hashCode()
, 返回一个int
整数 - 作为
key
的对象, 必须正确覆写hashCode()
方法 - 如果两个对象相等, 则
hashCode()
必须相等 - 如果两个对象不相等, 则
hashCode()
尽量不相等 - 可以使用
Objects.hash(a, b, c)
进行覆写
延伸阅读
HashMap
默认大小只有16,int index = key.hashCode() & 0xf
- 数组不够用了, 扩容一倍, 重写计算
key
- 频繁扩容影响性能, 所以一开始制定容量,
Map<String, Integer> map = new HashMap<>(1000)
HashMap
实际上不是直接存储的实例对象, 而是对应一个entry
的list
- 如果
key.hashCode()
以后相同, 就放到不同的两个entry
上面. - 然后遍历整个list, 找到对应的
entry
使用EnumMap
- 如果
key
是enum
对象 - 内部使用非常紧凑的数组存储
value
- 根据
enum
类型的key, 直接定义到内部数组的索引, 不需要计算hashCode()
- EnumMap时间和空间都可以保证, 优先使用
EnumMap
使用的时候, 持有Map
接口
使用TreeMap
SortedMap
会对key
进行排序.SortedMap
是接口,TreeMap
是实现类- 保证遍历时, 以
key
的顺序进行排序 key
必须实现Comparable
接口String.compareTo()
在两个值相同的时候, 返回0
, 就是判断一样TreeMap
的key
不需要实现equals
和hashCode
使用Properties
- 配置文件特定: Key-Value是
String
-String
Properties
表示一组配置
读取配置文件
- 创建
Properties
实例 - 调用
load()
读取文件 - 调用
getProperty()
获取配置 - 接受一个
InputStream
, 表示字节流, 所以文件字节流和jar包中的资源流都可以 - 也可以从内存中直接读取一个字节流
- 多个
.properties
文件, 反复load()
读取, 后面读取的key-value
会覆盖已经读取的 - 历史问题, 从
Hashtable
派生, 只使用getProperty
和setProperty
, 不适用继承的get()
和put()
方法
写入配置文件
setProperty()
修改属性, 使用store()
进行存储
编码
load(InputStream)
默认总是以ASCII编码, 所以会导致读到乱码load(Reader)
方法解决, 一个字节流, 一个字符流, 字符在内容已经char
类型表示了, 不涉及编码问题
使用Set
- 存储不重复的key, 不需要存储映射的value, 使用
Set
- 添加:
boolean add(E e)
- 删除:
boolean remove(Object e)
- 判断是否含有:
boolean contains(Object e)
Set
是只存key, 不存value的mapSet
中的元素需要实现equals()
和hashCode()
HashSet
仅仅是对HashMap
的一个简单封装Set
接口不能保证有序, 但是SortedSet
接口可以保证有序TreeSet
可以保证有序, 元素正确实现Comparable
接口
使用Queue
- 队列只有两个操作:
- 把元素添加到队列末尾
- 从队列头部取出元素
- 方法:
int size()
: 获取队列长度boolean add(E)/boolean offer(E)
添加元素到队尾E remove() / E poll()
获取首元素并从队列中删除E element() / E peek()
获取首元素但不从队列中删除
- 区别:
add()
添加失败, 抛出异常 /offer()
添加失败, 返回false- 如果空队列:
remove()
, 抛出异常 /pull()
, 返回null 所以不要把null放到队列 pull()
获取并删除 /peek()
获取不删除, 重复使用
LinkedList
即实现了List
接口, 又实现了Queue
接口.
使用PriorityQueue
- 出队顺序和元素的优先级有关.
- 调用
remove()
或者poll()
方法, 返回的总是优先级最高的元素 - 放入其中的元素必须实现
Comparable
接口, 从而根据优先级进行出队
使用Deque
- 双端队列, 允许两头进, 两头出.
- 可以添加元素到队尾, 也可以添加到队首
- 可以从队尾获取, 也可以从队首获取
addLast()
/addFirst()
- 扩展自
Queue
, 但最好不要使用add()
等方法 Deque
的实现类ArrayList
和LinkedList
- 面向对象编程的原则: 尽量持有接口, 而不是具体的实现类
使用Stack
- 栈: 是一种后进先出的数据结构
- 压栈:
push(E)
/addFirst(E)
- 栈顶元素弹出:
E pop()
/E removeFirst()
- 取栈顶元素但不弹出
E peek()
/E peekFirst()
- 没有
Stack
接口, 使用Deque
进行模拟, 只使用push, pop, peek
方法
Stack的作用
-
jvm使用栈结构维护方法的调用顺序
调用的时候, 会先把参数压栈, 然后执行对应的方法; 方法返回时, 返回值压栈, 调用方法通过出栈操作获得方法返回值.
-
调用方法有容量现在, 调用过多会造成栈溢出:
StackOverflowError
-
对整数进行进制转换
-
计算表达式后缀
使用iterator
- Java的集合类都可以使用
for each
循环
for (Iterator<String> it = list.iterator();it.hasNext();) {
String s = it.next();
System.out.println(s);
}
- Iterator对象是在内部创建, 对象知道如果高效处理自己的结构
- 调用方只关心统一的方法调用
- 实现要求:
- 集合类实现了
Iterable
接口, 要求返回一个Iterator
对象 - 用
Iterator
对象迭代集合内部数据
- 集合类实现了
import java.util.*;
public class Main {
public static void main(String[] args) {
ReverseList<String> rList = new ReverseList();
rList.add("A");
rList.add("B");
rList.add("C");
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 Iterator<T> {
int index;
ReverseIterator(int index) {
this.index = index;
}
@Override
public boolean hasNext() {
return index > 0;
}
public T next() {
index--;
return ReverseList.this.list.get(index);
}
}
}
-
通过内部类实现
Iterator
接口 -
这个内部类, 可以直接访问对应外部类的全部字段和方法
-
使用
外部类.this
获取 -
集合类实现了
Iterable
接口, 提供iterator
方法, 返回Iterator
实例
使用Collections
Collections
工具类, 提供一系列操作集合的方法
创建空集合
- 创建空List:
List<T> emptyList()
- 创建空Map:
Map<T> emptyMap()
- 创建空Set:
Set<T> emptySet()
- 与
.of()
相同: 空集合是不可变集合, 无法添加或者删除元素.
List<String> list1 = List.of();
List<String> list2 = Collections.emptyList();
创建单元素集合
-
List<T> singletonList(T t)
-
Map<K, V> singletonMap(K k, V v)
-
Set<T> singleton(T t)
-
返回的单元素集合, 或者多个元素集合, 也是不可变的, 还是
.of()
方便
排序
Collections.sort()
进行排序, 修改List
中元素的位置
洗牌
Collections.shuffle()
进行洗牌, 打乱元素位置
不可变集合
- 提供一系列方法, 将集合变成不可变的
- 不可变List:
List<T> uList = Collections.unmodifiableList(list);
- Set, Map一个样
- 其实就是拦截修改方法
- 但是可以操作原来的数组, 所以最好是马上放弃原来的索引
线程安全集合
List<T> synchronizedList(List<T> list)
- 后面设计度线程, java5开始引入更高效并发类, 线程安全集合没有什么用了.
疑问
- 文件流的文件位置始终不对.