Java1.0/1.1中遗留的集合

一、Vector和Enumeration

在Java1.0/1.1中,Vector是唯一可以自我扩展的序列,所以它被大量使用。它可以被看做ArrayList,但是具有又长又难记的方法名。在修改过的Java容器类库中,Vector被改造过,可以将其归类为Collection和List。这样做有点不妥,可能会让人误会Vector变好了,实际上这样做只是为了支持Java2之前的代码。

因为Vector类实现了List接口,所以可以像使用ArrayList实例那样使用向量,也可以使用Vector类的遗留方法操作向量。例如,在实例化Vector后,可以调用addElement()方法来添加元素;为了获取特定位置的元素,可以调用elementAt()方法;为了获取向量中的第一个元素,可以调用firstElement()方法;为了检索最后一个元素,可以调用lastElement()方法;使用indexOf和lastIndexOf()方法可以获取元素的索引;为了移除元素,可以调用removeElement()或removeElementAt()方法。

Java1.0/1.1的迭代器发明了一个新名字----枚举,取代了为人熟知的术语(迭代器)。此Enumeration接口比Iterator小,只有两个方法名字很长:一个为boolean hasMoreElements(),如果此枚举包含更多的元素,该方法就返回true;另一个为Object nextElement(),该方法返回此枚举中的下一个元素(如果还有的话),否则抛出异常。

Enumeration只是接口而不是实现,所以有时新的类库仍然使用了旧的Enumeration,这令人十分遗憾,但通常不会造成伤害。但在你的代码中应该尽量使用Iterator,但也得有所准备,类库可能给你返回一个Enumeration。

此外,还可以通过使用Collections.enumeration()方法来从Collection生成一个Enumeration。

下面的例子演示Vector、Enumeration的使用(引自Think in Java):

package chaptr17;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Vector;

public class Enumerations {
	public static void main(String[] args) {
		Vector<String> v = new Vector<String>();
		for(int i = 0; i < 10; i ++) {
			v.add(String.valueOf(i));
		}
		
		Enumeration<String> e = v.elements();
		while(e.hasMoreElements()) {
			System.out.print(e.nextElement() + ", ");
		}
		
		//通过集合生成枚举集
		e = Collections.enumeration(new ArrayList<String>());
	}
}

运行结果:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 

可以调用elements()生成Enumeration,然后使用它进行前序遍历。最后一行代码创建了一个ArrayList,并且使用enumeration()将ArrayList的Iterator转变成了Enumeration。这样,即使有需要Enumeration的旧代码,你仍然可以使用新容器。

下面的例子演示不同的Vector操作(引自Java完全参考手册(第八版)):

package chapter17;

import java.util.Enumeration;
import java.util.Vector;

public class VectorDemo {
	public static void main(String[] args) {
		//初始化大小为3,增量为2的向量
		Vector<Integer> v = new Vector<Integer>(3, 2);
		
		System.out.println("Initial size: " + v.size());
		System.out.println("Initial capacity: " + v.capacity());
		
		v.addElement(1);
		v.addElement(2);
		v.addElement(3);
		v.addElement(4);
		
		System.out.println("Capacity after four additions:" + v.capacity());
		
		v.addElement(5);
		System.out.println("Current capacity: " + v.capacity());
		
		v.addElement(6);
		v.addElement(7);
		
		System.out.println("Current capacity: " + v.capacity());
		
		v.addElement(9);
		v.addElement(10);
		
		System.out.println("Current capacity: " + v.capacity());
		
		v.addElement(11);
		v.addElement(12);
		
		System.out.println("First element: " + v.firstElement());
		System.out.println("Last element: " + v.lastElement());
		
		if(v.contains(3)) {
			System.out.println("Vector contains 3.");
		}
		
		//通过向量构造一个枚举集
		Enumeration<Integer> vEnum = v.elements();
		
		System.out.println("\nElements in vector:");
		
		while(vEnum.hasMoreElements()) {
			System.out.print(vEnum.nextElement() + " ");
		}
		System.out.println();
	}
}
运行结果:

Initial size: 0
Initial capacity: 3
Capacity after four additions:5
Current capacity: 5
Current capacity: 7
Current capacity: 9
First element: 1
Last element: 12
Vector contains 3.


Elements in vector:
1 2 3 4 5 6 7 9 10 11 12 
对于新代码不推荐使用Enumeration接口,所以通常会使用迭代器或for-earch风格的for循环枚举向量的内容。当然,仍然存在许多使用Enumeration接口的遗留代码。幸运的是枚举和迭代的工作方式几乎相同。

二、Stack类

Java1.0/1.1的Stack很奇怪,竟然不是用Vector来构建Stack,而是继承Vector。所以它拥有Vector所有的特点和行为,再加上一些额外的Stack行为。很难了解设计者是否意识到道这样做特别有用处,或者只是一个幼稚的设计。唯一清楚的是,在匆忙发布之前它没有经过仔细审查,因此这个糟糕的设计仍然挂在这里(但你永远都不应该使用它)。

Stack是Vector的子类,实现了标准的后进先出堆栈。Stack只定义了默认的构造函数,用于创建空的堆栈。随着JDK 5发布,Java对Stack类进行了整修以使用泛型,其声明如下:

class Stack<E>
其中,E指定了在堆栈中存储的元素的类型。

为了将对象放入到堆栈的顶部,可以调用push()方法;为了移除并返回顶部的元素,可以调用pop()方法;可以使用peek()方法返回但是不移除顶部的元素;调用pop()或peek()方法时,如果栈为空,那么会抛出EmptyStackException异常;如果堆栈中没有内容,empty()放回true;search()方法确定对象是否存在于堆栈中,并返回将对象压入到堆栈顶部所需要弹出的次数。
下面是一个堆栈的简单例子:

package chapter17;

import java.util.EmptyStackException;
import java.util.Stack;

/**
 * 演示入栈和出栈
 */
public class StackDemo {
	
	static void showpush(Stack<Integer> st, int a) {
		st.push(a);
		System.out.println("push(" + a + ")");
		System.out.println("Stack: " + st);
	}
	
	static void showpop(Stack<Integer> st) {
		System.out.println("pop -> ");
		Integer a = st.pop();
		System.out.println(a);
		System.out.println("Stack: " + st);
	}
	
	public static void main(String[] args) {
		Stack<Integer> st = new Stack<Integer>();
		
		System.out.println("Stack: " + st);
		showpush(st, 42);
		showpush(st, 66);
		showpush(st, 99);
		showpop(st);
		showpop(st);
		showpop(st);
		
		try {
			//栈为空,抛出异常
			showpop(st);
		} catch (EmptyStackException e) {
			System.out.println("empty stack");;
		}
	}
}

运行结果:

Stack: []
push(42)
Stack: [42]
push(66)
Stack: [42, 66]
push(99)
Stack: [42, 66, 99]
pop -> 
99
Stack: [42, 66]
pop -> 
66
Stack: [42]
pop -> 
42
Stack: []
pop -> 
empty stack

下面的例子演示怎样把LinkedList当做栈来使用:

package chaptr17;

import java.util.LinkedList;
import java.util.Stack;

public class Stacks {
	public static void main(String[] args) {
		Stack<String> stack = new Stack<String>();
		for(Month m : Month.values()) {
			stack.push(m.toString());
		}
		System.out.println("stack = " + stack);
		
		//Treating a stack as a Vector
		stack.addElement("The last line");
		System.out.println("element 5 = " + stack.elementAt(5));
		System.out.println("popping elements:");
		while(!stack.empty()) {
			System.out.print(stack.pop() + " ");
		}
		
		//把LinkedList当做栈来使用
		LinkedList<String> lstack = new LinkedList<String>();
		for(Month m : Month.values()) {
			lstack.addFirst(m.toString());
		}
		System.out.println();
		System.out.println("lstack = " + lstack);
		while(!lstack.isEmpty()) {
			System.out.print(lstack.removeFirst() + " ");
		}
	}
}

enum Month {JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
	JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER}
运行结果:

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 

三、Dictionary类和Hashtable类

1、Dictionary类

Dictionary是表示键/值对存储库的抽象类,其操作与Map类似。给定建和值,就可以在Dictionary对象中存储值。一旦存储了值,就可以使用相应的键来检索这个值。因此,与映射类似,可以将字典想象成键/值对的列表。尽管目前不反对使用,但Dictionary被归为过时的遗留类,因为已被Map完全替代。JDK5中Dictionary被修改成泛型的,其声明如下:

public abstract class Dictionary<K,V>
其中,K指定了键的类型,V指定了值的类型。

要添加键和值,可以使用put()方法。使用get()方法可以检索给定键的值。分别通过keys()和elements()方法,键和值都可以作为Enumeration返回。size()返回在字典中存储的键/值对的数量,如果字典为空,isEmpty()方法将返回true。可以使用remove()方法移除键/值对。注意:Dictionary类是过时的,应该实现Map接口来获取键/值存储功能。

2、Hashtable类

Hashtable与HashMap很相似,甚至方法名也相似。所以,在新的程序中,如果对同步性与遗留代码的兼容性没有任何要求,没有理由再使用Hashtable而不使用HashMap。

Hashtable是原始java.util的一部分,是Dictionary的具体实现。但是随着集合的出现,Java对Hashtable进行了重新设计,使其也实现了Map接口。因此,Hashtable也被集成到集合框架中。Hashtable与HashMap相似,但它是同步的。

与HashMap类似,Hashtable在哈希表中存储键值对,但是键和值都不能为null,如果试图使用null作为键或者值,会抛出NullPointerException。JDK5将Hashtable修改为泛型的,其声明如下所示:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable
下面的例子演示Hashtable的使用:

package cn.itcast.jdbc;

import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * Hashtable使用示例
 */
public class HTDemo {
	public static void main(String[] args) {
		Dictionary<String, Double> balance = new Hashtable<String, Double>();
		
		Enumeration<String> names;
		String str;
		double bal;
		balance.put("John Doe", 3434.34);
		balance.put("Tom Smith", 123.22);
		balance.put("Jane Baker", 1378.00);
		balance.put("Tod Hall", 99.22);
		//编译时没错,运行时报NullPointerException
		//balance.put(null, null);
		
		//Show all balances is hashtable
		names = balance.keys();//获取键的集合
		while(names.hasMoreElements()) {
			str = names.nextElement();
			System.out.println(str + "; " + balance.get(str));
		}
		
		System.out.println();
		
		//往John Doe的账户存入1000元
		bal = balance.get("John Doe");
		balance.put("John Doe", bal + 100);
		System.out.println("John Doe`s new balance: " + balance.get("John Doe"));
	}
}
运行结果:

Tod Hall; 99.22
John Doe; 3434.34
Jane Baker; 1378.0
Tom Smith; 123.22


John Doe`s new balance: 3534.34

四、Properties(属性映射)

1、引自Java完全参考手册(第八版)

Properties是Hashtable的子类,用于保存值的列表。在列表中,键是String类型,值也是String类型。许多其他Java类都在使用Properties类。例如当获取环境值时,System.getProperties()方法返回对象的类型就是Properties。尽管Properties类不是泛型的,但其中一些方法是泛型的。

Properties定义了以下实例变量:

Properties defaults;
该变量包含与Properties对象关联的默认属性列表。Properties定义了以下构造函数:

public Properties() {
      this(null);
}
public Properties(Properties defaults) {
      this.defaults = defaults;
}
第一个版本创建没有默认值的Properties对象。第二个版本创建一个使用defaults作为默认值的Properties对象。对于这两个版本,属性列表都是空的。除了继承自Hashtable的方法以外,Properties类还定义了很多其他的方法,Properties还包含不赞成使用的方法save(),该方法已经被store()取代,因为save()方法无法正确的处理错误。

Properties类有一个有用的特性,就是可以指定默认属性,如果没有值与特定的键关联,就会返回默认属性,例如在getProperty()方法中,可以在指定键时指定默认值,例如getProperty("name","default value")。如果没有找到"name"值,就返回"default value"。当构造Properties对象时,可以传递另外一个Properties实例,用作新实例的默认属性。对于这种情况,如果对给定的Properties对象调用getProperties("foo"),并且"foo"不存在,那么Java会在默认的Properties对象中查找"foo",这使得可以任意嵌套默认属性。

下面的例子演示了Properties类的使用:

package chapter17;

import java.util.Properties;
import java.util.Set;

/**
 * Properties的例子:创建一个属性列表,在该列表中,
 * 键是州的名称,值是首府的名称。注意在该程序中,
 * 查找佛罗里达州的代码包含一个默认值
 */
public class PropDemo {
	public static void main(String[] args) {
		Properties capitals = new Properties();
		
		capitals.put("Illinois", "Springfield");
		capitals.put("Missouri", "Jefferson City");
		capitals.put("Washington", "Olympia");
		capitals.put("California", "Sacramento");
		capitals.put("Indiana", "Indianapolis");
		
		//获取键的Set集合
		Set<?> states = capitals.keySet();
		
		//显示所有的州和首府
		for(Object name :states) {
			System.out.println("The capital of " + name + " is" 
					+ capitals.getProperty((String) name) + ".");
		}
		
		System.out.println();
		
		//查找不在Properties中的州的首府,没有则返回默认值
		String str = capitals.getProperty("Florida", "not Found");
		System.out.println("The capital of Florida is " + str + ".");
	}
}
因为佛罗里达州不在列表中,所以使用默认值,下面是程序运行结果:

The capital of Missouri isJefferson City.
The capital of Illinois isSpringfield.
The capital of Indiana isIndianapolis.
The capital of California isSacramento.
The capital of Washington isOlympia.


The capital of Florida is not Found.
带有指定默认州的版本:

package chapter17;

import java.util.Properties;
import java.util.Set;

/**
 * 当构造Properties对象时,指定默认属性列表。 如果在主列表中没有找到期望的键,就在默认列表 中进行查找。
 */
public class PropDemDef {
	public static void main(String[] args) {
		Properties defList = new Properties();
		defList.put("Florida", "Tallahassee");
		defList.put("Wisconsin", "Masison");

		Properties capitals = new Properties(defList);

		capitals.put("Illinois", "Springfield");
		capitals.put("Missouri", "Jefferson City");
		capitals.put("Washington", "Olympia");
		capitals.put("California", "Sacramento");
		capitals.put("Indiana", "Indianapolis");

		// 获取键的Set集合
		Set<?> states = capitals.keySet();

		// 显示所有的州和首府
		for (Object name : states) {
			System.out.println("The capital of " + name + " is"
					+ capitals.getProperty((String) name) + ".");
		}

		System.out.println();

		// 弗罗里达在默认列表被找到
		String str = capitals.getProperty("Florida");
		System.out.println("The capital of Florida is " + str + ".");
	}
}
运行结果:

The capital of Missouri isJefferson City.
The capital of Illinois isSpringfield.
The capital of Indiana isIndianapolis.
The capital of California isSacramento.
The capital of Washington isOlympia.


The capital of Florida is Tallahassee.
使用store()和load()方法

Properties类最有用的方面之一,在Properties对象中保存信息,可以使用store()和load()方法很容易地存储到磁盘中以及从磁盘加载。在任何时候,都可以将Properties对象写入到流中或者从流中读取。这使得对于实现简单的数据库来说,属性列表特别方便。例如,下面的程序使用属性列表创建一个简单的计算机化的电话簿,用于存储姓名和电话号码。为了查找某个人的电话号码,输入他或她的姓名。该程序使用store()与load()方法存储和检索列表。

具体代码如下:

package chapter17;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;

/**
 * 使用Properties实现简单的电话簿
 */
public class Phonebook {
	public static void main(String[] args) throws IOException {
		Properties ht = new Properties();
		BufferedReader br = 
				new BufferedReader(new InputStreamReader(System.in));
		String name, number;
		FileInputStream fin = null;
		boolean changed = false;
		//尝试打开文件
		try {
			fin = new FileInputStream(
				"D:\\JAVAWEB\\MyeclipseCode\\JavaReference\\src\\chapter17\\phonebook.dat");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		//如果文件已经存在,加载存在的电话号码
		try {
			if(fin != null) {
				ht.load(fin);
				fin.close();
			}
		} catch (IOException e) {
			System.out.println("Error reading file.");
		}
		
		//让用户输入新的姓名和电话号码
		do {
			System.out.println("Enter new name" + " ('quit' to stop): ");
			name = br.readLine();
			if(name.equals("quit")) continue;
			
			System.out.println("Enter number: ");
			number = br.readLine();
			ht.put(name, number);
			changed = true;
		} while (!name.equals("quit"));
		
		if(changed) {
			FileOutputStream fout = new FileOutputStream(
				"D:\\JAVAWEB\\MyeclipseCode\\JavaReference\\src\\chapter17\\phonebook.dat");
			
			ht.store(fout, "Telephone Book");
			fout.close();
		}
		
		//根据姓名查找电话号码
		do {
			System.out.println("Enter name to find" + " ('quit' to stop): ");
			name = br.readLine();
			if(name.equals("quit")) continue;
			
			number = ht.getProperty(name);
			System.out.println(number);
		} while (!name.equals("quit"));
	}
}
运行结果:

Enter new name ('quit' to stop): 
kaka
Enter number: 
123456
Enter new name ('quit' to stop): 
quit
Enter name to find ('quit' to stop): 
kaka
123456
Enter name to find ('quit' to stop): 
quit

五、BitSet(位集)

如果想要高效率地存储大量"开/关"信息,BitSet是很好的选择。不过它的效率仅是对空间而言;如果需要高效的访问时间,BitSet比本地数组稍慢一些。此外,BitSet的最小容量是long:64位。如果存储的内容比较小,例如8位,那么BitSet就浪费了一些空间。因此如果空间对你很重要,最好撰写自己的类,或者直接采用数组来存储你的标志信息。普通的容器都会随着元素的加入而扩充其容量,BitSet也是。

BitSet类提供了一个便于读取、设置或清除各个位的接口。使用这个接口可以避免屏蔽和其他麻烦的位操作。如果将这些位存储在int或long变量中就必须进行这些繁琐的操作。

例如,对于一个名为bucketOfBits的BitSet,

bucketOfBits.get(i)

如果第i为处于“开”状态,就返回true;否则返回false。同样地,

bucketOfBits.set(i)

将第i位置为“开”状态。最后,

bucketOfBits.clear(i)

将第i位置为"关"状态。

这里给出一个采用"Eratosthenes筛子"算法查找素数的实现,其程序将计算2~2000000之间的所有素数(一共148933个素数),这里的关键是要遍历一个拥有200万个位集。实现将所有的位置为"开"状态,然后,将已知素数的倍数所对应的位置都置为"关"状态。经过这个操作,保留下来状态为"开"的位对应的就是素数。

下面是具体的代码:

package chapter13;

import java.util.BitSet;

/**
 * 用BitSet查找素数
 */
public class Sieve {
	public static void main(String[] args) {
		int n = 2000000;
		long start = System.currentTimeMillis();
		//创建一个位集
		BitSet b = new BitSet(n + 1);
		int count = 0;//用于保存素数的个数
		int i = 2;
		//遍历,将每个i设置为"开"状态
		for(i = 2; i <= n; i ++) {
			b.set(i);//将i的位置设置为开状态
		}
		i = 2;
		//计算小于等于根号n的素数数目
		while(i * i <= n) {
			if(b.get(i)) {
				count ++;
				int k = 2 * i;
				//将所有为i的倍数的数除去,即开关置为"关"
				while(k <= n) {
					//将第k为置为"关"状态
					b.clear(k);
					k += i;
				}
			}
			i ++;
		}
		//计算大于根号n的素数数目
		while(i <= n) {
			if(b.get(i)) {
				count ++;
			}
			i ++;
		}
		long end = System.currentTimeMillis();
		System.out.println(count + " primes.");
		System.out.println((end - start) + " milliseconds.");
	}
}
运行结果:

148933 primes.
66 milliseconds.










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值