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[访问顺序]
关键点说明:
-
继承自 HashMap:
- 利用哈希表实现快速查找
- 使用链表法解决哈希冲突
-
额外维护双向链表:
- 记录插入或访问顺序
- 实现按顺序遍历
-
特殊之处:
- 结合了 HashMap 的快速查找
- 又保持了元素的顺序
- 支持按访问频率排序
这就是 LinkedHashMap 的核心实现!🚀
6.通俗易懂的理解下
让我用一个生动的"图书馆"的例子来解释 LinkedHashMap:
1、整体结构 📚
想象一个现代化图书馆:
graph TD
A[图书馆系统] --> B[电脑检索系统<br/>HashMap部分]
A --> C[图书架顺序<br/>LinkedList部分]
B --> D[快速找书]
C --> E[保持书的顺序]
2、具体工作方式 🏢
- 电脑检索系统(HashMap部分):
class Library {
// 就像图书馆的电脑系统
val computerSystem = hashMapOf(
"红楼梦" to "A区1号",
"西游记" to "B区2号",
"三国演义" to "C区3号"
)
}
- 实体书架(LinkedList部分):
class BookShelf {
// 书架上的实际排列顺序
val books = linkedListOf(
"红楼梦" ➡️ "西游记" ➡️ "三国演义"
)
}
3、三个参数的比喻 📊
val library = LinkedHashMap<String, Book>(
initialCapacity = 10, // 初始书架数
loadFactor = 0.75f, // 书架扩建时机
accessOrder = true // 热门书籍排序
)
就像:
-
initialCapacity = 10- 图书馆开始准备10个书架
- 太少:经常要加书架
- 太多:浪费空间
-
loadFactor = 0.75f- 当书架放满75%时开始扩建
- 就像商场人流量到75%就要考虑扩建
-
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、形象的例子 📖
想象图书馆的一天:
- 早上整理书架(初始化):
val library = LinkedHashMap<String, Book>(
initialCapacity = 10, // 准备10个书架
loadFactor = 0.75f, // 75%满时扩建
accessOrder = true // 热门书放前面
)
- 上午新书上架(put操作):
// 像在书架上摆放新书
library["红楼梦"] = Book("红楼梦")
library["西游记"] = Book("西游记")
library["三国演义"] = Book("三国演义")
- 下午借书(get操作):
// 有人借了"西游记"
library["西游记"]?.let {
// 如果设置了 accessOrder = true
// "西游记"会被移到最近借阅区
// 顺序变成:红楼梦 -> 三国演义 -> 西游记
}
6、生动对比 🎭
就像:
-
商场商品架:
- 电脑系统(快速找商品)
- 实际货架(保持顺序)
-
餐厅点餐系统:
- 点餐系统(快速查菜)
- 出菜顺序(保持顺序)
-
医院挂号系统:
- 查号系统(快速找号)
- 就诊顺序(保持顺序)
这样理解 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.具体解释 📝
-
上半部分(商品电脑系统):
- 就像超市的电脑系统
- 可以快速查找任何商品位置
- 类似输入"牛奶",立即知道在"A区1号"
-
下半部分(实际货架):
- 就像超市的实体货架
- 商品按顺序一个挨一个摆放
- 类似:牛奶→面包→饮料
-
虚线连接:
- 就像"系统记录"和"实物位置"的对应关系
- 电脑查到"牛奶在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.生活场景对比 🏬
就像:
-
图书馆:
- 电脑检索系统(快速查找)
- 实际书架(按顺序排列)
-
停车场:
- 车位显示屏(快速查位)
- 实际车位(顺序排列)
-
医院:
- 导诊系统(快速查科室)
- 实际诊室(顺序排列)
5.优势说明 ⭐
-
查找快速:
- 像在电脑里直接搜索
- 不用一个个货架找
-
保持顺序:
- 像货架上的实际摆放
- 记住商品的前后关系
-
两者结合:
- 既能快速找到
- 又能保持顺序
这就是为什么 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[访问顺序]
就像一个智能商场:
-
HashMap部分:
- 像商品编号系统
- 可以快速找到商品位置
-
LinkedList部分:
- 像商品的陈列顺序
- 可以记住商品的摆放顺序
-
特点:
- 快速查找(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 // 保持固定顺序
)
}
五、参数选择建议 📋
-
initialCapacity(初始容量):
- 预计数据量的 1.5 倍左右
- 太小:频繁扩容
- 太大:浪费内存
-
loadFactor(装载因子):
- 0.75f 是最佳实践
- 小于 0.75:空间换时间
- 大于 0.75:时间换空间
-
accessOrder(访问顺序):
- true:需要记录访问顺序时(如:最近使用)
- false:需要保持原始顺序时(如:购物车)
就像开超市:
- initialCapacity:开始准备多少货架
- loadFactor:什么时候开始扩建
- accessOrder:商品怎么排列