数据结构 - LinkedHashMap(一)

182 阅读7分钟

LinkedHashMap 的底层原理

一、LinkedHashMap 的结构 🏪

1.核心结构实现 🏗️
/**
 * LinkedHashMap 的简化实现
 * 就像一个既有目录又有顺序的商店系统
 */
class SimpleLinkedHashMap<K, V> {
    // 1. 节点定义(商品展示架)
    private class Entry<K, V>(
        val key: K,
        var value: V,
        var next: Entry<K, V>? = null,    // HashMap 链表用
        var before: Entry<K, V>? = null,   // 双向链表前指针
        var after: Entry<K, V>? = null     // 双向链表后指针
    )

    // 2. 核心属性
    private val table: Array<Entry<K, V>?> = arrayOfNulls(16)  // 哈希表(商品目录)
    private var head: Entry<K, V>? = null  // 链表头(第一个商品)
    private var tail: Entry<K, V>? = null  // 链表尾(最后一个商品)
    private var size = 0                   // 当前大小
    private val loadFactor = 0.75f         // 装载因子
    private val accessOrder: Boolean       // 是否按访问顺序

    // 3. 构造函数
    constructor(
        initialCapacity: Int = 16,
        loadFactor: Float = 0.75f,
        accessOrder: Boolean = false
    ) {
        this.accessOrder = accessOrder
    }
}
2.核心方法实现 🔨
/**
 * LinkedHashMap 的核心操作
 */
class SimpleLinkedHashMap<K, V> {
    // ... 前面的代码 ...

    /**
     * 1. 添加元素
     */
    fun put(key: K, value: V) {
        val hash = hash(key)
        val i = indexFor(hash, table.size)

        // 1.1 查找是否已存在
        var e = table[i]
        while (e != null) {
            if (e.key == key) {
                // 更新已存在的值
                val oldValue = e.value
                e.value = value
                // 如果是访问顺序,则移动到末尾
                if (accessOrder) {
                    moveToLast(e)
                }
                return
            }
            e = e.next
        }

        // 1.2 创建新节点
        val newEntry = Entry(key, value)
        // 加入哈希表
        newEntry.next = table[i]
        table[i] = newEntry
        // 加入双向链表
        linkLast(newEntry)
        size++

        // 1.3 检查是否需要扩容
        if (size > table.size * loadFactor) {
            resize()
        }
    }

    /**
     * 2. 获取元素
     */
    fun get(key: K): V? {
        val hash = hash(key)
        val i = indexFor(hash, table.size)

        var e = table[i]
        while (e != null) {
            if (e.key == key) {
                // 如果是访问顺序,则移动到末尾
                if (accessOrder) {
                    moveToLast(e)
                }
                return e.value
            }
            e = e.next
        }
        return null
    }

    /**
     * 3. 维护双向链表
     */
    private fun linkLast(entry: Entry<K, V>) {
        val last = tail
        tail = entry
        if (last == null) {
            head = entry
        } else {
            last.after = entry
            entry.before = last
        }
    }

    /**
     * 4. 移动节点到末尾
     */
    private fun moveToLast(entry: Entry<K, V>) {
        // 如果已经是最后一个,不需要移动
        if (entry == tail) return

        // 4.1 处理前后指针
        val before = entry.before
        val after = entry.after
        
        // 4.2 断开当前位置
        before?.after = after
        after?.before = before
        if (head == entry) {
            head = after
        }

        // 4.3 移动到末尾
        linkLast(entry)
    }
}
3.辅助方法实现 🛠️
/**
 * LinkedHashMap 的辅助方法
 */
class SimpleLinkedHashMap<K, V> {
    // ... 前面的代码 ...

    /**
     * 1. 计算哈希值
     */
    private fun hash(key: K): Int {
        var h = key?.hashCode() ?: 0
        h = h xor (h ushr 20) xor (h ushr 12)
        return h xor (h ushr 7) xor (h ushr 4)
    }

    /**
     * 2. 计算数组下标
     */
    private fun indexFor(hash: Int, length: Int): Int {
        return hash and (length - 1)
    }

    /**
     * 3. 扩容操作
     */
    private fun resize() {
        // 3.1 创建新数组
        val newTable = arrayOfNulls<Entry<K, V>>(table.size * 2)
        
        // 3.2 重新哈希
        var e = head
        while (e != null) {
            val hash = hash(e.key)
            val i = indexFor(hash, newTable.size)
            // 保持原有的链表顺序
            e.next = newTable[i]
            newTable[i] = e
            e = e.after
        }
        
        // 3.3 更新表
        table = newTable
    }

    /**
     * 4. 删除元素
     */
    fun remove(key: K): V? {
        val hash = hash(key)
        val i = indexFor(hash, table.size)

        var prev: Entry<K, V>? = null
        var e = table[i]

        while (e != null) {
            if (e.key == key) {
                // 4.1 从哈希表中删除
                if (prev == null) {
                    table[i] = e.next
                } else {
                    prev.next = e.next
                }

                // 4.2 从双向链表中删除
                val before = e.before
                val after = e.after
                
                before?.after = after
                after?.before = before
                
                if (head == e) head = after
                if (tail == e) tail = before

                size--
                return e.value
            }
            prev = e
            e = e.next
        }
        return null
    }
}
4.使用示例 📝
/**
 * LinkedHashMap 的使用示例
 */
fun main() {
    // 1. 创建 LinkedHashMap(按访问顺序)
    val map = SimpleLinkedHashMap<String, String>(
        initialCapacity = 4,
        loadFactor = 0.75f,
        accessOrder = true
    )

    // 2. 添加元素
    map.put("1", "一")
    map.put("2", "二")
    map.put("3", "三")

    // 3. 访问元素(会影响顺序)
    map.get("1")  // "一"会移到最后

    // 4. 删除元素
    map.remove("2")
}
5.工作原理图解 🎯
graph TD
    A[LinkedHashMap] --> B[HashMap结构]
    A --> C[双向链表]
    
    B --> D[哈希表]
    B --> E[哈希冲突]
    
    C --> F[维护顺序]
    C --> G[快速遍历]
    
    D --> H[快速查找]
    E --> I[链表法]
    
    F --> J[插入顺序]
    F --> K[访问顺序]

关键点说明:

  1. 继承自 HashMap:

    • 利用哈希表实现快速查找
    • 使用链表法解决哈希冲突
  2. 额外维护双向链表:

    • 记录插入或访问顺序
    • 实现按顺序遍历
  3. 特殊之处:

    • 结合了 HashMap 的快速查找
    • 又保持了元素的顺序
    • 支持按访问频率排序

这就是 LinkedHashMap 的核心实现!🚀

6.通俗易懂的理解下

让我用一个生动的"图书馆"的例子来解释 LinkedHashMap:

1、整体结构 📚

想象一个现代化图书馆:

graph TD
    A[图书馆系统] --> B[电脑检索系统<br/>HashMap部分]
    A --> C[图书架顺序<br/>LinkedList部分]
    
    B --> D[快速找书]
    C --> E[保持书的顺序]
2、具体工作方式 🏢
  1. 电脑检索系统(HashMap部分):
class Library {
    // 就像图书馆的电脑系统
    val computerSystem = hashMapOf(
        "红楼梦" to "A区1号",
        "西游记" to "B区2号",
        "三国演义" to "C区3号"
    )
}
  1. 实体书架(LinkedList部分):
class BookShelf {
    // 书架上的实际排列顺序
    val books = linkedListOf(
        "红楼梦" ➡️ "西游记" ➡️ "三国演义"
    )
}
3、三个参数的比喻 📊
val library = LinkedHashMap<String, Book>(
    initialCapacity = 10,  // 初始书架数
    loadFactor = 0.75f,    // 书架扩建时机
    accessOrder = true     // 热门书籍排序
)

就像:

  1. initialCapacity = 10

    • 图书馆开始准备10个书架
    • 太少:经常要加书架
    • 太多:浪费空间
  2. loadFactor = 0.75f

    • 当书架放满75%时开始扩建
    • 就像商场人流量到75%就要考虑扩建
  3. accessOrder = true

    • true:热门书籍放在最容易拿到的位置
    • false:保持图书原有的排列顺序
4、实际操作演示 🎯
// 图书馆日常操作
class ModernLibrary {
    val books = LinkedHashMap<String, Book>(10, 0.75f, true)
    
    // 1. 新书上架
    fun addNewBook(name: String, book: Book) {
        books[name] = book
        // 自动排在最后一个位置
    }
    
    // 2. 借阅图书
    fun borrowBook(name: String): Book? {
        return books[name]?.also {
            // 如果是按访问顺序(true)
            // 这本书会被移到"最近借阅"的位置
        }
    }
    
    // 3. 查看书架
    fun displayBooks() {
        books.forEach { (name, book) ->
            // 按顺序显示所有图书
            println("书名:$name")
        }
    }
}
5、形象的例子 📖

想象图书馆的一天:

  1. 早上整理书架(初始化):
val library = LinkedHashMap<String, Book>(
    initialCapacity = 10,  // 准备10个书架
    loadFactor = 0.75f,    // 75%满时扩建
    accessOrder = true     // 热门书放前面
)
  1. 上午新书上架(put操作):
// 像在书架上摆放新书
library["红楼梦"] = Book("红楼梦")
library["西游记"] = Book("西游记")
library["三国演义"] = Book("三国演义")
  1. 下午借书(get操作):
// 有人借了"西游记"
library["西游记"]?.let {
    // 如果设置了 accessOrder = true
    // "西游记"会被移到最近借阅区
    // 顺序变成:红楼梦 -> 三国演义 -> 西游记
}
6、生动对比 🎭

就像:

  1. 商场商品架:

    • 电脑系统(快速找商品)
    • 实际货架(保持顺序)
  2. 餐厅点餐系统:

    • 点餐系统(快速查菜)
    • 出菜顺序(保持顺序)
  3. 医院挂号系统:

    • 查号系统(快速找号)
    • 就诊顺序(保持顺序)

这样理解 LinkedHashMap:

  • 既有电脑的快速查找(HashMap)
  • 又有实物的顺序摆放(LinkedList)
  • 可以根据需求调整排序方式(accessOrder)

二、图解结构 📊

graph LR
    A[商品目录<br/>HashMap] --> B1[商品1]
    A --> B2[商品2]
    A --> B3[商品3]
    
    C[展示顺序<br/>LinkedList] --> D1[商品1] --> D2[商品2] --> D3[商品3]
    
    B1 -.-> D1
    B2 -.-> D2
    B3 -.-> D3
   
形象解释

让我用更生动的方式重新解释这个结构:

1.超市购物的例子 🏪

想象一个超市的商品管理系统:

graph LR
    A[商品电脑系统<br/>快速查找] --> B1[牛奶<br/>A区1号]
    A --> B2[面包<br/>B区2号]
    A --> B3[饮料<br/>C区3号]
    
    C[实际货架<br/>顺序摆放] --> D1[牛奶] --> D2[面包] --> D3[饮料]
    
    B1 -.找到实物位置.-> D1
    B2 -.找到实物位置.-> D2
    B3 -.找到实物位置.-> D3
   
2.具体解释 📝
  1. 上半部分(商品电脑系统):

    • 就像超市的电脑系统
    • 可以快速查找任何商品位置
    • 类似输入"牛奶",立即知道在"A区1号"
  2. 下半部分(实际货架):

    • 就像超市的实体货架
    • 商品按顺序一个挨一个摆放
    • 类似:牛奶→面包→饮料
  3. 虚线连接:

    • 就像"系统记录"和"实物位置"的对应关系
    • 电脑查到"牛奶在A区1号"
    • 然后就能找到货架上的实际牛奶
3.实际运作 🔄
// 就像超市管理系统
class Supermarket {
    // 1. 电脑系统(快速查找)
    val computer = hashMapOf(
        "牛奶" to "A区1号",
        "面包" to "B区2号",
        "饮料" to "C区3号"
    )
    
    // 2. 货架顺序(按顺序摆放)
    val shelf = linkedListOf("牛奶", "面包", "饮料")
    
    // 3. 查找商品
    fun findProduct(name: String) {
        // 先在电脑查位置
        val location = computer[name]
        // 然后去货架找实物
        val product = shelf.find { it == name }
    }
}
4.生活场景对比 🏬

就像:

  1. 图书馆:

    • 电脑检索系统(快速查找)
    • 实际书架(按顺序排列)
  2. 停车场:

    • 车位显示屏(快速查位)
    • 实际车位(顺序排列)
  3. 医院:

    • 导诊系统(快速查科室)
    • 实际诊室(顺序排列)
5.优势说明 ⭐
  1. 查找快速:

    • 像在电脑里直接搜索
    • 不用一个个货架找
  2. 保持顺序:

    • 像货架上的实际摆放
    • 记住商品的前后关系
  3. 两者结合:

    • 既能快速找到
    • 又能保持顺序

这就是为什么 LinkedHashMap 这么实用:

  • 像有了电脑系统的超市
  • 既能快速找到商品
  • 又能记住商品顺序

三、基本操作演示 🎯

/**
 * LinkedHashMap 的基本操作
 * 就像商场的日常运营
 */
class LinkedHashMapDemo {
    // 创建一个容量为3的LinkedHashMap
    private val shop = LinkedHashMap<String, String>(3, 0.75f, true)
    
    /**
     * 1. 上新商品(put操作)
     */
    fun addNewProduct() {
        // 添加商品
        shop.put("A001", "手机")  // 第1个上架
        shop.put("A002", "电脑")  // 第2个上架
        shop.put("A003", "平板")  // 第3个上架
        
        // 展示当前商品(按上架顺序)
        // 输出: 手机 -> 电脑 -> 平板
        shop.forEach { println(it.value) }
    }
    
    /**
     * 2. 查找商品(get操作)
     */
    fun findProduct() {
        // 查询商品
        shop.get("A001")  // 查看手机
        
        // 再次展示(手机被移到最后)
        // 输出: 电脑 -> 平板 -> 手机
        shop.forEach { println(it.value) }
    }
    
    /**
     * 3. 商品上限(removeEldestEntry)
     */
    val limitedShop = object : LinkedHashMap<String, String>(3, 0.75f, true) {
        override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, String>): Boolean {
            // 超过3个商品就清理最旧的
            return size > 3
        }
    }
}

四、访问顺序演示 🔄

/**
 * 访问顺序的演示
 * 就像商品的热度排序
 */
class AccessOrderDemo {
    // true表示按访问顺序排序(像热销榜)
    val hotSales = LinkedHashMap<String, String>(3, 0.75f, true)
    
    // false表示按插入顺序排序(像新品区)
    val newArrivals = LinkedHashMap<String, String>(3, 0.75f, false)
    
    fun demo() {
        // 1. 添加商品
        listOf("A", "B", "C").forEach { 
            hotSales[it] = "商品$it"
            newArrivals[it] = "商品$it"
        }
        
        // 2. 访问商品B
        hotSales["B"]
        newArrivals["B"]
        
        // 3. 查看顺序
        println("热销榜:") // A -> C -> B(B被移到最后)
        hotSales.forEach { println(it) }
        
        println("新品区:") // A -> B -> C(顺序不变)
        newArrivals.forEach { println(it) }
    }
}

五、实际应用场景 💡

/**
 * 实际应用示例
 */
class RealWorldExamples {
    /**
     * 1. 最近浏览记录
     */
    class BrowsingHistory(private val maxSize: Int = 100) {
        private val history = object : LinkedHashMap<String, String>(
            maxSize, 0.75f, true
        ) {
            override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, String>): Boolean {
                return size > maxSize
            }
        }
        
        fun addRecord(url: String, title: String) {
            history[url] = title
        }
        
        fun getRecentHistory(): List<String> {
            return history.values.toList()
        }
    }
    
    /**
     * 2. 购物车系统
     */
    class ShoppingCart {
        private val cart = LinkedHashMap<String, Int>()  // 商品ID -> 数量
        
        fun addItem(productId: String, quantity: Int) {
            // 已有商品数量增加,新商品添加到最后
            cart[productId] = (cart[productId] ?: 0) + quantity
        }
        
        fun getItems(): List<String> {
            // 按添加顺序返回商品
            return cart.keys.toList()
        }
    }
    
    /**
     * 3. 缓存系统
     */
    class SimpleCache<K, V>(private val maxSize: Int) {
        private val cache = object : LinkedHashMap<K, V>(
            maxSize, 0.75f, true
        ) {
            override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>): Boolean {
                return size > maxSize
            }
        }
        
        @Synchronized
        fun put(key: K, value: V) {
            cache[key] = value
        }
        
        @Synchronized
        fun get(key: K): V? = cache[key]
    }
}

六、工作原理图解 🎯

graph TD
    A[LinkedHashMap] --> B[HashMap部分]
    A --> C[LinkedList部分]
    
    B --> D[快速查找]
    B --> E[存储数据]
    
    C --> F[维护顺序]
    C --> G[双向链表]
    
    D --> H[O1时间复杂度]
    E --> I[键值对存储]
    
    F --> J[插入顺序]
    F --> K[访问顺序]
    

就像一个智能商场:

  1. HashMap部分:

    • 像商品编号系统
    • 可以快速找到商品位置
  2. LinkedList部分:

    • 像商品的陈列顺序
    • 可以记住商品的摆放顺序
  3. 特点:

    • 快速查找(O(1)时间复杂度)
    • 有序存储(记住顺序)
    • 灵活排序(可按访问频率排序)

这就是为什么 LinkedHashMap 特别适合:

  • 需要快速查找
  • 又要保持顺序
  • 需要记住访问频率

LinkeHashMap三个参数的意义及应用

让我用生动的例子解释这三个参数:

一、参数详解 🏪

/**
 * LinkedHashMap(initialCapacity, loadFactor, accessOrder)
 * 就像开一家新超市时的三个重要决定:
 */
class SupermarketPlanning {
    val shop = LinkedHashMap<String, String>(
        initialCapacity = 3,    // 初始货架数
        loadFactor = 0.75f,     // 扩建阈值
        accessOrder = true      // 热销品排序
    )
}

二、参数解释 📝

graph TD
    A[开店三大决定] --> B[初始货架<br/>initialCapacity]
    A --> C[扩建时机<br/>loadFactor]
    A --> D[商品排序<br/>accessOrder]
    
    B --> B1[3个货架]
    B --> B2[太少要排队]
    B --> B3[太多浪费空间]
    
    C --> C1[75%货架占用]
    C --> C2[达到阈值扩建]
    C --> C3[平衡效率成本]
    
    D --> D1[true: 热度排序]
    D --> D2[false: 上架顺序]
   

三、生动示例 🎯

/**
 * 超市管理系统
 */
class SmartSupermarket {
    /**
     * 1. initialCapacity = 3
     * 就像开店时准备3个货架
     */
    fun explainInitialCapacity() {
        // 小型超市,3个货架
        val smallShop = LinkedHashMap<String, String>(3, 0.75f, true)
        // 大型超市,10个货架
        val bigShop = LinkedHashMap<String, String>(10, 0.75f, true)
        
        // 太小:经常需要加货架(扩容)
        // 太大:浪费空间
        // 要根据预计商品数量合理设置
    }
    
    /**
     * 2. loadFactor = 0.75f
     * 就像货架占用75%时开始扩建
     */
    fun explainLoadFactor() {
        // 保守型,60%就扩建
        val cautiousShop = LinkedHashMap<String, String>(3, 0.6f, true)
        // 激进型,90%才扩建
        val aggressiveShop = LinkedHashMap<String, String>(3, 0.9f, true)
        
        // 太小:浪费空间,经常扩建
        // 太大:查找效率下降
        // 0.75是经验值,平衡了空间和效率
    }
    
    /**
     * 3. accessOrder = true/false
     * 就像商品排序方式
     */
    fun explainAccessOrder() {
        // 热销品排序(按访问顺序)
        val hotItemsShop = LinkedHashMap<String, String>(3, 0.75f, true)
        hotItemsShop["A"] = "商品A"
        hotItemsShop["B"] = "商品B"
        hotItemsShop["C"] = "商品C"
        
        // 访问B商品后,B会移到最后(最近使用)
        hotItemsShop["B"]
        // 顺序变成:A -> C -> B
        
        // 新品排序(按插入顺序)
        val newItemsShop = LinkedHashMap<String, String>(3, 0.75f, false)
        newItemsShop["A"] = "商品A"
        newItemsShop["B"] = "商品B"
        newItemsShop["C"] = "商品C"
        
        // 访问B商品后,顺序不变
        newItemsShop["B"]
        // 顺序保持:A -> B -> C
    }
}

四、实际应用 💡

/**
 * 不同场景的应用
 */
class RealWorldExamples {
    /**
     * 1. 购物车(保持添加顺序)
     */
    val shoppingCart = LinkedHashMap<String, Int>(
        initialCapacity = 10,  // 预计10件商品
        loadFactor = 0.75f,    // 标准装载因子
        accessOrder = false    // 保持添加顺序
    )
    
    /**
     * 2. 最近浏览记录(访问顺序)
     */
    val recentViews = LinkedHashMap<String, String>(
        initialCapacity = 100,  // 预计100条记录
        loadFactor = 0.75f,     // 标准装载因子
        accessOrder = true     // 按访问顺序排序
    )
    
    /**
     * 3. 商品分类(固定顺序)
     */
    val categories = LinkedHashMap<String, List<String>>(
        initialCapacity = 5,   // 5个分类
        loadFactor = 0.75f,    // 标准装载因子
        accessOrder = false    // 保持固定顺序
    )
}

五、参数选择建议 📋

  1. initialCapacity(初始容量):

    • 预计数据量的 1.5 倍左右
    • 太小:频繁扩容
    • 太大:浪费内存
  2. loadFactor(装载因子):

    • 0.75f 是最佳实践
    • 小于 0.75:空间换时间
    • 大于 0.75:时间换空间
  3. accessOrder(访问顺序):

    • true:需要记录访问顺序时(如:最近使用)
    • false:需要保持原始顺序时(如:购物车)

就像开超市:

  • initialCapacity:开始准备多少货架
  • loadFactor:什么时候开始扩建
  • accessOrder:商品怎么排列