linkedhashmap 排序_LinkedHashMap 原理分析

本文深入探讨了LinkedHashMap,它是HashMap的子类,实现了Map接口。LinkedHashMap通过before和after引用形成双向链表,根据访问时间排序。文章详细解析了HashMap的扩容策略和Tree化机制,并介绍了LinkedHashMap如何在get操作时更新访问顺序,以及如何利用removeEldestEntry实现LRU缓存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

de3792a52afe60318e8f9d6631c639d9.png

LinkedHashMap的原理大致和上篇文章提到的数据结构类似。LRU算法实现

它继承自HashMap , 实现了map 接口。 所以,我们先来看下HashMap 的实现方式。

一. HashMap

HashMap 的目的是为了实现一种键值对, 能够在O(1)的时间复杂度内找到键所对应的值值所在的位置。

97294af6910ae60f33b087f5fc197962.png
HashMap内存结构图

主要的数据结构由以下两个属性实现。

// 每一个节点的hash值,由key 的hashcode 和value 的hashcode异或得到。
// next 指向此节点在这条链中的后继节点
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}
// The table, initialized on first use,
// and resized as necessary. When allocated, length is always a power of two.
transient Node<K,V>[] table;

table 被称作hash 桶,每个桶存储一条节点拉链, 感觉英文表述的比较清楚,我将jdk 的源码注释放在了上面。

二. HashMap 主要属性

上面注释提到了当需要的时候,table 进行扩容。

HashMap 的扩容策略:

用属性initialCapacity 表示初始化容量。loadFactor表示加载因子。threshold表示哈希表内元素数量的阈值,当哈希表内元素数量超过阈值时,会发生扩容resize()。threshold = 哈希桶.length * loadFactor;

// 参数列表为空的HashMap 使用默认的initialCapacity(16)和默认的loadFactor(0.75)
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
} 

扩容的策略可以简单的理解为申请一个新的两倍大小的hash桶, 并将原来hash桶中的元素逐个复制到新的hash 桶中, 属性table指向新桶, 并回收旧的hash桶。

// tree 化的阈值
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;

jdk 中的HashMap 还有一个很重要的特性: 当HashMap 中元素的个数远大于桶的个数, 发生hash 碰撞的概率就会提高。 因为每个桶中是一条单链表。 假设链表长度平均为n , 查询的时间复杂度就变成了 log(n), 为此, HashMap 做了一个很巧妙的设计,当插入节点链表长度大于8 的时候, 会把节点结构由Node转化为TreeNode , 把单链表进行Tree 化转化为红黑树, 以此来降低时间复杂度。 上面两个属性就行Tree 化和 逆Tree化的长度阈值。

tips: 至于为啥阈值是8,看到过一种说法, log(8)= 3 , 8/2 = 4 , 当8个元素的时候, 遍历查找的时间复杂度开始大于红黑树。

三. LinkedHashMap概述

LinkedHashMap 的特性, 每个节点间由一个before引用 和 after 引用串联起来成为一个双向链表。链表节点按照访问时间进行排序,最近访问过的链表放在链表尾。

// 10 是初始大小,0.75 是装载因子,true 是表示按照访问时间排序
HashMap<Integer, Integer> m = new LinkedHashMap<>(10, 0.75f, true);
m.put(3, 11);
m.put(1, 12);
m.put(5, 23);
m.put(2, 22);

m.put(3, 26);
m.get(5);

for (Map.Entry e : m.entrySet()) {
  System.out.println(e.getKey());
}

例如 上段代码, 输出的结果就是 1,2,3,5。

四. LinkedHashMap 主要结构

其结构图如图:

6d02e3d81b8ddaa0a012dc61beca0ca9.png
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

在HashMap 的节点的基础上, 加上了两个引用来将所有节点串联成一个双向链表。

LinkedHashMap 重写了get() 方法,在afterNodeAccess()函数中,会将当前被访问到的节点e,移动至内部的双向链表的尾部。

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

LinkedHashMap并没有重写任何put方法。但是其重写了构建新节点的newNode()方法。

newNode()会在HashMap的putVal()方法里被调用,HashMap 的 put()方法会调用putVal() 方法。 实现的逻辑: 根据hash值找到散列位置i,先判断table[i]是否存在node ,如果不存在,则调用newNode()并赋值给table[i],如果存在,则插入到单链表或红黑树的后面

//在构建新节点时,构建的是`LinkedHashMap.Entry` 不再是`Node`.
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

重新后的newnode 只添加了一条逻辑, 把节点添加到双链表的尾部。

在HashMap 的putVal() 中有一个空方法就是为LinkedHashMap 预留的 :afterNodeAccess () 在HashMap 中它是一个空方法, 而在LinkedHashMap 中我们可以看到其实现:

//回调函数,新节点插入之后回调 , 根据evict 和   判断是否需要删除最老插入的节点。如果实现LruCache会用到这个方法。
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    //LinkedHashMap 默认返回false 则不删除节点
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true); }
    }
//LinkedHashMap 默认返回false 则不删除节点。 返回true 代表要删除最早的节点。通常构建一个LruCache会在达到Cache的上限是返回true
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false; 
}

回到上篇内容,如果我们要根据LinkedHashMap 实现一个LruCashed , 我们只需要继承LinkedHashMap ,重写removeEldestEntry, 当当前长度> 缓存长度, 返回true 即可。

### RT-DETRv3 网络结构分析 RT-DETRv3 是一种基于 Transformer 的实时端到端目标检测算法,其核心在于通过引入分层密集正监督方法以及一系列创新性的训练策略,解决了传统 DETR 模型收敛慢和解码器训练不足的问题。以下是 RT-DETRv3 的主要网络结构特点: #### 1. **基于 CNN 的辅助分支** 为了增强编码器的特征表示能力,RT-DETRv3 引入了一个基于卷积神经网络 (CNN) 的辅助分支[^3]。这一分支提供了密集的监督信号,能够与原始解码器协同工作,从而提升整体性能。 ```python class AuxiliaryBranch(nn.Module): def __init__(self, in_channels, out_channels): super(AuxiliaryBranch, self).__init__() self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1) self.bn = nn.BatchNorm2d(out_channels) def forward(self, x): return F.relu(self.bn(self.conv(x))) ``` 此部分的设计灵感来源于传统的 CNN 架构,例如 YOLO 系列中的 CSPNet 和 PAN 结构[^2],这些技术被用来优化特征提取效率并减少计算开销。 --- #### 2. **自注意力扰动学习策略** 为解决解码器训练不足的问题,RT-DETRv3 提出了一种名为 *self-att 扰动* 的新学习策略。这种策略通过对多个查询组中阳性样本的标签分配进行多样化处理,有效增加了阳例的数量,进而提高了模型的学习能力和泛化性能。 具体实现方式是在训练过程中动态调整注意力权重分布,确保更多的高质量查询可以与真实标注 (Ground Truth) 进行匹配。 --- #### 3. **共享权重解编码器分支** 除了上述改进外,RT-DETRv3 还引入了一个共享权重的解编码器分支,专门用于提供密集的正向监督信号。这一设计不仅简化了模型架构,还显著降低了参数量和推理时间,使其更适合实时应用需求。 ```python class SharedDecoderEncoder(nn.Module): def __init__(self, d_model, nhead, num_layers): super(SharedDecoderEncoder, self).__init__() decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=nhead) self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers) def forward(self, tgt, memory): return self.decoder(tgt=tgt, memory=memory) ``` 通过这种方式,RT-DETRv3 实现了高效的目标检测流程,在保持高精度的同时大幅缩短了推理延迟。 --- #### 4. **与其他模型的关系** 值得一提的是,RT-DETRv3 并未完全抛弃经典的 CNN 技术,而是将其与 Transformer 结合起来形成混合架构[^4]。例如,它采用了 YOLO 系列中的 RepNCSP 模块替代冗余的多尺度自注意力层,从而减少了不必要的计算负担。 此外,RT-DETRv3 还借鉴了 DETR 的一对一匹配策略,并在此基础上进行了优化,进一步提升了小目标检测的能力。 --- ### 总结 综上所述,RT-DETRv3 的网络结构主要包括以下几个关键组件:基于 CNN 的辅助分支、自注意力扰动学习策略、共享权重解编码器分支以及混合编码器设计。这些技术创新共同推动了实时目标检测领域的发展,使其在复杂场景下的表现更加出色。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值