Java学习16(MAP)
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习16(MAP)相关的知识,希望对你有一定的参考价值。
Java学习16
就是不怎么想看那个课题,还是看看老杜吧哈哈
泛型(Generic)
JDK5.0之后推出的新特性:泛型 <>
用泛型来指定集合中存储的数据类型
用了泛型之后,集合中的元素类型就更统一了
泛型这种机制,只在程序编译阶段起作用,只是给编译器参考的(运行阶段意义不大)
泛型的好处:
第一:集合中存储的元素类型统一了
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
泛型的缺点:
导致集合中存储的元素缺乏多样性!
在实际业务中,一般一个集合中都是一种类型,所以泛型被认可
JDK8之后引入了:自动类型推断机制(又称位钻石表达式)
List<Animal> myList = new ArrayList<>();
//后面的泛型<>里面可以空着,里面的类型会自动推断,被称为钻石表达式(JDK8之后才允许)
也可以自定义泛型:<E>中的E是一个标识符,随便写,但是一般写E或者T,E是element单词首字母,T是type首字母
foreach 增强for循环
JDK5.0之后的新特性
语法:
for(元素类型 变量名:数组或集合){
System.out.println(变量名);
} //变量名代表的就是数组/集合中的每一个元素
foreach有一个缺点,没有下标,在需要使用下标时,不建议使用foreach
Set集合
HashSet集合无序不可重复
TreeSet无序不可重复,但会自动排序
Map集合
1.与Collection没有继承关系
2.以key和Value的方式存储数据:键值对
key和value都是引用数据类型
key和value存储的都是内存地址
key起主导地位,value是key的一个附属品
3.Map接口中常用方法
void clear() 清空Map
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
V get(Object key) 通过key获取value
Set<K> keySet() 获取Map集合中所有的key,所有的键是一个Set集合
boolean isEmpty() 判断集合中的元素个数是否为0
V put(K key, V value) 向Map集合中添加键值对
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数
Collection<V> values() 获取Map集合中所有的value,返回一个Collection
Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合,Set集合中元素的类型是Map.Entry<K,V>,Map.Entry是一个Map中静态内部类
例如:map1集合对象:
key value
1 zhangsan
2 lisi
3 wangwu
Set set = map1.entrySet();
set集合对象形式如下:
1=zhangsan
2=lisi
Map集合的遍历
第一种方式:先获取所有的key,通过遍历key,来遍历value
第二种方式:把map集合转换成Set集合,再遍历Set集合
第二种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值,这种方式比较适合大数据量
//遍历map集合
Map<Integer,String> map = new HashMap<>();
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
//第一种,根据key遍历value
Set<Integer> keySet = map.keySet();
Iterator<Integer> it = keySet.iterator();
while(it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key+"="+value);
}
//增强for循环,foreach
for(Integer key2 : keySet){
System.out.println(map.get(key2));
}
System.out.println("===========================");
//第二种,转换成Set集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()) {
Map.Entry<Integer, String> node = it2.next();
//也可行
//System.out.println(node);
Integer key3 = node.getKey();
String value3 = node.getValue();
System.out.println(key3 + "=" + value3);
}
输出:
1=张三
2=李四
3=王五
张三
李四
王五
===========================
1=张三
2=李四
3=王五
HashMap集合:
1.HashMap集合底层是哈希表/散列表数据结构
2.哈希表是一个怎样的数据结构?
哈希表是一个数组和单向链表的结合体
哈希表将两种数据结构融合在一起,充分发挥各自的优点
3.HashMap底层实际上是一个数组(一维数组),一维数组的每一个元素是一个单向链表
有一个静态的内部类HashMap.Node
static class Node<K,V> {
final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
final K key; // 存储到Map集合中的那个key
V value; // 存储到Map集合中的那个value
Node<K,V> next; // 下一个节点的内存地址。
哈希表/散列表数据结构
1.map.put(k,v)实现原理:
第一步:先将k,v封装到Node对象中
第二步:底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组下标,
下标位置上如果没有任何元素,就把Node加到这个位置上;
如果下标对应的位置上有链表,此时会拿着k和链表上的每一个节点的k进行equals,如果所有的equals方法都返回false,那么这个新节点将被添加到链表的末尾;如果其中有一个equals返回的是true,那么这个节点的value将会被覆盖
2.v=map.get(k)实现原理:
底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组下标,
下标位置上如果没有任何元素,返回null;
如果这个位置上有单向链表,那会拿着k和单向链表上的每个节点的k进行equals,如果所有equals方法返回false,那么get方法返回null;
只要其中有一个equals方法返回ture,那么此时这个节点的value就是我们要找的value,get方法最终返回这个value
3.为什么哈希表的随机增删,以及查询效率都高?
增删是在链表上完成
查询也不需要都扫描,只需要部分扫描
HashMap集合的key,会先后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都要重写
4.HashMap集合key的特点:
无序不可重复
为什么无序?因为不一定会挂到哪个单向链表上
不可重复?equals方法来保证HashMap集合的key不可重复,如果重复了就覆盖了
5.放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
6.同一个单向链表上所有节点的hash相同(也可以不同,存在哈希碰撞),因为他们的数组下标是一样的
但同一个链表上的k和k的equals方法返回都是false,都不相等
7.哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了
纯单向链表。这种情况我们成为:散列分布不均匀。
什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。
也是散列分布不均匀。散列分布均匀需要你重写hashCode()方法时有一定的技巧。
8.重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
9.HashMap集合的默认初始化容量为16,加载因子是0.75,扩容2倍
这个默认的加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容,扩容之后的容量是原容量的2倍
重点,记住:HashMap集合初始化容量必须是2的次幂,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
10. 在JDK8之后,如果哈希表单向链表中的元素超过8个,单向链表这种数据结构会变成红黑树这种数据结构;红黑树上的元素少于6个,会变回单向链表(这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围,提高效率)
重写hashCode()和equals()方法
1.向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用。
拿put(k,v)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
拿get(k)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
2.注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,在同一个单向链表上比较。
那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
所以hashCode()方法的返回值也应该相同。
3.终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
4.对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
5.HashMap集合key部分允许null吗?
允许
但是要注意:HashMap集合的key null值只能有一个。(有可能面试的时候遇到这样的问题)
Hashtable
6.Hashtable的key可以为null吗?
Hashtable的key和value都是不能为null的。
HashMap集合的key和value都是可以为null的。
7.Hashtable方法都带有synchronized:线程安全的。
线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。
Hashtable和HashMap一样,底层都是哈希表数据结构。
Hashtable的初始化容量是11,默认加载因子是:0.75f
Hashtable的扩容是:原容量 * 2 + 1
Properties
目前只需要掌握Properties属性类对象的相关方法即可。
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。
掌握两个方法:存和取
存:setProperty (调用map的put方法)
取:getProperty 通过键获得值
TreeSet
1、TreeSet集合底层实际上是一个TreeMap
2、TreeMap集合底层是一个二叉树。
3、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。
称为:可排序集合。
对自定义的类型来说,TreeSet可以排序吗?
在没有指定排序规则之前,无法排序,谁大谁小没有说明
运行时会出现java.lang.ClassCastException: Person cannot be cast to class java.lang.Comparable
出现这个异常的原因是:
自定义的类(Person)没有实现java.lang.Comparable接口
因此,放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
并且实现compareTo方法。equals可以不写。
需要在compareTo方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较
compareTo方法的返回值很重要:
返回0表示相同,value会覆盖。
返回>0,会继续在右子树上找。
返回<0,会继续在左子树上找。
最终的结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象Comparator。
(传入比较器可以改变比较规则)
Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
Comparator接口的设计符合OCP原则。
自平衡二叉树
1.TreeSet/TreeMap是自平衡二叉树,遵循左小右大原则存放
2.遍历二叉树的时候有三种方式:
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
//前中后说的是根的位置
3.TreeSet/TreeMap集合采用的是:中序遍历方式
Iterator迭代器采用的是中序遍历方式
集合工具类Collections
1.将list集合变成线程安全的:Collections.synchronizedList(list);
2.排序:Collections.sort(list);
对List集合中元素排序,需要保证List集合中元素实现了Comparable接口
或者采用这种方法:Collections.sort(list集合, 比较器对象);
以上是关于Java学习16(MAP)的主要内容,如果未能解决你的问题,请参考以下文章
elasticsearch代码片段,及工具类SearchEsUtil.java