zoukankan      html  css  js  c++  java
  • TreeMap、TreeSet、equals()、hashCode()、compareTo()

    为什么构造 TreeMap 或者 TreeSet的时候,无法指定容器的大小,跟其数据机构,也就是红黑树有关吗?

    个人推测:

      在构造容器类时,指定容器大小的主要作用是避免容器的频繁扩容带来的系统开销,这对于包含数组结构的容器来说是适用的,因为数组结构定义的时候长度是固定的,一旦数据长度超过大小限制,就需要扩容,据我了解扩容策略一般是加倍,比如HashMap,由8扩容为16。而TreeSet和TreeMap的底层实现,我记得是红黑树(不知道有没有搞错)。而红黑树与数组不同,大小并不是固定的,因此不存在扩容的系统开销问题,也就不需要指定所谓容器的大小。

      这里可以联想一下链表(LinkedList),因为链表相对树结构来说比较简单,在构造该容器时,不能也无需指定容器大小,往链表添加数据时,直接修改某个或者某些节点的指针即可,没有扩容操作。

      从操作系统层面来解释,数组在底层是使用连续内存存储的,然而非数组数据结构、例如链表和树,这些在内存是离散分布的。显然,连续内存才存在扩容的概念。

    注意 TreeSet 和 HashSet 判断相等的依据不同。

    HashSet  是以equals() 和 hashCode() 方法来判断是否相等,而TreeSet 是以compareTo() 方法来判断。

    测试例子1:

    import lombok.Data;
    
    import java.util.HashSet;
    import java.util.TreeSet;
    
    public class Test1 {
    
        public static void main(String[] args) {
    
            testTreeMap();
    
        }
    
        static void testTreeMap() {
            TreeSet<Test3> treeSet = new TreeSet<>();
            HashSet<Test3> hashSet = new HashSet<>();
    
            Test3 test3 = new Test3(12l);
            Test3 test31 = new Test3(20l);
    
            Test3 test32 = new Test3(12l);
            Test3 test33 = new Test3(2l);
    
            treeSet.add(test3);
            treeSet.add(test31);
            treeSet.add(test32);
            treeSet.add(test33);
            System.out.println("====treeSet=====");
            System.out.println(treeSet);
    
            hashSet.add(test3);
            hashSet.add(test31);
            hashSet.add(test32);
            hashSet.add(test33);
            System.out.println("====hashSet=====");
            System.out.println(hashSet);
    
    
        }
    
    
    }
    
    @Data
    class Test3 implements Comparable {
        private Long count;
    
        public Test3(Long count) {
            this.count = count;
        }
    
        @Override
        public boolean equals(Object o) {
            return false;
        }
    
        @Override
        public int hashCode() {
            return 1;
        }
    
        @Override
        public int compareTo(Object o) {
            return 0;
        }
    }

    输出结果: 

    ====treeSet=====
    [Test3(count=12)]
    ====hashSet=====
    [Test3(count=12), Test3(count=20), Test3(count=12), Test3(count=2)]

    测试例子2:(把上面代码的 equals() 方法返回值改为true, compareTo() 返回值改为非0)

    输出结果:

    ====treeSet=====
    [Test3(count=12), Test3(count=20), Test3(count=12), Test3(count=2)]
    ====hashSet=====
    [Test3(count=12)]

    注意:对象的hashCode()函数,如果不重写,则默认调用Object.hashCode();函数,每个对象都是不同的(JDK8 的默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift scheme随机数算法得到的一个随机数--这句话摘抄自下面的链接);而java的一些内置对象,比如 Integer  String 等,都重写了hashCode()方法,保证相同内容的Integer 或者 String对象的hashCode() 是相等的。因此如果我们希望自定义对象的比较规则,则需要同时实现equals()和hashCode()方法。

    Java Object.hashCode()返回的是否对象内存地址? https://www.jianshu.com/p/be943b4958f4

    当需要使用TreeSet来存放对象时,对象的类必须实现Comparable 接口,实现比较的规则,即方法:compareTo(),该方法包含两层意义:两个对象是否相等,如果相等,那么在 TreeSet 中不能重复添加;以及如果对象不相等,则按照排序规则升序排序

    注意,如果往TreeSet存放没有实现Comparable接口的对象时,会抛出异常:java.lang.ClassCastException: com.reconova.vehicle.AlphaAndCounts cannot be cast to java.lang.Comparable

    测试例子:

    import lombok.Data;
    
    import java.util.Set;
    import java.util.TreeSet;
    
    public class Test1 {
    
        public static void main(String[] args) {
            testSet();
        }
    
        static void testSet() {
            AlphaAndCounts alphaAndCounts = new AlphaAndCounts("a", 1);
            AlphaAndCounts alphaAndCounts2 = new AlphaAndCounts("b", 3);
            AlphaAndCounts alphaAndCounts3 = new AlphaAndCounts("c", 2);
            AlphaAndCounts alphaAndCounts4 = new AlphaAndCounts("c", 5);
    
            Set<AlphaAndCounts> set = new TreeSet<>();
            set.add(alphaAndCounts);
            set.add(alphaAndCounts2);
            set.add(alphaAndCounts3);
            set.add(alphaAndCounts4);
            System.out.println(set);
    
    
        }
    
    }
    
    @Data
    class AlphaAndCounts implements Comparable {
    
        private String vocabulary;
    
        private int counts;
    
        public AlphaAndCounts(String vocabulary, int counts) {
            this.vocabulary = vocabulary;
            this.counts = counts;
        }
    
        @Override
        public int compareTo(Object o) {
            if (o == null || !(o instanceof AlphaAndCounts)) {
                return 1;
            }
    
            AlphaAndCounts that = (AlphaAndCounts) o;
            if (that.getVocabulary().equals(this.vocabulary)) {
                return 0;
            }
    
            return this.counts - that.counts;
        }
    }

    输出结果

    [AlphaAndCounts(vocabulary=a, counts=1), AlphaAndCounts(vocabulary=c, counts=2), AlphaAndCounts(vocabulary=b, counts=3)]

    结论:实现了Comparable接口的类,其重载的方法compareTo() 既可以进行大小比较,也可以进行相等比较,因此可以在TreeSet集合中过滤相等的元素,默认是按照升序排序的。

    除了直接让对象实现Comparable接口,还有另外一种实现排序的方法:独立创建一个比较器类,让该类实现Comparator 接口,然后构造容器时,传入该构造器对象

    测试例子:

    import lombok.Data;
    
    import java.util.Comparator;
    import java.util.Set;
    import java.util.TreeSet;
    
    public class Test1 {
    
        public static void main(String[] args) {
            testSet();
        }
    
        static void testSet() {
            AlphaAndCounts alphaAndCounts = new AlphaAndCounts("a", 1);
            AlphaAndCounts alphaAndCounts2 = new AlphaAndCounts("b", 3);
            AlphaAndCounts alphaAndCounts3 = new AlphaAndCounts("c", 2);
            AlphaAndCounts alphaAndCounts4 = new AlphaAndCounts("c", 5);
    
            Set<AlphaAndCounts> set = new TreeSet<>(new MyComparator());
            set.add(alphaAndCounts);
            set.add(alphaAndCounts2);
            set.add(alphaAndCounts3);
            set.add(alphaAndCounts4);
            System.out.println(set);
    
    
        }
    
    }
    
    class MyComparator implements Comparator<AlphaAndCounts> {
        @Override
        public int compare(AlphaAndCounts o1, AlphaAndCounts o2) {
    
            if (o1 == null) {
                if (o2 == null) {
                    return 0;
                } else {
                    return -1;
                }
            }
            if (o1.getVocabulary() != null && o2.getVocabulary() != null && o1.getVocabulary().equals(o2.getVocabulary())) {
                return 0;
            }
            return o1.getCounts() - o2.getCounts();
    
        }
    }
    
    @Data
    class AlphaAndCounts {
    
        private String vocabulary;
    
        private int counts;
    
        public AlphaAndCounts(String vocabulary, int counts) {
            this.vocabulary = vocabulary;
            this.counts = counts;
        }
    
    }

    输出结果跟上面的例子是一样的

    但是要注意,上面的程序有个隐藏的bug。假如构造的对象中,其中有任意两个对象的counts 值是相等的,那即使字符串不同,也会被认为是同一个对象,因此Set容器会舍弃一个。为了防止这种情况,应该把对象的相等比较,以及大小比较两类逻辑分开实现,不要糅合到一块,否则逻辑也比较混乱。

    解决方法: 可以在类里面重写 equals 和 hashCode() 方法,独立定义一个比较器,使用list来处理对象数据,最后使用Collections.sort() 方法对list进行排序,即可获得最终排序结果。

    import lombok.Data;
    
    import java.util.*;
    
    public class Test1 {
    
        public static void main(String[] args) {
            testHashMap();
        }
    
        static void testHashMap() {
            String keya = "a";
            String keyb = "b";
            String keyc = "c";
            String keyd = "d";
    
            ExtendHashMap hashMap = new ExtendHashMap();
            hashMap.inc(keya);
            hashMap.inc(keya);
            hashMap.inc(keya);
            hashMap.inc(keya);
            hashMap.inc(keya);
    
            hashMap.inc(keyb);
            hashMap.inc(keyb);
    
            hashMap.inc(keyc);
            hashMap.inc(keyc);
            hashMap.inc(keyc);
    
            hashMap.inc(keyd);
            hashMap.inc(keyd);
    
            System.out.println(hashMap);
    
            System.out.println("======");
    
            Collection<AlphaAndCounts> values = hashMap.values();
            ArrayList<AlphaAndCounts> arrayList = new ArrayList<>(values);
            System.out.println(arrayList);
    
            Collections.sort(arrayList, new MyComparator());
            System.out.println(arrayList);
    
        }
    
    }
    
    class ExtendHashMap extends HashMap<String, AlphaAndCounts> {
    
        public void inc(String key) {
            AlphaAndCounts value = this.get(key);
            if (value == null) {
                AlphaAndCounts alphaAndCounts = new AlphaAndCounts(key, 1);
                this.put(key, alphaAndCounts);
            } else {
                value.setCounts(value.getCounts() + 1);
            }
        }
    
    }
    
    class MyComparator implements Comparator<AlphaAndCounts> {
        @Override
        public int compare(AlphaAndCounts o1, AlphaAndCounts o2) {
    
            if (o1 == null) {
                if (o2 == null) {
                    return 0;
                } else {
                    return -1;
                }
            }
            if (o1.getVocabulary() == null) {
                if (o2.getVocabulary() == null) {
                    return 0;
                } else {
                    return -1;
                }
            }
    
            return o1.getCounts() - o2.getCounts();
    
        }
    }
    
    @Data
    class AlphaAndCounts {
    
        private String vocabulary;
    
        private int counts;
    
        public AlphaAndCounts(String vocabulary, int counts) {
            this.vocabulary = vocabulary;
            this.counts = counts;
        }
    
        public void incCounts() {
            this.counts++;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            AlphaAndCounts that = (AlphaAndCounts) o;
            return Objects.equals(vocabulary, that.vocabulary);
        }
    
        @Override
        public int hashCode() {
    
            return Objects.hash(vocabulary);
        }
    }

    输出结果:

    {a=AlphaAndCounts(vocabulary=a, counts=5), b=AlphaAndCounts(vocabulary=b, counts=2), c=AlphaAndCounts(vocabulary=c, counts=3), d=AlphaAndCounts(vocabulary=d, counts=2)}
    ======
    [AlphaAndCounts(vocabulary=a, counts=5), AlphaAndCounts(vocabulary=b, counts=2), AlphaAndCounts(vocabulary=c, counts=3), AlphaAndCounts(vocabulary=d, counts=2)]
    [AlphaAndCounts(vocabulary=b, counts=2), AlphaAndCounts(vocabulary=d, counts=2), AlphaAndCounts(vocabulary=c, counts=3), AlphaAndCounts(vocabulary=a, counts=5)]

    参考链接:

      equals()和hashCode()区别? https://www.cnblogs.com/jesonjason/p/5492208.html

  • 相关阅读:
    Linux 命令后台运行
    Linux Mint,Ubuntu 18 ,Deepin15.7 安装mysql 没有提示输入密码,修改root用户密码过程
    爬虫第一篇基本库的使用——urllib
    Python开发第五篇
    php 5.6 安装openssl extension 出现编译错误
    bash 中的变量可以这么用
    查表法现实数学函数
    封装boto3 api用于服务器端与AWS S3交互
    python 打包详解
    Python使用boto3操作AWS S3中踩过的坑
  • 原文地址:https://www.cnblogs.com/zhangxuezhi/p/11574742.html
Copyright © 2011-2022 走看看