Map、Set、二叉搜索树及哈希冲突解决

在这里插入图片描述
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值