首页 > 代码库 > 容器深入研究 --- Set和存储顺序

容器深入研究 --- Set和存储顺序


常见的:

我们在Set中很方便的使用了诸如Integer和String这样的Java预定义类型,这些类型被设计为可以在容器内部使用。

当你创建自己的类型时,要意识到Set需要一种方式来维护存储顺序,而存储顺序如何维护,则是在Set的不同实现之间会有所变化。因此,不同的Set实现不仅具有不同的行为,而且它们对于可以在特定的Set中放置的元素的类型也有不同的要求:

 * ----------------------------------------------------------------------------------------
 * 	Set(interface)		存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
 *	HashSet(*)		为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()
 *	TreeSet			保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口。
 *	LinkedHashSet		具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按照插入的次序显示。元素也必须定义hashCode()方法。


在HashSet上打上星号表示,如果没有其他的限制,这就应该是你的默认的选择,因为它对速度进行了优化。

你必须为散列存储和树状存储都创建一个equals()方法,但是hashCode()只有在这个类将会被置于HashSet或者LinkedHashSet中才是必须的。但是,对于良好的编程风格而言,你应该在覆盖equals()方法时,总是同时覆盖hashCode()方法。

例子:

class SetType {
	int i;

	public SetType(int n) {
		i = n;
	}

	@Override
	public boolean equals(Object o) {
		return o instanceof SetType && (i == ((SetType) o).i);
	}

	@Override
	public String toString() {
		return Integer.toString(i);
	}
}

class HashType extends SetType {

	public HashType(int n) {
		super(n);
	}

	@Override
	public int hashCode() {
		return i;
	}
}

class TreeType extends SetType implements Comparable<TreeType> {

	public TreeType(int n) {
		super(n);
	}

	public int compareTo(TreeType o) {
		return (o.i < i ? -1 : (o.i == i ? 0 : 1));
	}

}

public class TypesForSets {

	static <T> Set<T> fill(Set<T> set, Class<T> type) {
		try {
			for (int i = 0; i < 10; i++) {
				set.add(type.getConstructor(int.class).newInstance(i));
			}
		} catch (Exception e) {
		}
		return set;
	}

	static <T> void test(Set<T> set, Class<T> type) {
		fill(set, type);
		fill(set, type);
		fill(set, type);
		System.out.println(set);
	}

	public static void main(String[] args) { // 上方的是结果
		// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
		test(new HashSet<HashType>(), HashType.class);
		// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
		test(new LinkedHashSet<HashType>(), HashType.class);
		// [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 倒序排序
		test(new TreeSet<TreeType>(), TreeType.class);
		// [7, 3, 1, 9, 6, 5, 9, 1, 3, 8, 9, 0, 5, 2, 6, 1, 5, 4, 2, 0, 7, 4, 3,
		// 8, 8, 7, 6, 4, 0, 2]
		test(new HashSet<SetType>(), SetType.class);
		// [8, 0, 3, 9, 0, 2, 8, 5, 2, 3, 4, 1, 2, 9, 4, 1, 9, 6, 7, 7, 4, 1, 3, 8, 6, 5, 7, 5, 6, 0]
		test(new HashSet<TreeType>(), TreeType.class);
		// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
		test(new LinkedHashSet<SetType>(), SetType.class);
		// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
		test(new LinkedHashSet<TreeType>(), TreeType.class);
		
		try {
			test(new TreeSet<SetType>(), SetType.class);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		
	}

}

注意,在compareTo(),我没有使用“简洁明了”的形式return i - i2;因为这是一个常见的编程错误,它只有在i和i2都是无符号的int(如果Java确实有unsigned关键字的话,但实际上并没有)时才能正确工作。对于java的有符号int,它就出错,因为int不够大,不足以表现两个有符号的int的差。例如i是很大的正整数,而j是很大的负整数,i-j就会溢出并且返回负值,这就不正确了。

容器深入研究 --- Set和存储顺序