Java编程思想笔记——容器深入研究3
对Set的选择
public class SetPerformance {
static List<Test<Set<Integer>>> tests =
new ArrayList<Test<Set<Integer>>>();
static {
tests.add(new Test<Set<Integer>>("add") {
@Override
int test(Set<Integer> set, TestParam tp) {
int loops = tp.loops;
int size = tp.size;
for(int i = 0; i < loops; i++) {
set.clear();
for(int j = 0; j < size; j++) {
set.add(j);
}
}
return loops * size;
}
});
tests.add(new Test<Set<Integer>>("contains") {
@Override
int test(Set<Integer> set, TestParam tp) {
int loops = tp.loops;
int span = tp.size * 2;
for(int i = 0; i < loops; i++) {
for(int j = 0; j < span; j++) {
set.contains(j);
}
}
return loops * span;
}
});
tests.add(new Test<Set<Integer>>("iterate") {
@Override
int test(Set<Integer> set, TestParam tp) {
int loops = tp.loops * 10;
for(int i = 0; i < loops; i++) {
Iterator<Integer> it = set.iterator();
while(it.hasNext()) {
}
it.next();
}
return loops * set.size();
}
});
}
public static void main(String[] args) {
if(args.length > 0) {
Tester.defaultParams = TestParam.array(args);
}
Tester.fieldWidth = 10;
Tester.run(new TreeSet<Integer>(), tests);
Tester.run(new HashSet<Integer>(), tests);
Tester.run(new LinkedHashSet<Integer>(), tests);
}
} /*
------------- TreeSet -------------
size add contains iterate
10 746 173 89
100 501 264 68
1000 714 410 69
10000 1975 552 69
------------- HashSet -------------
size add contains iterate
10 308 91 94
100 178 75 73
1000 216 110 72
10000 711 215 100
---------- LinkedHashSet ----------
size add contains iterate
10 350 65 83
100 270 74 55
1000 303 111 54
10000 1615 256 58
*/
HashSet的性能基本上总是比TreeSet好,特别是在添加和查询元素时。TreeSet存在的唯一原因是它可以维持元素的排序状态;所以,只有当需要一个排好序的Set时,才应该使用TreeSet。因为其内部结构支持排序,并且因为迭代是更有可能执行的操作,所以,用TreeSet迭代通常比用HashSet更快。
对于插入操作,LinkedHashSet比HashSet的代价更高,这是由维护链表所带来额外开销造成的。
对Map的选择
public class MapPerformance {
static List<Test<Map<Integer,Integer>>> tests =
new ArrayList<Test<Map<Integer,Integer>>>();
static {
tests.add(new Test<Map<Integer,Integer>>("put") {
@Override
int test(Map<Integer,Integer> map, TestParam tp) {
int loops = tp.loops;
int size = tp.size;
for(int i = 0; i < loops; i++) {
map.clear();
for(int j = 0; j < size; j++) {
map.put(j, j);
}
}
return loops * size;
}
});
tests.add(new Test<Map<Integer,Integer>>("get") {
@Override
int test(Map<Integer,Integer> map, TestParam tp) {
int loops = tp.loops;
int span = tp.size * 2;
for(int i = 0; i < loops; i++) {
for(int j = 0; j < span; j++) {
map.get(j);
}
}
return loops * span;
}
});
tests.add(new Test<Map<Integer,Integer>>("iterate") {
@Override
int test(Map<Integer,Integer> map, TestParam tp) {
int loops = tp.loops * 10;
for(int i = 0; i < loops; i ++) {
Iterator it = map.entrySet().iterator();
while(it.hasNext()) {
it.next();
}
}
return loops * map.size();
}
});
}
public static void main(String[] args) {
if(args.length > 0) {
Tester.defaultParams = TestParam.array(args);
}
Tester.run(new TreeMap<Integer,Integer>(), tests);
Tester.run(new HashMap<Integer,Integer>(), tests);
Tester.run(new LinkedHashMap<Integer,Integer>(),tests);
Tester.run(
new IdentityHashMap<Integer,Integer>(), tests);
Tester.run(new WeakHashMap<Integer,Integer>(), tests);
Tester.run(new Hashtable<Integer,Integer>(), tests);
}
} /*
---------- TreeMap ----------
size put get iterate
10 748 168 100
100 506 264 76
1000 771 450 78
10000 2962 561 83
---------- HashMap ----------
size put get iterate
10 281 76 93
100 179 70 73
1000 267 102 72
10000 1305 265 97
------- LinkedHashMap -------
size put get iterate
10 354 100 72
100 273 89 50
1000 385 222 56
10000 2787 341 56
------ IdentityHashMap ------
size put get iterate
10 290 144 101
100 204 287 132
1000 508 336 77
10000 767 266 56
-------- WeakHashMap --------
size put get iterate
10 484 146 151
100 292 126 117
1000 411 136 152
10000 2165 138 555
--------- Hashtable ---------
size put get iterate
10 264 113 113
100 181 105 76
1000 260 201 80
10000 1245 134 77
*/
除了IdentityHashMap,所有Map实现的插入操作都会随着Map的尺寸的变大而明显变慢。但是,查找的代价通常比插入小得多。
Hashtable的性能大体上与HashMap相当,因为HashMap是用来替换Hashtable的,因此它们使用了相同的底层存储和查找机制。
TreeMap通常比HashMap要慢,并且不必进行特殊排序。一旦填充了一个TreeMap,就可以调用keySet()方法来获取键的Set视图,然后调用toArray()来产生由这些键构成的数组。之后可以使用Arrays.binarySearch()在排序数组中查找对象。当然,这只有HashMap的行为不可接受的情况下才有意义。因为hashMap本身就被设计为可以快速查找键。
LinkedHashMap在插入时比HashMap慢一点,因为它维护散列数据结构的同时还要维护链表(以保持插入顺序)。正是因为这给列表,使得其迭代速度更快。
IdentityHashMap则具有完全不同的性能,因为它使用==而不是equals来比较元素。
HashMap的性能因子
容量:表中的桶位数
初始容量:表在创建时所拥有的桶位数,HashMap和HashSet都具有允许指定初始化容量的构造器
尺寸:表中当前存储的项数
负载因子:尺寸/容量。空表的负载因子是0,而半满表的负载因子是0.5。以此类推。负载轻的表产生冲突的可能性小,因此对于插入和查找都是最理想的。HashMap和HashSet都具有允许指定负载因子的构造器,表示当负载情况达到该负载因子的水平时,容器将自动增加其容量,实现方式是是容量大致加倍,并重新将现有对象分布到新的桶位集中(成为再散列)。
HashMap使用默认的负载因子0.75,这个因子在时间和空间代价之间到达了平衡。更高的负载因子可以降低表所需的空间,但是会增加查找代价。因为查找是我们在大多数时间里所做的操作。
如果知道将要在HashMap中存储多少项,那么创建一个具有恰当大小的初始容量将可以避免自动再散列的开销。
实用方法
Java中有大量用于容器的卓越使用方法,它们被表示为java.util.Collections类内部的静态方法。如addAll(),reverseOrder()和binarySeacrch()。下面是另一部分:
注意,min()和max()只作用于Collection对象,而不能作用于List,所以无须担心Collection是否应该被排序。
public class Utilities {
static List<String> list = Arrays.asList(
"one Two three Four five six one".split(" "));
public static void main(String[] args) {
print(list);
print("'list' disjoint (Four)?: " +
Collections.disjoint(list,
Collections.singletonList("Four")));
print("max: " + Collections.max(list));
print("min: " + Collections.min(list));
print("max w/ comparator: " + Collections.max(list,
String.CASE_INSENSITIVE_ORDER));
print("min w/ comparator: " + Collections.min(list,
String.CASE_INSENSITIVE_ORDER));
List<String> sublist =
Arrays.asList("Four five six".split(" "));
print("indexOfSubList: " +
Collections.indexOfSubList(list, sublist));
print("lastIndexOfSubList: " +
Collections.lastIndexOfSubList(list, sublist));
Collections.replaceAll(list, "one", "Yo");
print("replaceAll: " + list);
Collections.reverse(list);
print("reverse: " + list);
Collections.rotate(list, 3);
print("rotate: " + list);
List<String> source =
Arrays.asList("in the matrix".split(" "));
Collections.copy(list, source);
print("copy: " + list);
Collections.swap(list, 0, list.size() - 1);
print("swap: " + list);
Collections.shuffle(list, new Random(47));
print("shuffled: " + list);
Collections.fill(list, "pop");
print("fill: " + list);
print("frequency of 'pop': " +
Collections.frequency(list, "pop"));
List<String> dups = Collections.nCopies(3, "snap");
print("dups: " + dups);
print("'list' disjoint 'dups'?: " +
Collections.disjoint(list, dups));
Enumeration<String> e = Collections.enumeration(dups);
Vector<String> v = new Vector<String>();
while (e.hasMoreElements()) {
v.addElement(e.nextElement());
}
ArrayList<String> arrayList =
Collections.list(v.elements());
print("arrayList: " + arrayList);
}
} /*
[one, Two, three, Four, five, six, one]
'list' disjoint (Four)?: false
max: three
min: Four
max w/ comparator: Two
min w/ comparator: five
indexOfSubList: 3
lastIndexOfSubList: 3
replaceAll: [Yo, Two, three, Four, five, six, Yo]
reverse: [Yo, six, five, Four, three, Two, Yo]
rotate: [three, Two, Yo, Yo, six, five, Four]
copy: [in, the, matrix, Yo, six, five, Four]
swap: [Four, the, matrix, Yo, six, five, in]
shuffled: [six, matrix, the, Four, Yo, five, in]
fill: [pop, pop, pop, pop, pop, pop, pop]
frequency of 'pop': 7
dups: [snap, snap, snap]
'list' disjoint 'dups'?: true
arrayList: [snap, snap, snap]
*/
由于大小写的缘故而造成的使用String.CASE_INSENSITIVE_ORDER Comparator时min()和max()的差异。
List的排序和查询
List排序和查询使用的方法与对象数组所使用的相应方法有相同的名字与语法,只是用Collections的static方法代替Arrays的方法:
public class ListSortSearch {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Utilities.list);
list.addAll(Utilities.list);
print(list);
Collections.shuffle(list, new Random(47));
print("Shuffled: " + list);
ListIterator<String> it = list.listIterator(10);
while (it.hasNext()) {
it.next();
it.remove();
}
print("Trimmed: " + list);
Collections.sort(list);
print("Sorted: " + list);
String key = list.get(7);
int index = Collections.binarySearch(list, key);
print("Location of " + key + " is " + index +
", list.get(" + index + ") = " + list.get(index));
Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
print("Case-insensitive sorted: " + list);
key = list.get(7);
index = Collections.binarySearch(list, key,
String.CASE_INSENSITIVE_ORDER);
print("Location of " + key + " is " + index +
", list.get(" + index + ") = " + list.get(index));
}
} /*
[one, Two, three, Four, five, six, one, one, Two, three, Four, five, six, one]
Shuffled: [Four, five, one, one, Two, six, six, three, three, five, Four, Two, one, one]
Trimmed: [Four, five, one, one, Two, six, six, three, three, five]
Sorted: [Four, Two, five, five, one, one, six, six, three, three]
Location of six is 7, list.get(7) = six
Case-insensitive sorted: [five, five, Four, one, one, six, six, three, three, Two]
Location of three is 7, list.get(7) = three
*/
public class ReadOnly {
static Collection<String> data =
new ArrayList<String>(Countries.names(6));
public static void main(String[] args) {
Collection<String> c =
Collections.unmodifiableCollection(
new ArrayList<String>(data));
print(c); // Reading is OK
//! c.add("one"); // Can't change it
List<String> a = Collections.unmodifiableList(
new ArrayList<String>(data));
ListIterator<String> lit = a.listIterator();
print(lit.next()); // Reading is OK
//! lit.add("one"); // Can't change it
Set<String> s = Collections.unmodifiableSet(
new HashSet<String>(data));
print(s); // Reading is OK
//! s.add("one"); // Can't change it
// For a SortedSet:
Set<String> ss = Collections.unmodifiableSortedSet(
new TreeSet<String>(data));
Map<String, String> m = Collections.unmodifiableMap(
new HashMap<String, String>(Countries.capitals(6)));
print(m); // Reading is OK
//! m.put("Ralph", "Howdy!");
// For a SortedMap:
Map<String, String> sm =
Collections.unmodifiableSortedMap(
new TreeMap<String, String>(Countries.capitals(6)));
}
} /*
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]
ALGERIA
[BULGARIA, BURKINA FASO, BOTSWANA, BENIN, ANGOLA, ALGERIA]
{BULGARIA=Sofia, BURKINA FASO=Ouagadougou, BOTSWANA=Gaberone, BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers}
*/
对特定类型的“不可修改的”方法的调用并不会产生编译时的检查,但是转换成功后,任何会改变容器内容的操作都会引起UnsupportedOperationException异常。
Collection或Map的同步控制
关键字synchronized是多线程议题中的重要部分。Collections类有办法能够自动同步整个容器。其语法与不可修改方法相似:
public class Synchronization {
public static void main(String[] args) {
Collection<String> c =
Collections.synchronizedCollection(
new ArrayList<String>());
List<String> list = Collections.synchronizedList(
new ArrayList<String>());
Set<String> s = Collections.synchronizedSet(
new HashSet<String>());
Set<String> ss = Collections.synchronizedSortedSet(
new TreeSet<String>());
Map<String, String> m = Collections.synchronizedMap(
new HashMap<String, String>());
Map<String, String> sm =
Collections.synchronizedSortedMap(
new TreeMap<String, String>());
}
}
最好是如上所示,直接将新生成的容器传递给了适当的“同步方法”;这样做就不会有任何机会暴露出不同步的版本。
快速报错
Java容器有一种保护机制,能够防止多个进程同时修改同一容器的内容。如果在迭代遍历某个容器的过程中,另一个进程介入其中,并且插入、删除或修改此容器内的某个对象,那就会出问题。Java容器类类库采用快速报错(fail-fast)机制。它会探查容器上的任何除了进程所进行的操作以外的所有变化,一旦它发现其他进程修改了容器,就会立刻抛出ConcurrentModificationException异常。这就是快速报错的意思——不是使用复杂的算法在事后来检查问题。
public class FailFast {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
Iterator<String> it = c.iterator();
c.add("An object");
try {
String s = it.next();
} catch (ConcurrentModificationException e) {
System.out.println(e);
}
}
} /*
java.util.ConcurrentModificationException
*/
程序运行时发生了异常,因为在容器取得迭代器之后,有东西被放入该容器中。当程序的不同部分修改了同一容器时,就可能导致容器的状态不一致。所以,此异常提醒你,应该修改代码。
ConcurrentHashMap、CopyOnWriteArrayList和CopyOnWriteArraySet都使用了可以避免ConcurrentModificationException的技术。
持有引用
java.lang.ref类库包含了一组类,这些类为垃圾回收提供了更大的灵活性。当存在可能会耗尽内存的大对象时,这些类显得特别有用。有三个继承自抽象类Reference的类:SoftReference、WeakReference和PhantomReference。当垃圾回收器正在考察的对象只能通过某个Reference对象才可获得时,上诉这些不同的派生类为垃圾回收器提供了不同级别的间接性指示。
对象是可获得的(reachable),是指此对象可在程序中的某处找到,这意味着在栈中有一个普通引用,它指向此对象。如果一个对象是可获得的,垃圾回收器就不能释放它,因为它仍然为程序所用。如果一个对象不是可获得的,那程序将无法使用它,所以将其回收是安全的。
如果想继续持有对某个对象的引用,希望以后还可以访问到,但也希望能够允许垃圾回收器释放它,这时就应该使用Reference。这样可以继续使用该对象,而在北村消耗殆尽时又允许释放该对象。
以Reference对象作为和普通引用之间的媒介(代理),另外一定不能有普通引用指向那个对象。如果垃圾回收器发现某个对象通过普通对象可获得的,该对象就不会被释放。
SoftReference、WeakReference和PhantomReference由强到弱排列,对应不同级别“可获得性”。SoftReference用以实现内存敏感的高速缓存。WeakReference是为实现规范映射而设计,它不妨碍垃圾回收器回收映射的键或值。规范映射中对象的实例可以在程序的多出被同时使用,以节省存储空间。PhantomReference用以调度回收前的清理工作,它比Java终止机制更灵活。
使用SoftReference、WeakReference时,可以选择是否要将它们放入ReferenceQueue(用作“回收前清理工作”的工具)。而PhantomReference只能依赖于ReferenceQueue:
class VeryBig {
private static final int SIZE = 10000;
private long[] la = new long[SIZE];
private String ident;
public VeryBig(String id) {
ident = id;
}
@Override
public String toString() {
return ident;
}
@Override
protected void finalize() {
System.out.println("Finalizing " + ident);
}
}
public class References {
private static ReferenceQueue<VeryBig> rq =
new ReferenceQueue<VeryBig>();
public static void checkQueue() {
Reference<? extends VeryBig> inq = rq.poll();
if (inq != null) {
System.out.println("In queue: " + inq.get());
}
}
public static void main(String[] args) {
int size = 10;
// Or, choose size via the command line:
if (args.length > 0) {
size = new Integer(args[0]);
}
LinkedList<SoftReference<VeryBig>> sa = new LinkedList<SoftReference<VeryBig>>();
for (int i = 0; i < size; i++) {
sa.add(new SoftReference<VeryBig>(
new VeryBig("Soft " + i), rq));
System.out.println("Just created: " + sa.getLast());
checkQueue();
}
LinkedList<WeakReference<VeryBig>> wa =
new LinkedList<WeakReference<VeryBig>>();
for (int i = 0; i < size; i++) {
wa.add(new WeakReference<VeryBig>(
new VeryBig("Weak " + i), rq));
System.out.println("Just created: " + wa.getLast());
checkQueue();
}
SoftReference<VeryBig> s =
new SoftReference<VeryBig>(new VeryBig("Soft"));
WeakReference<VeryBig> w =
new WeakReference<VeryBig>(new VeryBig("Weak"));
System.gc();
LinkedList<PhantomReference<VeryBig>> pa =
new LinkedList<PhantomReference<VeryBig>>();
for (int i = 0; i < size; i++) {
pa.add(new PhantomReference<VeryBig>(
new VeryBig("Phantom " + i), rq));
System.out.println("Just created: " + pa.getLast());
checkQueue();
}
}
}/*
Just created: java.lang.ref.SoftReference@6f75e721
Just created: java.lang.ref.SoftReference@69222c14
Just created: java.lang.ref.SoftReference@606d8acf
Just created: java.lang.ref.SoftReference@782830e
Just created: java.lang.ref.SoftReference@470e2030
Just created: java.lang.ref.SoftReference@3fb4f649
Just created: java.lang.ref.SoftReference@33833882
Just created: java.lang.ref.SoftReference@200a570f
Just created: java.lang.ref.SoftReference@16b3fc9e
Just created: java.lang.ref.SoftReference@e2d56bf
Just created: java.lang.ref.WeakReference@244038d0
Just created: java.lang.ref.WeakReference@5680a178
Just created: java.lang.ref.WeakReference@5fdef03a
Just created: java.lang.ref.WeakReference@3b22cdd0
Just created: java.lang.ref.WeakReference@1e81f4dc
Just created: java.lang.ref.WeakReference@4d591d15
Just created: java.lang.ref.WeakReference@65ae6ba4
Just created: java.lang.ref.WeakReference@48cf768c
Just created: java.lang.ref.WeakReference@59f95c5d
Just created: java.lang.ref.WeakReference@5ccd43c2
Finalizing Weak
Finalizing Weak 9
Finalizing Weak 8
Finalizing Weak 7
Finalizing Weak 6
Finalizing Weak 5
Finalizing Weak 4
Finalizing Weak 3
Finalizing Weak 2
Finalizing Weak 1
Finalizing Weak 0
Just created: java.lang.ref.PhantomReference@277c0f21
In queue: null
Just created: java.lang.ref.PhantomReference@6073f712
In queue: null
Just created: java.lang.ref.PhantomReference@43556938
In queue: null
Just created: java.lang.ref.PhantomReference@3d04a311
In queue: null
Just created: java.lang.ref.PhantomReference@7a46a697
In queue: null
Just created: java.lang.ref.PhantomReference@5f205aa
In queue: null
Just created: java.lang.ref.PhantomReference@6d86b085
In queue: null
Just created: java.lang.ref.PhantomReference@75828a0f
In queue: null
Just created: java.lang.ref.PhantomReference@3abfe836
In queue: null
Just created: java.lang.ref.PhantomReference@2ff5659e
In queue: null
*/
运行此程序可以看到,尽管还要通过Reference对象访问那些对象(使用get()取得实际的对象引用),但对象还是被垃圾回收器回收了。还可以看到,ReferenceQueue总是生成一个包含null对象的Reference。要利用此机制,可以继承特定的Reference类,然后为这个新类添加一些更有用的方法。
WeakHashMap
WeakHashMap用来保存WeakReference。它使得规范映射更易于使用,在这种映射中,每个值只保存一份实例以节省存储空间。当程序需要那个值的时候,便在映射中查询现有的对象,然后使用它。映射可将值做为其初始化的一部分,不过通常在需要的时候才生成值。
这是一种节约存储空间的技术,因为WeakHashMap允许垃圾回收器自动清理键和值,所以十分便。对于WeakHashMap添加键和值的操作,则没有什么特殊要求,映射会自动使用WeakReference包装他们。允许清理元素的触发条件是,不再需要此键了:
class Element {
private String ident;
public Element(String id) {
ident = id;
}
@Override
public String toString() {
return ident;
}
@Override
public int hashCode() {
return ident.hashCode();
}
@Override
public boolean equals(Object r) {
return r instanceof Element &&
ident.equals(((Element) r).ident);
}
@Override
protected void finalize() {
System.out.println("Finalizing " +
getClass().getSimpleName() + " " + ident);
}
}
class Key extends Element {
public Key(String id) {
super(id);
}
}
class Value extends Element {
public Value(String id) {
super(id);
}
}
public class CanonicalMapping {
public static void main(String[] args) {
int size = 1000;
// Or, choose size via the command line:
if (args.length > 0) {
size = new Integer(args[0]);
}
Key[] keys = new Key[size];
WeakHashMap<Key, Value> map =
new WeakHashMap<Key, Value>();
for (int i = 0; i < size; i++) {
Key k = new Key(Integer.toString(i));
Value v = new Value(Integer.toString(i));
if (i % 3 == 0) {
keys[i] = k; // Save as "real" references
}
map.put(k, v);
}
System.gc();
}
}
Key类必须有hashCode()和equals(),因为在散列数据结构中,它被用作键。运行此程序,会看到垃圾回收器每隔三个键就跳过一个,因为指向那个键的普通引用被存入keys数组,所以有些对象不能被垃圾回收器回收。
Java 1.0/1.1 的容器
Vector和Enumeration
在Java 1.0/1.1 中,Vector是唯一可以自我扩展的序列。基本可以看做ArrayList,但是具有又长又难的方法名。在订正过Java容器类类库中,Vector被改造过,可将其归类为Collection和List。
Java 1.0/1.1版的迭代器发明了一个新名字——枚举,Enumeration接口比Iterator小。只有连个名字很长的方法。
public class Enumerations {
public static void main(String[] args) {
Vector<String> v =
new Vector<String>(Countries.names(10));
Enumeration<String> e = v.elements();
while (e.hasMoreElements()) {
System.out.print(e.nextElement() + ", ");
}
// Produce an Enumeration from a Collection:
e = Collections.enumeration(new ArrayList<String>());
}
} /*
ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN REPUBLIC,
*/
最后一行代码创建了一个ArrayList,并且使用enumeration将ArrayList的Iterator转换成了Enumeration,这样即使有需要Enumeration的旧代码,仍然可以使用新容器。
Hashtable
基本上Hashtable和HashMap很相似,甚至方法名也相似。
Stack
Java1.0/1.1的Stack是继承Vector。所以他拥有Vector的所有特点和行为,再加上一些额外的Stack行为。
一个简单的Stack实例,将enum中的每个String表示压入Stack,还展示了可以如何方便地将LinkedList或者之前创建过的Stack类用作栈:
enum Month {
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER
}
public class Stacks {
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
for (Month m : Month.values()) {
stack.push(m.toString());
}
print("stack = " + stack);
// Treating a stack as a Vector:
stack.addElement("The last line");
print("element 5 = " + stack.elementAt(5));
print("popping elements:");
while (!stack.empty()) {
printnb(stack.pop() + " ");
}
print();
// Using a LinkedList as a Stack:
LinkedList<String> lstack = new LinkedList<String>();
for (Month m : Month.values()) {
lstack.addFirst(m.toString());
}
print("lstack = " + lstack);
while (!lstack.isEmpty()) {
printnb(lstack.removeFirst() + " ");
}
print();
// Using the Stack class from
// the Holding Your Objects Chapter:
net.mindview.util.Stack<String> stack2 =
new net.mindview.util.Stack<String>();
for (Month m : Month.values()) {
stack2.push(m.toString());
}
print("stack2 = " + stack2);
while (!stack2.empty()) {
printnb(stack2.pop() + " ");
}
}
} /*
stack = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER]
element 5 = JUNE
popping elements:
The last line NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL MARCH FEBRUARY JANUARY
lstack = [NOVEMBER, OCTOBER, SEPTEMBER, AUGUST, JULY, JUNE, MAY, APRIL, MARCH, FEBRUARY, JANUARY]
NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL MARCH FEBRUARY JANUARY
stack2 = [NOVEMBER, OCTOBER, SEPTEMBER, AUGUST, JULY, JUNE, MAY, APRIL, MARCH, FEBRUARY, JANUARY]
NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL MARCH FEBRUARY JANUARY
*/
String表示从Month enum常量中生成的,用push插入Stack,然后再从栈的顶端弹出来。
如果需要栈的行为,应该使用LinkedList,或者从LinkedList类中创建的net.mindview.Stack类。
BitSet
高效率存储大量“开/关”信息,BitSet是最好的选择。不过它的效率仅是对空间而言;如果需要搞笑的访问时间,BitSet比本地数组稍慢一点。此外,BitSet的最小容量是long:64位。如果存储的内容比较小,例如8位,那么BitSet就浪费了一些空间。因此如果空间对你很重要,最好撰写自己的类,或者直接使用数组(只有在创建包含开关信息列表的大量对象,并且促使你作出决定的依据仅仅是性能和其他度量因素时,才属于这种情况。如果你做出这个决定只是因为你认为某些对象太大了,那么你最终会产生不需要的复杂度,并会浪费大量的时间)。
普通的容器都会随元素加入而扩充,BitSet也是。
public class Bits {
public static void printBitSet(BitSet b) {
print("bits: " + b);
StringBuilder bbits = new StringBuilder();
for (int j = 0; j < b.size(); j++) {
bbits.append(b.get(j) ? "1" : "0");
}
print("bit pattern: " + bbits);
}
public static void main(String[] args) {
Random rand = new Random(47);
// Take the LSB of nextInt():
byte bt = (byte) rand.nextInt();
BitSet bb = new BitSet();
for (int i = 7; i >= 0; i--) {
if (((1 << i) & bt) != 0) {
bb.set(i);
} else {
bb.clear(i);
}
}
print("byte value: " + bt);
printBitSet(bb);
short st = (short) rand.nextInt();
BitSet bs = new BitSet();
for (int i = 15; i >= 0; i--) {
if (((1 << i) & st) != 0) {
bs.set(i);
} else {
bs.clear(i);
}
}
print("short value: " + st);
printBitSet(bs);
int it = rand.nextInt();
BitSet bi = new BitSet();
for (int i = 31; i >= 0; i--) {
if (((1 << i) & it) != 0) {
bi.set(i);
} else {
bi.clear(i);
}
}
print("int value: " + it);
printBitSet(bi);
// Test bitsets >= 64 bits:
BitSet b127 = new BitSet();
b127.set(127);
print("set bit 127: " + b127);
BitSet b255 = new BitSet(65);
b255.set(255);
print("set bit 255: " + b255);
BitSet b1023 = new BitSet(512);
b1023.set(1023);
b1023.set(1024);
print("set bit 1023: " + b1023);
}
} /*
byte value: -107
bits: {0, 2, 4, 7}
bit pattern: 1010100100000000000000000000000000000000000000000000000000000000
short value: 1302
bits: {1, 2, 4, 8, 10}
bit pattern: 0110100010100000000000000000000000000000000000000000000000000000
int value: -2014573909
bits: {0, 1, 3, 5, 7, 9, 11, 18, 19, 21, 22, 23, 24, 25, 26, 31}
bit pattern: 1101010101010000001101111110000100000000000000000000000000000000
set bit 127: {127}
set bit 255: {255}
set bit 1023: {1023, 1024}
*/
随机数生成器被用来生成随机的byte,short和int,每一个都被转换为BitSet中相应的位模式。因为BitSet是64位的,所以任何生成的随机数都不会导致BitSet扩充容量、在必要时扩充。
如果拥有一个可以命名的固定的标志集合,那么EnumSet (枚举章节)与BitSet相比,通常是一种更好的选择,因为EnumSet允许你按照名字而不是数字位的位置进行操作,因此可以减少错误。EnumSet还可以防止你因不注意而添加新的标志位置,这种行为能够引发严重的、难以发现的缺陷。你应该使用BitSet而不是EnumSet的理由只包括:只有在运行时才知道需要多少个标志;对标志命名不合理,需要BitSet中的某种特殊操作(查看BitSet和EnumSet的JDK文档)。
上一篇: 23种JAVA设计模式(1)-原则
下一篇: Java 23种设计模式
推荐阅读