今天早上我主要学习了HashMap的相关知识
回顾以前的知识
Collection 容器 下辖的子接口:List,Queue,Set
List ArrayList,LinkedList,Stack,Vector(向量)
Queue LinkedList,PriorityQueue
Set HashSet,LinkedHashSet,TreeSet
优先级队列和TreeSet里面放的值必须实现Comparable接口
HashMap
Map叫做映射表,里面放置的也是一个容器,但不是collection,它里面
放置的内容是一个一个的k-v对
其中k代表key 中文翻译过来叫做键
V代表Value 中文翻译过来叫值,也就是说Map里面放置的内容是键值对
那什么事键值对?所谓的键值对,就是一个键其关联一个值,如果我们想要从容器里面获得
一个值,那么前提是必须要知道建。
(注:其实我们说的list也可以理解成一个键值对,如果我们想要获得里面的某一个元素
那么我们必须要知道角标,而我们的map就是摒弃了角标,将键替换成了某一个对象,于是,我们得到了一个对象映射另一个对象
这个结构,我们叫做映射表)
package com.ma1;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class Main {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
// map.put("键libing", "值gxy");
// System.out.println(map.get("键libing"));
map.put("libin","liguong");
map.put("libin","guoxiyu");
map.remove("libin");
System.out.println(map.containsKey("libin"));
System.out.println(map.containsValue("liguong"));
System.out.println(map.get("libin"));
//遍历
Set<String> keys = map.keySet();
for(String item : keys) {
System.out.println(item + " " + map.get(item));
}
Set<Entry<String, String>> entrys = map.entrySet();
for(Entry<String , String> item : entrys) {
System.out.println(item.getKey());
System.out.println(item.getValue());
}
}
}
HashMap底层实现原理
描述的HashMap实现原理是基于JDK1.8.x版本的
第一:存储方式:
HashMap从命名上讲,存在一个Hash,所以底层其实使用的是散列Hash的方式来存储内容
的,而散列Hash具有一些弊端(当发生Hash碰撞后,不同解决冲突的办法会造成不同的
效率损失,所以效率不稳定),我们发现单纯使用散列Hash存储数据,是有弊端的,所以
JAVA使用数组来实现散列Hash过程并将碰撞后的数据以链表的形式来保存。
于是我们得到了HashMap的底层数据存储方式:数组(实现散列Hash)+链表的方式来保存
数据。
注意:这里没有结束,因为在链表长度超过一定值以后,链表的定位效率会直线下
降,所以需要将链表进化成为一种树结构,叫做红黑树(AVL),是一种弱平衡的二叉查找树。这个部分在下文中。
什么是Hash,什么是散列Hash
Hash是一个算法簇,是满足某种条件的算法,我们都叫Hash算法
1、算法结果是固定的:固定的输入能得到固定的结果。
2、算法过程是不可逆的:可以从输入推到出来结果,但是不能从结果推到输入。
满足以上两种条件的算法,我们都叫他Hash算法。比如加法就是一个Hash算法
3+5=8,在任何条件下,3+5都等于8,其中3+5是输入,8是结果,我们可以从3+5推算出来结果是8,但是不能从结果8,推算出来输入就一定是3+5,也可能是1+7.
什么是散列Hash呢?散列Hash是一种存放数据的方式,他并不是简单的加法或者减法。我们现在给出一系列数据。用这一系列数据举例。
7,3,8,32,91,24,53
现在需要将这7个数字,放在某个容器里面。首先,我们使用数组(或者顺序表)容器来存放这7个数字。
第一步:我们需要创建一个长度至少是7的数组。
然后依次将数据放在数组中
内容 | 角标 |
7 | 0 |
3 | 1 |
8 | 2 |
32 | 3 |
91 | 4 |
24 | 5 |
53 | 6 |
这个就是用顺序表来存放数据的方式,但是除了这种存放方式以外,我们还有其他的存放方式,比如链表(不举例了),也还有其他的结构可以存内容,比如树,图,这其中我们用的真正保存过程最多的是Hash散列。
Hash散列也是一种存放过程,和数组存放非常的像,只不过内容并不是按照数组保存的顺序来保存
首先,我们也要创建一个长度至少是7的数组。咱也不要至少是7了,我们就创建一个长度是7的数组。
散列过程和线性表过程不同的地方,在于输入的数据,是要根据某种算法来决定这个数据放置在哪个位置上的。而不是根据输入的先后顺序来决定数据应该放在数组的哪个位置上。
我们用最简单的算法来处理,求余数。
当前数组长度是7,那么我们用要放置的元素去针对7取余数,这个余数只能是0-6之间。
按照这个过程,我们重新放置7,3,8,32,91,24,53这个内容
内容 | 角标 | 最终位置 |
7 | 0 | 0 |
3 | 3 | 3 |
8 | 1 | 1 |
32 | 4 | 4 |
91 | 0? | 2 |
24 | 3 | 5 |
53 | 4 | 6 |
我们发现有几个值的角标算完和之前放置进来的值的角标是一样的,但是数组中这个位置如果已经有数据保存在这里了,就不能将新的数据保存在同一个角标下。
我们通过偏移当前要放置的值,来改变这个值可能的角标。
于是乎,我们最终得到的结果就像下表中描述的结果
上面说的解决Hash冲突的过程是一种解决方式,但是JAVA并没有选择用这个方式来解决实际发生在HashMap中的Hash冲突
首先,HashMap的底层,也是一个数组,是一个Node的数组,这个Node里面,包含了Key和Value
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//此处省略部分实现
}
Node的部分源码见上:HashMap创建的就是这个Node的数组。
通过上图我们知道HashMap实际上使用了拉链法来解决Hash冲突问题。
常用参数以及参数含义
我们通过几个问题来引出参数:
1、当我们知道HashMap底层使用的是数组加链表来保存数据的时候,那么数组是有一个特点的,特点是数组是有固定长度的,那这个数组长度是多少?
2、如果我们放置的内容过多的时候,这个数组是不是要扩容呢?怎么扩容呢?(我们知道ArrayList是会扩容的,但HashMap因为已经通过链表保证每一个元素都可以放在数组的某一个位置上,放不下就形成链表吗,那么它还需要扩容吗?)
3、他一定都是使用链表来保存数据吗?用链表保存数据会不会出现啥问题呢?遇到这个问题的时候,怎么解决呢?
DEFAULT_INITIAL_CAPACITY= 1 << 4;默认初始化容量(1左移4位)
MUST be a power of two.(因为1、求余数快2、rehash只有两个位置)
MAXIMUM_CAPACITY = 1 << 30;最大容量(1左移30位)
ACITY= 1 << 4;默认初始化容量(1左移4位)MUST be a power of two.(因为1、求余数快2、rehash只有两个位置)
MAXIMUM_CAPACITY = 1 << 30;最大容量(1左移30位)
下午我主要在牛客网上练了一些有关于HashMap的习题
例一:缺失的第一个正整数
package com.ma1;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
public class Main {
public int minNumberDisappeared (int[] nums) {
//遍历nums,得到的数字,我们把它当做Map中的一个Key值Value值都是1
//因为Value都是1,所以Value存在部存在其实没有啥意义,但是有意义的是,Key值
//如果能排序出来,就会方便我去找第一个没有出现的正数了。
TreeSet<Integer> set = new TreeSet<>();
// Map<Integer, String> map = new TreeMap<>();
for(int item : nums) {
if(item <= 0) {
continue;
}
//map.put(item, "");
set.add(item);
}
//Set<Integer> keys = map.keySet();
int result = 1;
// for(int item : keys) {
for(int item : set) {
if(item == result) {
result++;
} else {
return result;
}
}
return result;
}
}
}
习题二:字符串出现次数的TopK问题
public String[][] topKstrings (String[] strings, int k) {
//首先,我想用任何一种Map,泛型是String Integer,表示一个字符串如果出现过,就给他的Value+1
Map<String, Integer> map = new HashMap<>();
for(String item : strings) {
if(map.containsKey(item)) {
Integer time = map.get(item);
map.put(item, time + 1);
} else {
map.put(item, 1);
}
}
String[][]result = new String[k][];
for(int i = 0; i<k; i++) {
String key = "";
Integer value = 0;
Set<String> keys = map.keySet();
for(String ky : keys) {
Integer vl = map.get(ky);
if(vl > value) {
value = vl;
key = ky;
} else if(vl == value) {
if(ky.compareTo(key) < 0) {
key = ky;
}
}
}
//当内部的这个循环完成时,表示本次出现的次数最多的键值对,已经找出来了。
result[i] = new String[] {key,value.toString()};
map.remove(key);
}
return result;
}
习题三:最长不含重复字符的子字符串
方法一:
public int lengthOfLongestSubstring (String s) {
//abcdatyuie
//方法一:
char[] arr = s.toCharArray();
Map<Character, Integer> map = new HashMap<>();
int result = 0;
for(int i = 0; i < arr.length; i++) {
if(!map.containsKey(arr[i])) {
map.put(arr[i], i);
} else {
//之前出现这个map里面了
int max = map.get(arr[i]);
Set<Character> keys = map.keySet();
for(Character item : keys) {
if(map.get(item) <= max) {
map.remove(item);
}
}
map.put(arr[i], i);
}
result = result > map.size() ? result : map.size();
}
return result;
方法二:
public int lengthOfLongestSubstring (String s) {
char[] arr = s.toCharArray();
Map<Character, Integer> map = new HashMap<>();
int result = 0;
for(int i = 0; i < arr.length; i++) {
if(!map.containsKey(arr[i])) {
map.put(arr[i], i);
} else {
//之前出现这个map里面了
int max = map.get(arr[i]);
Set<Character> keys = map.keySet();
Iterator<Character> iterator = keys.iterator();
while(iterator.hasNext()) {
if(map.get(iterator.next()) <= max) {
iterator.remove();
}
}
map.put(arr[i], i);
}
result = result > map.size() ? result : map.size();
}
return result;
}
}