`
Neil_yang
  • 浏览: 126495 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
社区版块
存档分类
最新评论

java对象的集合(下)

    博客分类:
  • JAVA
阅读更多

JAVA基础:
对象的集合(下)
(12)Map 的功能:Map用put(Object key, Object value)方法会往 Map 里面加一个值,并且把这个值同键(你查找时所用的对象)联系起来。给出键之后,get(Object key)就会返回与之相关联的值。你也可以用 containsKey( ) 和 containsValue( )测试 Map 是否包含有某个键或值。map中的KeySet( )方法返回了一个由Map 的键所组成的 Set。values( )也差不多,它返回的是由 Map 的值所组成的 Collection。(注意,键必须是唯一的,而值却可以有重复。)map它不是慢慢地一个个地找这个键,而是用了一种被称为 hash code 的特殊值来进行查找的。
[1]Map (接口):维持键-值的关联(即 pairs),这样就能用键来找值了。
[2]HashMap*:基于 hash 表的实现。(用它来代替Hashtable。)提供时间恒定的插入与查询。在构造函数中可以设置 hash 表的capacity 和 load factor。可以通过构造函数来调节其性能。
[3]LinkedHashMap(JDK 1.4):很像 HashMap,但为了提高速度,LinkedHashMap 对所有东西都作了 hash,而且遍历的时候(println( )会遍历整个 Map,所以你能看到这个过程)还会按插入顺序返回 pair。但是用 Iterator 进行遍历的时候,它会按插入顺序或最先使用的顺序(least-recently-used (LRU)order)进行访问。除了用 Iterator 外,其他情况下,只是比 HashMap 稍慢一点。用 Iterator 的情况下,由于是使用链表来保存内部顺序,因此速度会更快。
[4]TreeMap    基于红黑树数据结构的实现。当你查看键或 pair 时,会发现它们是按顺序 (根据Comparable 或Comparator,我们过一会讲)排列的。TreeMap 的特点是,你所得到的是一个有序的 Map。TreeMap是 Map 中唯一有 subMap( )方法的实现。这个方法能让你获取这个树中的一部分。
[5]WeakHashMap:一个 weak key 的 Map,是为某些特殊问题而设计的。它能让 Map 释放其所持有的对象。如果某个对象除了在 Map 当中充当键之外,在其它地方都没有其 reference的话,那它将被当作垃圾回收。
[6]IdentityHashMap (JDK 1.4):一个用==,而不是 equals( )来比较键的 hash map。不是为我们平常使用而设计的,是用来解决特殊问题的。
     SortedMap:SortedMap(只有 TreeMap 这一个实现)的键肯定是有序的,因此这个接口里面就有一些附加功能的方法了。
[1]Comparator comparator( ):返回 Map 所使用的comparator,如果是用 Object 内置的方法的话,则返回null。
[2]Object firstKey( ): 返回第一个键。
[3]Object lastKey( ): 返回最后一个键。
[4]SortedMap subMap(fromKey, toKey): 返回这个 Map 的一个子集,其键从 fromKey 开始到 toKey 为止,包括前者,不包括后者。
[5]SortedMap headMap(toKey): 返回这个 Map 的一个子集,其键均小于 toKey。
[6]SortedMap tailMap(fromKey): 返回这个 Map 的一个子集,其键均大于等于 fromKey。

(13)散列算法与 Hash 数:
public class Groundhog {
protected int number;
public Groundhog(int n) { number = n; }
public String toString( ) {
    return "Groundhog #" + number;
}
} ///:~

public class Prediction {
private boolean shadow = Math.random( ) > 0.5;
public String toString( ) {
    if(shadow)
      return "Six more weeks of Winter!";
    else
      return "Early Spring!";
}
} ///:~

import com.bruceeckel.simpletest.*;
import java.util.*;
import java.lang.reflect.*;
public class SpringDetector {
private static Test monitor = new Test( );
// Uses a Groundhog or class derived from Groundhog:
public static void
detectSpring(Class groundHogClass) throws Exception {
    Constructor ghog =groundHogClass.getConstructor(new Class[] {int.class});
    Map map = new HashMap( );
    for(int i = 0; i < 10; i++)
       map.put(ghog.newInstance(new Object[]{ new Integer(i) }), new Prediction( ));
    System.out.println("map = " + map + "\n");
    Groundhog gh = (Groundhog)
       ghog.newInstance(new Object[]{ newInteger(3) });
    System.out.println("Looking up prediction for "+ gh);
    if(map.containsKey(gh))
       System.out.println((Prediction)map.get(gh));
    else
       System.out.println("Key not found: " + gh);
}
public static void main(String[] args) throws Exception {
    detectSpring(Groundhog.class);
   }
} ///:~
毛病就出在,Groundhog 是继承Object 根类的(如果你不指明它的父类,它就自动继承根类,而最终所有的类都继承 Object)。这样,对象的 hash 数是由 Object 的hashCode( )生成的,缺省情况下这就是对象的内存地址。这样Groundhog(3)的第一个实例的 hash 数会与其第二个实例的 hash数不相符。而后者正是我们用来查找的键。或许你会认为,你所要做的就是覆写一个合适的 hashCode( )。但是除非你再作另一件事,把同属 Object 的 equals( )方法也覆写了,否则还是不行。HashMap 要用equals( )来判断查询用的键是不是与表里面的其它键相等。一个合适的 equals( )必须做到以下五点:
[1]反身性:对任何 x,x.equals(x)必须是 true 的。
[2]对称性:对任何 x 和 y,如果 y.equals(x)是 true 的,那么x.equals(y)也必须是 true 的。
[3]传递性:对任何 x,y 和 z,如果 x.equals(y)是 true 的,且y.equals(z)也是 true 的,那么 x.equals(z)也必须是 true 的。
[4]一致性:对任何 x 和 y,如果对象里面用来判断相等性的信息没有修改过,那么无论调用多少次 x.equals(y),它都必须一致地返回true 或 false。
[5]对于任何非空的 x,x.equals(null)必须返回 false。
    默认的 Object.equals( )只是简单地比较两个对象的地址,所以一个Groundhog(3)会不等于另一个 Groundhog(3)。因此,如果你想把你自己写的类当 HashMap 的键来用的话,你就必须把hashCode( )和 equals( )都给覆写了,就像下面这个程序:
public class Groundhog2 extends Groundhog {
public Groundhog2(int n) { super(n); }
public int hashCode( ) { return number; }
public boolean equals(Object o) {
    return (o instanceof Groundhog2)
      && (number == ((Groundhog2)o).number);
}
} ///:~
//: c11:SpringDetector2.java
// A working key.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class SpringDetector2 {
public static void main(String[] args) throws
Exception {
    SpringDetector.detectSpring(Groundhog2.class);
}
} ///:~

(14)理解 hashCode( ):如果你不覆写键的 hashCode( )和 equals( )的话,散列数据结构(HashSet,HashMap,LinkedHashSet,或 LinkedHashMap)就没法正确地处理键。首先想想我们为什么要用散列:要通过一个对象来查找另一个对象。不过TreeSet 或 TreeMap 也能做这件事。当然,你也可以实现一个你自己的 Map。这么做的前提是,先得定义一个会返回 Map.Entry 对象集合的 Map.entrySet( )方法。我们为 Map.Entry 定义一个新的 MPair类。要想把它放到 TreeSet 里面,就得定义它的 equals( ),并且实现Comparable:
/: c11:MPair.java
// A new type of Map.Entry.
import java.util.*;
public class MPair implements Map.Entry, Comparable
{
   private Object key, value;
   public MPair(Object k, Object v) {
     key = k;
     value = v;
   }
   public Object getKey( ) { return key; }

public Object getValue( ) { return value; }
   public Object setValue(Object v) {
     Object result = value;
     value = v;
     return result;
   }
   public boolean equals(Object o) {
     return key.equals(((MPair)o).key);
   }
   public int compareTo(Object rv) {
     return ((Comparable)key).compareTo(((MPair)rv).key);
   }
} ///:~

(15)覆写 hashCode( ):创建 hashCode( )最重要的一点就是,对同一个对象,无论在什么时候调用 hashCode( ),它都应该返回同一个值。你大概也不会根据对象的“唯一性信息(unique objectinformation)”来生成 hashCode( )——特别是 this,这会是一个很糟糕的 hashCode( )。因为一般情况下,你会把一个『键-值 pair』直接 put( )进 HashMap,而用了这种 hashCode( )之后,你就不能这么做了。SpringDetector.java 讲的就是这个问题。由于缺省的hashCode( )用的就是对象的地址,因此你应该在 hashCode( )里面用一些能标识对象的有意义的信息。
import com.bruceeckel.simpletest.*;
public class StringHashCode {
public static void main(String[] args) {
    System.out.println("Hello".hashCode( ));
    System.out.println("Hello".hashCode( ));
   }
} ///:~
很明显,String 是根据其内容计算 hashCode( )的。
java中比较好的算 hashCode( )的方法:
[1]Boolean:c = (f ? 0 : 1)
[2]Byte,char,short或int:c = (int)f
[3]Long:c = (int)(f ^ (f >>>32))
[4]Float:c = Float.floatToIntBits(f);
[5]Double:long l =Double.doubleToLongBits(f);c = (int)(l ^ (l >>> 32))
[6]它的equals( )调用equals( )的了其中的数据字段的Object:c = f.hashCode( )
[7]数组:对于其中每个元素都使用上述规则

(16)持有 reference:java.lang.ref 类库里有一套能增进垃圾回收器工作的灵活性的类。一旦碰到了“对象大到要耗光内存”的时候,这些类就会显得格外有用。有三个类是继承抽象类 Reference 的:SoftReference,WeakReference 和PhantomReference。如果待处理的对象只能通过这些 Reference 进行访问的话,那么这些 Reference 对象就会向垃圾回收器提供一些不同级别的暗示。如果对象还能访问的到,那么在程序的某个地方应该还能找到这个对象。或许栈里还有一个普通的 reference 直接指着这个对象,或许在你“引用(reference)”的对象里面还有一个指向那个要找的对象的reference;这中间可能会有很多层。但是,只 要对象还能访问的到,也就是说程序还要用,垃圾回收器就不能回收。如果对象已经访问不到了,程序也就无从使用了,因此回收就应该是安全的了。
你可以用 Reference 对象来持有那个你想继续持有的那个对象的reference;你要能访问那个对象,但是有允许垃圾回收器回收它。于是,你就有了一种“能继续使用那个对 象,但是当内存即将耗尽的时候,又能释放那个对象”的方法了。要达到这个目的,你可以把 Reference 对象当作你和『普通的reference』之间的中介,此外那个对象上面还不能附有其它『普通的reference』(指没有用 Reference 类包覆的 reference)。如果垃圾回收器发现你还可以通过『普通的 reference』访问某个对象,那它就不会释放那个对象了。
   从 SoftReference 到 WeakReference,到PhantomRefernce,它们的功能依次减弱,而“访问级别(level of reachability)”又各自不同。SoftReference 是为内存敏感的缓存而实现的。WeakReference 是为了“规范化映射(canonical mappings)”而实现的,也就是为了节省存储空间,对象的实例可以被同时用于程序的多个地方,这样你就不用重新申请它的键(或值)了。 PhantomReference 则用于调度“回收前的清理工作(premortem cleanup action)”,这种清理可以比Java 的 finalization 的机制更为灵活。对于 SoftReference 和 WeakReference,你可以选择是不是把它们放进ReferenceQueue(一个用于“回收前的清理工作”的工具),但是对于 PhantomReference,你只能把它放进ReferenceQueue。下面就是一个简单的演示:
import java.lang.ref.*;
class VeryBig {
private static final int SZ = 10000;
private double[] d = new double[SZ];
private String ident;
public VeryBig(String id) { ident = id; }
public String toString( ) { return ident; }
public void finalize( ) {
    System.out.println("Finalizing " + ident);
}
}
public class References {
private static ReferenceQueue rq = new
ReferenceQueue( );
public static void checkQueue( ) {
    Object inq = rq.poll( );
    if(inq != null)
      System.out.println("In queue: " +
        (VeryBig)((Reference)inq).get( ));
}
public static void main(String[] args) {
    int size = 10;
    // Or, choose size via the command line:
    if(args.length > 0)
      size = Integer.parseInt(args[0]);
    SoftReference[] sa = new SoftReference[size];
    for(int i = 0; i < sa.length; i++) {
      sa[i] = new SoftReference(
        new VeryBig("Soft " + i), rq);
      System.out.println("Just created: " +
        (VeryBig)sa[i].get( ));
      checkQueue( );
    }
    WeakReference[] wa = new WeakReference[size];
    for(int i = 0; i < wa.length; i++) {
      wa[i] = new WeakReference(
        new VeryBig("Weak " + i), rq);
      System.out.println("Just created: " +
        (VeryBig)wa[i].get( ));
      checkQueue( );
    }
    SoftReference s =
      new SoftReference(new VeryBig("Soft"));
    WeakReference w =
      new WeakReference(new VeryBig("Weak"));
    System.gc( );
    PhantomReference[] pa = new
PhantomReference[size];
    for(int i = 0; i < pa.length; i++) {
      pa[i] = new PhantomReference(
        new VeryBig("Phantom " + i), rq);
      System.out.println("Just created: " +
        (VeryBig)pa[i].get( ));
      checkQueue( );
    }
}
} ///:~
运行这个程序的时候(你应该将用“more”把输出重定向到一个管道里,这样就能看到分页的输出了),你会发现,尽管你还能通过 Reference对象进行访问(要想获取真实对象的 reference,你得用 get( )),但对象还是被回收了。

 

你还会看到ReferenceQueue 总是会返回保存 null对象的 Reference 对象。要利用它,你可以继承某个你感兴趣的Reference 类,并且给新的 Reference 类型加上一些有用的方法。

(17)如何挑选 List(对10000个数据进行测试):
Type       Get Iteration Insert Remove
array      172 516       na     na
ArrayList 281 1375      328    30484
LinkedList 5828 1047      109    16
Vector     422 1890      360    30781
   ArrayList 的随机访问(get( ))要比 LinkedList 快。(但奇怪的是 LinkedList 的顺序访问居然会比 ArrayList 的快,真是有点不可思议。)另一方面,LinkedList 的插入和删除,特别是删除,要比ArrayList 的快得多的多。通常情况下,Vector 的速度比不上ArrayList 的,所以你就不要再用了;它之所以还呆在类库里面,只是为了要对遗留下来的老代码提供支持(这里还能测试 Vector,也只是因为它在 Java 2 里摇身一变为 List 了)。也许最佳的做法就是,先选用ArrayList,当发现“在列表的中间进行插入和删除的操作太多所引发的”性能问题时,把它改成 LinkedList。当然,处理固定数量的元素时,还是用数组。

(18)如何挑选 Set(对50000个数据进行测试):
Type          Test   Add   Contains    Iteration
              size
                 10     25.0    23.4         39.1
TreeSet        100   17.2   27.5       45.9
                 1000   26.0    30.2         9.0
                 10     18.7    17.2         64.1
HashSet        100   17.2   19.1        65.2
                 1000   8.8     16.6         12.8
                 10     20.3    18.7         64.1
LinkedHashSet 100   18.6   19.5       49.2
                 1000   10.0    16.3         10.0
总的说来,HashSet 的各项性能都比 TreeSet 的好 (尤其是在“加入”和“查询”这两个最重要的方面)。而 TreeSet 的意义在于,它会按顺序保存元素,因此只有在需要有序的 Set 时,你才应该用它。注意 LinkedHashSet 的插入比 HashSet 的稍慢一些。这是因为它要承担维护链表和 hash 容器的双重代价。但是由于链接表的缘故,LinkedHashSet 的遍历比较快。


(19)如何挑选 Maps(对50000个数据进行测试):
      Type       Test     Put     Get     Iteration
                 size
                    10        26.6     20.3      43.7
TreeMap          100      34.1    27.2     45.8
                    1000      27.8     29.3       8.8
                    10        21.9     18.8       60.9
HashMap           100      21.9   18.6      63.3
                    1000      11.5     18.8        12.3
                    10        23.4     18.8        59.4
LinkedHashMap     100     24.2     19.5      47.8
                     1000     12.3     19.0         9.2
                     10       20.3      25.0        71.9
IdentityHashMap    100    19.7     25.9       56.7
                     1000     13.1      24.3        10.9
 

p;         10       26.6      18.8        76.5
WeakHashMap        100    26.1     21.6       64.4
                      1000    14.7      19.2         12.4
                      10      18.8      18.7         65.7
Hashtable          100     19.4    20.9        55.3
                     1000      13.1     19.9         10.8
    Hashtable 的性能同 HashMap 的不相上下。(可能你也注意到了,一般情况下 HashMap 会稍快些;HashMap 是用来代替 Hashtable 的。)TreeMap 通常要比 HashMap 慢,那么为什么还要它呢?答案是,它是用来创建有序列表的。树总是有序的,所以根本用不着为它去做排序。往 TreeMap 里面填完数据之后,你就能用keySet( )获取包含这个 Map 的键的 Set 了,接下来用 toArray()把这个 Set 转换成数组。然后就能用 static 的Arrays.binarySearch( )方法(下面再讲)在有序数组里面进行快速查找对象了。当然,这一切是在无法使用 HashMap 的情况下做的,因为HashMap 就是为快速查找而设计的。此外,你还能轻而易举地用TreeMap 创建一个 HashMap。结论是,选择 Map 的时候,首选应该是 HashMap,只有在要用恒定有序的Map 的情况下,你才应该选用TreeMap。LinkedHashMap 比 HashMap 稍慢一些,这是因为它除了要保存hash 数据结构之外,它还要保存链表。IdentityHashMap 和上面没法作比较,因为它是用 == 而不是 equals( )来比较对象的相等性的。

(20)实用工具:Collections 类还有很多很实用的工具:
max(Collection)         用自然对象内置的算法进行比较,
min(Collection)           返回 Collection 中最大和最小的
                              元素。
              
max(Collection,               用 Comparator 进行比较,返回
Comparator)                   最大或最小的元素。
min(Collection,
Comparator)

indexOfSubList(List           获取 target 第一次出现在
source, List target)          source 中的位置。

lastIndexOfSubList(List       返回 target 最后一次出现在
source, List target)          source 中的位置。

replaceAll(List list,         将所有的 oldVal 替换成
Object oldVal, Object         newVal.
newVal)

reverse( )                    颠倒 List 的顺序。

rotate(List list, int         把所有的元素向后移 distance 位,
distance)                     将最后面的元素接到最前面。

copy(List dest, List src)     将 src 的元素拷贝到 dest。

swap(List list, int i, int j) 互换 list 的 i 和 j 位置上的元素。
                                   可能会比你写代码要快。

fill(List list, Object o)     把 list 里面的全部元素全都替换成o。

nCopies(int n, Object o)      返回一个有 n 个元素的不可变的
                              List,而且这个 List 中的所有元素
                                   全都指向 o。
                                 
enumeration(Collection)       返回一个老式的 Enumeration。

list(Enumeration e)           用这个 Enumeration 生成一个
                              ArrayList,并且返回这个
                              ArrayList。是用来处理遗留下来
                                  的老代码的。

(21)把 Collection 和 Map 设成不可修改的:
Collection c = new ArrayList( );
c = Collections.unmodifiableCollection(c);
List a = new ArrayList( );
a = Collections.unmodifiableList(a);
Set s = new HashSet( );
s = Collections.unmodifiableSet(s);
Map m = new HashMap( );
m = Collections.unmodifiableMap(m);

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics