java的特殊集合类

概述

本文将介绍一些不算常用的特殊集合类,包括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>>
EnumSet<E>
allOf(Class<E> elementType)

返回一个包含给定枚举类型所有值的枚举集合。

static <E extends Enum<E>>
EnumSet<E>
noneOf(Class<E> elementType)

返回一个初始为空的指定枚举类型的集合。

static <E extends Enum<E>>
EnumSet<E>
of(E e)

返回一个可变集,包括不为null的所有元素。

static <E extends Enum<E>>
EnumSet<E>
of(E first, E... rest)

返回一个可变集,包括不为null的所有元素。

static <E extends Enum<E>>
EnumSet<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等)来处理配置文件。这些方法通常提供更丰富的功能、更灵活的配置选项,并且更适合现代应用程序的需求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值