集合框架之Map接口

时间:2022-01-13 13:48:05

Map是将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。

Map 接口提供三种collection视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。

所有通用的映射实现类应该提供两个“标准的”构造方法:一个 void(无参数)构造方法,用于创建空映射;一个是带有单个 Map 类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。实际上,后一个构造方法允许用户复制任意映射,生成所需类的一个等价映射。尽管无法强制执行此建议(因为接口不能包含构造方法),但是 JDK 中所有通用的映射实现都遵从它。

Map接口的方法列表如下:

void clear()

从此映射中移除所有映射关系(可选操作)。

boolean containsKey(Object key)

如果此映射包含指定键的映射关系,则返回true。

boolean containsValue(Object value)

如果此映射将一个或多个键映射到指定值,则返回 true。

Set<Map.Entry<K,V>> entrySet()

返回此映射中包含的映射关系的 Set 视图。

boolean equals(Object o)

比较指定的对象与此映射是否相等。

V get(Object key)

返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。

int hashCode()

返回此映射的哈希码值。

boolean isEmpty()

如果此映射未包含键-值映射关系,则返回true。

Set<K> keySet()

返回此映射中包含的键的 Set 视图。

V put(K key, V value)

将指定的值与此映射中的指定键关联(可选操作)。

void putAll(Map<? extends K,? extends V>m)

从指定映射中将所有映射关系复制到此映射中(可选操作)。

V remove(Object key)

如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。

int size()

返回此映射中的键-值映射关系数。

Collection<V> values()

返回此映射中包含的值的Collection 视图。

Map有三个通用实现类:HashMapTreeMapLinkedHashMap,它们的行为及性能上的对比,可以参照Set接口中的HashSet,TreeSet,LinkedHashSet,十分类似

下面我们通过三个实例来看一下这三种实现类的区别:

             String[] message = {"if", "it", "is", "to","be", "it", "is", "up", "to","me", "to", "delegate"};
             Map<String,Integer> map = new HashMap<String,Integer>();
             for(String str : message){
                    Integer freq = map.get(str);
                    map.put(str, freq == null ? 1 : freq+1);
             }
             System.out.println("单词总数:"+map.size());
             System.out.println(map);

控制台输出:

单词总数:8

{to=3, is=2,it=2, if=1, me=1, delegate=1, up=1, be=1}

该实例使用HashMap作为容器,形成单词和词频的映射

假如我们想让Map中的元素按照字母顺序排序,把实现类换成TreeMap即可。

             String [] message = {"if", "it", "is", "to","be", "it", "is", "up", "to","me", "to", "delegate"};
             Map<String,Integer> map = new TreeMap<String,Integer>();
             for(String str : message){
                    Integer freq = map.get(str);
                    map.put(str, freq == null ? 1 : freq+1);
             }
             System.out.println("单词总数:"+map.size());
             System.out.println(map);

控制台输出:

单词总数:8

{be=1, delegate=1, if=1, is=2,it=2, me=1, to=3, up=1}

类似地,如果我们想让打印结果按照单词第一次出现的顺序排序,将实现类改成LinkedHashMap即可

             String [] message = {"if", "it", "is", "to","be", "it", "is", "up", "to","me", "to", "delegate"};
             Map<String,Integer> map = new TreeMap<String,Integer>();
             for(String str : message){
                    Integer freq = map.get(str);
                    map.put(str, freq == null ?1 : freq+1);
             }
             System.out.println("单词总数:"+map.size());
             System.out.println(map);

控制台输出:

单词总数:8

{if=1, it=2,is=2, to=3, be=1, up=1, me=1, delegate=1}

在上面的三个实例中,我们只改变了实现类的名称,其他代码一点都没做改动,就达到了想要的效果,这就是面向接口编程赋予程序的弹性。

Map提供了三种转换为Collection视图(可以将Map当做Collection来看)的方法:

keySet():Map中的键Set型集合

values():Map中的值Collection集合,不能为Set,值集合中可能会有重复值

entrySet():Map中的键值对Set型集合

Map的Collection视图提供了唯一的Map的迭代方法。Map并没有提供返回Iterator迭代器的方法,只能借助于Collection视图的迭代器来遍历Map

需要注意的是,entrySet()返回的Set里面的元素是Map.Entry<K,V>,该接口提供了一些方法来操作键值对,方法列表如下:

boolean equals(Object o)

比较指定对象与此项的相等性。

K getKey()

返回与此项对应的键。

V getValue()

返回与此项对应的值。

int hashCode()

返回此映射项的哈希码值。

V setValue(V value)

用指定的值替换与此项对应的值(可选操作)。

Map.entrySet 方法返回映射的collection 视图,其中的元素属于此类。获得映射项引用的唯一方法是通过此collection 视图的迭代器来实现。这些Map.Entry 对象仅在迭代期间有效;更确切地讲,如果在迭代器返回项之后修改了底层映射,则某些映射项的行为是不确定的,除了通过 setValue 在映射项上执行操作之外。

下面给出具体的实例:

             String [] message = {"if", "it", "is", "to","be", "it", "is", "up", "to","me", "to", "delegate"};
             Map<String,Integer> map = new HashMap<String,Integer>();
             for(String str : message){
                    Integer freq = map.get(str);
                    map.put(str, freq == null ? 1 : freq+1);
             }

             for(String key : map.keySet()){
                    System.out.println(key+"——》"+map.get(key));
             }

该实例通过遍历键集合打印出所有的键值对

还可以通过遍历entrySet集合实现上面的过程,下面的代码与上面的for-each循环是等价的

for(Map.Entry<String,Integer>entry : map.entrySet()){
                    System.out.println(entry.getKey()+"——》"+entry.getValue());
             }

HashMap

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap 的实例有两个参数影响其性能:初始容量加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

通常,默认加载因子 (0.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

扩展的构造方法(即除了Map实现类都有的一个无参的构造方法和一个以Map为参数的构造方法之外,自身独有的构造方法)

Hashtable(int initialCapacity)

用指定初始容量和默认的加载因子(0.75) 构造一个新的空哈希表。

Hashtable(int initialCapacity, float loadFactor)

用指定初始容量和指定加载因子构造一个新的空哈希表。 HashMap实现了Map接口的所有方法,没有自身特有的扩展方法

TreeMap

基于红黑树(Red-Black tree)同时继承了NavigableMap接口的实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

注意,如果要正确实现 Map 接口,则有序映射所保持的顺序(无论是否明确提供了比较器)都必须与 equals 一致。(关于与 equals 一致的精确定义,请参阅 Comparable 或 Comparator)。这是因为 Map 接口是按照 equals 操作定义的,但有序映射使用它的 compareTo(或 compare)方法对所有键进行比较,因此从有序映射的观点来看,此方法认为相等的两个键就是相等的。即使排序与 equals 不一致,有序映射的行为仍然是定义良好的,只不过没有遵守 Map 接口的常规协定。

TreeMap的扩展的构造方法:

TreeMap(Comparator<?super K> comparator)

构造一个新的、空的树映射,该映射根据给定比较器进行排序。

TreeMap(SortedMap<K,?extends V> m)

构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。

扩展的方法:

Comparator<?super K> comparator()

返回对此映射中的键进行排序的比较器;如果此映射使用键的自然顺序,则返回 null。

NavigableSet<K> descendingKeySet()

返回此映射中所包含键的逆序NavigableSet 视图。

NavigableMap<K,V> descendingMap()

返回此映射中所包含映射关系的逆序视图。

Map.Entry<K,V> firstEntry()

返回一个与此映射中的最小键关联的键-值映射关系;如果映射为空,则返回 null。

K firstKey()

返回此映射中当前第一个(最低)键。

Map.Entry<K,V> floorEntry(K key)

返回一个键-值映射关系,它与小于等于给定键的最大键关联;如果不存在这样的键,则返回 null。

K floorKey(K key)

返回小于等于给定键的最大键;如果不存在这样的键,则返回 null。

SortedMap<K,V> headMap(K toKey)

返回此映射的部分视图,其键值严格小于toKey。

NavigableMap<K,V> headMap(K toKey,boolean inclusive)

返回此映射的部分视图,其键小于(或等于,如果 inclusive 为 true)toKey。

Map.Entry<K,V> higherEntry(K key)

返回一个键-值映射关系,它与严格大于给定键的最小键关联;如果不存在这样的键,则返回 null。

K higherKey(K key)

返回严格大于给定键的最小键;如果不存在这样的键,则返回 null。

Map.Entry<K,V> lastEntry()

返回与此映射中的最大键关联的键-值映射关系;如果映射为空,则返回 null。

K lastKey()

返回映射中当前最后一个(最高)键。

Map.Entry<K,V> lowerEntry(K key)

返回一个键-值映射关系,它与严格小于给定键的最大键关联;如果不存在这样的键,则返回 null。

K lowerKey(K key)

返回严格小于给定键的最大键;如果不存在这样的键,则返回 null。

NavigableSet<K> navigableKeySet()

返回此映射中所包含键的NavigableSet 视图。

Map.Entry<K,V> pollFirstEntry()

移除并返回与此映射中的最小键关联的键-值映射关系;如果映射为空,则返回 null。

Map.Entry<K,V> pollLastEntry()

移除并返回与此映射中的最大键关联的键-值映射关系;如果映射为空,则返回 null。

NavigableMap<K,V> subMap(K fromKey,boolean fromInclusive, K toKey, boolean toInclusive)

返回此映射的部分视图,其键的范围从fromKey 到 toKey。

SortedMap<K,V> subMap(K fromKey, K toKey)

返回此映射的部分视图,其键值的范围从fromKey(包括)到 toKey(不包括)。

SortedMap<K,V> tailMap(K fromKey)

返回此映射的部分视图,其键大于等于fromKey。

NavigableMap<K,V> tailMap(K fromKey,boolean inclusive)

返回此映射的部分视图,其键大于(或等于,如果 inclusive 为 true)fromKey。

LinkedHashMap

Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。注意,如果在映射中重新插入键,则插入顺序不受影响。(如果在调用 m.put(k, v) 前 m.containsKey(k) 返回了 true,则调用时会将键 k 重新插入到映射 m 中。)

可以重写 removeEldestEntry(Map.Entry)方法来实施策略,以便在将新映射关系添加到映射时自动移除旧的映射关系。

此类提供所有可选的 Map 操作,并且允许 null 元素。与 HashMap 一样

扩展的构造方法:

LinkedHashMap(int initialCapacity)

构造一个带指定初始容量和默认加载因子(0.75) 的空插入顺序 LinkedHashMap 实例。

LinkedHashMap(int initialCapacity, float loadFactor)

构造一个带指定初始容量和加载因子的空插入顺序 LinkedHashMap 实例。

LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

构造一个带指定初始容量、加载因子和排序模式的空 LinkedHashMap 实例。

扩展的方法:

protected  boolean removeEldestEntry(Map.Entry<K,V> eldest)

如果此映射移除其最旧的条目,则返回true。

WeakHashMap

以弱键实现的基于哈希表的 Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。

WeakHashMap 中的每个键对象间接地存储为一个弱引用的指示对象。因此,不管是在映射内还是在映射之外,只有在垃圾回收器清除某个键的弱引用之后,该键才会自动移除。

WeakHashMap 中的值对象由普通的强引用保持。因此应该小心谨慎,确保值对象不会直接或间接地强引用其自身的键,因为这会阻止键的丢弃。注意,值对象可以通过 WeakHashMap 本身间接引用其对应的键;这就是说,某个值对象可能强引用某个其他的键对象,而与该键对象相关联的值对象转而强引用第一个值对象的键。处理此问题的一种方法是,在插入前将值自身包装在 WeakReferences 中,如:m.put(key, new WeakReference(value)),然后,分别用 get 进行解包。

下面用一个例子来展示WeakHashMap的特点:

        String a = new String("a");
        String b = new String("b");
        String c = new String("c");

        Map<String,String> weakmap = new WeakHashMap<>();
        weakmap.put(a, "A");
        weakmap.put(b, "B");
        weakmap.put(c, "C");

        System.out.println("初始值:"+weakmap); //现在map中含有三个键值对

        a = null;
        b = "foo";

        System.gc();  //进行垃圾回收
        System.out.println("垃圾回收后:"+weakmap);  //现在键为a和b的键值对已被移除

打印结果如下:

初始值:{a=A, c=C, b=B}

垃圾回收后:{c=C}

上例中,往WeakHashMap中放进了三个键值对,此时,堆中存在三个String对象,每个对象都有两个引用,一个是与变量a、b、c关联的强引用,另个一是与WeakHashMap的key关联的弱引用。

当给变量a、b重新赋值之后,则a、b与原先的两个String对象之间的强引用失效,两个String对象只剩下了与WeakHashMap的key之间的弱引用关系。

只有弱引用的对象会被垃圾回收器回收,当回收了该key所对应的实际对象后,WeakHashMap会自动删除该key所对应的键值对。

上例中,经过GC之后,WeakHashMap中只剩了一个键值对。

与HashMap比较之下,Entry不直接引用Key这个对象,而是将引用关系放到了父类WeakReference中,WeakHashMap将传入的key包装成了WeakReference,并传入了一个ReferenceQueue。

当GC之后,WeakHashMap对象里面get、put数据或者调用size方法的时候,WeakHashMap比HashMap多了一个 expungeStaleEntries()方法。

expungeStaleEntries方法 就是将ReferenceQueue列队中的WeakReference依依poll出来去和Entry[]数据做比较,如果发现相同的,则说明这个Entry所保存的对象已经被GC掉了,那么将Entry[]内的Entry对象剔除掉,这样就把被GC掉的 WeakReference对应的Entry从WeakHashMap中移除了。

WeakHashMap使用弱引用来作为Map的Key,利用虚拟机的垃圾回收机制能自动释放Map中没有被使用的条目。但是WeakHashMap释放条目是有条件的:首先条目的Key在系统中没有强引用指向;另外,条目的释放是在垃圾回收之后第一次访问这个WeakHashMap时完成的

而当我们想要获取一个Integer对象时,为了利用Integer类本身的缓存,减少堆中Integer对象的重复申请和释放,我们通常会采用Ingeter.valueOf(int)方法来获取Integer对象,而不是直接使用new Integer(int)。Integer类会将0~127的整数对象缓存在一个Map中,而这个Map中保存的是这些Integer对象的强引用,如果我们想要使用Integer作为WeakHashMap的Key,那就需要注意不能再使用Integer.valueOf(int)方法获取WeakHashMap中Key的对象,否则所有以0~127作为Key的条目不会被自动释放。

下面一段代码比较了三种方式获取到的Integer对象分别作为WeakHashMap的Key的区别,三种方式分别是:1.使用Integer.valueOf(int);2.使用newInteger(int);3.使用Java的自动装箱。

     public static void main(String[] args) {

        Map<Integer, String> wmap = new WeakHashMap<Integer, String>();
        for(int i = 0; i <= 160; i += 20){
            wmap.put(Integer.valueOf(i),"" + i);
        }

        System.out.println("Before GC1:");
        System.out.println(wmap);
        System.gc();
        System.out.println("After GC1:");
        System.out.println(wmap);

        wmap.clear();
        for(int i = 0; i <= 160; i += 20){
            wmap.put(new Integer(i),"" + i);
        }
        System.out.println("Before GC2:");
        System.out.println(wmap);
        System.gc();
        System.out.println("After GC2:");
        System.out.println(wmap);

        wmap.clear();
        for(int i = 0; i <= 160; i += 20){
            wmap.put(i, "" + i);
        }
        System.out.println("Before GC3:");
        System.out.println(wmap);
        System.gc();
        System.out.println("After GC3:");
        System.out.println(wmap);

    }

打印结果如下:

Before GC1:

{120=120, 60=60,160=160, 40=40, 140=140, 80=80, 20=20, 100=100, 0=0}

After GC1:

{120=120, 60=60,40=40, 80=80, 20=20, 100=100, 0=0}

Before GC2:

{120=120, 60=60,160=160, 40=40, 140=140, 80=80, 20=20, 100=100, 0=0}

After GC2:

{}

Before GC3:

{120=120, 60=60,160=160, 40=40, 140=140, 80=80, 20=20, 100=100, 0=0}

After GC3:

{120=120, 60=60,40=40, 80=80, 20=20, 100=100, 0=0}

可以看到第一种方式使用Integer.valueOf(int)得到的Key,垃圾回收之后只释放了140和160两个条目,128一下都仍然保留;使用newInteger(int)得到的Key垃圾回收之后全部被释放。值得注意的是,我们利用自动装箱得到的Integer对象和使用Integer.valueOf(int)结果一样,说明自动装箱也会利用Integer类的缓存。在使用Integer或类似的存在缓存的对象作为WeakHashMap的Key的时候,一定要注意对象缓存中对Key对象是否存在无法释放的强引用,否则WeakHashMap自动释放不使用条目的效果无法达到。

Hashtable

HashTable与HashMap主要有以下几方面的不同:

Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类。

在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。

当get()方法返回null值时,即可以表示HashMap中没有该键,也可以表示该键所对应的值为null。

因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。

而在Hashtable中,无论是key还是value都不能为null 。

这两个类最大的不同在于:

(1)Hashtable是线程安全的,它的方法是同步了的,可以直接用在多线程环境中。

(2)而HashMap则不是线程安全的。在多线程环境中,需要手动实现同步机制。

synchronizedMap

在Collections类中提供了一个方法返回一个同步版本的HashMap用于多线程的环境:

public static<K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
  return new SynchronizedMap<K,V>(m);
 } 

该方法返回的是一个SynchronizedMap的实例,SynchronizedMap类是定义在Collections中的一个静态内部类,它实现了Map接口,并对其中的每一个方法实现,通过synchronized 关键字进行了同步控制。

这个版本中的方法都进行了同步,但是这并不等于这个类就一定是线程安全的。

在某些时候会出现一些意想不到的结果。

如下面这段代码:

// shm是SynchronizedMap的一个实例
if(shm.containsKey('key')){
        shm.remove(key);
} 

这段代码用于从map中删除一个元素之前判断是否存在这个元素。

这里的containsKey和reomve方法都是同步的,但是整段代码却不是。

考虑这么一个使用场景:

线程A执行了containsKey方法返回true,准备执行remove操作;

这时另一个线程B开始执行,同样执行了containsKey方法返回true,并接着执行了remove操作;

然后线程A接着执行remove操作时发现此时已经没有这个元素了。

要保证这段代码按我们的意愿工作,一个办法就是对这段代码进行同步控制,但是这么做付出的代价太大。

在进行迭代时这个问题更改明显。Map集合共提供了三种方式来分别返回键、值、键值对的集合:

Set<K>keySet();

Collection<V>values();

Set<Map.Entry<K,V>>entrySet();

在这三个方法的基础上,我们一般通过如下方式访问Map的元素:

Iterator keys =map.keySet().iterator();
 while(keys.hasNext()){
        map.get(keys.next());
} 

在这里,有一个地方需要注意的是:得到的keySet和迭代器都是Map中元素的一个“视图”,而不是“副本” 。

问题也就出现在这里,当一个线程正在迭代Map中的元素时,另一个线程可能正在修改其中的元素。

此时,在迭代元素时就可能会抛出ConcurrentModificationException异常。

为了解决这个问题通常有两种方法,

(1)一是直接返回元素的副本,而不是视图。这个可以通过

集合类的 toArray() 方法实现,但是创建副本的方式效率比之前有所降低,

特别是在元素很多的情况下;

(2)另一种方法就是在迭代的时候锁住整个集合,这样的话效率就更低了。

更好的选择就是ConcurrentHashMap。

ConcurrentHashMap

java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。

ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的锁机制。

Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。

ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。

在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。

在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 。

iterator完成后再将头指针替换为新的数据 。

这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。

Properties

Properties类是Hashtable的子类,Hashtable实现了Map接口,但是它并没有实现Map的方法,它与上面的其他子类有本质的区别,由于在应用中广泛使用property 作为配置文件,在此一并列出并给出代码实例

Properties 类表示了一个持久的属性集。Properties可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串

一个属性列表可包含另一个属性列表作为它的“默认值”;如果未能在原有的属性列表中搜索到属性键,则搜索第二个属性列表。

因为 Properties 继承于 Hashtable,所以可对Properties 对象应用 put 和 putAll 方法。但不建议使用这两个方法,因为它们允许调用者插入其键或值不是 String 的项。相反,应该使用 setProperty 方法。如果在“不安全”的 Properties 对象(即包含非 String 的键或值)上调用 store 或 save 方法,则该调用将失败。类似地,如果在“不安全”的 Properties 对象(即包含非 String 的键)上调用 propertyNames 或 list 方法,则该调用将失败。

此类是线程安全的:多个线程可以共享单个Properties 对象而无需进行外部同步。

load(Reader) /store(Writer, String) 方法按下面所指定的、简单的面向行的格式在基于字符的流中加载和存储属性。除了输入/输出流使用 ISO 8859-1 字符编码外,load(InputStream) / store(OutputStream, String) 方法与load(Reader)/store(Writer, String) 对的工作方式完全相同。可以使用Unicode 转义来编写此编码中无法直接表示的字符;转义序列中只允许单个 'u' 字符。可使用 native2ascii 工具对属性文件和其他字符编码进行相互转换。

loadFromXML(InputStream)和 storeToXML(OutputStream, String, String) 方法按简单的 XML 格式加载和存储属性。默认使用 UTF-8 字符编码,但如果需要,可以指定某种特定的编码。XML 属性文档具有以下 DOCTYPE 声明:

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

注意,导入或导出属性时不访问系统 URI (http://java.sun.com/dtd/properties.dtd);该系统 URI 仅作为一个唯一标识 DTD的字符串:

  <?xml version="1.0"encoding="UTF-8"?>
    <!-- DTD for properties -->
    <!ELEMENT properties ( comment?, entry*) >
    <!ATTLIST properties version CDATA#FIXED "1.0">
    <!ELEMENT comment (#PCDATA) >
    <!ELEMENT entry (#PCDATA) >
    <!ATTLIST entry key CDATA #REQUIRED>

方法列表:

String getProperty(String key)

用指定的键在此属性列表中搜索属性。

String getProperty(String key, StringdefaultValue)

用指定的键在属性列表中搜索属性。

void list(PrintStream out)

将属性列表输出到指定的输出流。

void list(PrintWriter out)

将属性列表输出到指定的输出流。

void load(InputStream inStream)

从输入流中读取属性列表(键和元素对)。

void load(Reader reader)

按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。

void loadFromXML(InputStream in)

将指定输入流中由 XML 文档所表示的所有属性加载到此属性表中。

Enumeration<?> propertyNames()

返回属性列表中所有键的枚举,如果在主属性列表中未找到同名的键,则包括默认属性列表中不同的键。

Object setProperty(String key, String value)

调用 Hashtable 的方法 put。

void store(OutputStream out, String comments)

以适合使用load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。

void store(Writer writer, String comments)

以适合使用 load(Reader) 方法的格式,将此Properties 表中的属性列表(键和元素对)写入输出字符。

void storeToXML(OutputStream os, Stringcomment)

发出一个表示此表中包含的所有属性的 XML文档。

void storeToXML(OutputStream os, Stringcomment, String encoding)

使用指定的编码发出一个表示此表中包含的所有属性的 XML 文档。

Set<String> stringPropertyNames()

返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。

代码实例:

      private staticFile filePath = new File("d:\\test.properties");

      //读取所有的属性
      public static void readAllProps(){
             Properties props = newProperties();
             try{
                    InputStream in = new BufferedInputStream(new FileInputStream(filePath));
                    props.load(in);

                    Enumeration en =props.propertyNames();
                    System.out.println("property文件的所有属性如下:");
                    while(en.hasMoreElements()){
                           String key =(String)en.nextElement();
                           String value =props.getProperty(key);
                           System.out.println(key+"= "+value);
                    }
             }catch(Exception e){
                    e.printStackTrace();
             }
      }

      //写入property文件
      public static void writeProperty(Stringkey,String value,String comment){
             Properties props = new Properties();
             try{
                    InputStream in = new BufferedInputStream(new FileInputStream(filePath));
                    props.load(in);
                    OutputStream out = new FileOutputStream(filePath);
                    props.setProperty(key,value);
                    props.store(out,comment);
             }catch(Exception e){
                    e.printStackTrace();
             }
      }

      //根据键来取值
      public static String getPropertyByKey(String key){
             Properties props = new Properties();
             try{
                    InputStream in = new BufferedInputStream(new FileInputStream(filePath));
                    props.load(in);
                    String value =props.getProperty(key);
                    return value;
             }catch(Exception e){
                    e.printStackTrace();
                    return null;
             }
      }

      public static void main(String[] args){
             readAllProps();
             getPropertyByKey("name");
             writeProperty("address","Beijing,China", "My Address");
             readAllProps();
      }