JavaSE高级程序设计总结、训练——集合的基本使用方法总结2(Set接口)

集合的基本使用方法总结

三、 HashSet 集合的使用方法

3.1、几个HashSet使用的注意事项:

① HashSet 类是按照哈希算法来存储集合中的元素的。虽然Set集合存放元素在地址(底层)上是无序的,不能从底层上进行O(1)的随机访问、随机存取。但是由于按照哈希值存储元素,就相当于给元素带上了一个index,这个index就是哈希值。所以HashSet具有良好的存取、查找性能,比基于红黑树、平衡树的存储:TreeSet要好很多。但是这种HashSet是无序的,如果需要排序,最好就还是用TreeSet了,因为平衡树的建树过程就自动地排序、去重了!

② HashSet的对象是可以存储 null 的!这个十分需要注意,如果不小心在存储的过程中存入了 null ,就不会报错,那么在使用的时候, 就很可能空指针异常!

③ 这种集合的操作是线程不安全的,所谓线程不安全,就是对资源的访问控制有问题。当多线程竞争使用某资源的时候,不能做到线程同步、资源互斥!所以可以使用锁、或者写时拷贝的方式进行(Java自带写时拷贝的类!)

这是最重要的一点!! 那就是,对于没有重写 equals() 和 hashcode() 的类,不能随便使用HashSet 去存储,因为 Set 的去重是根据判断集合中是否有另一个元素 e’ 满足 e’.equals(e) == true 来实现的!!所以如果没有重写equals() 方法,就会去比较引用指向的对象的地址!之所以还要实现hashcode() 方法是为了更高效地比较,因为hashcode() 先判断,如果 hash 值都不相等,则肯定两个对象不同,否则再执行 equals() 方法(有可能哈希碰撞,所以即使hash值相等也要判断equals() )

3.2、看一个简单的例子:

package SetInterface;

import java.util.*;

/**
 * @author jiangzl
 */
public class MyHashSet {
    
    
    public static void main(String[] args){
    
    
        HashSet mySet = new HashSet();
        mySet.add(null);
        mySet.add(123);
        mySet.add("hello");
        mySet.add(new String("hello"));
        mySet.add(new Student("jiangzl", 92));
        mySet.add(new Student("jiangzl", 92));

        Iterator iterator = mySet.iterator();
        while(iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }
    }
}

/**
 * @author jiangzl
 */
class Student{
    
    
    private String name;
    private int score;

    public Student(String name, int score){
    
    
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString(){
    
    
        return new String("姓名:" + name + " , 分数:" + score);
    }
}

第一:先来看看输出结果

在这里插入图片描述

第二,分析一下这个例子

① null 空指针是可以存放到HashSet里面去的,输出也可以输出,显示的就是 null ,但是轻易不要去操作它,建议在访问HashSet的元素的时候,来个判空,避免空指针异常!

② 说好的 HashSet 具有去重的功能,但是为何两个 Student 对象,成员变量完全一样,却保存在同一个 HashSet 中呢?原因我们上面已经提过了,因为 HashSet 判重是根据hashcode() 和 equals() 方法做的。不妨看看阿里巴巴的代码规约如何提示:
在这里插入图片描述
③ 为何两个 “hello” 的String 对象没有在HashSet 中同时保存呢?哪怕都是 new 出来的对象,或者是字符串常量池的对象,他们都是String类的对象,String类是重写了hashcode() 和 equals() 方法的!所以会被判重机制算作重复!

第三,改进一下这个例子

如何重写 equals() 方法

这个问题其实是个很简单的问题,最好的建议就是判断每个成员变量是否相等即可。不够我们最好是合理利用一些既有的类的方法去处理,比如你如果有个String类的成员对象,那么最好是先判断hashcode(),再判断是否equals()

如何重写 hashcode() 方法

这个问题就是一个很值得讨论的问题了!

第一种好方法:
在IDEA中有快捷键帮助你完成这个工作,点击你的类,然后右键,选择Generate,然后就可以选择生成一些常用的方法了,其中就有hashcode() 和 equals() 方法!按照这种方法来修改,代码如下:
package SetInterface;

import java.util.*;

/**
 * @author jiangzl
 */
public class MyHashSet {
    
    
    public static void main(String[] args){
    
    
        HashSet mySet = new HashSet();
        mySet.add(null);
        mySet.add(123);
        mySet.add("hello");
        mySet.add(new String("hello"));
        mySet.add(new String("hello"));
        mySet.add(new Student("jiangzl", 92));
        mySet.add(new Student("jiangzl", 92));

        Iterator iterator = mySet.iterator();
        while(iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }
    }
}

/**
 * @author jiangzl
 */
class Student{
    
    
    private String name;
    private int score;

    public Student(String name, int score){
    
    
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString(){
    
    
        return new String("姓名:" + name + " , 分数:" + score);
    }

    @Override
    public boolean equals(Object o) {
    
    
        if (this == o) {
    
    
            return true;
        }
        if (!(o instanceof Student)) {
    
    
            return false;
        }
        Student student = (Student) o;
        return score == student.score && name.equals(student.name);
    }

    @Override
    public int hashCode() {
    
    
        return Objects.hash(name, score);
    }
}

看看这种方法的结果如何:

在这里插入图片描述
可以发现完成了去重!

第而种好方法:

如果你压根不用IDEA这种好的IDE呢?如果你工作的环境没有这么优秀的Generate工具呢?如何书写属于你自己的,相对好使的hashcode() 方法呢?

生成 hashcode() 的第一步:

定义一个初始值,一般设为 : res = 17

生成 hashcode() 的第二步:

根据你的成员变量,逐步生成各自的哈希值:

如果是 Boolean 类型的变量 var1 :[hashcode_var1] = var1 ? 1 : 0
如果是 int 类型(或者size小于int)的变量 var2 :[hashcode_var2] = (int)var2
如果是 long 类型的变量 var3 :[hashcode_var3] = (int) (var3 ^var3 >>>32)
如果是 double 类型的变量 var4 : [hashcode_var4] = var4.hashcode()
如果是某个对象的引用 var5 :[hashcode_var5] = var5.hashcode()
如果是数组类型,则类比String类的hashcode() 方法,计算一个以素数 p = 31(或者131、13131等)为基数的多项式

生成 hashcode() 的第三步:

生成最终的哈希值,也就是对每一个成员变量,依次、递推地计算:
res = res * 31 + [hashcode_varX] 即可!

最后,本例中我的示例代码(重写hashcode()与equals())如下:
package SetInterface;

import java.util.*;

/**
 * @author jiangzl
 */
public class MyHashSet {
    
    
    public static void main(String[] args){
    
    
        HashSet mySet = new HashSet();
        mySet.add(null);
        mySet.add(123);
        mySet.add("hello");
        mySet.add(new String("hello"));
        mySet.add(new String("hello"));
        mySet.add(new Student("jiangzl", 92));
        mySet.add(new Student("jiangzl", 92));

        Iterator iterator = mySet.iterator();
        while(iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }
    }
}

/**
 * @author jiangzl
 */
class Student{
    
    
    private String name;
    private int score;

    public String getName() {
    
    
        return name;
    }

    public int getScore(){
    
    
        return score;
    }

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

    public Student(String name, int score){
    
    
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString(){
    
    
        return new String("姓名:" + name + " , 分数:" + score);
    }

    @Override
    public boolean equals(Object o) {
    
    
        if(this == o){
    
    
            return true;
        }
        if(null == o || this.getClass() != o.getClass()){
    
    
            return false;
        }
        Student temp = (Student)o;
        if(temp.getScore() == this.getScore()){
    
    
            if(temp.getName().hashCode() == this.getName().hashCode()){
    
    
                return temp.getName().equals(this.getName());
            }
        }
        return false;
    }

    @Override
    public int hashCode() {
    
    
        int res = 17;
        res = 31 * res + (int)score;
        res = 31 * res + name.hashCode();
        return res;
    }
}

结果展示如下:
在这里插入图片描述
可以看到,效果还是ok的!

四、 TreeSet 集合的使用方法

4.1、看一个简单的例子

package SetInterface;

import java.util.*;

public class MyTreeSet {
    
    
    public static void main(String[] args){
    
    
        TreeSet myTreeSet = new TreeSet();
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        for (int i = 0;i < n;++i){
    
    
            int var = sc.nextInt();
            myTreeSet.add(var);
        }

        Iterator iterator = myTreeSet.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }
    }
}

看看它的输出结果:

在这里插入图片描述
从中可以看到,我们在插入元素到TreeSet集合的过程中就已经完成了排序!这也体现了它的底层是二叉排序树,而且是从小到大排序的。那么也就是说,对于一颗二叉树而言,它的递归定义是:左子树小于根节点小于右子树。

并且需要注意的是: 无论是HashSet还是TreeSet对集合的元素进行访问只能用迭代器进行操作,不能用get(index)之类的方法。原因就是:它们在底层实现上是无序的(也就是地址是不连续的),所以不存在有下标index的随机访问,只能用迭代器进行访问!

4.2、比较好用的一些方法

再看看这个例子:

package SetInterface;

import java.util.*;

public class MyTreeSet {
    
    
    public static void main(String[] args){
    
    
        TreeSet myTreeSet = new TreeSet();
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        for (int i = 0;i < n;++i){
    
    
            int var = sc.nextInt();
            myTreeSet.add(var);
        }

        System.out.println(myTreeSet.subSet(3, 7));
        System.out.println(myTreeSet.headSet(6));
        System.out.println(myTreeSet.tailSet(6));
        System.out.println(myTreeSet.tailSet(11));
    }
}

看看输出的结果:
在这里插入图片描述
说实话哈,一种python的既视感!也就是说,如果我们要使用这些个方法在实际开发或者算法解体中,切记要注意格式的变化,要先去掉中括号,然后按照 “逗号” 进行分割哈!

4.3、关于TreeSet的排序

TreeSet的排序还是老样子,有自然排序和自定义排序。这个就不赘述了,只要是jdk中有的数据类型,都可以自然排序,我们就记录《自定义排序》吧!

如何实现TreeSet的自定义排序?

在TreeSet对象的构造方法中,有这么些方法可以定义排序比较规则:

	public TreeSet(Comparator<? super E> var1) {
    
    
        this((NavigableMap)(new TreeMap(var1)));
    }

	public TreeSet(SortedSet<E> var1) {
    
    
        this(var1.comparator());
        this.addAll(var1);
    }
    

上面的代码来自jdk1.8的源代码,从此我们可以看到,我们只需要一个 实现 了Comparetor的对象,去给TreeSet对象进行实例化, 就完成了自定义排序的设计

看一个实现自定义排序的例子

package SetInterface;

import java.util.*;

/**
 * @author jiangzl
 */
public class MyTreeSet {
    
    
    public static void main(String[] args){
    
    
        TreeSet myTreeSet = new TreeSet(new MyComparetor());
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        for (int i = 0;i < n;++i){
    
    
            String name = sc.next();
            int prio = sc.nextInt();
            myTreeSet.add(new TreeSetTask(name, prio));
        }

        Iterator iterator = myTreeSet.iterator();
        while (iterator.hasNext()){
    
    
            System.out.println(iterator.next());
        }
    }
}

/**
 * @author jiangzl
 */
class TreeSetTask{
    
    
    private String taskName;
    private int taskPriority;

    public TreeSetTask(String name, int prio){
    
    
        this.taskName = name;
        this.taskPriority = prio;
    }

    public String getTaskName(){
    
    
        return taskName;
    }

    public int getTaskPriority(){
    
    
        return taskPriority;
    }

    @Override
    public String toString(){
    
    
        return new String("任务名:" + taskName + " , 任务优先级:" + taskPriority);
    }

    @Override
    public boolean equals(Object o) {
    
    
        if (this == o){
    
    
            return true;
        }
        if (!(o instanceof TreeSetTask)){
    
    
            return false;
        }
        TreeSetTask arraytask = (TreeSetTask) o;
        return getTaskPriority() == arraytask.getTaskPriority() && getTaskName().equals(arraytask.getTaskName());
    }

    @Override
    public int hashCode() {
    
    
        return Objects.hash(getTaskName(), getTaskPriority());
    }
}

/**
 * @author jiangzl
 */
class MyComparetor implements Comparator{
    
    

    @Override
    public int compare(Object o1, Object o2) {
    
    
        if((o1 instanceof TreeSetTask) && (o2 instanceof TreeSetTask)){
    
    
            TreeSetTask task1 = (TreeSetTask) o1;
            TreeSetTask task2 = (TreeSetTask) o2;
            if(task1.getTaskPriority() != task2.getTaskPriority()){
    
    
                return task2.getTaskPriority() - task1.getTaskPriority();
            }
            else{
    
    
                return task1.getTaskName().compareTo(task2.getTaskName());
            }
        }
        return 0;
    }
}

看看输出的效果先:
在这里插入图片描述
然后这里需要注意的是:中文编码里,字符串比较用compareTo()方法计算出来的,和ASCii编码计算出来的是相反的!这个也是有点奇怪的,好像昨天实验的过程中,没发现这个问题嗷!!这个问题,有比较懂的大哥还请评论或者私信一下,谢谢啦!

最后的一点点总结!!

要注意哈,HashSet 集合里头是可以插入不同类型的对象的!!我是这么理解的哈,因为HashSet是根据哈希值来index到对象的,所以只需要知晓对象的地址就可以进行存取。但是TreeSet 是需要排序的,如果是不同类型的对象,那压根无法排序,所以就会不支持嗷!

猜你喜欢

转载自blog.csdn.net/qq_44274276/article/details/107788924