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开始引入更高效并发类, 线程安全集合没有什么用了.
疑问
- 文件流的文件位置始终不对.