Java核心技术:集合——映射

  • 基本映射操作
  • 更新映射项
  • 映射视图
  • 弱散列映射
  • 连接散列集与映射
  • 枚举集与映射
  • 标识散列映射

基本映射操作

Java类库为映射提供了两个通用的实现:HashMap和TreeMap。这两个类都实现了Map接口。

  • 散列映射对键进行散列;
  • 树映射用键的整体顺序对元素进行排序,并将其组织成搜索树;

散列或比较函数只能作用于键
如果不需要按照排列顺序访问键,就最好选择散列

如果在映射中没有与

往映射中添加对象时,使用public V put(K key, V value)方法,必须提供一个唯一的键。对同一个键两次调用put方法,第二个值会取代第一个值。put将返回上一个值。

要检索一个对象,使用public V get(Object key)方法。没有与给定键对应的信息,get将返回null。可以使用V getOrDefault(Object key, V defaultValue)方法指定一个默认值。

    @Overridepublic V getOrDefault(Object key, V defaultValue) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;}

V remove(Object key)方法用于从映射中删除给定键对应的元素。

    public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;}final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {Node<K,V>[] tab; Node<K,V> p; int n, index;if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {Node<K,V> node = null, e; K k; V v;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<K,V>)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<K,V>)node).removeTreeNode(this, tab, movable);else if (node == p)tab[index] = node.next;elsep.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;}

int size()方法用于返回映射中的元素数。

    public int size() {return size;}




更新映射项

  • 得到与键关联的原值,完成更新后,再放回更新后的值。
counts.put(word, counts.get(word) + 1);

有一个问题是:如果word原本不存在,get会返回null,会出现一个NullPointerException异常

  • 可以使用getOrDefault方法补救
counts.put(word, counts.getOrDefault(word, 0) + 1);
  • 另一种方式是先调用putIfAbsent,当键原先不存在时才会放入一个值。
counts.putIfAbsent(word, 0);
counts.put(word, counts.get(word) + 1);
    @Overridepublic V putIfAbsent(K key, V value) {return putVal(hash(key), key, value, true, true);}/*** Implements Map.put and related methods.** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { ... }
  • 更好的方法是使用merge方法。
counts.merge(word, 1, Integer::sum);
 /*** 如果key对应的值不存在,使用value关联key。如果key对应的值存在,使用remappingFunction(old, value)计算新值,如果结果计算为null,移出key;否则使用新值关联key。**/@Overridepublic V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {if (value == null)throw new NullPointerException();if (remappingFunction == null)throw new NullPointerException();int hash = hash(key);Node<K,V>[] tab; Node<K,V> first; int n, i;int binCount = 0;TreeNode<K,V> t = null;Node<K,V> old = null;if (size > threshold || (tab = table) == null ||(n = tab.length) == 0)n = (tab = resize()).length;if ((first = tab[i = (n - 1) & hash]) != null) {if (first instanceof TreeNode)old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);else {Node<K,V> e = first; K k;do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {old = e;break;}++binCount;} while ((e = e.next) != null);}}if (old != null) {V v;if (old.value != null)// 如果key对应的值存在且不为null,使用remappingFunction计算新值v = remappingFunction.apply(old.value, value);else// 如果key对应的值存在但为null,使用value作为新值v = value;if (v != null) {// 如果新值不是null,关联到keyold.value = v;afterNodeAccess(old);}else// 如果新值是null,移除keyremoveNode(hash, key, null, false, true);return v;}// 如果key对应的值不存在,value不是null,添加<key, value>为新结点if (value != null) {if (t != null)t.putTreeVal(this, tab, hash, key, value);else {tab[i] = newNode(hash, key, value, first);if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);}++modCount;++size;afterNodeInsertion(true);}return value;}

如果键原先不存在,将把word与1关联,否则使用Integer::sum函数组合原值和1。

映射视图

集合框架不认为映射本身是一个集合。可以的得到映射的视图(view)——这是实现了Collection接口或某个子接口的对象。
有3中视图:键集、值集合键/值对集。

Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K,V>> entrySet();

keySet返回的不是HashSet或TreeSet,而是实现了Set接口的另外某个类的对象。

    public Set<K> keySet() {Set<K> ks = keySet;if (ks == null) {ks = new KeySet();keySet = ks;}return ks;}final class KeySet extends AbstractSet<K> {public final int size()                 { return size; }public final void clear()               { HashMap.this.clear(); }public final Iterator<K> iterator()     { return new KeyIterator(); }public final boolean contains(Object o) { return containsKey(o); }public final boolean remove(Object key) {return removeNode(hash(key), key, null, false, true) != null;}public final Spliterator<K> spliterator() {return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);}public final void forEach(Consumer<? super K> action) {Node<K,V>[] tab;if (action == null)throw new NullPointerException();if (size > 0 && (tab = table) != null) {int mc = modCount;for (int i = 0; i < tab.length; ++i) {for (Node<K,V> e = tab[i]; e != null; e = e.next)action.accept(e.key);}if (modCount != mc)throw new ConcurrentModificationException();}}}
Set<String> keys = map.keySet();
for (String key : keys)
{do something with key
}
for (Map.Entry<String, Employee> entry : staff.entrySet())
{String k = entry.getKey();Employee v = entry.getValue();do something with k, v
}

如果再键集视图 上调用迭代器的remove方法,会删除这个键和与它关联的值。

 // java.util.HashMap.KeySet#removepublic final boolean remove(Object key) {return removeNode(hash(key), key, null, false, true) != null;}

如果视图调用add方法,会抛出一个UnsupportedOperationException异常。

 // java.util.AbstractCollection#addpublic boolean add(E e) {throw new UnsupportedOperationException();}


弱散列映射

垃圾回收器跟踪活动的对象。只要映射对象是活动的,其中的所有桶也是活动的, 它们不能被回收。因此, 需要由程序负责从长期存活的映射表中删除那些无用的值。 或者使用 WeakHashMap 完成这件事情。

WeakHashMap可以做到当对键的唯一引用来自散列条目时,这一数据结构将于垃圾回收器协同工作一起删除键/值对。

WeakHaskMap使用弱引用(weak references)保存键。

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {/*** Creates new entry.*/Entry(Object key, V value,ReferenceQueue<Object> queue,int hash, Entry<K,V> next) {super(key, queue);this.value = value;this.hash  = hash;this.next  = next;}
}public class WeakReference<T> extends Reference<T> {/*** Creates a new weak reference that refers to the given object.  The new* reference is not registered with any queue.** @param referent object the new weak reference will refer to*/public WeakReference(T referent) {super(referent);}/*** Creates a new weak reference that refers to the given object and is* registered with the given queue.** @param referent object the new weak reference will refer to* @param q the queue with which the reference is to be registered,*          or <tt>null</tt> if registration is not required*/public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}}

WeakReference对象将引用保存到另外一个对象中,在这里就是散列键。

public abstract class Reference<T> {Reference(T referent, ReferenceQueue<? super T> queue) {this.referent = referent;this.queue = (queue == null) ? ReferenceQueue.NULL : queue;}
}

如果某个对象只能由WeakReference引用,垃圾回收器仍然回收它,但要将引用这个对象的弱引用放入队列中。

下面实例来自:关于Java中的WeakReference - 简书

/*** Client2 class** @author BrightLoong* @date 2018/5/27*/
public class Client2 {public static void main(String[] args) {ReferenceQueue<Apple> appleReferenceQueue = new ReferenceQueue<>();WeakReference<Apple> appleWeakReference = new WeakReference<Apple>(new Apple("青苹果"), appleReferenceQueue);WeakReference<Apple> appleWeakReference2 = new WeakReference<Apple>(new Apple("毒苹果"), appleReferenceQueue);System.out.println("=====gc调用前=====");Reference<? extends Apple> reference = null;while ((reference = appleReferenceQueue.poll()) != null ) {//不会输出,因为没有回收被弱引用的对象,并不会加入队列中System.out.println(reference);}System.out.println(appleWeakReference);System.out.println(appleWeakReference2);System.out.println(appleWeakReference.get());System.out.println(appleWeakReference2.get());System.out.println("=====调用gc=====");System.gc();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("=====gc调用后=====");//下面两个输出为null,表示对象被回收了System.out.println(appleWeakReference.get());System.out.println(appleWeakReference2.get());//输出结果,并且就是上面的appleWeakReference、appleWeakReference2,再次证明对象被回收了Reference<? extends Apple> reference2 = null;while ((reference2 = appleReferenceQueue.poll()) != null ) {//如果使用继承的方式就可以包含其他信息了System.out.println("appleReferenceQueue中:" + reference2);}}
}

结果输出如下:

=====gc调用前=====
java.lang.ref.WeakReference@6e0be858
java.lang.ref.WeakReference@61bbe9ba
Apple{name='青苹果'}, hashCode:1627674070
Apple{name='毒苹果'}, hashCode:1360875712
=====调用gc=====
Apple: 毒苹果 finalize。
Apple: 青苹果 finalize。
=====gc调用后=====
null
null
appleReferenceQueue中:java.lang.ref.WeakReference@6e0be858
appleReferenceQueue中:java.lang.ref.WeakReference@61bbe9baProcess finished with exit code 0

一个弱引用进入队列意味着这个键不再被他人使用,并且已经被收集起来。WeakHashMap将删除对应的条目。

连接散列集与映射

LinkedHashSet和LinkedHashMap类用来记住插入元素项的顺序。当条目插入到表中时,就会并入到双向链表中。

LinkedHashMap存储数据是有序的,而且分为两种:插入顺序和访问顺序。

枚举集与映射

EnumSet是一个枚举类型元素集的高效实现。EnumSet类没有公共的构造器。可以使用静态工厂方法构造这个集:

enum Weekday { MONDAY, TUESDAY, WENDESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
EnumSet<Weekday> always = EnumSet.allOf(Weekday.class);
EnumSet<Weekday> never = EnumSet.noneOf(Weekday.class);
EnumSet<Weekday> workday = EnumSet.range(Weekday.MONDAY, Weekday.FRIDAY);
EnumSet<Weekday> mwf = EnumSet.of(Weekday.MONDAY, Weekday.WENDESDAY, Weekday.FRIDAY);

标识散列映射

类IdentityHashMap键的散列值不是用hashCode函数计算的,而是用System.identityHashCode方法计算的。这是Object.hashCode方法根据对象的内存地址来计算散列码所使用的方式。在对两个对象进行比较时,IdentityHashMap类使用==,不使用equals。


Java核心技术:集合——映射相关推荐

  1. Java核心技术点之集合框架

    1. 概述     Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ...

  2. Java核心技术基础知识学习之Java集合(三)

    文章目录 七.Java集合 7.6 Java 8 增强的 Map 集合 7.6.1 Java 8 中 Map 新增的方法 7.6.2 Java 8 改进的 HashMap 和 HashTable 实现 ...

  3. java核心技术 基础知识<集合并发part1>

    文章目录 java核心技术 基础知识<集合&并发part1> 9 泛型程序设计 9.5 算法 9.6 遗留的集合 14 并发 14.2 中断线程 14.3 线程状态 14.4 线程 ...

  4. java之hiberante之集合映射之list映射

    这篇讲解 集合映射之List映射 1.通常对于集合,在hibernate中的处理都是使用set来完成.但是hibernate也提供了对于其他几种集合的映射. 在这里实现List的映射,List是有序的 ...

  5. java集合——映射表+专用集合映射表类

    [0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java集合--映射表+专用集合映射表类 的相关知识: 0.2) for full sour ...

  6. Java核心技术卷一 -第九章:集合

    系列文章目录 Java核心技术卷一 -第一章:java"白皮书"的关键术语 Java核心技术卷一 -第三章:数据类型 Java核心技术卷一 -第三章:变量与常量 Java核心技术卷 ...

  7. Java核心技术卷一读书笔记

    文章目录 Java核心技术卷一读书笔记 第一章 Java程序设计概述 1.1 关键特性 第二章 Java程序设计环境 2.1 使用命令行工具 第三章 Java的基本查询设计结构 3.1 数据类型 3. ...

  8. Java 核心技术专题

      什么是 Java 核心技术? Java 语言与编程实践 Java 虚拟机技术 Java SE 平台技术 Java 高级编程技术 IBM Java 运行时与 SDK Java 理论与实践 本专题帮助 ...

  9. Java核心技术·卷二·第一章笔记

    Java核心技术·卷二·笔记 第一章:Java8的流库 Java8引入的用来以"做什么而非怎么做"的方式的处理集合 1.1 从迭代到流的操作 package com.package ...

最新文章

  1. UI设计培训之UI设计系统知识
  2. QQ2012 Under Ubuntu
  3. pacman常用命令
  4. Python的集合set
  5. StyleGAN如何定制人脸生成
  6. 判断控件是否绑定了数据集的方法
  7. 网盘用户分享独播剧链接 百度未及时封禁一审被判赔偿百万余元
  8. linux 强行安装软件,Linux下强制不检测依赖安装VNC
  9. HTTP与HTTPS协议
  10. C#文件过滤器filter
  11. 非科班前端人的一道送命题:0.1+0.2 等于 0.3 吗?
  12. IE无法浏览网页,而QQ可以上解决方案
  13. 数据挖掘导论——可视化分析实验
  14. uniapp 表格组件,冻结首行首列
  15. android 自定义menu菜单按键功能
  16. Kubernetes_MindMap
  17. 【Python】【pygame】更逼真的星星、连绵细雨
  18. 【2020】【论文笔记】太赫兹新型探测——太赫兹特性介绍、各种太赫兹探测器
  19. [javascript] js删除数组中的元素
  20. mail 465邮件配置

热门文章

  1. 希沃白板如何解决手机端播放课件内视频出现黑屏闪退
  2. 适合送女朋友的情人节礼物?畅销火热的好物分享
  3. php 遍历数组 车牌,javascript,html_为什么我最后输出的车牌号全部是数字啊?明明数组里的是字母多啊!,javascript,html - phpStudy...
  4. 3.3 CPU共享功能
  5. python的整体设计目标_python之总体理解
  6. 项目实战——文档扫描OCR识别
  7. leetcode学习打卡--572. 另一个树的子树(递归,二叉树遍历)
  8. 图片上传系统在淘系中的实践
  9. python画钢铁侠标志_pyecharts绘制复联超级英雄战斗力
  10. C语言——经典200道实例【基础例题100道——进阶例题100道】