概述
本文将介绍一些不算常用的特殊集合类,包括LinkedHashSet、LinkedHashMap、WeakHashMap、IdentityHashMap、EnumSet、EnumMap、BitSet、Properties。
具体内容
LinkedHashSet和LinkedHashMap
这两个集合类的特点是"Linked",意味着它们相比Hash的散列属性,多了有序的特点,其内部是通过双向链表实现的,当我们使用迭代器遍历时,将会按照元素入集的顺序进行遍历。
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapExample {
public static void main(String[] args) {
// 创建一个LinkedHashMap,指定初始容量和加载因子
LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>(5, 0.75f);
// 添加键值对
linkedHashMap.put(1, "One");
linkedHashMap.put(2, "Two");
linkedHashMap.put(3, "Three");
// 遍历键值对,顺序与添加顺序一致
for (Map.Entry<Integer, String> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 输出:1: One
// 2: Two
// 3: Three
}
}
虽然LinkedHashSet和LinkedHashMap都以Linked开头,但事实上它们并不继承于List,它们本质上其实是Set和Map,因此并不能调用Deque接口中有关队列操作的算法,需要注意。
WeakHashMap
该集合中的"Weak"中文意思是虚弱的,在介绍这个集合的特点时,我们需要明确,在普通的HashMap中,每个键值对加入了HashMap后,如果不手动删除的话是不会被垃圾回收器回收的,这是因为HashMap对每个键的引用都是强引用。
而WeakHashMap使用弱引用保存键,这意味着一定要有另外一个对该键的强引用,该键值对才不会被垃圾回收器回收,以下用一个代码例子展示这个特性。
import java.util.Map;
import java.util.WeakHashMap;
public class WeakHashMapExample {
public static void main(String[] args) {
// 创建一个WeakHashMap
Map<Key, String> weakHashMap = new WeakHashMap<>();
// 创建两个键对象,它们没有强引用
Key key1 = new Key(1);
Key key2 = new Key(2);
// 添加键值对
weakHashMap.put(key1, "Value 1");
weakHashMap.put(key2, "Value 2");
// 输出键值对
System.out.println("Before garbage collection:");
for (Map.Entry<Key, String> entry : weakHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 手动清除对键对象的强引用
key1 = null;
key2 = null;
// 强制进行垃圾回收
System.gc();
// 输出键值对,此时键2对应的键值对可能被垃圾回收
System.out.println("\nAfter garbage collection:");
for (Map.Entry<Key, String> entry : weakHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
class Key {
private int id;
public Key(int id) {
this.id = id;
}
@Override
public String toString() {
return "Key-" + id;
}
}
Before garbage collection:
Key-2: Value 2
Key-1: Value 1
After garbage collection:
Process finished with exit code 0
可以看到,当我们将两个键的引用(key1、key2)指向空后,随着强制垃圾回收指令System.gc()的执行,WeakHashMap的遍历已经没有任何键值对了,这表明WeakHashMap对键的引用是弱引用,区别于普通HashMap的强引用。
IdentityHashMap
该集合类的名称中"Identity"的中文意思是身份,这种HashMap可以保存值相同的两个键,也就是说,只要两个键不是在同一个地址块中的同一个键,就会被认为是两个不同的键,即便他们的值完全相同,这同时意味着判断该集合中的两个键是否相同,应当使用==号判断地址的相同与否而不是equals方法。
import java.util.IdentityHashMap;
public class IdentityHashMapExample {
public static void main(String[] args) {
// 创建一个IdentityHashMap
IdentityHashMap<String, Integer> identityHashMap = new IdentityHashMap<>();
// 创建两个相同值但不同对象引用的字符串
String key1 = new String("Key");
String key2 = new String("Key");
// 添加键值对
identityHashMap.put(key1, 1);
identityHashMap.put(key2, 2);
// 遍历键值对
for (String key : identityHashMap.keySet()) {
System.out.println(key + ": " + identityHashMap.get(key));
}
}
}
Key: 2
Key: 1
Process finished with exit code 0
上面这个例子中,两个键值对的键都为值是"Key"的String对象,但被集合保存为两个键值对,此处展现了IdentityHashMap中的"身份"特点。
EnumSet和EnumHashMap
这两个集合类可以将Enum对象存如集合,后者中Enum对象作为键存入Map。
EnumSet没有公共构造器,要使用以下几种静态工厂方法构造这个集合:
static <E extends Enum<E>> | allOf(Class<E> elementType) 返回一个包含给定枚举类型所有值的枚举集合。 |
static <E extends Enum<E>> | noneOf(Class<E> elementType) 返回一个初始为空的指定枚举类型的集合。 |
static <E extends Enum<E>> | of(E e) 返回一个可变集,包括不为null的所有元素。 |
static <E extends Enum<E>> | of(E first, E... rest) 返回一个可变集,包括不为null的所有元素。 |
static <E extends Enum<E>> | range(E from, E to) 返回一个包含from到to之间所有元素闭区间的集合。 |
EnumHashMap的初始化构造方式如下所示,需要指定键所在的Enum类:
EnumMap<Weekday,Employee> map = new EnumMap<>(Weekday.class);
BitSet
java平台的BitSet类会存储一个位序列,由于将位包装在字节里,这要比使用Boolean对象的ArrayList集合要高效的多,还提供了位集与位集之间的与、或等运算。
BitSet默认创建时可以给出容量capacity,使用cardinality()方法可以返回当前处于"开"状态的元素个数;而length()方法将返回逻辑长度,由处于"开状态"的最高位决定;size()方法返回这个BitSet在内部数据结构中的当前可用位数,其值可能会略大于capacity。
下面介绍一下BitSet类的核心方法:
//如果第i位处于"开"状态,就返回true,否则返回false
bits.get(i);
//将第i位设置为"开"
bits.set(i);
//将第i位设置为"关"
bits.clear()
由以上内容我们可以对埃氏筛算法做出示例:
public class Main {
public static void main(String[] args) {
final int n = 2000000;
BitSet bits = new BitSet(n+1);
int i;
for (i = 2; i <= n; i++)
bits.set(i);
i = 2;
while (i * i <= n) {
if(bits.get(i)){
int k = i * i;
while(k <= n){
bits.clear(k);
k += i;
}
}
i++;
}
System.out.println(bits.cardinality());
}
}
Properties
属性映射(property map)是一个特殊的映射结构,包含以下3个特性:
- 键与值都是字符串。
- 很容易保存到文件并从文件加载。
- 有一个二级表存放默认值。
在早期java版本中,常用来在".properties"文件中记录和加载配置文件,它提供了一种简单的方式来加载、读取、修改和保存属性文件中的配置信息,在我的世界java版服务端的根目录中就能看到它的身影,用来轻松配置服务器的玩家数量、视距、端口号等信息。
Properties类继承了HashTable,你可以理解为这是一个线程安全的HashMap类,它是遗留类,为了保证线程安全我们也不会再使用HashTable,这里我们可以将Properties当做是一个HashMap的派生类去处理,但同时我们也并不建议这么做,最好是坚持使用setProperty和getProperty方法。
输出和读取
以下代码将在program.properties文件中生成下面的内容:
public class Main {
public static void main(String[] args) throws IOException {
Properties settings = new Properties();
settings.setProperty("width","600.0");
settings.setProperty("height","400.0");
FileWriter out = new FileWriter("program.properties", StandardCharsets.UTF_8);
settings.store(out, "Test");
}
}
#Test
#Sun Sep 24 11:54:56 CST 2023
width=600.0
height=400.0
要从文件中加载这个属性文件,可以使用如下调用:
FileReader in = new FileReader("program.properties", StandardCharsets.UTF_8);
settings.load(in);
映射默认值的设置
Priorities类有两种提供默认值的机制,最简单的方法是在使用getPriority方法时,增加一个默认值的参数,当查找的键不存在时就会自动使用这个默认值:
String filename = settings.getPriority("filename", "");
第二种方式是把所有默认值都放在一个二级映射中,然后构造主属性映射时提供这个二级映射作为构造方法的参数:
Properties defaultSettings = new Properties();
defaultSettings.setProperty("width","600.0");
defaultSettings.setProperty("height","400.0");
. . .
Properties settings1 = new Properties(defaultSettings);
获取系统映射信息
System.getPriority方法会生成一个Priorities对象来描述系统信息,并返回其对应的value属性,下面举一个user.home的例子,除此之外还有很多其他系统属性可以获取,包括java.version获取当前安装的java版本号等。
String userDir = System.getPriority("user.home");
Priorities对未来的展望
然而,随着时间的推移,Java开发社区逐渐趋向于使用更现代的配置管理和属性文件处理方式,如使用JSON、YAML等格式来存储配置信息,以及使用外部库(如Typesafe Config、Apache Commons Configuration等)来处理配置文件。这些方法通常提供更丰富的功能、更灵活的配置选项,并且更适合现代应用程序的需求。