说到集合类,之前介绍的arraylist类,hashmap可能是大家日常用的最多的类,但是对于另一个集合类 linkedhashmap,可能大家用的不多,但是这种链式哈希集合,有些情况确实特别好用。
1、linkedhashmap 定义linkedhashmap 是基于 hashmap 实现的一种集合,具有 hashmap 集合上面所说的所有特点,除了 hashmap 无序的特点,linkedhashmap 是有序的,因为 linkedhashmap 在 hashmap 的基础上单独维护了一个具有所有数据的双向链表,该链表保证了元素迭代的顺序。
所以我们可以直接这样说:linkedhashmap = hashmap + linkedlist。linkedhashmap 就是在 hashmap 的基础上多维护了一个双向链表,用来保证元素迭代顺序。
更形象化的图形展示可以直接移到文章末尾。
public class linkedhashmap extends hashmap implements map
2、字段属性①、entry
static class entry extends hashmap.node { entry before, after; entry(int hash, k key, v value, node next) { super(hash, key, value, next); } }linkedhashmap 的每个元素都是一个 entry,我们看到对于 entry 继承自 hashmap 的 node 结构,相对于 node 结构,linkedhashmap 多了 before 和 after 结构。
下面是map类集合基本元素的实现演变。
linkedhashmap 中 entry 相对于 hashmap 多出的 before 和 after 便是用来维护 linkedhashmap 插入 entry 的先后顺序的。
②、其它属性
//用来指向双向链表的头节点transient linkedhashmap.entry head;//用来指向双向链表的尾节点transient linkedhashmap.entry tail;//用来指定linkedhashmap的迭代顺序//true 表示按照访问顺序,会把访问过的元素放在链表后面,放置顺序是访问的顺序//false 表示按照插入顺序遍历final boolean accessorder;注意:这里有五个属性别搞混淆的,对于 node next 属性,是用来维护整个集合中 entry 的顺序。对于 entry before,entry after ,以及 entry head,entry tail,这四个属性都是用来维护保证集合顺序的链表,其中前两个before和after表示某个节点的上一个节点和下一个节点,这是一个双向链表。后两个属性 head 和 tail 分别表示这个链表的头节点和尾节点。
3、构造函数①、无参构造
public linkedhashmap() { super(); accessorder = false; }调用无参的 hashmap 构造函数,具有默认初始容量(16)和加载因子(0.75)。并且设定了 accessorder = false,表示默认按照插入顺序进行遍历。
②、指定初始容量
public linkedhashmap(int initialcapacity) { super(initialcapacity); accessorder = false; }③、指定初始容量和加载因子
public linkedhashmap(int initialcapacity, float loadfactor) { super(initialcapacity, loadfactor); accessorder = false; }4、添加元素linkedhashmap 中是没有 put 方法的,直接调用父类 hashmap 的 put 方法。关于 hashmap 的put 方法,可以参看我之前对于 hashmap 的介绍。
我将方法介绍复制到下面:
//hash(key)就是上面讲的hash方法,对其进行了第一步和第二步处理 public v put(k key, v value) { return putval(hash(key), key, value, false, true); } /** * * @param hash 索引的位置 * @param key 键 * @param value 值 * @param onlyifabsent true 表示不要更改现有值 * @param evict false表示table处于创建模式 * @return */ final v putval(int hash, k key, v value, boolean onlyifabsent, boolean evict) { node[] tab; node p; int n, i; //如果table为null或者长度为0,则进行初始化 //resize()方法本来是用于扩容,由于初始化没有实际分配空间,这里用该方法进行空间分配,后面会详细讲解该方法 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //注意:这里用到了前面讲解获得key的hash码的第三步,取模运算,下面的if-else分别是 tab[i] 为null和不为null if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newnode(hash, key, value, null);//tab[i] 为null,直接将新的key-value插入到计算的索引i位置 else {//tab[i] 不为null,表示该位置已经有值了 node e; k k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//节点key已经有值了,直接用新值覆盖 //该链是红黑树 else if (p instanceof treenode) e = ((treenode)p).puttreeval(this, tab, hash, key, value); //该链是链表 else { for (int bincount = 0; ; ++bincount) { if ((e = p.next) == null) { p.next = newnode(hash, key, value, null); //链表长度大于8,转换成红黑树 if (bincount >= treeify_threshold - 1) // -1 for 1st treeifybin(tab, hash); break; } //key已经存在直接覆盖value if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key v oldvalue = e.value; if (!onlyifabsent || oldvalue == null) e.value = value; afternodeaccess(e); return oldvalue; } } ++modcount;//用作修改和新增快速失败 if (++size > threshold)//超过最大容量,进行扩容 resize(); afternodeinsertion(evict); return null; }5、删除元素同理也是调用 hashmap 的remove 方法,这里我不作过多的讲解,着重看linkedhashmap 重写的第 46 行方法。
public v remove(object key) { node e; return (e = removenode(hash(key), key, null, false, true)) == null ? null : e.value; } final node removenode(int hash, object key, object value, boolean matchvalue, boolean movable) { node[] tab; node p; int n, index; //(n - 1) & hash找到桶的位置 if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { node node = null, e; k k; v v; //如果键的值与链表第一个节点相等,则将 node 指向该节点 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; //如果桶节点存在下一个节点 else if ((e = p.next) != null) { //节点为红黑树 if (p instanceof treenode) node = ((treenode)p).gettreenode(hash, key);//找到需要删除的红黑树节点 else { do {//遍历链表,找到待删除的节点 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } //删除节点,并进行调节红黑树平衡 if (node != null && (!matchvalue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof treenode) ((treenode)node).removetreenode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modcount; --size; afternoderemoval(node); return node; } } return null; }6、查找元素public v get(object key) { node e; if ((e = getnode(hash(key), key)) == null) return null; if (accessorder) afternodeaccess(e); return e.value; }相比于 hashmap 的 get 方法,这里多出了第 5,6行代码,当 accessorder = true 时,即表示按照最近访问的迭代顺序,会将访问过的元素放在链表后面。
对于 afternodeaccess(e) 方法,在前面第 4 小节 添加元素已经介绍过了,这就不在介绍。
7、遍历元素在介绍 hashmap 时,我们介绍了 4 中遍历方式,同理,对于 linkedhashmap 也有 4 种,这里我们介绍效率较高的两种遍历方式:
①、得到 entry 集合,然后遍历 entry
linkedhashmap map = new linkedhashmap(); map.put(a,1); map.put(b,2); map.put(c,3); map.get(b); set< map.entry> entryset = map.entryset(); for(map.entry entry : entryset ){ system.out.println(entry.getkey()+---+entry.getvalue()); }②、迭代
iterator< map.entry> iterator = map.entryset().iterator(); while(iterator.hasnext()){ map.entry entry = iterator.next(); system.out.println(entry.getkey()+----+entry.getvalue()); }8、小结好了,这就是jdk中java.util.linkedhashmap 类的介绍。
STM32接口中FSMC/FMC难点问题理解
触摸超极本热潮带动 英特尔力荐三触摸方案商
微软发布开源框架驱动程序模块新框架
HJT异质构叠层钙钛矿光伏电池
扭矩传感器的分类及其应用
Map类集合基本元素的实现演变
EVM和RF的各种技巧知识详解
随着不限量套餐的降价增量推广,运营商的量收剪刀差持续扩大
Core i7-1185G7处理器3DMark跑分截图曝光 采用四核心八线程设计且加速频率为4.3GHZ
Aurora OS是个什么来头?
主流ZigBee芯片大盘点(3):Silicon labs EM35x
将USB3.0用于存储媒体应用
食品中蛋白质含量测定仪的相关功能介绍
Rxiry昕锐XR2000激光测高仪
用于深海作业中海底光缆铺设设备的交流电机
新一代通信技术普及,5G测试是重头戏!—主题月
户外一体化水质微型自动监测站应用实施方案
人脸识别技术应用于工地 大大提高了建筑工地管理的效率
华为存储,真正关注客户的使用体验和服务体验
2023慕尼黑上海电子展现场看点提前揭幕!七成展位已售罄