为什么构造 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