Map和Set:作用------用来帮助用户进行搜索的容器 || 数据结构
用来搜索的集合类
查找方式:
1、顺序查找:----->O(N)
2、二分查找:----->要求:序列必须是有序的----->O(logN)
静态类型查找:查找的过程中,不会再改变数据的结构----->不会再插入和删除
Map和Set:动态查找
Map和Set:存储元素的类型:
Map:
1、Map是一个接口,没有继承自Collecction
2、Map中存储的是一个k-v结构的键值对,键值对中的key必须要唯一(key不能重复),V是可以重复的
2、所谓的k-v结构,实际就是新定义的一种类型Entry,该类型是Map内定义的,该接口包含有k,v
Map的常用方法:
Map的使用:
注意:Map是一个接口,必须使用TreeMap或HashMap来进行实例化
Map代码测试:
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class MapTest {
public static void testTreeMap(){
Map<String, String> m1 = new TreeMap<>();
m1.put("peach", "桃子");
m1.put("orange", "橘子");
m1.put("apple", "苹果");
System.out.println(m1.size());
//验证key是否可以重复
//如果key不存在,将该key-value组成的键值对直接插入
//如果key存在,使用value替换原key所对应的value
//在插入key-value期间,Map会保证key是一个有序的序列
//延伸:插入期间肯定要对key进行比较大小;
//延伸:如果key是自定义类型的元素,该类的对象必须要能比较大小--->实现Comparable接口Comparator
//返回值:如果key不存在,将键值对插入,返回null
// 如果key存在,用value覆盖,返回value的值
m1.put("orange", "橙子");
System.out.println(m1.size());
System.out.println(m1);
//key是一定不能为空的,如果为空会抛出NullPointerException--->原因是要进行key的比较
//m1.put(null, "无名");
//value可以为空null
m1.put("banana", null);
System.out.println(m1.size());
System.out.println(m1);
//get(key)
//如果key存在,就返回与key对应的value;
//如果key不存在,就返回null
//如果key为null,就抛出空指针异常
System.out.println(m1.get("apple"));
System.out.println(m1.get("watermelon"));
//System.out.println(m1.get(null));
System.out.println(m1.getOrDefault("apple", "苹果手机"));
System.out.println(m1.getOrDefault("watermelon", "西瓜"));
System.out.println(m1.size());
//remove(key),将Map中key所对应的键值对删除掉
//如果key存在,删除该键值对,然后返回该键值对中的value
//如果key不存在,直接返回null
//注意:时间复杂度----O(logN)----找key
System.out.println(m1);
System.out.println(m1.remove("banananana"));
if(m1.containsKey("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1.remove("banana"));
if(m1.containsKey("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1);
//注意:时间复杂度----O(N)----找value实际要遍历
System.out.println(m1);
System.out.println(m1.remove("banananana"));
if(m1.containsValue("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1.remove("banana"));
if(m1.containsValue("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1);
//打印所有的key
//keySet()将Map中的所有key放在set中返回
for (String s : m1.keySet()){
System.out.print(s + " ");
}
System.out.println();
//打印所有的value
for (String s : m1.values()){
System.out.print(s + " ");
}
System.out.println();
for (Map.Entry<String, String> e : m1.entrySet()){
System.out.print(e.getKey() + "---->" +e.getValue());
}
System.out.println();
}
public static void testHashMap(){
Map<String, String> m1 = new HashMap<>();
m1.put("peach", "桃子");
m1.put("orange", "橘子");
m1.put("apple", "苹果");
System.out.println(m1.size());
//验证key是否可以重复
//如果key不存在,将该key-value组成的键值对直接插入
//如果key存在,使用value替换原key所对应的value
//在插入key-value期间,Map会保证key是一个有序的序列
//延伸:插入期间肯定要对key进行比较大小;
//延伸:如果key是自定义类型的元素,该类的对象必须要能比较大小--->实现Comparable接口Comparator
//返回值:如果key不存在,将键值对插入,返回null
// 如果key存在,用value覆盖,返回value的值
m1.put("orange", "橙子");
System.out.println(m1.size());
System.out.println(m1);
//key是一定不能为空的,如果为空会抛出NullPointerException--->原因是要进行key的比较
//m1.put(null, "无名");
//value可以为空null
m1.put("banana", null);
System.out.println(m1.size());
System.out.println(m1);
//get(key)
//如果key存在,就返回与key对应的value;
//如果key不存在,就返回null
//如果key为null,就抛出空指针异常
System.out.println(m1.get("apple"));
System.out.println(m1.get("watermelon"));
//System.out.println(m1.get(null));
System.out.println(m1.getOrDefault("apple", "苹果手机"));
System.out.println(m1.getOrDefault("watermelon", "西瓜"));
System.out.println(m1.size());
//remove(key),将Map中key所对应的键值对删除掉
//如果key存在,删除该键值对,然后返回该键值对中的value
//如果key不存在,直接返回null
//注意:时间复杂度----O(logN)----找key
System.out.println(m1);
System.out.println(m1.remove("banananana"));
if(m1.containsKey("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1.remove("banana"));
if(m1.containsKey("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1);
//注意:时间复杂度----O(N)----找value实际要遍历
System.out.println(m1);
System.out.println(m1.remove("banananana"));
if(m1.containsValue("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1.remove("banana"));
if(m1.containsValue("banana")){
System.out.println("banana is in map");
}else{
System.out.println("banana is not in map");
}
System.out.println(m1);
//打印所有的key
//keySet()将Map中的所有key放在set中返回
for (String s : m1.keySet()){
System.out.print(s + " ");
}
System.out.println();
//打印所有的value
for (String s : m1.values()){
System.out.print(s + " ");
}
System.out.println();
for (Map.Entry<String, String> e : m1.entrySet()){
System.out.print(e.getKey() + "---->" +e.getValue());
}
System.out.println();
}
public static void main(String[] args) {
//testTreeMap();
testHashMap();
}
}
**Set:**是一个接口,该接口继承了Collection,该接口中只能存放K
注意:Set只能使用TreeSet和HashSet
Set里面的key是不能够重复的,TreeSet:去重 + 排序 HashSet去重
Set中元素的遍历:
----->迭代器:设计模式----->依次寻访容器中的元素,而又无需了解其底层数据结构或者无需暴露底层接口实现
Set代码测试:
public static void testTreeSet(){
Set<String> s = new TreeSet<>();
System.out.println(s.add("orange"));
System.out.println(s.add("peach"));
System.out.println(s.add("apple"));
System.out.println(s.size());
System.out.println(s.add("apple"));
System.out.println(s);
if(s.contains("watermelon")){
System.out.println("zai");
}else{
System.out.println("buzai");
}
if(s.contains("apple")){
System.out.println("zai");
}else{
System.out.println("buzai");
}
//遍历:
Iterator<String> it = s.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println(s.remove("watermelon"));
System.out.println(s.remove("apple"));
s.clear();
}
public static void testHashSet(){
Set<String> s = new HashSet<>();
System.out.println(s.add("orange"));
System.out.println(s.add("peach"));
System.out.println(s.add("apple"));
System.out.println(s.size());
System.out.println(s.add("apple"));
System.out.println(s);
if(s.contains("watermelon")){
System.out.println("zai");
}else{
System.out.println("buzai");
}
if(s.contains("apple")){
System.out.println("zai");
}else{
System.out.println("buzai");
}
//遍历:
Iterator<String> it = s.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println(s.remove("watermelon"));
System.out.println(s.remove("apple"));
s.clear();
}
public static void main(String[] args) {
//testTreeMap();
//testHashMap();
//testTreeSet();
testHashSet();
}
面试题:
class Solution {
public int singleNumber(int[] nums) {
Set<Integer> s = new HashSet<>();
for(int i = 0; i < nums.length; i++){
if(!s.add(nums[i])){
s.remove(nums[i]);
}
}
Object[] o = s.toArray();
return (int)o[0];
//最佳方式是用下面异或的方式
// int ret = 0;
// for(int i = 0; i < nums.length; i++){
// ret ^= nums[i];
// }
// return ret;
}
}
这里的add()方法解释如下:
思路:新的方法:使用HashMap
/*
// Definition for a Node.
class Node {
public int val;
public Node next;
public Node random;
public Node() {}
public Node(int _val,Node _next,Node _random) {
val = _val;
next = _next;
random = _random;
}
};
*/
class Solution {
//使用HashMap解决:
public Node copyRandomList(Node head){
if(head == null){
return null;
}
Map<Node, Node> m = new HashMap<>();
Node node = head;
while(node != null){
m.put(node, new Node(node.val));
node = node.next;
}
node = head;
while(node != null){
m.get(node).next = m.get(node.next);
m.get(node).random = m.get(node.random);
node = node.next;
}
return m.get(head);
}
// public Node copyRandomList(Node head) {
// if(head == null){
// return null;
// }
// //1、将新老结点串为一个链表
// Node cur = head;
// while(cur != null){
// Node node = new Node(cur.val,cur.next,null);
// Node tmp = cur.next;
// cur.next = node;
// cur = tmp;
// }
// //2、处理random指针域
// cur = head;
// while(cur != null){
// if(cur.random != null){
// cur.next.random = cur.random.next;
// cur = cur.next.next;
// }else{
// cur.next.random = null;
// cur = cur.next.next;
// }
// }
// //3、将老新链表拆开,返回新链表
// cur = head;
// Node newHead = cur.next;
// while(cur.next != null){
// Node tmp = cur.next;
// cur.next = tmp.next;
// cur = tmp;
// }
// return newHead;
// }
}
class Solution {
public int numJewelsInStones(String J, String S) {
//1、统计S中每个石头出现的次数
Map<Character, Integer> m = new HashMap<>();
for(int i = 0; i < S.length(); i++){
char ch = S.charAt(i);
int count = m.getOrDefault(ch, 0);
m.put(ch, count + 1);
}
//2、统计每个宝石出现的次数
int jewelsCount = 0;
for(int i = 0; i < J.length(); i++){
jewelsCount += m.getOrDefault(J.charAt(i), 0);
}
return jewelsCount;
}
}
import java.util.Scanner;
import java.util.Set;
import java.util.HashSet;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNextLine()){
//1、循环接收用户的输入
String right = sc.nextLine().toUpperCase();
String wrong = sc.nextLine().toUpperCase();
//2、将wrong中的每个字符放到Set中
//将正常的键保存在Set中
Set<Character> s = new HashSet<>();
for(int i = 0; i < wrong.length(); i++){
s.add(wrong.charAt(i));
}
//3、检测right中每个字符是否在Set中出现过
for(int i = 0; i < right.length(); i++){
char ch = right.charAt(i);
if(!s.contains(right.charAt(i))){
s.add(right.charAt(i));
System.out.print(right.charAt(i));
}
}
System.out.println();
}
}
}
//比较器:默认情况下:
class CmpKV implements Comparator<Map.Entry<String, Integer>>{
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2){
if(o2.getValue() > o1.getValue()){
return 1;
}
if(o2.getValue() == o1.getValue() && o1.getKey().compareTo(o2.getKey()) > 0){
return 1;
}
if(o2.getValue() == o1.getValue() && o1.getKey().compareTo(o2.getKey()) == 0){
return 1;
}
return -1;
}
}
class Solution {
public List<String> topKFrequent(String[] words, int k) {
//1、统计每个单词出现的次数
Map<String, Integer> m = new HashMap<>();
for(int i = 0; i < words.length; i++){
m.put(words[i], m.getOrDefault(words[i], 0) + 1);
}
//2、创建一个优先级队列
PriorityQueue<Map.Entry<String, Integer>> p = new PriorityQueue<>(new CmpKV());
for(Map.Entry<String, Integer> e : m.entrySet()){
p.offer(e);
}
List<String> lRet = new ArrayList<>(k);
for(int i = 0; i < k; i++){
lRet.add(p.poll().getKey());
}
return lRet;
}
}
TreeMap的底层数据结构:红黑树
红黑树:首先红黑树是一颗二叉搜索树 + 增加结点的颜色限制以及性质约束----->保证:最长路径中结点个数一定不会超过最短路径中结点个数的两倍,就认为其近似平衡
虽然红黑树是近似平衡的二叉树,但是大量的使用表明:红黑树性能比AVL树高,红黑树的实现更加简单
性质:
1、每个节点不是红色就是黑色
2、根结点一定是黑色
3、没有连在一起的红色结点
4、每条路径中黑色结点个数一样
5、没用(所有叶子结点(指空节点(空引用))都是黑色的)
保证:最长路径中结点个数一定不会超过最短路径中结点个数的两倍 查找时间复杂度:O(logN)
二叉搜索树:
进行中序遍历---->可以得到一个有序的序列
树中最左侧节点一定是最小的节点,最右侧节点一定是最大的
a、二叉搜索树—>最主要的作用是用来进行搜索的
b、二叉搜索树插入:
0、空树—直接插入
1、找待插入元素在二叉搜索树中的位置并保存其双亲
如果找到该元素,则不插入,直接返回
否则:进行2
2、插入新节点
c、删除节点:
找待删除结点在树中的位置
没有找到—>直接返回
找到:继续往下
删除该结点cur
对cur的孩子分情况:(第1种情况可以与第2种或者第3中情况合并起来,一共为3种情况)
1、cur是一个叶子结点
2、cur只有右孩子
3、cur只有左孩子
4、cur左右孩子均有
二叉搜索树代码:
//binary search tree
public class BSTree {
//静态内部类
public static class BSTNode{
BSTNode left = null;
BSTNode right = null;
int val;
BSTNode(int val){
this.val = val;
}
}
private BSTNode root = null;
//检测val是否在二叉搜索树中
public boolean contains(int val){
BSTNode cur = root;
while(cur != null){
if(val == cur.val){
return true;
}else if(val < cur.val){
cur = cur.left;
}else{
cur = cur.right;
}
}
return false;
}
//将val插入二叉搜索树中,插入成功返回true,否则返回false
public boolean put(int val){
//空树
if(root == null){
root = new BSTNode(val);
return true;
}
//非空,找待插入元素在二叉搜索树中的插入位置并保存其双亲
BSTNode cur = root;
BSTNode parent = null;
while(cur != null){
parent = cur;
if(val < cur.val){
cur = cur.left;
}else if(val > cur.val){
cur = cur.right;
}else{
return false;
}
}
//找到待插入节点的位置--->插入新节点
//将新节点插入到parent的左侧或者右侧
cur = new BSTNode(val);
if(val < parent.val){
parent.left = cur;
}else{
parent.right = cur;
}
return true;
}
public void inOrder(){
inOrder(root);
}
private void inOrder(BSTNode root){
if(root != null){
inOrder(root.left);
System.out.println(root.val + " ");
inOrder(root.right);
}
}
//最左侧节点----最小的节点
public int leftMost(){
if(root == null){
//抛异常--->空指针异常
}
BSTNode cur = root;
while(cur.left != null){
cur = cur.left;
}
return cur.val;
}
//最右侧节点----最大的节点
public int rightMost(){
if(root == null){
//抛异常--->空指针异常
}
BSTNode cur = root;
while(cur.right != null){
cur = cur.right;
}
return cur.val;
}
boolean remove(int val){
if(root == null){
return false;
}
//树非空,找待删除节点在树中的位置
BSTNode cur = root;
BSTNode parent = null;
while (cur != null){
if(val == cur.val){
break;
}else if(val < cur.val){
parent = cur;
cur = cur.left;
}else{
parent = cur;
cur = cur.right;
}
}
//没有找到
if(cur == null){
return false;
}
//已经找到待删除的节点在树中的位置---删除该节点
//删除节点
//必须对cur的孩子节点分情况
//1、cur没有孩子
//2、cur只有左孩子
//3、cur只有右孩子
//4、cur左右孩子均有
if(cur.left == null){
//cur只有右孩子
if(parent == null){
//cur双亲不存在,cur就是根节点
root = cur.right;
}else{
if(cur == parent.left){
parent.left = cur.right;
}else{
parent.right = cur.right
}
}
}else if(cur.right == null){
//cur只有左孩子
if(parent == null){
root = cur.left;
}else{
if(cur == parent.left){
parent.left = cur.left;
}else{
parent.right = cur.left;
}
}
}else{
//cur的左右孩子均存在
//在cur子树中找一个替代的节点删除
//方式一:在其右子树中找最小的节点:即最左侧节点
//方式二:在其左子树中找最大的节点:即最右侧节点
BSTNode del = cur.right;
parent = cur;
while(del.left != null){
parent = del;
del = del.left;
}
//替代节点找到
cur.val = del.val;
//删除替代节点
if(del == parent.left){
parent.left = del.right;
}else{
parent.right = del.right;
}
}
return true;
}
public static void main(String[] args) {
int[] array = {5, 3, 4, 1, 7, 8, 2, 6, 0, 9};
BSTree t = new BSTree();
for (int e : array) {
t.put(e);
}
t.inOrder();
System.out.println();
System.out.println(t.leftMost());
System.out.println(t.rightMost());
}
}
面试问题:有一颗二叉搜索树,将该二叉搜索树转化为排序的双向链表?
双向链表有结点:next和prev(指向下一个和前一个结点)
二叉搜索树中有结点:left和right(指向左子树和右子树)
二叉搜索树---->按照中序遍历的规则来改变结点left和right引用的指向
约定:left指向比当前结点小的结点,right指向大的结点
1、找链表的首结点---->就是树中最小的结点---->就是树中最左侧的结点
2、按照中序遍历的规则来修改每个结点的左右孩子的指向
分析可知:每拿到一个结点----只能修改当前结点的左指针域—原因:因为按照中序规则进行遍历,肯定知道当前结点的前一个结点是哪个结点(因为其前一个结点刚刚遍历过),当前结点的后序还没有遍历。
假设:cur是当前结点
prev:来标记刚刚遍历过的结点
cur.left = prev;
cur.right----->无法操作
prev.right = cur;
BSTNode prev = null;//标记中序遍历刚刚遍历过的结点
public BSTNode BSTree2DList(){
if(root == null){
return null;
}
//找树中最左侧的结点,即双向链表的头
BSTNode head = root;
while(head.left != null){
head = head.left;
}
//2、修改每个结点的left和right的指向
BSTree2DList(root);
return head;
}
public void BSTree2DList(BSTNode root){
if(root == null){
return;
}
//转化根结点的左子树
BSTree2DList(root.left);
//转化根结点
root.left = prev;
if(prev != null){
prev.right = root;
}
//用prev将刚刚遍历的结点保存起来
prev = root;
//转化根结点的右子树
BSTree2DList(root.right);
}
搜索:
1、循环遍历----->O(N)
2、二分查找,条件:序列必须有序---->O(logN)
3、如果不是有序---->可以将数据按照平衡二叉树的方式进行组织---->O(logN)
以上3种搜索的方式共同点:都要进行比较
哈希:(散列表)
而理想的查找方式可以不用进行比较,快速将数据找到
如果通过某种方式,能够将数据与其存储位置之间建立一一对应的关系
例如:数据集合{1,7,6,4,5,9}
哈希冲突解决-闭散列
解决哈希冲突问题采用闭散列,其中包括了线性探测和二次探测
闭散列最大的缺陷就是空间利用率比较低,是使用空间换时间的做法,这也是哈希的缺陷
哈希冲突解决-开散列(哈希桶)
import java.util.HashMap;
import java.util.Map;
//哈希桶----数组 + 链表实现的---->数组:可以帮助用户快速定位要将元素插入到哪个链表
// 来组织链表(不带头结点的单链)
//数组中存储的元素实际为结点的引用
public class HashBucket {
public static class Node{
int key;
int value;
Node next;
public Node(int key, int value){
this.key = key;
this.value = value;
next = null;
}
}
//哈希桶中的成员数据
Node[] table;
int capacity; //表格的容量---桶的个数
int size; //有效元素的个数
double loadFactor = 0.75;//装载因子
//保证哈希桶初识的容量至少为10个
public HashBucket(int initCap){
capacity = initCap;
if(initCap < 10){
capacity = 10;
}
table = new Node[capacity];
size = 0;
}
public int put(int key, int value){
resize();
//1、通过哈希函数,计算key所在的桶号
int bucketNo = hashFunc(key);
//2、在bucketNo桶中检测key是否存在
//检测方式:遍历链表
Node cur = table[bucketNo];
while(cur != null){
if(cur.key == key){
int oldValue = cur.value;
cur.value = value;
return oldValue;
}
cur = cur.next;
}
//3、key不存在,将key-value插入到哈希桶中
cur = new Node(key, value);
cur.next = table[bucketNo];
table[bucketNo] = cur;
size++;
return value;
}
//
public boolean remove(int key){
//1、通过哈希函数计算key的桶号
int bucketNo = hashFunc(key);
//2、在bucketNo桶中找key所对应的结点
//找到后将该结点删除
Node cur = table[bucketNo];
Node prev = null;
while(cur != null){
if(cur.key == key){
//找到与key所对应的结点,将该结点删除
if(prev == null){
//删除的结点是第一个结点
table[bucketNo] = cur.next;
}else{
//删除其他结点
prev.next = cur.next;
}
--size;
return true;
}else{
cur = cur.next;
}
}
return false;
}
//O(1)
public boolean containsKey(int key){
//1、计算key所在的桶号
int bucketNo = hashFunc(key);
//2、在bucketNo桶中找key
Node cur = table[bucketNo];
while(cur != null){
if(cur.key == key){
return true;
}
cur = cur.next;
}
return false;
}
//O(n)
public boolean containsValue(int value){
//注意:哈希桶是根据key来计算哈希地址的
//因此:找value,不能计算出value在哪个桶中
//找value时,必须要遍历所有桶才行
for(int bucketNo = 0; bucketNo < capacity; bucketNo++){
Node cur = table[bucketNo];
while(cur != null){
if(cur.value == value){
return true;
}
cur = cur.next;
}
}
return false;
}
public int size(){
return size;
}
public boolean empty(){
return size == 0;
}
private void resize(){
//装载因子超过0.75时按照2倍的方式进行扩容
if(size * 10 / capacity > loadFactor * 10){
int newCap = capacity * 2;
Node[] newTable = new Node[capacity * 2];
//将table中的结点搬移到newTable中
for(int i = 0; i < capacity; i++){
Node cur = table[i];
//将table中i号桶所对应链表中所有的结点插入到newTable中
while(cur != null){
table[i] = cur.next;
//将cur结点插入到newTable中
//1、计算cur在newTable中的桶号
//int bucketNo = hashFunc(cur.key);不行--->hashFunc里面用的是旧桶的容量
int bucketNo = cur.key % newCap;
//2、将cur插入到newTable中
cur.next = newTable[bucketNo];
newTable[bucketNo] = cur;
//取table中i号桶的下一个结点
cur = table[i];
}
}
table = newTable;
capacity = newCap;
}
}
private int hashFunc(int key){
return key % capacity;
}
public void printHashBucket(){
for(int bucketNo = 0; bucketNo < capacity; bucketNo++){
System.out.printf("table[%d]", bucketNo);
Node cur = table[bucketNo];
while(cur != null){
System.out.print("[" + cur.key + "," + cur.value + "]--->");
cur = cur.next;
}
System.out.println("null");
}
}
public static void main(String[] args) {
HashBucket ht = new HashBucket(5);
ht.put(1, 1);
ht.put(11, 11);
ht.put(2, 2);
ht.put(22, 22);
ht.put(6, 6);
ht.put(5, 5);
ht.put(51, 51);
ht.put(8, 8);
System.out.println(ht.size());
ht.printHashBucket();
//验证扩容
ht.put(3, 3);
ht.printHashBucket();
System.out.println(ht.containsKey(5));
System.out.println(ht.containsValue(15));
ht.remove(5);
System.out.println(ht.containsKey(5));
ht.printHashBucket();
//Map<String, String> m = new HashMap<>();
}
}
看源代码文档:好好的去看 里面每个方法基本都加了注释了 大家把主要的思想搞清楚就可以了 比如:hashMap为什么要按照2的n次幂扩容,为什么必须要重写hashCode和equals方法?是不是线程安全的 在多线程下扩容会造成什么问题 这些文档中都有介绍,都是面试常考的问题,把原理搞清楚了 面试期间HashMap基本难不倒你
Java8: HashMap
1、哈希桶默认容量为16
2、哈希桶的最大容量为2^30
3、如果哈希桶中一条链表中结点个数比较多,HashMap会将链表转换为红黑树
条件:哈希表格桶的容量超过64 && 链表中结点个数超过8
4、如果红黑树中结点个数小于6个时,红黑树会退化为链表
5、HashMap中链表对应的结点的结构:实现了Map.Entry接口
一个结点: hash:哈希值
key
value
next
6、哈希函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
如果key不为空,首先让key调用自己的hashCode的方法,拿到key的哈希码----->如果是自定义类型:该类的hashCode方法必须重写
哈希值具体的计算方式:将h的高16位与低16位异或
如果哈希表的容量没有超过2^16次方,高位全部是0
如果哈希表的容量超过2^16时,高位不一定是0—>让高16位与低16位进行异或
7、扩容时机
capacity * factor ----> 16 * 0.75 ----> 12