HashSet,LinkedHashSet和TreeSet

Java集合框架详解
本文通过对比HashSet、LinkedHashSet和TreeSet的特点及使用场景,详细解释了它们在存储顺序上的区别。并通过具体示例展示了不同集合类在存储自定义对象时的行为差异。

先来看看HashSet和LinkedHashSet的一段测试代码:


package set;

import java.util.HashSet;
import java.util.LinkedHashSet;

public class Test1 {
	
	public static void main(String a[]) {

		HashSet<String> set = new HashSet<String>();

		set.add("a5");
		set.add("a6");
		set.add("a7");
		set.add("a3");
		set.add("a4");
		set.add("a");
		set.add("a1");
		set.add("a2");

		System.out.println(set);

		LinkedHashSet<String> linkedHashSet = new LinkedHashSet<String>();

		linkedHashSet.add("a5");
		linkedHashSet.add("a6");
		linkedHashSet.add("a7");
		linkedHashSet.add("a3");
		linkedHashSet.add("a4");
		linkedHashSet.add("a");
		linkedHashSet.add("a1");
		linkedHashSet.add("a2");

		System.out.println(linkedHashSet);

	}
}


打印结果:
[a, a1, a2, a3, a4, a5, a6, a7]
[a5, a6, a7, a3, a4, a, a1, a2]


个人对HashSet的无序性理解:

  1. 前提,对于同一个HashSet来说
  2. 无序性并不是说一个HashSet对象每次打印时候,顺序是随机的;
  3. 而是说,当插入一个HashSet对象时,其插入的顺序和存储(打印)的顺序是不一样的。

我们来看看下面具体的测试代码:

package set;

import java.util.HashSet;

import org.apache.commons.lang.builder.CompareToBuilder;

public class HashSetTest {

	public static void main(String a[]) {

		//////////////////////////// testcase1: HashSet 里面包含了String对象,
		// 因为String已经重写了hashCode(),所以不管插入到HashSet的顺序如何,其存储(打印)顺序是按照hashCode()的实现方式,规定好的
		HashSet<String> stringHashSet = new HashSet<String>();
		for (int i = 0; i < 5; i++) {
			String str = "a" + i;
			stringHashSet.add(str);
		}
		// 每次创建出来的stringHashSet,其打存储(印顺)序不变!
		System.out.println(stringHashSet); // (你会发现打印顺序都是升序的,感觉和String的hashCode()的实现方式有关。)

		
		
		////////////////////////// testcas2: HashSet 里面包含了Cat自定义对象,Cat本身的Comparable功能失效
		// 因为Cat没有重写了hashCode(),所以不管插入到HashSet的顺序如何,其存储(打印)顺序是总是随机的!
		HashSet<Cat> catHashSet = new HashSet<Cat>();
		for (int i = 0; i < 5; i++) {
			Cat p = new Cat("c" + i, i);
			catHashSet.add(p);
		}

		// 每次创建出来的catHashSet,存储(打印)的顺序与插入的顺序不同
		System.out.println(catHashSet);
		System.out.println(catHashSet); // 再次打印同一个对象(catHashSet)的时候,顺序就不会变了
		System.out.println(catHashSet); // 再次打印同一个对象(catHashSet)的时候,顺序就不会变了

		
		
		
		
		///////////////////////////// testcas3: HashSet 里面包含了Person自定义对象,Person本身的Comparable功能失效
		// 因为Person已经重写了hashCode(),所以不管插入到HashSet的顺序如何,其存储(打印)顺序是按照hashCode()的实现方式,规定好的
		HashSet<Person1> personHashSet = new HashSet<Person1>();
		for (int i = 0; i < 5; i++) {
			Person1 p = new Person1("p" + i, i);
			personHashSet.add(p);
		}
		// 每次创建出来的personHashSet,其打存储(印顺)序不变!
		System.out.println(personHashSet);// (你会发现打印顺序都是固定顺序的,感觉和Person的hashCode()的实现方式有关。)

	}
}

class Cat implements Comparable<Cat> {

	public Cat(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}

	private String name;
	private Integer age;

	@Override
	public int compareTo(Cat o) {
		CompareToBuilder ctb = new CompareToBuilder();
		ctb = ctb.append(this.getName(), o.getName()).append(this.getAge(), o.getAge());
		return 0;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Cat [name=" + name + ", age=" + age + "]";
	}
}

class Person implements Comparable<Person> {
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

	private String name;
	private Integer age;

	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public int compareTo(Person o) {
		CompareToBuilder ctb = new CompareToBuilder();
		ctb = ctb.append(this.getName(), o.getName()).append(this.getAge(), o.getAge()); // ASC
																							// 升序
		return ctb.toComparison();
	}

}







LinkedHashSet

是有序的,与HashSet比较来说,插入的顺序和存储(打印)的顺序是一样的


TreeSet

保证集中的元素是有序的,有2种方法可以实现对象之间的可比较性:

1,添加到TreeSet的对象实现了Comparable接口

2,给规则集的元素指定一个比较器(Comparator)

具体实现如下:


package set;

import java.util.Comparator;
import java.util.HashSet;
import java.util.TreeSet;

import org.apache.commons.lang.builder.CompareToBuilder;

public class TreeSetTest {

	public static void main(String a[]) {

		HashSet<Person> hashSet = new HashSet<Person>();
		for (int i = 0; i < 5; i++) {
			Person p = new Person("a" + i, i);
			hashSet.add(p);
		}

		System.out.println(hashSet);

		// 使用TreeSet无参的构造方法,则应用Person实现Comparable接口的compareTo()
		TreeSet<Person> treeSet = new TreeSet<Person>();

		for (int i = 0; i < 5; i++) {
			Person p = new Person("a" + i, i);
			treeSet.add(p);
		}

		System.out.println(treeSet);

		// TreeSet 如果想应用Comparator
		// 得在构造方法传入,这样一来Person实现Comparable接口的compareTo()方法,就失效了
		// 这一点和List使用Comparator不同: Collections.sort(personList, new
		// PersonComparator<Persion>());
		treeSet = new TreeSet<Person>(new PersonComparator());

		for (int i = 0; i < 5; i++) {
			Person p = new Person("a" + i, i);
			treeSet.add(p);
		}

		System.out.println(treeSet);

	}
}

class PersonComparator implements Comparator<Person> {

	@Override
	public int compare(Person o1, Person o2) {

		// 这里使用org.apache.commons.lang.builder.CompareToBuilder,非常好用哦!!!!!
		CompareToBuilder ctb = new CompareToBuilder();

		ctb = ctb.append(o2.getName(), o1.getName()).append(o2.getAge(), o1.getAge()); // DESC
																						// 降序
		// ctb = ctb.append(o1.getName(), o2.getName()).append(o1.getAge(),
		// o2.getAge()); // ASC 升序:注意 o1 o2的先后顺序的变化

		return ctb.toComparison();

	}

}

class Person1 implements Comparable<Person1> {
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person1 other = (Person1) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

	private String name;
	private Integer age;

	public Person1(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public int compareTo(Person1 o) {
		CompareToBuilder ctb = new CompareToBuilder();
		ctb = ctb.append(this.getName(), o.getName()).append(this.getAge(), o.getAge()); // ASC
																							// 升序
		return ctb.toComparison();
	}

}







<think>首先,用户的问题是:“我想理解Java集合框架中HashSetLinkedHashSetTreeSet的区别 请问Java中HashSet, LinkedHashSetTreeSet的区别”。所以,我需要清晰地解释这三种Set实现之间的区别。 从系统指令中,我必须: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如[^1]。 用户提供的引用是: - [^1]: HashSet基于HashMap,允许null,不保证顺序,操作平均O(1)。 - [^2]: LinkedHashSetHashSet的子类,使用LinkedHashMap维护插入顺序。 - [^3]: LinkedHashSet继承HashSet,使用LinkedHashMap。 - [^4]: 性能比较:HashSet最高,LinkedHashSet次之,TreeSet最差(因为排序)。 - [^5]: 遍历Set的示例。 这些引用来自之前的上下文,但用户说“上述引用仅供参考”,所以我应该基于这些内容构建回答,但确保回答真实可靠。 关键区别点: 1. **HashSet**:无序,基于哈希表实现,操作平均时间复杂度O(1)。 2. **LinkedHashSet**:维护插入顺序,基于LinkedHashMap,操作时间复杂度类似HashSet,但略有开销。 3. **TreeSet**:有序(自然排序或自定义排序),基于红黑树实现,操作平均时间复杂度O(log n)。 其他方面: - 允许null值:HashSetLinkedHashSet允许null,TreeSet可能不允许(取决于Comparator)。 - 线程安全:三者都不是线程安全的。 - 性能:如引用[^4]所述,HashSet最快,LinkedHashSet次之,TreeSet最慢。 结构回答: - 介绍每个Set。 - 比较关键特性:顺序、底层实现、性能、允许null等。 - 使用表格或列表清晰展示区别。 - 添加代码示例(如果合适)。 - 自然地引用提供的引用。 最后,生成相关问题。 回答结构: 1. **引言**:简要介绍Set接口三种实现。 2. **详细解释每个Set**: - HashSet - LinkedHashSet - TreeSet 3. **比较表格**:总结区别。 4. **代码示例**:展示简单使用。 5. **结论**:总结适用场景。 6. **相关问题**:生成2-3个相关问题。 确保使用中文。 在回答中,当引用信息时,添加引用标识如[^1]。例如,提到HashSet的性能时,可以引用[^1]。 生成相关问题: - 基于内容,如:如何选择哪个Set?TreeSet的排序如何工作?等。 现在,构建回答。</think>### Java中HashSetLinkedHashSetTreeSet的区别 在Java集合框架中,`Set`接口表示不包含重复元素的集合。`HashSet`、`LinkedHashSet``TreeSet`是`Set`的三种主要实现类,它们在底层实现、元素顺序、性能适用场景上有显著区别。以下我将逐步解释这些区别,帮助您全面理解。 #### 1. **HashSet** - **底层实现**:基于`HashMap`实现,使用哈希表存储元素。哈希表通过哈希函数计算元素位置,实现快速访问。 - **元素顺序**:不保证元素的迭代顺序(即无序),顺序可能随时间变化[^1]。 - **性能**:添加、删除查找操作的平均时间复杂度为$O(1)$(常数时间),在理想哈希条件下效率最高[^1]。 - **允许null值**:是,允许一个`null`元素。 - **线程安全**:非线程安全,需外部同步。 - **适用场景**:当需要快速查找去重,且不关心元素顺序时,如缓存或集合运算。 示例代码: ```java Set<String> hashSet = new HashSet<>(); hashSet.add("Apple"); hashSet.add("Banana"); hashSet.add(null); // 允许null System.out.println(hashSet); // 输出可能为[null, Apple, Banana]或其他无序顺序 ``` #### 2. **LinkedHashSet** - **底层实现**:继承自`HashSet`,但使用`LinkedHashMap`维护一个双向链表,记录元素的插入顺序[^2][^3]。 - **元素顺序**:保证元素的迭代顺序与插入顺序一致(即有序),适合需要保留添加顺序的场景。 - **性能**:操作平均时间复杂度也为$O(1)$,但由于链表维护的开销,性能略低于`HashSet`但高于`TreeSet`[^4]。 - **允许null值**:是,允许一个`null`元素。 - **线程安全**:非线程安全。 - **适用场景**:当需要保留插入顺序且高效访问时,如LRU缓存或日志记录。 示例代码: ```java Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("Apple"); linkedHashSet.add("Banana"); linkedHashSet.add("Cherry"); System.out.println(linkedHashSet); // 输出总是[Apple, Banana, Cherry],保持插入顺序 ``` #### 3. **TreeSet** - **底层实现**:基于`TreeMap`实现,使用红黑树(一种自平衡二叉搜索树)存储元素。元素按自然顺序或自定义`Comparator`排序[^4]。 - **元素顺序**:保证元素按升序排序(自然排序)或指定顺序排序(自定义排序),即有序集合。 - **性能**:添加、删除查找操作的平均时间复杂度为$O(\log n)$(对数时间),因为需要维护树结构平衡,性能低于前两者[^4]。 - **允许null值**:否(如果使用自然排序,添加`null`会抛出`NullPointerException`;自定义`Comparator`可能支持,但不推荐)。 - **线程安全**:非线程安全。 - **适用场景**:当需要元素排序范围查询(如`subSet()`方法)时,如排行榜或有序数据集。 示例代码: ```java Set<String> treeSet = new TreeSet<>(); treeSet.add("Cherry"); treeSet.add("Apple"); treeSet.add("Banana"); System.out.println(treeSet); // 输出总是[Apple, Banana, Cherry],按字母升序排序 ``` #### 关键区别总结 下表总结了三种`Set`实现的核心区别: | 特性 | HashSet | LinkedHashSet | TreeSet | |----------------------|------------------|-----------------------------|----------------------| | **底层实现** | 基于HashMap | 基于LinkedHashMap(HashSet子类)[^2][^3] | 基于TreeMap(红黑树) | | **元素顺序** | 无序 | 插入顺序有序 | 排序顺序(自然或自定义) | | **平均时间复杂度** | $O(1)$(操作) | $O(1)$(略低于HashSet)[^4] | $O(\log n)$(操作) | | **允许null** | 是 | 是 | 否(通常) | | **主要优势** | 最高性能 | 保留插入顺序 | 自动排序 | | **典型应用场景** | 去重、快速查找 | 有序迭代(如FIFO缓存) | 排序集合、范围查询 | #### 性能比较 根据基准测试,在插入查找操作上: - `HashSet`性能最高(哈希表直接定位)。 - `LinkedHashSet`次之(链表维护增加轻微开销)。 - `TreeSet`最慢(树结构需动态平衡)[^4]。 #### 如何选择? - 需要**最高性能无序存储**?选`HashSet`。 - 需要**保留插入顺序**?选`LinkedHashSet`。 - 需要**元素排序或范围操作**?选`TreeSet`。 总之,理解这些区别有助于优化代码性能。如果您有具体使用场景,我可以提供更针对性的建议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值