java集合解析

来源:互联网 发布:链接买卖源码 编辑:程序博客网 时间:2024/05/21 11:21
1.java中的向量榆数组类似,但是能被扩大或者缩小。Java中向量与数组的区别是:
  1. java的数组可存储任何类型的数组元素。包括数值类和所有类类型。
  2. java的向量只能存储对象类的实例。
当插入项超过向量的容量时,向量能重定位和调整自己的容量,它能找到更大的家,并自动的将当前存储的所有对象都拷贝到新家中。缺省情况下,向量每重定位一次,其容量就扩大一倍。但是可以指定容量增量来作为向量构造函数的第二个参数。
 例如: Vector items = new Vector(3,10);
    另一方面看,若java必须经常为向量重新分配空间,则将降低效率,所以应该为向量设置适当的初始容量和容量增量。
2.表的方法put返回上次用该键值为参数存储的值。即如果返回一个非空值,则表明已经替换了以前的表项(entry)。
 
3. 应该将哈希表的大小设为期望元素个数的1.5倍,并为质数,这样可以避免键值过于密集。也可以在哈希表构造函数中指定装载因子(load factor).(缺省值为0.75,这样,当哈希表装满0.75时,java就会自动重新分配一个更大的表,新表的容量是原来的2倍)。这是个好策略, 因为哈希表快满时,其效率变得很低。
 
4. Array是java中用来“存储和随机访问一连串对象(其实是对象的reference)”的各种做法中,最有效率的一种。是个极简单的线性序列,其中 元素能被快速访问。不过效率带来的牺牲是:当你产生Array时,其容量固定而且无法动态改变。而ArrayList则无此缺点,只是这种容量上的弹性带 来的后果是:ArrayLi.st的效率明显比Array差。
 
5.与c++的vector不同,java中无论是array还是其他容器都会进行边界检查。C++ vector之所以不在每次访问时进行边界检查,是基于效率考虑;同样,java array及其他容器都会因为边界检查而带来额外的效率负担。
 
6. 容器类仅能持有reference(指对象)。但是对于array,我们既可以产生直接持有基本型别数值的array,也可以产生持有reference 的array。当然,如果操作对象是基本类型,而且需要在空间不足时自动增容,array便不适合。此时就需要使用外覆类的容器了。(5.0有改进吗?)
 
7.根据书上(《java编程思想》第2版p309.) :Array的相等测试是依据其内容来决定(通过Object.equals()),所以:
 String[] s1 = new String[5];
 Arrays.fill(s1,”hi”);
 String[] s2 = {“hi”, “hi”, “hi”, “hi”, “hi”};
 System.out.println(Arrays.equals(s1,s2));
      的结果为”true”。
补充一下~~:对于非String对象,Arrays.equals比较的是引用的值(即若想正确比较自己定义的类的内容,需要重新定义该方法)
8. List.fill()方法只能替换容器中已有元素,不能添加。
9jdk1.4以下的版本中(50可以使用泛型),Java的容器在将对象加入容器时就丢失了类型信息,它保存Object对象的引用,因此可以保存任何类型的对象。不过:
 1. 因为在你将对象的引用加入容器时就丢失了类型的信息,所以对于添入容器的对象没有类型限制,即使你刻意保持容器的类型,例如类型的容器。别人还是可以轻易将放入容器。
 2. 因为丢失了类型信息,容器只知道它保存的是Object类型的引用。在使用容器中的元素前必须要做类型转换操作
10注意无意识中造成的递归
由于Java标准容器(与其他的类一样)继承自Object,所以它们都有toString()方法。重载过的toString()生成可以代表容器自身的String,以及容器持有的对象。例如,ArrayListtoString()方法遍历ArrayList的所有元素,对每个元素都调用toString()。假设你要打印类的地址,见下例,你可能会简单地使用this关键字(C++程序员会特别倾向于这个方法):
//: InfiniteRecursion.java
// Accidental recursion.
// {RunByHand}
import java.util.*;
public class InfiniteRecursion {
public String toString() {
return " InfiniteRecursion address: " + this + "/n";
}
public static void main(String[] args) {
List v = new ArrayList();
for(int i = 0; i < 10; i++)
v.add(new InfiniteRecursion());
System.out.println(v);
}
如果直接创建一个InfiniteRecursion对象,然后打印它,你会收到一个无穷无尽的异常序列。如果将InfiniteRecursion对象放入ArrayList,然后打印ArrayList也会如此。问题在于String的自动类型转换,如果你这样写:
"InfiniteRecursion address: " + this
编译器见到String后跟着一个’+’号,而’+’后的对象却不是String,于是编译器尝试将this转变成String类型。此类型转换操作调用的是toString()方法,于是产生递归调用。
在此例中,如果你确实想打印对象的地址,应该调用ObjecttoString()方法,它专门做此工作。因此使用super.toString()取代this即可。
 
11.为了避免并发修改列表的异常发生,应该遵循以下简单规则:可以根据需要为容器附加许多迭代器,但是这些迭代器都只能读取列表。另外,再附加一个既能读取又能写入的迭代器。
然而,对于并发修改列表的探测,有一个奇怪的例外。链表只负责跟踪对列表的结构性修改,比如添加和移出结点,而set方法并不被视为结构性修改。所以,可以将多个迭代器附加给一个链表,所有的迭代器都调用set方法来修改现有结点的内容。
 
 
12. JavaAPI的链表不支持快速随机访问。尽管如此LinkedList类还是提供了一个get方法用来访问特定的元素:
      LinkerList<String> list = ;
      String obj = list.get(n);
    当然,这个方法不高效。如果你发现自己正在使用该方法,那么你可能在使用错误的数据结构来解决你的问题。
 
12-1. 如果你有一个整数索引n,那么List.listIterator(n)将返回一个迭代器,它指向索引为n的元素前面的位置。也就是说,此时调用next产生的元素与调用list.get(n)方法得到的元素是相同的,只是要获取迭代器的效率是比较低的。
 
12-2.如果链表只有很少几个元素,那么我们完全不必为get和set方法的开销而烦恼。但是,我们为什么而是用链表呢?唯一的理由是为了尽量减少在列表中插入和移出元素时所花的代价。如果需要创建的列表只有少数几个元素。完全可以使用ArrayList。
     
12-3. 尽量避免使用以整数索引来表示链表中的位置的所有方法。如果需要随机访问某个集合。那么请使用数组或者ArrayList,而不是链表。
 
13. Vector和ArrayList的选择(当需要创建一个动态数组时)。Vector类的所有方法都是同步的(Synchronized);相反,ArrayList的方法是不同步的。所以,如果只用单个线程来访问Vector对象这是更加常见的现象那么你的代码会在同步操作上浪费相当多的时间。同样,ArrayList类的方法不是同步的,因此在不需要同步时应选择使用ArrayList而不是Vector
 
 
14. 讲一个元素添加到树集中的速度比将它添加到散列表中的速度要慢,但是仍然比将它添加到数组或者链表中的恰当位置要快。如果树集包含n个元素,那么平均需要log2 n次比较,才能找到新元素的正确位置
 
15. 如果要将自己的对象插入到树集中,必须通过实现Comparable借口来定义一个排序的顺序。Object类种的compareTo方法没有提供任何默认的实现
 
15-1. 使用Comparable结构来定义排序是有明显的局限性的。对于一个给定类,该接口只能实现一次。如果想在一个集合中需要按部件编号队一组项进行排序, 而在另一个集合中却要按描述信息对项进行排序,此外,如果必须对一个类的对象进行排序,而这个类的创建者又不愿意劳神地实现Comparable接口时:
      这些情况下可以让树集使用不同的比较方法,将一个Comparator对象传递给树集的构造器,Comparator接口声明一个compare方法,它带有2个参数:
         public interface Comparator<T>
    {
        int compare(T a, T b)
    }
     
 
16. 对于是否应该总是使用树集来代替散列集,取决于想要收集的数据。因为使用数集时,添加元素并不需要花费太长的时间,各个元素就能够自动进行排序。但如果不 需要对数据进行排序,那么就没有理由在排序上花费不必要的开销。更为重要的事,对某些数据来说,要进行排序是非常困难的。
 
17. 映射表(Map)可以得到视图的3个方法:
    Set<K> keySet()
    Collection<K> values()
    Set<Map.Entry<K,V>> entrySet()
 
17-1. 从上面得到的迭代器(Set),如果调用迭代器的remove方法,实际上就是从映射表中移除某个键值以及与它相关联的值。但是。不能将元素添加到这个视图。
 
18.一些专用的集和映射表类:
    a. WeakHashMap(弱散列映射表)。当对键的唯一引用来自散列表元素项时,该数据结构与垃圾回收器相互合作,移除该键/值对。
    b. LinkedHashSet(链接散列表)和LinkedHashMap(链接散列映射表)。它们能够记住插入元素的顺序。这样就可以避免散列表中各个元素项从表面上看上去是随机排列的。
链接散列映射表将使用访问顺序而不是插入顺序来迭代映射表的各个元素项。访问顺序对实现高速缓存的“最近很少使用”原则非常有用。比如,在一些情况下,我们可以让迭代器进入该表,将它所枚举的最开头的几个元素移除掉,因为这些元素是最近最少使用的元素项。
c.     EnumSet(枚 举集)。EnumSet类是一个包含枚举类型元素的集得非常高效的实现。因为枚举类型只有有限个实例,所以EnumSet在内部是使用位序列来实现的。如 果某个值存在于集中,则相对应的位将被打开。EnumSet没有公共的构造器,我们可以使用静态工厂方法来构造枚举集。
d.     EnumMap(枚举表)。EnumMap是键类型为枚举类型的映射表。它可以简单而高效的使用一个值数组来实现。需要在构造器中指定键的类型。
e.     IdentityHashMap(表 示散列映射表)。在此类中,键的散列码不是由hashCode方法计算得来的,而是由System.identityHashCode方法计算得出。这是 Object.hashCode方法根据对象的内存地址来计算散列码时所使用的方法,另外,IdentityHashMap是用==,而不是equals 来对各个对象进行比较。IdentityHashMap类对实现对象遍历算法(比如对象的序列化)非常有用,在进行对象遍历时,它可以跟踪哪些对象已经被 遍历了。
 
     
19. Arrays类有个asList方法,它返回包装了一个普通java数组的List包装器。使用这个方法可以将一个数组传递给一个期望得到列表或者集合参数的方法。例如:
    Card[] cardDeck = new Card[52];
   
    List<Card> cardList = Arrays.asList(cardDeck);
    上面代码返回的对象不是一个ArrayList,而是一个视图对象,它可以使用其get和set方法来访问底层的数组。所有能改变数组大小的方法(比如迭代其相关的add和remove方法)都抛出一个UnsupportedOperationException异常。从jdk5.0开始,asList方法被声明为一个具有可变数量参数的方法。除了可以传递一个数组外,还可以将各个元素直接传递给该方法。
视图技术在集合框架中有许多非常有用的应用(比如19。其他参考书中P106。。《java核心技术》第7版中文版第2卷,或e文版第2卷chapter2 section3 )