您的位置:首页 > 编程语言 > Java开发

容器深入研究笔记(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>());
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Collection Java