Set接口(Hashset LinkedHashset Treeset)

本文详细介绍了Java中Set接口的三种实现:HashSet、TreeSet和LinkedHashSet的特点及应用场景。探讨了它们之间的区别,包括存储原理、排序方式及性能表现。

Set接口(HashSet,TreeSet和LinkedHashSet)

Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象。

事实1:set就是就是相当于Map的kay。

事实2:hashSet是为快速查找而设计的set,存入hashSet的元素必须定义hashCode。而treeSet是保持次序的set,使用treeSet可以从set中提取有序的序列,元素必须实现conparable接口。而

          linkedHashSet具有hashSet的查询速度,且内部是链表结构,因此遍历linkedHashSet时,结果会按照插入顺序显示!

 

HashSet
HashSet有以下特点
 不能保证元素的排列顺序,顺序有可能发生变化
 不是同步的
 集合元素可以是null,但只能放入一个null
当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

   demo1:

用户自定义类型放进set容器里面,必须要重写equals和hashCode方法,否则会被认为是两个不同的元素。

import java.util.HashSet;
import java.util.Set;

public class overwriteHahcodeAndEquals {
	
	public static void main(String[] args) {
		Set<Object> set = new HashSet<Object>();
		set.add(new Student("lopez",23));
		set.add(new Student("lopez",23));
		set.add(new Student("shengliang",33));
		set.add(new Student("weikun",23));
		set.add(new Student("chencheng",28));
		System.out.println(set);
		//output:[set.Student@1420a8f4, set.Student@9b725c34, set.Student@c2ce75cf, set.Student@8d6b816e]
		//一共4个对象
	}
}

class Student{//内部类
	
	String name;
	int age;
	
	public Student(String name , int age){
		this.name = name;
		this.age = age;
	}
	
	public int hashCode(){//重写hsahCode方法
		return age*name.hashCode();
	}
	
	public boolean equals(Object o){//重写equals方法
		Student s = (Student)o;
		return s.age == this.age && s.name.equals(this.name);
	}
}
   1.分别在hashCode和equals打断点debug,发现 在往Set中插入新的对象时,首先会用该对象的hashCode()与已经存在对象的hashCode()做比较,如果相等,那就不能插入,如果不等,才会调用equals()方法,如果equals结果为true,说明已经存在,          就不能再插入,如果为false,可以插入。  
   2.hashSet分别插入字符串对象。
set.add(new String("asd"));
set.add(new String("asd"));
//set只得到一个asd,因为内容相同的String,即使不重写hashCode方法,得到的hashCode都是相同的。
 
   demo2:
package set;

import java.util.HashSet;
import java.util.Set;

public class overwriteHahcodeAndEquals {
	
	public static void main(String[] args) {
		Set<Object> set = new HashSet<Object>();
		set.add(new Student("lopez",23));
		set.add(new Student("lopez",23));
		set.add(new Student("shengliang",33));
		set.add(new Student("weikun",23));
		set.add(new Student("chencheng",28));
		System.out.println(set);
		//output:[set.Student@c2ea3f, set.Student@1d5550d, set.Student@c21495, set.Student@1186fab, set.Student@14b7453]
		//一共5个对象
	}
}

class Student{//内部类
	
	String name;
	int age;
	
	public Student(String name , int age){
		this.name = name;
		this.age = age;
	}
}
   针对demo2,做了个小测试:

 

package set;

import java.util.HashSet;
import java.util.Set;

public class overwriteHahcodeAndEquals {
	
	public static void main(String[] args) {
		Set<Object> set = new HashSet<Object>();
		Student stu = new Student("lopez",23);
		Student stu1 = new Student("lopez",23);
		System.out.println("stu的hashCode值为:" + stu.hashCode());
		System.out.println("stu1的hashCode值为:" + stu1.hashCode());
		System.out.println("stu和stu1 equals比较" + stu.equals(stu1));
		set.add(stu);
		set.add(stu1);
		set.add(new Student("shengliang",33));
		set.add(new Student("weikun",23));
		set.add(new Student("chencheng",28));
		System.out.println(set);
	}
}
class Student{//内部类
	
	String name;
	int age;
	
	public Student(String name , int age){
		this.name = name;
		this.age = age;
	}
	
	/*public int hashCode(){
		return age*name.hashCode();
	}
	
	public boolean equals(Object o){
		Student s = (Student)o;
		return s.age == this.age && s.name.equals(this.name);//int类型的比较和String类型的比较
	}*/
}
  由以下输出可得知:
             //output:stu的hashCode值为:18378667
               //stu1的hashCode值为:21722195
              //stu和stu1 equals比较false
             /[set.Student@c2ea3f, set.Student@1d5550d, set.Student@c21495, set.Student@1186fab, set.Student@14b7453]
  当不重写对象的equals和hashCode方法,则stu和stu1对象比较equals和hashCode得到的值是不相等,所以得到5个对象。

 

 

 

LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好(链表结构),但是插入时性能稍微逊色于HashSet,因为插入时linkedHashSet需要维护链表结构。

package set;

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

public class CompareHashSetAndLinkedHashSet {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashSet set = new LinkedHashSet();
		set.add("lopez1");
		set.add("lopez2");
		set.add("lopez3");
		set.add("lopez4");
		for (Object obj : set) {
			System.out.println(obj.toString());
		}
		//output:
		//lopez1
		//lopez2
		//lopez3
		//lopez4
		
		
		HashSet set1 = new HashSet();
		set1.add("lopez1");
		set1.add("lopez2");
		set1.add("lopez3");
		set1.add("lopez4");
		for (Object obj : set1) {
			System.out.println(obj.toString());
		}
		//lopez1
		//lopez4
		//lopez3
		//lopez2
	}

}

 

 

TreeSet类
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法

 

按自然顺序demo1:

 

package set;

import java.util.TreeSet;
import java.util.Iterator;

public class TestTreeSet
{
    public static void main(String[] args)
    {
        TreeSet tree = new TreeSet();
        tree.add("China");
        tree.add("America");
        tree.add("England");
        tree.add("Japan");
        tree.add("Chinese");
        tree.add("lapez");
        tree.add("lopez");
        tree.add("lopez1");
        
        Iterator iter = tree.iterator();
        while(iter.hasNext())
        {
            System.out.println(iter.next());
        }
    }
   //output: America
    //China
    //Chinese
    //England
    //Japan
    //lapez
    //lopez
    //lopez1

}

   

   定制排序

package set;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

public class TestTreeSet {

    /** *//**
     * @param args
     */
    public static void main(String[] args) {

        MyComparator<String> myComparator = new MyComparator<String>();

        // /////////////////////不添加自定义排序
        TreeSet<String> treeSet1 = new TreeSet<String>();
        treeSet1.add("c");
        treeSet1.add("a");
        treeSet1.add("b");

        Iterator<String> iterator1 = treeSet1.iterator();
        while (iterator1.hasNext()) {
            System.out.println(iterator1.next());
        }

        // /////////////////////添加自定义排序
        TreeSet<String> treeSet2 = new TreeSet<String>(myComparator);
        treeSet2.add("c");
        treeSet2.add("a");
        treeSet2.add("b");

        Iterator<String> iterator2 = treeSet2.iterator();
        while (iterator2.hasNext()) {
            System.out.println(iterator2.next());
        }
    }

}

class MyComparator<T> implements Comparator<T> {

    public int compare(T arg0, T arg1) {
        if (arg0.equals(arg1)) {
            return 0;
        }
        return ((Comparable<T>) arg0).compareTo(arg1) * -1;
    }

}

 

 

 

<think>首先,用户的问题是:“我想理解Java集合框架中HashSetLinkedHashSetTreeSet的区别 请问JavaHashSet, 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]。 生成相关问题: - 基于内容,如:如何选择哪个SetTreeSet的排序如何工作?等。 现在,构建回答。</think>### JavaHashSetLinkedHashSetTreeSet的区别 在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、付费专栏及课程。

余额充值