最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java Map和Set使用詳解(最新推薦)

 更新時(shí)間:2025年09月29日 08:50:19   作者:英雄不問出處  
本文介紹Java中Map和Set的實(shí)現(xiàn)結(jié)構(gòu),TreeMap/TreeSet基于紅黑樹,HashMap/HashSet基于哈希表,討論哈希沖突處理、負(fù)載因子、線程安全及hashCode/equals的作用,涵蓋二叉搜索樹特性、查找效率、插入刪除方法等關(guān)鍵內(nèi)容,感興趣的朋友一起看看吧

Map和Set

  1. map和set用于搜索
  2. 搜索樹,二叉搜索樹 -> AVL樹 -> 紅黑樹
  3. AVL樹:高度平衡的二叉搜索樹
  4. TreeMap和TreeSet底層是紅黑樹,每次存儲(chǔ)元素都得進(jìn)行大小比較

二叉搜索樹

  1. 二叉搜索樹:如果左子樹不為空,那么左子樹所有節(jié)點(diǎn)都小于根節(jié)點(diǎn),如果右子樹不為空,那么右子樹所有節(jié)點(diǎn)都大于根節(jié)點(diǎn),它的左右子樹都是二叉搜索樹
  2. 二叉搜索樹的中序遍歷是有序的

查找

  1. 比key大往右找,比key小往左找
// 查找
    public boolean search(int key){
        TreeNode cur = root;
        while(cur != null){
            if(cur.val > key){
                cur = cur.left;
            }else if(cur.val < key){
                cur = cur.right;
            }else{
                return true;
            }
        }
        return false;
    }

分析:

  1. 時(shí)間復(fù)雜度:
    最好情況:O(logN),完全二叉樹
    最壞情況:O(N),單分支的二叉樹

插入

// 插入
    public boolean insert(int key){
        TreeNode node = new TreeNode(key);
        TreeNode parent = null;
        if(root == null){
            root = node;
            return true;
        }
        TreeNode cur = root;
        while(cur != null){
            if(cur.val < key){
                parent = cur;
                cur = cur.right;
            }else if(cur.val > key){
                parent = cur;
                cur = cur.left;
            }else{
                // 在二叉搜索樹中只能不能有相同的數(shù)字,比如5,有一個(gè)5就可以了,只要有這個(gè)數(shù)就可以了
                return false;
            }
        }
        if(parent.val < key){
            parent.right= node;
        }else{
            parent.left = node;
        }
        return true;
    }

刪除

  1. 第一種情況:cur.left == null
    要?jiǎng)h除的節(jié)點(diǎn)是cur
    cur是根節(jié)點(diǎn)
    cur是某個(gè)節(jié)點(diǎn)的左邊
    cur是某個(gè)節(jié)點(diǎn)的右邊

2. 第二種情況:cur.right == null
要?jiǎng)h除的節(jié)點(diǎn)是cur
cur是根節(jié)點(diǎn)
cur是某個(gè)節(jié)點(diǎn)的左邊
cur是某個(gè)節(jié)點(diǎn)的右邊

3. 第三種情況:cur.left != null && cur.right != null
使用替換法進(jìn)行刪除
替換為左樹中最大的值
或者是右樹中最小的值
替換完之后刪除這個(gè)去替換的值

// 刪除
    private void removeNode(TreeNode cur, TreeNode parent) {
        if(cur.left == null){
            // 要?jiǎng)h除的是根節(jié)點(diǎn)
            if(cur == root){
                root = cur.right;
            }else if(cur == parent.left){
                parent.left = cur.right;
            }else{
                parent.right = cur.right;
            }
        }else if(cur.right == null){
            if(cur == root){
                root = cur.left;
            }else if(cur == parent.left){
                parent.left = cur.left;
            }else{
                parent.right = cur.left;
            }
        }else{
            // cur.left != null && cur.right != null
            TreeNode parentTarget = cur;
            TreeNode target = cur.right;
            // 在右樹中找最小值
            while(target.left != null){
                parentTarget = target;
                target = target.left;
            }
            // 直到找到右樹中的最左邊的樹
            cur.val = target.val;
            // 刪除target
            if(parentTarget.left == target) {
                parentTarget.left = target.right;
            }else{
                // parentTarget.right == target
                parentTarget.right = target.right;
            }
        }
    }

Map

  1. map是一種(k,v)結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu)
  2. map可以進(jìn)行去重,TreeMap不可以插入null的key,HashMap可以插入null的key,因?yàn)榧t黑樹是要進(jìn)行比較的,哈希表是不進(jìn)行比較的
 Map<String,Integer> map = new TreeMap<>();// 底層是紅黑樹,查找的時(shí)間復(fù)雜度O(N*logN)
 Map<String,Integer> map1 = new HashMap<>();// 底層是哈希表,查找的時(shí)間復(fù)雜度O(1)
 // 哈希表 = 數(shù)組 + 鏈表 + 紅黑樹

Map的使用

public static void main(String[] args) {
        Map<String,Integer> map = new TreeMap<>();// 底層是紅黑樹,查找的時(shí)間復(fù)雜度O(N*logN)
        // 插入元素
        map.put("push",3);// push出現(xiàn)了3次
        // 獲取元素,給定一個(gè)key值可以獲取它的value值
        Integer val = map.get("push");
        Integer val1 = map.get("aaa");// null
        // 獲取val值,如果沒有這個(gè)值,返回一個(gè)默認(rèn)值
        Integer val2 = map.getOrDefault("bbb",99999);
        System.out.println(val);
        // 刪除key值
        // map.remove("push");
        // 把所有的key放入一個(gè)集合中
        Set<String> set = map.keySet();
        System.out.println(set);
        // 獲取values中的所有值
        ArrayList<Integer> value = new ArrayList(map.values());
        System.out.println(value);
        // 把Map.Entry<String,Integer>當(dāng)做Set中的一個(gè)節(jié)點(diǎn)
        // map.entrySet()用于獲取這種節(jié)點(diǎn)
        Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
        for(Map.Entry<String,Integer> entry : entrySet){
            System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());
        }
        // boolean map.containsKey("push"); 判斷是否含有key
        // boolean map.containsValue(3); 判斷是否含有value
        Map<String,Integer> map1 = new HashMap<>();// 底層是哈希表,查找的時(shí)間復(fù)雜度O(1)
        // 哈希表 = 數(shù)組 + 鏈表 + 紅黑樹
    }

Set

  1. set是一種只有key的模型

Set的使用

  1. Set是要進(jìn)行去重的
  2. TreeSet不可以插入null的key,HashSet可以插入null的key,因?yàn)榧t黑樹是要進(jìn)行比較的,哈希表是不進(jìn)行比較的
public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("push");
        set.add("hello");
        set.add("world");
        // set是無(wú)序的
        System.out.println(set);
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }

哈希表

  1. 查找可以一次定位到該元素,時(shí)間復(fù)雜度為O(1)

哈希沖突(碰撞):不同的key通過相同的哈希函數(shù)得到相同的值
哈希沖突是必然產(chǎn)生的,我們要做的是降低沖突的概率

解決哈希沖突:哈希函數(shù)的設(shè)計(jì)要合理
哈希函數(shù)要簡(jiǎn)單
哈希表中要均勻分到數(shù)組中去
哈希表的范圍要合理,比如有m個(gè)地址,存儲(chǔ)位置就是[0,m-1]

負(fù)載因子的調(diào)節(jié)(重點(diǎn))

  1. 負(fù)載因子影響了哈希沖突,負(fù)載因子越大沖突率越高
  2. 哈希表中的負(fù)載因子定義為:
    a = 填入表中的元素個(gè)數(shù) / 哈希表的長(zhǎng)度
    比如:a = 8 / 10 = 0.8

如果降低沖突率就要降低負(fù)載因子,因此要擴(kuò)容哈希表的大小,不增加插入的元素是不現(xiàn)實(shí)的,給定一個(gè)閾值,如果超過了就擴(kuò)大哈希表的容量

閉散列

  1. 開放定址法,如果沒有達(dá)到閾值,但是沖突了,就放到?jīng)_突的下一個(gè)空的位置上,這個(gè)也叫線性探測(cè)
  2. 線性探測(cè)的缺點(diǎn):把沖突的元素都集中放到了一起
  3. 二次探測(cè):為了解決線性探測(cè)的缺點(diǎn),通過公式進(jìn)行處理,H0是當(dāng)前沖突的位置,i是出現(xiàn)沖突的次數(shù),m是哈希表的大小,Hi表示沖突后,下一次要放的位置

4. 線性探測(cè)對(duì)于空間的利用率不高

開散列

  1. 開散列:又叫鏈地址法,為了解決空間利用率不高的問題,開散列是數(shù)組 + 鏈表 + 紅黑樹的模式
  2. 把沖突的元素掛到同一個(gè)空間下的鏈表上
  3. Java是采用開散列的方式實(shí)現(xiàn)的

4. 擴(kuò)容之后需要重新哈希,因?yàn)閿?shù)組長(zhǎng)度變了,要重新計(jì)算節(jié)點(diǎn)存放的位置
5. 遍歷哈希數(shù)組中每個(gè)數(shù)組元素,都要重新計(jì)算節(jié)點(diǎn)位置

package Demo1;
import java.util.Arrays;
public class HashBuck {
    // 鏈表數(shù)組,數(shù)組中的每一個(gè)元素都時(shí)鏈表的頭結(jié)點(diǎn)
    public class Node{
        public int key;
        public int val;
        public Node next;
        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
    public Node[] array;
    public int usedSize;
    public static final float DEFAULT_LOAD_FACTOR = 0.75f;
    public HashBuck(){
        array = new Node[10];
    }
    public void put(int key,int value){
        int index = key % array.length;
        // 遍歷index下標(biāo)的鏈表 是否存在key 存在就更新value 不存在就頭插這個(gè)節(jié)點(diǎn)
        Node node = new Node(key,value);
        // 該鏈表的頭結(jié)點(diǎn)
        Node cur = array[index];
        while(cur != null){
            if(cur.key == key){
                // 如果插入的這個(gè)key相同就替換這個(gè)key
                cur.val = value;
                return;
            }
            cur = cur.next;
        }
        // 沒有找到這個(gè)節(jié)點(diǎn)就頭插
        node.next = array[index];
        array[index] = node;
        usedSize++;
        // 負(fù)載因子大于閾值
        if(doLoadFactor() > DEFAULT_LOAD_FACTOR){
            // 擴(kuò)容
            // array = Arrays.copyOf(array,2*array.length);
            resize();
        }
    }
    private void resize(){
        // 建一個(gè)新的數(shù)組
        Node[] newArray = new Node[2*array.length];
        for(int i = 0;i < array.length;i++){
            Node cur = array[i];
            while(cur != null){
                Node tmp = cur.next;
                // 每次都要算新數(shù)組的下標(biāo)因?yàn)槭且粋€(gè)鏈表有很多個(gè)節(jié)點(diǎn)
                int newIndex = cur.key % newArray.length;
                // 頭插法
                cur.next = newArray[newIndex];
                newArray[newIndex] = cur;
                cur = tmp;
            }
        }
        array = newArray;
    }
    // 計(jì)算負(fù)載因子
    private float doLoadFactor(){
        return usedSize * 1.0f / array.length;
    }
    // 獲取對(duì)應(yīng)key的value值
    public int get(int key){
        int index = key % array.length;
        Node cur = array[index];
        while(cur != null){
            if(cur.key == key){
                // 如果插入的這個(gè)key相同就替換這個(gè)key
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }
}
  1. HashMap是線程不安全的,因?yàn)椴捎昧祟^插法,后面采用了尾插法變得安全了,ConcurrentHashMap是線程安全的,之后學(xué)到了線程就可以理解了

  2. 如果key是String,Person類型就不能除以數(shù)組的長(zhǎng)度了,該怎么找到對(duì)應(yīng)的下標(biāo)呢?
    可以用hashcode來將自定義類型轉(zhuǎn)化為整形類型

hashCode和equals

HashMap和HashSet

public static void main(String[] args) {
        HashMap<String,Integer> hashMap = new HashMap<>();
        hashMap.put("hello",2);
        hashMap.put("abcde",10);
        hashMap.put("abc",11);
        Integer val = hashMap.get("hello");
        System.out.println(val);
        // 遍歷map
        System.out.println(hashMap);
        for(Map.Entry<String,Integer> entry : hashMap.entrySet()){
            System.out.println("key:" + entry.getKey() + " value:" + entry.getValue());
        }
        // Map不支持迭代器遍歷,Set支持迭代器遍歷
        // 可以將Map轉(zhuǎn)化為Set進(jìn)行迭代器遍歷
        HashMap<Student,Integer> hashMap1 = new HashMap<>();
        hashMap1.put(new Student(),2);
        hashMap1.put(new Student(),2);
        hashMap1.put(null,2);
        // TreeMap<Student,Integer> hashMap2 = new TreeMap<>();
        // hashMap2.put(new Student(),3);
        // hashMap2.put(new Student(),3);
        // Sutdent不能進(jìn)行比較
        // set可以去重,Set的底層是HashMap
        // 每次存儲(chǔ)元素的時(shí)候,默認(rèn)的value都是一個(gè)Object對(duì)象
        HashSet<String> set = new HashSet<>();
        set.add("hello");
        set.add("world");
        set.add("hello");
        System.out.println(set);
    }

面試題

只出現(xiàn)一次的數(shù)字
寶石與石頭
舊鍵盤
隨機(jī)鏈表的復(fù)制

統(tǒng)計(jì)6個(gè)數(shù)中數(shù)字出現(xiàn)的次數(shù)

public static void main(String[] args) {
        int[] array = {1,1,2,2,3,3};
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i = 0;i < array.length;i++){
            if(!map.containsKey(array[i])){
                map.put(array[i],1);
            }else{
                int k = map.get(array[i]);
                k++;
                map.put(array[i],k);
            }
        }
        System.out.println(map);
    }

前k個(gè)高頻單詞

如果頻率相同放入堆中要使用大根堆,要讓love排在i的前面

class Solution {
   public List<String> topKFrequent(String[] words, int k) {
        // 1. 統(tǒng)計(jì)單詞出現(xiàn)的次數(shù)
        Map<String,Integer> map = new HashMap<>();
        for(String s : words){
            if(!map.containsKey(s)){
                map.put(s,1);
            }else{
                int val = map.get(s);
                map.put(s,val+1);
            }
        }
        // 2. 把單詞和出現(xiàn)的次數(shù)當(dāng)做一個(gè)整體放入小根堆中
        PriorityQueue<Map.Entry<String,Integer>> minHeap = new PriorityQueue<>(
                new Comparator<Map.Entry<String,Integer>>(){
        public int compare(Map.Entry<String,Integer> o1,Map.Entry<String,Integer> o2){
            // 放元素的時(shí)候,如果頻率相同,我們轉(zhuǎn)變?yōu)榇蟾?-> 按照單詞的字典序進(jìn)行排序
            if(o1.getValue().compareTo(o2.getValue()) == 0){
                return o2.getKey().compareTo(o1.getKey());
            }
            return o1.getValue().compareTo(o2.getValue());
        }
        });
        for(Map.Entry<String,Integer> entry : map.entrySet()){
            if(minHeap.size() < k){
                // 沒有放滿小根堆
                minHeap.add(entry);
            }else{
                // 放滿了和堆頂元素比較大小
                // 如果比堆頂元素還大,就入堆
                int v = minHeap.peek().getValue();
                if(v < entry.getValue()){
                    minHeap.poll();
                    minHeap.offer(entry);
                }else{
                    // 出現(xiàn)頻率相同,比較字典序大小
                    if(v == entry.getValue()){
                        if(minHeap.peek().getKey().compareTo(entry.getKey()) > 0){
                            minHeap.poll();
                            minHeap.offer(entry);
                        }
                    }
                }
            }
        }
        // 2 3 4 -> 4 3 2
        List<String> arr = new ArrayList<>();
        for(int i = 0;i < k;i++){
            Map.Entry<String,Integer> top = minHeap.poll();
            arr.add(top.getKey());
        }
        // 逆置
        Collections.reverse(arr);
        return arr;
    }
}

HashMap的源碼

如果達(dá)到一定條件會(huì)把哈希表編程紅黑樹:如果鏈表的長(zhǎng)度大于8并且數(shù)組的長(zhǎng)度大于64就會(huì)進(jìn)行樹化

到此這篇關(guān)于Java Map和Set的文章就介紹到這了,更多相關(guān)Java Map和Set內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決RestTemplate 的getForEntity調(diào)用接口亂碼的問題

    解決RestTemplate 的getForEntity調(diào)用接口亂碼的問題

    這篇文章主要介紹了解決RestTemplate 的getForEntity調(diào)用接口亂碼的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 如何將默認(rèn)的maven倉(cāng)庫(kù)改為阿里的maven倉(cāng)庫(kù)

    如何將默認(rèn)的maven倉(cāng)庫(kù)改為阿里的maven倉(cāng)庫(kù)

    這篇文章主要介紹了如何將默認(rèn)的maven倉(cāng)庫(kù)改為阿里的maven倉(cāng)庫(kù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • IDEA自定義Maven倉(cāng)庫(kù)的實(shí)現(xiàn)

    IDEA自定義Maven倉(cāng)庫(kù)的實(shí)現(xiàn)

    使用Maven進(jìn)行Java程序開發(fā)時(shí),開發(fā)者能夠極大地提高開發(fā)效率,本文主要介紹了IDEA自定義Maven倉(cāng)庫(kù)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-03-03
  • SpringBoot使用WebJars統(tǒng)一管理靜態(tài)資源的方法

    SpringBoot使用WebJars統(tǒng)一管理靜態(tài)資源的方法

    這篇文章主要介紹了SpringBoot使用WebJars統(tǒng)一管理靜態(tài)資源的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-12-12
  • SpringBoot配置加載順序?qū)嵺`

    SpringBoot配置加載順序?qū)嵺`

    這篇文章主要介紹了SpringBoot配置加載順序?qū)嵺`,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2026-03-03
  • MyBatis-Plus 邏輯刪除的實(shí)現(xiàn)示例

    MyBatis-Plus 邏輯刪除的實(shí)現(xiàn)示例

    邏輯刪除通過標(biāo)記字段實(shí)現(xiàn)數(shù)據(jù)“假刪除”,既保障了數(shù)據(jù)安全,又簡(jiǎn)化了恢復(fù)操作,是核心業(yè)務(wù)數(shù)據(jù)的最佳刪除方案,下面就來介紹一下MyBatis-Plus邏輯刪除的實(shí)現(xiàn),感興趣的可以了解一下
    2026-01-01
  • mybatis-plus分表實(shí)現(xiàn)案例(附示例代碼)

    mybatis-plus分表實(shí)現(xiàn)案例(附示例代碼)

    MyBatis-Plus是一個(gè)MyBatis的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開發(fā)、提高效率而生,這篇文章主要介紹了mybatis-plus分表實(shí)現(xiàn)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-12-12
  • JSON在Java中的使用方法實(shí)例

    JSON在Java中的使用方法實(shí)例

    對(duì)于java來說,json的對(duì)象也是分為JSONObject和JSONArray這兩種,每種對(duì)象的操作方式不一樣哦,下面這篇文章主要給大家介紹了關(guān)于JSON在Java中使用的相關(guān)資料,需要的朋友可以參考下
    2023-03-03
  • java教學(xué)筆記之對(duì)象的創(chuàng)建與銷毀

    java教學(xué)筆記之對(duì)象的創(chuàng)建與銷毀

    面向?qū)ο蟮木幊陶Z(yǔ)言使程序能夠直觀的反應(yīng)客觀世界的本來面目,并且使軟件開發(fā)人員能夠運(yùn)用人類認(rèn)識(shí)事物所采用的一般思維方法進(jìn)行軟件開發(fā),是當(dāng)今計(jì)算機(jī)領(lǐng)域中軟件開發(fā)和應(yīng)用的主流技術(shù)。
    2016-01-01
  • springboot項(xiàng)目打包的可執(zhí)行jar運(yùn)行報(bào)錯(cuò)問題及解決

    springboot項(xiàng)目打包的可執(zhí)行jar運(yùn)行報(bào)錯(cuò)問題及解決

    本文介紹了SpringBoot項(xiàng)目打包成可執(zhí)行jar文件后無(wú)法使用java-jar命令啟動(dòng)的問題及解決方法,主要是需要在pom.xml文件中添加spring-boot-maven-plugin插件,以便生成正確的MANIFEST.MF文件
    2026-04-04

最新評(píng)論

韶关市| 义马市| 资兴市| 巫溪县| 偃师市| 兴安县| 定州市| 封开县| 莱芜市| 甘孜| 颍上县| 宜宾县| 中宁县| 宁河县| 七台河市| 大姚县| 宁河县| 土默特左旗| 密云县| 延吉市| 连云港市| 安溪县| 宁夏| 罗定市| 鹤岗市| 涞水县| 巫山县| 龙井市| 吉隆县| 南澳县| 娱乐| 神农架林区| 上思县| 延庆县| 荔浦县| 察哈| 清丰县| 淳安县| 益阳市| 和田市| 新巴尔虎右旗|