java2023-1-1

今天早上我主要学习了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的数组。

然后依次将数据放在数组中

内容角标
70
31
82
323
914
245
536

这个就是用顺序表来存放数据的方式,但是除了这种存放方式以外,我们还有其他的存放方式,比如链表(不举例了),也还有其他的结构可以存内容,比如树,图,这其中我们用的真正保存过程最多的是Hash散列。

Hash散列也是一种存放过程,和数组存放非常的像,只不过内容并不是按照数组保存的顺序来保存

首先,我们也要创建一个长度至少是7的数组。咱也不要至少是7了,我们就创建一个长度是7的数组。

散列过程和线性表过程不同的地方,在于输入的数据,是要根据某种算法来决定这个数据放置在哪个位置上的。而不是根据输入的先后顺序来决定数据应该放在数组的哪个位置上。

我们用最简单的算法来处理,求余数。

当前数组长度是7,那么我们用要放置的元素去针对7取余数,这个余数只能是0-6之间。

按照这个过程,我们重新放置7,3,8,32,91,24,53这个内容

内容角标最终位置
700
333
811
3244
910?2
2435
5346

我们发现有几个值的角标算完和之前放置进来的值的角标是一样的,但是数组中这个位置如果已经有数据保存在这里了,就不能将新的数据保存在同一个角标下。

我们通过偏移当前要放置的值,来改变这个值可能的角标。
在这里插入图片描述

于是乎,我们最终得到的结果就像下表中描述的结果
在这里插入图片描述
上面说的解决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; 
	    }

	 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值