Map(HashMap、LinkedHashMap、TreeMap)双列集合

目录

 

HashMap:   

一、核心特性:

二、底层实现原理:

三、扩容机制:

四、常用方法:

五、遍历操作:

LinkedHashMap:

TreeMap:

HashMap、LinkedHashMap、TreeMap的异同:

一、相同点:

二、不同点:

三、有序性:


 

HashMap:   

   HashMap 是 Java 集合框架中最常用的 键值对(key-value)存储结构,实现了 Map 接口,基于哈希表(数组 + 链表 +红黑树)实现,具有高效的增删改查操作。

一、核心特性:

(1)存储结构:键值对(key-value),通过 key 快速定位 value。

(2)允许 null:可以存储 null 键(仅允许一个)和 null 值(多个)。

(3)无序性:元素存储顺序与插入顺序无关,遍历顺序不固定。

(4)高效性:添加、删除、查询的平均时间复杂度为 O(1)

(5)非线程安全:多线程环境下可能出现并发问题(如使用 ConcurrentHashMap 替代)。

二、底层实现原理:

HashMap 的底层是 哈希表,由数组、链表和红黑树组成:

(1)数组:每个元素是一个链表 / 红黑树的头节点,初始容量默认是 16(2 的幂次方)。

(2)链表:当多个 key 的哈希值相同时(哈希冲突),会以链表形式存储在同一数组中。

(3)红黑树:当链表长度超过阈值(默认 8)时,会转为红黑树,以提高查询效率(时间复杂度从 O (n) 优化为 O (log n))。

三、扩容机制:

      当 HashMap 中存储的键值对数量(size)超过 扩容阈值(threshold) 时,会自动触发扩容。
阈值计算公式:threshold = 当前容量(capacity) × 加载因子(loadFactor)

  • 默认初始容量为 16,默认加载因子为 0.75f,因此初始阈值为 16 × 0.75 = 12
  • 当元素数量超过 12 时,容量会从 16 扩容至 32,新阈值为 32 × 0.75 = 24,以此类推。

 (1)计算新容量:新容量 = 旧容量 × 2(始终保持为 2 的幂次方,这是 HashMap 的设计要求)。

 (2)创建新哈希表:初始化一个容量为新容量的数组(新 "桶" 数组)

(3)迁移元素:将旧数组中的所有键值对重新计算哈希值,并迁移到新数组中:

  1. 对于链表中的元素:重新计算在新数组中的位置(因容量翻倍,哈希计算逻辑简化)。

  2. 对于红黑树中的元素:若树节点数少于 6,则退化为链表;否则拆分红黑树并迁移。

 (4)更新参数:将新容量和新阈值(新容量 × 加载因子)赋值给当前 HashMap 实例。

四、常用方法:

(1)put(K key, V value):添加键值对(若 key 已存在则覆盖 value)。

(2) putAll(map m) :添加一个map双列集合,将指定map中的所有键值对存入当前map对象 泛型类型一致,如果存在相同的键,值直接覆盖

(3)get(Object key):根据 key 获取对应的 value(不存在返回 null)。

(4) getOrDefault(object key,v defaultValue)//根据key,返回【value值】:如果key不存在,则返回【默认值】

(5)remove(Object key):根据 key 删除键值对。

(6) remove(Object key, Object value):根据 key和所应用的值删除键值对。

(6)containsKey(Object key):判断是否包含指定 key。

(7)containsValue(Object value):判断是否包含指定 Value。

(8)isEmpty():判空

(9)size():查看集合大小

(10)replace(K key, V value):替换key 所对应的值为value

(11)replace(K key, V oldValue, V newValue)替换key 所对应的值value为newValue

package com.yuan.hashmap;

import java.util.HashMap;

public class Demo02 {
    public static void main(String[] args) {
        HashMap<String, String> hashMap = new HashMap<>();
        insertElement(hashMap);
//        selectElement(hashMap);
        deleteAndReplace(hashMap);

    }

    public static void insertElement(HashMap<String, String> hashMap) {
        System.out.println("=========进入到添加方法===========");
        //1.V put(K key,V value)存键值对,如果集合个存在此键,直接存,返回值为null
        //当存在此键时,返回值为上一次的键对应的value,用此value覆盖
        String item1 = hashMap.put("110", "北京");
        String item2 = hashMap.put("110", "天津");
        hashMap.put("130", "河北");
        hashMap.put("610", "陕西");
        System.out.println("第一次存返回值:" + item1);
        System.out.println("第二次存返回值:" + item2);

        //void putAll(map m)
        HashMap<String, String> hs1 = new HashMap<>();
        hs1.put("620", "甘肃");
        hs1.put("630", "宁夏");
        hs1.put("610", "内蒙");

        //将指定map中的所有键值对存入当前map对象  泛型类型一致,如果存在相同的键,值直接覆盖
        hashMap.putAll(hs1);

        //V putIfAbsent(K key,V value)如果存在此键,则不存,返回值为键所对应的旧值,如果不存在,直接放
        String oldValue = hashMap.putIfAbsent("611", "西安");
        System.out.println("putIfAbsent再次存放610键:" + oldValue);
        System.out.println(hashMap);
    }

    public static void selectElement(HashMap<String, String> hashMap) {
        System.out.println("=========进入到查看方法===========");
        System.out.println("目前当前元素为:" + hashMap);
        //V get(object key)根据key,返回【value值】:如果key不存在,则返回【null】
        String value1 = hashMap.get("120");
        System.out.println("键120对应的值为:" + value1);

        //V getOrDefault(object key,v defaultValue)//根据key,返回【value值】:如果key不存在,则返回【默认值】
        String value2 = hashMap.getOrDefault("120", "不知道");
        System.out.println("键120对应的值为:" + value2);
        //boolean containsKey(Object key)
        boolean b1 = hashMap.containsKey("611");
        System.out.println("键611是否存在:" + b1);
        //boolean containsValue(Object value)
        boolean b2 = hashMap.containsValue("宁夏1");
        System.out.println("值宁夏1是否存在:" + b2);
        //boolean isEmpty()
        System.out.println("集合map是否为空:" + hashMap.isEmpty());
        //int size():查看集合大小
        System.out.println("集合的size:" + hashMap.size());
    }
    public static void deleteAndReplace(HashMap<String, String> hashMap) {
        System.out.println("===========进入到删除和修改方法中===========");
        System.out.println("目前map中的元素为:" + hashMap);
        //根据key,替换value,并返回【原value值】;如果key不存在,则返回【null】
        String value = hashMap.replace("110", "北京");
        System.out.println(value);
        //根据key和value,替换value,返回true或者false
        boolean b = hashMap.replace( "610",  "内蒙",  "陕西");
        System.out.println(b);
        //V remove(Object key)
        String oldValue = hashMap.remove("630");
        System.out.println("删除的键630对应的值为:" + oldValue);
        System.out.println("hashMap操作后:" + hashMap);

        boolean b2 = hashMap.remove("620", "甘肃");
        System.out.println("删除简直对是否成功:" + b2);
        System.out.println("hashMap操作后:" + hashMap);

        hashMap.clear();
        System.out.println("清空操作后:" + hashMap);
    }
}

五、遍历操作:

(1)keySet():返回所有 key 的集合。

(2)values():返回所有 value 的集合。

(3)entrySet():返回所有键值对(Map.Entry)的集合,用于遍历。

package com.yuan.hashmap;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo03 {
    public static void main(String[] args) {
        HashMap<String, String> hashMap = new HashMap<>();

        hashMap.put("110", "北京");
        hashMap.put("120", "天津");
        hashMap.put("130", "河北");
        hashMap.put("610", "陕西");
        hashMap.put("620", "甘肃");

        //1.Set<K> keySet() 获取所有的键
        Set<String> keys = hashMap.keySet();
        for (String key : keys) {
            String value = hashMap.get(key);
            System.out.printf("键为:%s - 值为%s\n", key, value);
        }
        //2.获取所有的value Collection<V> values()
        Collection<String> values = hashMap.values();
        System.out.println(values);
        //3.获取所有的键值对集合对象
        Set<Map.Entry<String, String>> entries = hashMap.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

LinkedHashMap:

              LinkedHashMap继承自HashMap,它的多种操作都是建立在HashMap操作的基础上的。和HashMap拥有相同的方法。同HashMap 不同的是,LinkedHashMap维护了一个双向链表,保证了插入的Entry中的顺序。这也是Linked的含义。

加入插入顺序为key1, key2, key3, key4, 那么就会维护一个红线所示的双向链表。为了实现双向链表,LinkedHashMap中提供了如下的Entry:

//LinkedHashMap中的node直接继承自HashMap中的Node。并且增加了双向的指针

static class Entry<K,V> extends HashMap.Node<K,V> {
        //before表示上一个节点,after表示下一个节点
        //通过 before + after 属性,我们就可以形成一个以 Entry 为节点的链表。
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

TreeMap:

    HashMap是一种以空间换时间的映射表,它的实现原理决定了内部的Key是无序的,即遍历HashMapKey时,其顺序是不可预测的(但每个Key都会遍历一次且仅遍历一次)。还有一种Map,它在内部会对Key进行排序,这种Map就是SortedMap。注意到SortedMap是接口,它的实现类是TreeMap。SortedMap保证遍历时以Key的顺序来进行排序。例如,放入的Key是"apple"、"pear"、"orange",遍历的顺序一定是"apple"、"orange"、"pear",因为String默认按字母排序:

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new TreeMap<>();
        map.put("orange", 1);
        map.put("apple", 2);
        map.put("pear", 3);
        for (String key : map.keySet()) {
            System.out.println(key);
        }
        // apple, orange, pear
    }
}

          使用TreeMap时,放入的Key必须实现Comparable接口。StringInteger这些类已经实现了Comparable接口,因此可以直接作为Key使用。作为Value的对象则没有任何要求。如果作为Keyclass没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义比较器

public class Main {
    public static void main(String[] args) {
        Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
            public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }
        });
        map.put(new Person("Tom"), 1);
        map.put(new Person("Bob"), 2);
        map.put(new Person("Lily"), 3);
        for (Person key : map.keySet()) {
            System.out.println(key);
        }
        // {Person: Bob}, {Person: Lily}, {Person: Tom}
        System.out.println(map.get(new Person("Bob"))); // 2
    }
}

class Person {
    public String name;
    Person(String name) {
        this.name = name;
    }
    public String toString() {
        return "{Person: " + name + "}";
    }
}

          注意到Comparator接口要求实现一个比较方法,它负责比较传入的两个元素ab,如果a<b,则返回负数,通常是-1,如果a==b,则返回0,如果a>b,则返回正数,通常是1TreeMap内部根据比较结果对Key进行排序。从上述代码执行结果可知,打印的Key确实是按照Comparator定义的顺序排序的。如果要根据Key查找Value,我们可以传入一个new Person("Bob")作为Key,它会返回对应的Integer值2

       另外,注意到Person类并未覆写equals()hashCode(),因为TreeMap不使用equals()hashCode()

HashMap、LinkedHashMap、TreeMap的异同:

一、相同点:

1、都实现了 Map 接口,提供了基本的键值对操作方法(如 put()get()remove() 等)。

2、HashMap和LinkedHashMap允许存储 null 值(但对 null 键的支持有所不同)。

3、都是非线程安全的(多线程环境下需额外处理同步问题)

二、不同点:

特性HashMapLinkedHashMapTreeMap
底层实现基于哈希表(数组 + 链表 / 红黑树)哈希表 + 双向链表红黑树(一种自平衡二叉搜索树)
有序性无序(插入顺序与遍历顺序无关)有序(默认按插入顺序,可配置为访问顺序)有序(按 key 的自然排序或自定义比较器)
查询效率平均 O (1),最坏 O (n)(哈希冲突严重时)同 HashMap(额外维护链表不影响查询)O (log n)(红黑树特性)
插入 / 删除效率平均 O (1),最坏 O (n)同 HashMap(需维护链表指针,略低)O(log n)
key 是否允许 null允许 1 个 null 键允许 1 个 null 键不允许 null 键(会抛 NullPointerException)
排序依据插入顺序或访问顺序(LRU 缓存常用)key 的自然排序(如 Integer 升序)或自定义 Comparator
适用场景一般场景,追求查询 / 插入效率需要保持插入顺序或实现 LRU 缓存需要按 key 排序的场景(如范围查询)

三、有序性:

1、HashMap:完全无序,遍历顺序与插入顺序无关。

2、LinkedHashMap:通过双向链表记录插入顺序,遍历顺序与插入顺序一致;若构造时指定 accessOrder=true,则按访问顺序排序(最近访问的元素放最后,适合 LRU 缓存)

3、TreeMap:默认按 key 的自然排序(如 String 按字典序、Integer 按数值),或通过 Comparator 自定义排序规则。

 

 

 

 

### 关于双列集合的数据结构、使用方法及其实现原理 #### 数据结构概述 双列集合是一种基于键值对(key-value pair)存储数据的集合类型。它在 Java 中主要由 `Map` 接口及其具体实现类构成,例如 `HashMap`、`TreeMap` 和 `LinkedHashMap` 等[^1]。这些实现类提供了不同的功能和性能特点。 - **键值对存储**:双列集合的核心特点是通过键来定位其对应的值。每个键只能映射到一个值,而多个键可以共享同一个值[^2]。 - **无序性与有序性**:大多数双列集合(如 `HashMap`)不保证元素的迭代顺序。然而,某些实现类(如 `TreeMap` 和 `LinkedHashMap`)则提供特定的排序或插入顺序保障[^3]。 --- #### 使用方法详解 以下是双列集合的主要操作方式: 1. **创建实例** 创建一个双列集合可以通过指定具体的实现类完成。例如: ```java Map<String, Integer> map = new HashMap<>(); ``` 2. **添加键值对** 使用 `put()` 方法将键值对插入集合中。如果键已存在,则会覆盖原有的值。 ```java map.put("Alice", 25); map.put("Bob", 30); ``` 3. **获取值** 利用 `get()` 方法通过键检索对应的值。如果键不存在,则返回 `null`。 ```java int ageOfAlice = map.get("Alice"); // 返回 25 ``` 4. **删除键值对** 调用 `remove()` 方法移除指定键的条目。 ```java map.remove("Bob"); ``` 5. **判断是否存在某个键或值** - 检查键的存在性:`containsKey()` - 检查值的存在性:`containsValue()` ```java boolean hasAlice = map.containsKey("Alice"); // true boolean hasAge30 = map.containsValue(30); // false ``` 6. **遍历集合** 遍历双列集合有多种方式,常见的包括: - 遍历键集 (`keySet`): ```java for (String key : map.keySet()) { System.out.println(key + ": " + map.get(key)); } ``` - 遍历条目集 (`entrySet`): ```java for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } ``` 7. **清空集合** 使用 `clear()` 方法清除所有键值对。 ```java map.clear(); ``` --- #### 实现原理分析 1. **哈希表机制(以 `HashMap` 为例)** - `HashMap` 基于哈希表实现,内部维护了一个数组用于存储桶(bucket),每个桶可能是一个链表或红黑树[^4]。 - 当调用 `put()` 插入键值对时,计算键的哈希码并将其映射到相应的桶位置。如果发生冲突(两个键具有相同的哈希码),则通过链地址法解决冲突。 - 如果链表长度超过阈值(默认为8),该链表会被转换为红黑树以提高查询效率。 2. **平衡二叉树(以 `TreeMap` 为例)** - `TreeMap` 基于红黑树实现,能够保持按键的自然顺序或自定义比较器规定的顺序排列。 - 所有的插入、删除和查找操作的时间复杂度均为 O(log n),因为每次调整都会重新平衡树结构。 3. **链接列表(以 `LinkedHashMap` 为例)** - `LinkedHashMap` 继承自 `HashMap` 并额外维护了一条双向链表,记录了键值对的插入顺序。 - 这种设计使其既具备高效访问能力又保留了迭代顺序。 --- #### 总结 双列集合Java 编程中非常重要的工具之一,适用于需要快速按键查找值的场景。理解其基本特性和底层实现有助于开发者更合理地选择合适的实现类,并优化程序性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呼哧呼哧.

栓Q!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值