容器深入研究 --- 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就会溢出并且返回负值,这就不正确了。

时间: 2024-10-11 17:51:29

容器深入研究 --- Set和存储顺序的相关文章

Thinking in Java:容器深入研究

1.虚线框表示Abstract类,图中大量的类的名字都是以Abstract开头的,它们仅仅是部分实现了特定接口的工具,因此创建时能够选择从Abstract继承. Collections中的实用方法:挑几个经常使用的: 1. reverse(List):逆转次序 2. rotate(List,int distance)全部元素向后移动distance个位置,将末尾元素循环到前面来(用了三次reverse) 3.sort(List,Comparator) 排序,可依照自己的Comparator 4.

容器深入研究 --- 散列与散列码(二)

为速度而散列: SlowMap.java说明了创建一个新的Map并不困难.但正如它的名称SlowMap所示,它不会很快,如果有更好的选择就应该放弃它.它的问题在于对键的查询,键没有按照任何特定的顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式. 散列的价值在于速度: 散列使得查询得以快速进行.由于瓶颈在于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询. 散列则更进一步,它将键保存在某处,以便能够很快的找到.

容器深入研究 --- 理解Map

通常的: 映射表(也称关联数组)的基本思想是它维护的键-值(对)关联,因此你可以使用键来查找值. 标准的Java类库中包含了Map的几种实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap. 它们都有同样的基本接口Map,但是行为特性各不相同,这表现在效率.键值对的保存及呈现次序.对象的保存周期.映射表如何在多线程程序中工作的判定"键"等价的策略方面.Map接口实现的数量应该让

Set和存储顺序深入探讨、SortedSet排序的示例

package org.rui.collection2.set; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; //TypesForSets.java /** * 下面演示了为了成功地使用特定的Set 实现类型而必须定义的方法 * * ================= * 为了证明哪些方法对于某种特定的Set是必须需的,并且同时还

容器深入研究 --- 散列与散列码(三)

如何覆盖hashCode(): 明白了如何散列之后,编写自己的hashCode()就更有意义了. 首先,你无法控制bucket数组的下标值的产生.这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子有关.hashCode()生成的结果,经过处理后称为桶位的下标. 设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值.如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()

容器深入研究 --- 散列与散列码(一)

通常的: 当标准类库中的类被作用HashMap的键.它用的很好,因为它具备了键所需的全部性质. 当你自己创建用作HashMap的键的类,有可能会忘记在其中放置必须的方法,而这时通常会犯的一个错误. 例如:考虑一个天气系统,将Groundhog对象与Prediction对象联系起来. class Groundhog { protected int number; public Groundhog(int n) { number = n; } public String toString() { r

容器的研究思路

容器的研究思路 为什么研究容器 容器是JDK里的基础功能,平时使用得较多. 容器相对简单,比较容易研究 已经有很多人对容器进行了研究,相关资料比较齐全 容器是什么,要研究那些特性 容器是一段程序,是一系列的对象. 作为程序,它由 数据结构+算法构成 作为对象,它由 属性+方法构成 作为类,它有自己的继承树 容器有很多的工具类,如collections.Arrays.Iterator等等 怎么去研究容器 评价软件的4个因素:可维护性,可靠性,移植性,效率 如何去测试评估程序的特性 程序的并发性能

C#遍历容器存储顺序

foreach (Control c in mForm.Controls) { } 存储的顺序是依据mForm上添加控件的顺序, m1.Parent = myPanel; m3.Parent = myPanel; m4.Parent = myPanel; m2.Parent = myPanel; 那么存储的顺序就是1 3 4 2 如果要存储界面上多个容器,可以进行递归 但是如果 m1.Parent = myPanel; m3.Parent = myPanel; m4.Parent = myPan

Java编程思想——第17章 容器深入研究(two)

六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add 在尾部增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常 remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常 element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementExce