,但是需要注意的是TreeMap
它还实现了NavigableMap
接口和SortedMap
接口 。
实现 NavigableMap
接口让 TreeMap
有了对集合内元素的搜索的能力 。
实现SortedMap
接口让 TreeMap
有了对集合中的元素根据键排序的能力 。默认是按 key 的升序排序 , 不过我们也可以指定排序的比较器 。示例代码如下:
/** * @author shuang.kou * @createTime 2020年06月15日 17:02:00 */public class Person {private Integer age;public Person(Integer age) {this.age = age;}public Integer getAge() {return age;}public static void main(String[] args) {TreeMap<Person,String> treeMap = new TreeMap<>(new Comparator<Person>() {@Overridepublic int compare(Person person1,Person person2) {int num = person1.getAge() - person2.getAge();return Integer.compare(num,0);}});treeMap.put(new Person(3),"person1");treeMap.put(new Person(18), "person2");treeMap.put(new Person(35), "person3");treeMap.put(new Person(16), "person4");treeMap.entrySet().stream().forEach(personStringEntry -> {System.out.println(personStringEntry.getValue());});}}
输出:
person1person4person2person3
可以看出 , TreeMap
中的元素已经是按照 Person
的 age 字段的升序来排列了 。
上面 , 我们是通过传入匿名内部类的方式实现的,你可以将代码替换成 Lambda 表达式实现的方式:
TreeMap<Person,String> treeMap = new TreeMap<>((person1,person2) -> {int num = person1.getAge() - person2.getAge();return Integer.compare(num, 0);});
综上 , 相比于HashMap
来说 TreeMap
主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力 。
HashSet 如何检查重复以下内容摘自《Head first java》第二版:
当你把对象加入HashSet
时 , HashSet
会先计算对象的hashcode
值来判断对象加入的位置 , 同时也会与其他加入的对象的 hashcode
值作比较 , 如果没有相符的 hashcode
,HashSet
会假设对象没有重复出现 。但是如果发现有相同 hashcode
值的对象,这时会调用equals()
方法来检查 hashcode
相等的对象是否真的相同 。如果两者相同,HashSet
就不会让加入操作成功 。
HashMap 的底层实现JDK1.8 之前JDK1.8 之前 HashMap
底层是 数组和链表 结合在一起使用也就是 链表散列 。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突 。
所谓扰动函数指的就是 HashMap 的 hash 方法 。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞 。
JDK 1.8 HashMap 的 hash 方法源码:
JDK 1.8 的 hash 方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变 。
static final int hash(Object key) {int h;// key.hashCode():返回散列值也就是hashcode// ^ :按位异或// >>>:无符号右移,忽略符号位,空位都以0补齐return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
所谓 “拉链法” 就是:将链表和数组相结合 。也就是说创建一个链表数组,数组中每一格就是一个链表 。若遇到哈希冲突,则将冲突的值加到链表中即可 。
文章插图
DK1.8 之后相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间 。
TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树 。红黑树就是为了解决二叉查找树的缺陷 , 因为二叉查找树在某些情况下会退化成一个线性结构HashMap 的长度为什么是 2 的幂次方为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀 。我们上面也讲到了过了,Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的 。但问题是一个 40 亿长度的数组 , 内存是放不下的 。所以这个散列值是不能直接拿来用的 。用之前还要先做对数组的长度取模运算 , 得到的余数才能用来要存放的位置也就是对应的数组下标 。这个数组下标的计算方法是“
推荐阅读
- Java单例模式,看这一篇就够了
- 微信支付v3接口的 官方 Java SDK
- Java函数式编程:二、高阶函数,闭包,函数组合以及柯里化
- 夯实Java基础,一篇文章全解析线程问题
- 四 Java多线程-ThreadPool线程池-2
- 生成器函数 javascript异步编程之generator与asnyc/await语法糖
- Java Timer使用介绍
- 三 Java多线程-ThreadPool线程池
- 二 Java多线程-线程关键字
- 二 Java 编码那些事