set

 

Set集合里的多个对象之间没有明显的顺序。Set继承自Collection接口,不能包含有重复元素。判断两个对象相同不是使用"=="运算符,而是根据equals方法。也就是说,我们在加入一个新元素的时候,如果这个新元素对象和Set中已有对象进行注意equals比较都返回false,则Set就会接受这个新元素对象,否则拒绝。因为Set的这个制约,在使用Set集合的时候,应该注意两点:1) 为Set集合里的元素的实现类实现一个有效的equals(Object)方法、2) 对Set的构造函数,传入的Collection参数不能包含重复的元素。

 

如果想要査看某个指定的元素, 却又忘记了它的位置, 就需要访问所有元素, 直到找到为止。如果集合中包含的元素很多, 将会消耗很多时间。如果不在意元素的顺序,可以有几种能够快速査找元素的数据结构。其缺点是无法控制元素出现的次序。

 

有一种众所周知的数据结构, 可以快速地査找所需要的对象, 这就是散列表(hash表由hashCode 函数得出的散列码table )。散列表为每个对象计算一个整数, 称为散列码(hashcode。) 散列码是由对象的实例域产生的一个整数。更准确地说, 具有不同数据域的对象将产生不同的散列码。a.euqals(b)就是运用散列表的原理。

 

在 Java 中,散列表用链表数组实现。每个列表被称为桶( bucket) (参看图 9-10。) 要想査找表中对象的位置, 就要先计算它的散列码, 然后与桶的总数取余, 所得到的结果就是保存这个元素的桶的索引。例如, 如果某个对象的散列码为 76268, 并且有 128 个桶, 对象应该保存在第 108 号桶中(76268除以 128余 108 )。或许会很幸运, 在这个桶中没有其他元素,此时将元素直接插人到桶中就可以了。

 

当然,有时候会遇到桶被占满的情况, 这也是不可避免的。这种现象被称为散列冲突( hashcollision) 。这时, 需要用新对象与桶中的所有对象进行比较,査看这个对象是否已经存在。如果散列码是合理且随机分布的, 桶的数目也足够大, 需要比较的次数就会很少。

 

散列表可以用于实现几个重要的数据结构。 其中最简单的是 set 类型。set 是没有重复元素的元素集合。set 的 add方法首先在集中查找要添加的对象,如果不存在,就将这个对象添加进去。

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

HashSet类

Java集合类库中提供了HashSet类实现了Set接口。这个类的底层是使用HashMap实现的:

 

private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();

我们知道,HashMap是一个键值对,而HashSet存储的不是键值对,因此为了使用HashMap存储HashSet的元素,就需要构造一个键值对。可以使用需要存储在HashSet中的元素作为键,而上面的PRESENT作为值,就构成了一个键值对,这样就可以存在HashMap中了。

也就是说,HashSet与HashMap的原理一样,不同的是HashSet的值都一样,都是PRESENT。

由于在前一节中已经详细介绍了HashMap的原理,这里不再叙述了。只说一下HashSet的使用。

下面的代码从System.in中读取单词,然后将它们添加到HashSet中,再打印出所有的单词。由于HashSet中不存储相同的元素,所以打印出来的单词是不重复的。运行这个程序时使用下面的命令行:

java SetTest < alice.txt

这样就把alice.txt作为输入,程序就会读取所有的单词。代码如下:

import java.util.*; public class SetTest { public static void main(String[] args) { Set<String> words = new HashSet<>(); // HashSet implements Set long totalTime = 0; Scanner in = new Scanner(System.in); while (in.hasNext()) { String word = in.next(); long callTime = System.currentTimeMillis(); words.add(word); callTime = System.currentTimeMillis() - callTime; totalTime += callTime; } Iterator<String> iter = words.iterator(); for (int i = 1; i <= 20 && iter.hasNext(); i++) System.out.println(iter.next()); System.out.println(". . ."); System.out.println(words.size() + " distinct words. " + totalTime + " milliseconds."); } }

 

可以看见,一共有5392个不同的单词。

 

 

Set 集合是无序不可以重复的的、List 集合是有序可以重复的。

 

HashSet操作的时间复杂度:HashSet的基础数据结构是散列表。因此,对于添加,移除和查找(包含方法)操作,HashSet需要O(1)次的时间复杂度(平均或通常情况下)的时间复杂度。

 

 

 

注意:重写queals和hashcode 防止类中出现相同的信息出现

原因:

1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;

2、如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;

3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

 

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有 例如内存中有这样的位置 0 1 2 3 4 5 6 7 而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。 但如果用hashcode那就会使效率提高很多。 我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。 2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。 也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。 那么。重写了equals(),为什么还要重写hashCode()呢? 想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊。

public class DateStruct { public static class Person { // 性别 String sex; // 姓名 String name; // 身高 Double hei; // 体重 Double wei; public Person(String n, String s, Double h, Double w) { this.name = n; this.sex = s; this.hei = h; this.wei = w; } public String toString() { return "\n姓名:" + this.name + " 性别:" + this.sex + " 身高:" + this.hei + " 体重:" + this.wei; } /* Jack是同一个人,却在集合中出现了两次,这是什么原因呢? 这是因为,Person是Object的子类, 而Object类的equals()方法是根据对象的内存地址来判断两个对象是否相等的,由于两次插入的Jack的内存地址肯定不相同, 所以判断的结果是不相等,所以两次都插入了。 于是,我们需要覆写equals()方法来判断两个对象是否是同一个对象。 因为Object的Hash码返回的是对象的Hash地址, 而两个对象的Hash地址肯定是不相等的,所以6次插入的对象被存储在6个存储区域,equals()方法根本没有运行。 于是,还需要覆写hashCode()方法,根据姓名来计算对象的Hash码 */ // 覆写hashCode方法 public int hashCode(){ return this.name.hashCode(); } // 覆写equals方法 public boolean equals (Object obj){ // 地址相等,则肯定是同一个对象 if(this==obj){ return true; } // 类型不同,则肯定不是同一类对象 if(!(obj instanceof Person)){ return false; } // 类型相同,向下转型 Person per=(Person) obj; // 如果两个对象的姓名和性别相同,则是同一个人 if(this.name.equals(per.name)&&this.sex.equals(per.sex)) return true; return false; } } public static void main(String[] args){ Set<Person> mySet = new HashSet<>(); mySet.add(new Person("Tom","Male",170.0,70.0)); mySet.add(new Person("Peter","Male",175.0,70.0)); mySet.add(new Person("Kate","Female",168.0,60.0)); mySet.add(new Person("Alice","Female",161.0,55.0)); mySet.add(new Person("Jack","Male",190.0,95.0)); mySet.add(new Person("Jack","Male",190.0,95.0)); System.out.println(mySet); } }

 

方法:

  1. boolean add(E e):用于添加指定元素(如果不存在),如果存在则返回false。
  2. void clear():用于删除set中的所有元素。
  3. boolean contains(Object o): 如果元素存在于set中,则返回true。
  4. boolean remove(Object o):用于删除元素,如果它存在于set中。
  5. Iterator iterator(): 用于返回集合中元素的迭代器。
  6. boolean isEmpty():用于检查集合是否为空。返回true表示空,false表示非空条件。
  7. int size():用于返回集合的大小。
  8. Object clone():用于创建集合的浅表副本。

 

package com.datastruct; import java.util.HashSet; import java.util.Iterator; /*** * @ClassName: DateStruct * @Description:DATA STRUCT OF JAVA * @Auther: binbin * @Date: 2019/1/13 13:19 * @version: */ public class DateStruct { public static void main(String[] args){ HashSet h1 = new HashSet(); h1.add(1); h1.add("gg"); h1.add(2.3454); h1.add(1);//不重复 Iterator ee = h1.iterator(); while(ee.hasNext()){ System.out.println(ee.next()); } } }

 

//可以这样的初始化 public static final HashSet<String> salesWords = new HashSet<String>() {{ add("销售额"); add("销售"); add("销售金额"); add("销售值"); add("销售总金额"); add("销售总额"); add("销售总值"); }};

、、、、、、、、、、、、、、、TreeSet、、、、、、、、、、、、、、、、、

 

  1. 特点

用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列

  1. 使用方式
    • a.自然顺序(Comparable)
      • TreeSet类的add()方法中会把存入的对象提升为Comparable类型
      • 调用对象的compareTo()方法和集合中的对象比较
      • 根据compareTo()方法返回的结果进行存储
    • b.比较器顺序(Comparator)
      • 创建TreeSet的时候可以制定 一个Comparator
      • 如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
      • add()方法内部会自动调用Comparator接口中compare()方法排序
      • 调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
    • c.两种方式的区别
      • TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
      • TreeSet如果传入Comparator, 就优先按照Comparator。

 

 

使用 TreeSet 的对象注意要实现重写的三个接口:

* 1. hashCode()

* cause:TreeSet 依靠 TreeMap,TreeMap 依赖HashTable

* 2. equals()

* cause:比较对象是否相同

* 3. compareTo() - Comparable<Person>

* cause:排序

 

方法:

•TreeSet()

•TreeSet(Comparator<? super E> comparator)

构造一个空树集。

•TreeSet(Col 1 ection<? extends E> elements)

• TreeSet(SortedSet<E> s)

构造一个树集, 并增加一个集合或有序集中的所有元素(对于后一种情况, 要使用同样的顺序)

 

Comparator ? super E> comparator )

•返回用于对元素进行排序的比较器。如果元素用 Comparable 接口的 compareTo方法进行比较则返回 null。

• E firstO

• E 1 ast()

返回有序集中的最小元素或最大元素。

 

• E higher(E value)

• E 1 ower(E value)

返回大于 value 的最小元素或小于 value 的最大元素,如果没有这样的元素则返回 null•E cei 1 ing(E value)

• E floor(E value)

返回大于等于 vaiue 的最小元素或小于等于 value 的最大元素, 如果没有这样的元素

则返回 null。• E pollFirst()

• E pol 1 Last

删除并返回这个集中的最大元素或最小元素, 这个集为空时返回 null。•Iterator<E> descend *

ngIterator()

返回一个按照递减顺序遍历集中元素的迭代

 

public class Test { public static void main(String[] args) { TreeSet<String> treeSet=new TreeSet<>(); treeSet.add("Bili"); treeSet.add("Amy"); treeSet.add("cDy"); for (String string : treeSet) { System.out.println(string); //排序 } } //如果想插入自定义的对象,必须通过实现Comparable接口来定义一个排列的顺序 /**  当然也可以自定义一个比较器Comparator,实现compare方法。   注意:   1、TreeSet的排列顺序必须是全局顺序,也就是说任何两个元素都是必须可比的,同时只有当他们比较相同时才返回0。   2、如果树集包含了n个元素,那么平均需要进行log2n次比较,才能找到新元素的正确位置。 */ public class DateStruct { public static class Person implements Comparable<Person> { int age; String name; String address; public Person(int age, String name) { super(); this.age = age; this.name = name; } @Override public int compareTo(Person o) { return this.age-o.age; } } //我们需要告诉TreeSet如何来进行比较元素,如果不指定,就会出现异常。 // public int compareTo(Person o) { // int num = this.age - o.age; //年龄是比较的主要条件 // return num == 0 ? this.name.compareTo(o.name) : num;//姓名是比较的次要条件 // } // public int compareTo(Person o) { // int num = this.name.compareTo(o.name); //姓名是主要条件 // return num == 0 ? this.age - o.age : num; //年龄是次要条件 // } // public int compareTo(Person o) { // int length = this.name.length() - o.name.length(); //比较长度为主要条件 // int num = length == 0 ? this.name.compareTo(o.name) : length; //比较内容为次要条件 // return num == 0 ? this.age - o.age : num; //比较年龄为次要条件 // } // public static void main(String[] args){ TreeSet<Person> treeSet=new TreeSet<>(); treeSet.add(new Person(12, "xujian")); treeSet.add(new Person(6, "luyang")); treeSet.add(new Person(10, "xiewei")); for (Person person : treeSet) { System.out.println(person.name+":"+person.age); } } }

 

 

===========================HashSet================================

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值