Java笔记_12
集合
Collection
接口
- 集合类的基本接口是
Collection
接口, 有两个基本方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public interface Collection<E> {
boolean add(E element);
Iterator<E> iterator();
}
public interface Iterator<E> {
E next();
boolean hasNext();
void remove();
default void forEachRemaining(Consumer<? super E> action);
}
// 如果到达末尾依然调用next(), 会抛出NoSuchElementException
// remove()会删除上一次调用next()返回的元素
// remove()和next()有依赖性, 如果不先调用next(), 会抛出IllegalStateException
// 如果需要删除两个元素
it.next();
it.remove();
it.next();
it.remove();
LinkedList
- 对于
LinkedList
, 还存在一个previous()
, 支持从后往前遍历 - 如果调用了
previous()
, 则remove
或set
将修改右边的元素 - 如果有两个迭代器, 一个修改元素, 另一个在遍历元素, 就会产生错误, 抛出
ConcurrentModificationException
- 这里的修改只有增加或删除
add(), remove()
,set()
不会引发错误
- 这里的修改只有增加或删除
LinkedList
的get()
方法不是随机访问, 因为是链表格式, 只是做了一点优化, 如果索引大于n / 2
, 则从后往前调用previous()
访问
ArrayList
- 支持动态数组, 并且不是同步的
Vector
也是动态数组, 但是是同步的, 如果只有一个线程访问Vector
, 会在同步操作上花费大量时间, 此时用ArrayList
会比较好
散列表
Java
中散列表使用LinkedList
实现, 每个列表称为一个桶- 先计算一个对象的散列码, 然后与桶的个数取余, 得到的就是存储桶的索引
Java 8
中, 桶满了会从链表转换为平衡二叉树- 散列表的键要尽可能属于实现了
Comparable
接口的类, 这样可以避免散列码分布不均的问题 - 桶的个数一般设计为预计元素个数的
75%~150%
, 默认桶个数是16个 - 默认装填因子是0.75, 也就是当哈希表中已经转满了75%就会触发再散列
TreeSet
TreeSet
是一个有序集合, 使用红黑树实现- 插入元素比普通散列表慢, 但是查找一个元素只需要
- 因为是有序的, 所以其中的元素必须实现了
Comparable
接口
Map
- 同样分为
HashMap
,TreeMap
更新
counts.put(word, counts.get(word) + 1)
, 可能word
本身不存在, 会抛出NullPointerException
counts.put(word, counts.getOrDefault(word, 0) + 1);
counts.putIfAbsent(word, 0)
, 表示如果word
不存在, 则赋值为0, 然后调用put
即可正常更新, 避免null
- 也可以使用
merge
简化操作,counts.merge(word, 1, Integer::sum)
- 如果
word
不存在, 就设置为1 - 如果存在, 就使用
Integer::sum
设置为word
与1的求和
- 如果
视图
1 | Set<String> keys = map.keySet(); |
- 可以在视图上调用
remove()
删除散列表的元素, 但是不能添加元素 keySet
方法返回了一个Set
接口的类对象
WeakHashMap
- 如果一个
HashMap
中删除了元素, 但是因为HashMap
还在使用, 所以GC
不会释放这个元素的空间 - 因此使用
WeakHashMap
可以释放删除元素的空间, 使用弱引用保存键
LinkedHashSet
与 LinkedHashMap
- 会记住插入元素的顺序, 在元素加入哈希表后, 会添加到
LinkedList
双向链表中
EnumSet
- 使用位序列实现, 如果对应的值出现了, 则在相应的位置设为1
- 没有公共构造器, 要使用静态工厂方法构造
1
2
3enum Weekday {MONDAY, TUESDAY, ...};
EnumSet<Weekday> always = EnumSet.allOf(Weekday.class);
EnumSet<Weekday> never = EnumSet.noneOf(Weekday.class);
标识散列映射
IdentityHashMap
, 键的散列值不是通过hashCode()
计算得到, 而是使用System.indentityHashCode()
计算Object.hashCode()
计算散列码就使用这个方法, 比较两个对象,IdentityHashMap
使用==
, 而不是equals
小集合
-
Java 9
引入了一些静态方法, 可以生成给定元素的集合或列表, 以及给定的键值对List<String> names = List.of("1", "2", "3");
Set<Integer> nums = Set.of(1, 2, 3);
Map<String, Integer> m = Map.of("1", 1, "2", 2, "3", 3);
-
List, Set
有11个of
方法, 还有一个参数可变的of
方法, 这样是为了提高效率 -
Map
没有这个参数可变的版本, 因为参数类型会交替为键类型和值类型- 不过有一个
ofEntries
, 可以接受任意多个Map.Entry<K, V>
对象1
2
3
4Map<Stirng, Integer> socres = ofEntries(
entry("1", 1),
entry("2", 2),
entry("3", 3));
- 不过有一个
-
集合对象是不可修改的, 会导致
UnsupportedOperationException
-
如果需要一个可修改的集合, 可以把这个不可修改的集合传递到构造器中
var names = new ArrayList<>(List.of("1", "2"));
-
Collections.nCopies(n, anObject)
会返回一个实现了List
接口的不可变对象List<String> settings = Collections.nCopies(100, "DEFAULT");
- 这样构造包含了100个
"DEFAULT"
的List
, 存储开销很小, 对象只会存储一次
-
Array.asList
会返回一个可以更改, 但是大小不可改变的列表, 即可以调用set()
, 但是不能使用add(), remove()
不可修改的副本和视图
1 | ArrayList<String> names = ... |
同步视图
-
如果多个线程访问集合, 一个在修改, 另一个在查看, 结果会发生错误
-
synchronizedMap()
可以将任何一个映射转换成有同步访问方法的Map
var map = Collections.synchronizedMap(new HashMap<String, Integer>());
- 这样,
get(), put()
方法就是同步的, 只有一个执行完了才会调用另一个
-
对链表排序可以使用归并排序, 但是
Java
中是先将链表中的数据复制到数组中, 然后在数组中排序完以后再复制回链表 -
Collections.sort()
可以对List
数据进行排序 -
Collections.binarySearch
可以对实现了List
的接口的列表去进行二分搜索- 如果集合没有采用
Comparable
接口的compareTo
方法进行排序, 则还需要提供一个比较器对象 - 如果为
binarySearch()
提供一个链表, 则会退化为线性查找 binarySearch()
返回值ret
如果小于零, 则-ret - 1
的位置就是这个元素应该插入的位置
- 如果集合没有采用
批操作
-
coll1.removeAll(coll2)
, 在coll1
中删除所有coll2
包含的元素 (补) -
coll1.retainAll(coll2)
, 在coll1
中删除所有coll2
中没有包含的元素 (交) -
数组转集合
1
2String[] names = ...;
List<String> staff = List.of(names); -
集合转数组
1
2
3
4
5Object[] names = staff.toArray();
// 得到一个Object[]数组, 无法进行类型转换
String[] names = (String[]) staff.toArray(); // Error
// 需要向toArray传入一个数组构造器表达式
String[] names = staff.toArray(String[]::new);
遗留
-
Hashtable
类和HashMap
类作用相同, 但是HashTable
是同步的, 不需要兼容性应该使用HashMap
, 需要并发应该使用ConcurrentHashMap
-
Enumeration
有两个方法hasMoreElements(), nextElements()
, 可以使用Collections.list
将元素收集到一个ArrayList
-
属性映射的特性
- 键值都是字符串
- 映射可以保存到文件并从文件加载
- 有一个二级表存放默认值
-
实现属性映射的
Java
平台类名为Properties
, 对于配置项很有用1
2
3var settings = new Properties();
settings.setProperty("1", 1);
settings.setProperty("filename", "/home); -
使用
store()
将属性映射表保存到一个文件中, 比如program.properties
1
2
3
4
5var out = new FileWriter("program.properties", StandardCharsets.UTF_8);
settings.store(out, "Program Properties");
var in = new FileReader("program.properties", StandardCharsets.UTF_8);
settings.load(in);
String userDir = System.getProperty("user.home"); -
Properties
因为历史原因实现了Map<>
, 因此可以使用get(), put()
, 但是get()
返回类型是Object
,put()
允许插入任意对象- 所以最好使用处理字符串的
getProperty(), setProperty()
- 所以最好使用处理字符串的
-
栈,
Stack
, 扩展的Vector
, 但是可以使用insert(), remove()
添加删除任意的位置 -
位集,
BitSet
, 如果需要存储一个位序列, 比如标志, 可以使用位集- 因为位集将位包装在字节里, 所以使用位集比使用
Boolean, ArrayList
高效 bitSet.get(i)
会返回第i
位的结果,true, false
bitSet.set(i)
将第i
位的结果设置为true
bitSet.clear(i)
将第i
位的结果设置为false
- 因为位集将位包装在字节里, 所以使用位集比使用