容器深入研究笔记(Map)
2017-11-07 20:49
253 查看
Map学习笔记
理解Map
映射表(也称关联数组)的基本思想是它维护的是键-值(对)关联,因此你可以使用键来查找值。标准的Java类库汇总包含了Map的几种基本实现:HashMap(最常用)、TreeMap、LinkedHashMap、WeakHashMap、ConcurrentHashMap、IdentityHashMap。它们都有相同的基本接口Map,但是行为特性各不相同,这表现在效率、键值对的保存及呈现次序、对象的保存周期、映射表如何在多线程程序中工作和判断“键”等价的策略等方面。关联数组最基本的用法就是分别通过put()、get()方法去存入、读取。
public class MapExample { public static void main(String[] args) { HashMap<String, String> map = new HashMap<>(); map.put("i", "John"); map.put("u", "Marry"); System.out.println(map.get("i")); // toString方法别覆盖为打印所有键值对 System.out.println(map.toString()); } } -------------------output------------------- John {u=Marry, i=John} --------------------------------------------
性能
性能是映射表中的一个重要问题,当get()是使用线性搜索实现时,执行速度会相当的慢,而正是HashMap提高速度的地方。HashMap使用了特殊的值,称作***散列码***,来取代对键的缓慢搜索。*散列码*[b]是“相对唯一”的、用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。hashCode()是根类Object中的方法,因此所有Java对象都能产生散列码[/b]。HashMap就是通过对象的散列码进行快速查询。注意:HashMap在所有Map实现中是速度是最快的。
HashMap
HashMap是我们最常用的映射表。为了在调整HashMap时让你理解性能问题,某些术语是必需了解的:
容量:表中的桶位数。
初始容量:initialCapacity,表在创建是所拥有的桶位数。
尺寸:表中当前储存的项数。
负载因子:尺寸/容量。空表的负载因子是0,半满表的负载因子是0.5。负载轻的表产生冲突的可能性小,因此对于插入和查找都是最理想的(但会减慢使用迭代器进行遍历的过程)。HashMap和HashSet都具有允许你指定负载因子的构造器,表示当负载情况达到该负载因子的水平时,容器将自动增加其容量(桶位数),实现方式是使容量加倍(x2),并重新将现有对象分布到新的桶位集中(再散列)。HashMap默认的loadFactor为0.75。
下面是一个简单的HashMap的实现:
接口如下:
/** * Created by Mr.W on 2017/11/07. * 创建一个功能简单的类似hashmap的map对象 * warming: 非线程安全的 */ public interface SimpleHashMap<K, V> { /** * 放入键值对 * @param key 键 * @param val 值 * @return V 返回被覆盖的值,如果没有则返回null */ V put(K key, V val); /** * 获取键对应的值 * @param key 键 * @return 值 */ V get(K key); /** * 删除指定key对应的entry * @param key * @return */ V remove(K key); }
实现如下:
/** * Created by Mr.W on 2017/11/07. */ public class SimpleHashMapImpl<K, V> implements SimpleHashMap<K, V> { private static final int LIST_SIZE = 5; @SuppressWarnings("unchecked") private LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[LIST_SIZE]; @Override public V put(K key, V val) { // 判断key是否为null if (null == key) { return null; } int index; V oldVal = null; index = Math.abs(key.hashCode()) % LIST_SIZE; if (buckets[index] == null) { buckets[index] = new LinkedList<>(); } LinkedList<MapEntry<K, V>> bucket = buckets[index]; boolean found = false; for (MapEntry<K, V> tmpVal : bucket) { if (tmpVal.getKey().equals(key)) { oldVal = tmpVal.getValue(); tmpVal.setValue(val); found = true; break; } } if (!found) { bucket.add(new MapEntry<K, V>(key, val)); } return oldVal; } @Override public V get(Object key) { int index; index = Math.abs(key.hashCode()) % LIST_SIZE; LinkedList<MapEntry<K, V>> bucket = buckets[index]; if (bucket == null) { return null; } for (MapEntry<K, V> tmpVal : bucket) { if (tmpVal.getKey().equals(key)) { return tmpVal.getValue(); } } return null; } @Override public V remove(K key) { int index; index = Math.abs(key.hashCode()) % LIST_SIZE; LinkedList<MapEntry<K, V>> bucket = buckets[index]; if (bucket == null) { return null; } for (MapEntry<K, V> tmpVal : bucket) { if (tmpVal.getKey().equals(key)) { bucket.remove(tmpVal); return tmpVal.getValue(); } } return null; } private class MapEntry<K, V> implements Map.Entry<K, V> { private final K key; private V value; //构造函数 public MapEntry(K k, V v) { key = k; value = v; } @Override public final K getKey() { return key; } @Override public final V getValue() { return value; } @Override public final V setValue(V v) { V tmpVal = value; value = v; return tmpVal; } @Override public boolean equals(Object o) { return this.hashCode() == o.hashCode(); } @Override public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } } }
其中
index = Math.abs(key.hashCode()) % LIST_SIZE
这是一个散列函数。
SortedMap
TreeMap是SortedMap现阶段的唯一实现,可以确保键处于排序状态。这使得它可以具有额外的功能,这些功能是由SortedMap接口中的下列方法实现:
Comparator comparator(): 返回当前Map使用的Comparator;或者返回null,说明使用自然方式排序。
T firstKey()返回Map中的第一个键。
T lastKey()返回Map中的最后一个键。
SortedMap subMap(fromKey, toKey)生成此Map的子集,范围由fromKey(包含)到toKey(不包含)的键构成。
SortedMap headMap(toKey)生成从Map的子集,由键小于toKey的所有键值对组成。
SortedMap tailMap(fromKey)生成从Map的子集,由键小于fromKey的所有键值对组成。
下面是例子:
// 键需要扩展Comparable接口接口 public class MapExample implements Comparable<MapExample> { private static int index = 0; private int c; public MapExample() { c = index++; } public static void main(String[] args) { // 第二种方法是实例化TreeMap时添加一个扩展Comparator接口的匿名类(这是优先的,通过JDK源码可以看到) TreeMap<MapExample, String> treeMap = new TreeMap<>((o1, o2) -> (o1.c > o2.c?-1:1)); MapExample e1 = new MapExample(); MapExample e2 = new MapExample(); MapExample e3 = new MapExample(); MapExample e4 = new MapExample(); MapExample e5 = new MapExample(); treeMap.put(e1, "1"); treeMap.put(e3, "3"); treeMap.put(e2, "2"); treeMap.put(e4, "4"); treeMap.put(e5, "5"); System.out.println(treeMap.toString()); } // 定义排序原则为升序 @Override public int compareTo(MapExample o) { return this.c > o.c?1:-1; } } -----------------------output---------------------- {com.stupidzhe.jdklearning.base.collection.MapExample@67117f44=1, com.stupidzhe.jdklearning.base.collection.MapExample@5d3411d=2, com.stupidzhe.jdklearning.base.collection.MapExample@2471cca7=3, com.stupidzhe.jdklearning.base.collection.MapExample@5fe5c6f=4, com.stupidzhe.jdklearning.base.collection.MapExample@6979e8cb=5} ---------------------------------------------------
LinkedHashMap
LinkedHashMap继承HashMap,为了提高速度,LinkedHashMap散列化所有的元素,但是在遍历的键值对时,却又以元素的插入顺序返回键值对。此外,可以在构造器中设置LinkedHashMap,使之采用基于访问的最近最少使用算法(LRU),于是没有被访问过的元素就会出现在队列的前面。对于需要定期清理元素以节省空间的程序来说,此功能使得程序很容易得以实现。public static void main(String[] args) { /** * 参数1:initialCapacity,初始时的容量 * 参数2:loadFactor,加载因子 * 参数3:accessOrder, true:LRU算法进行的排序 false:插入排序 */ LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(2, 0.5f, true); linkedHashMap.put("1", "a"); linkedHashMap.put("2", "b"); linkedHashMap.put("3", "c"); linkedHashMap.put("4", "d"); System.out.println(linkedHashMap); linkedHashMap.get("1"); linkedHashMap.get("2"); linkedHashMap.get("1"); System.out.println(linkedHashMap); } -------------------output------------------- {1=a, 2=b, 3=c, 4=d} {3=c, 4=d, 2=b, 1=a} --------------------------------------------
散列与散列码
当你编写好hashCode()方法后,再覆盖equals()方法。因为HashMap使用equals()判断当前的键是否与表中存在的键相同。正确的equals()方法必须满足5个条件:
自反性。对任意非null的x,x.equals(x)一定返回true。
对称性。对任意非null的x和y,如果x.equals(y)返回true,则,y.equals(x)返回true。
传递性。对任意非null的x、y、z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)返回true。
一致性,对于任意非null的x、y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,结果是一致的。
对任何不是null的x,x.equals(null)一定返回false。
注意:默认的Object.equals()只是比较对象的地址!!!
散列
散列的价值在于速度:散列使得查询得以快速进行。储存一组元素最快的数据结构是数组,所以使用它来表示键的信息(数组并不是保存键本身,而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码)。注意:对于String类而言,hashCode()明显是基于String的内容的。
也就是说hashCode不比是唯一的。
不可修改/同步
Collections类提供了实例化一个只读的容器的方法,比如:List list = Collections.unmodifiableList(new LinkedList<String>());
Collections类提供了实例化一个线程安全的容器的方法,比如:
List list = Collections.synchronizedList(new LinkedList<String>());
相关文章推荐
- 4.[Think in Java笔记]容器深入研究
- thinking in java笔记 17 容器深入研究
- think in java 笔记 Chart17 容器的深入研究
- Set, List , Map 容器的深入研究
- 容器深入研究笔记
- 20,21,22-Hibernate容器映射技术(Set、List、Map) -mldn学习笔记 -hxzon
- 再读thinking in java -- 第十七章 容器深入研究(二)
- 深入研究 STL Deque 容器
- 深入研究Clang(六) Clang Lexer代码阅读笔记之Preprocesser
- 深入Java源码解析容器类List、Set、Map
- STL学习笔记8— —容器map和multimap
- 对于法线贴图(Normal Map) 的深入研究
- 优先级队列 to-do列表-容器深入研究
- 【深入剖析Tomcat笔记】第三篇 基本容器模型
- 深入研究 C++中的 STL Deque 容器
- 《Thinking in Java》十七章_容器深入研究_练习12(Page484)
- OpenJDK源码研究笔记(十三):Javac编译过程中的上下文容器(Context)、单例(Singleton)和延迟创建(LazyCreation)3种模式
- 对于法线映射(Normal Map) 的深入研究
- STL学习笔记-map/multimap容器
- STL学习笔记之容器--map