《Thinking in Java》学习——17章容器深入研究(二)

理解Map

1.映射表的基本思想是它维护的是键-值(对)关联,因此你可以使用键来查找值。
2.下面是一个简单的关联数组的创建:

public class AssociativeArray {
    private Object[][] pairs;
    private int index;
    public AssociativeArray(int length) {
        pairs = new Object[length][2];
    }
    public void put(K key, V value) {
        if (index >= pairs.length) 
            throws new ArrayIndexOutOfBoundsException();
        pairs[index++] = new Object[]{key, value};
    }
    @SupressWarning("unchecked")
    public V get(K key) {
        for (int i = 0; i < pairs.length; i++) {
            if (key.equals(pairs[i][0])) {
                return (V) pairs[i][1];
            }
        }
        return null;
    }
}

上面的代码只是具有一定的说明性,但是缺乏效率,并且由于具有固定尺寸而显得很不灵活。

一.性能

1.HashMap使用了特殊的值,称为散列码,来取代对键的缓慢搜索。散列码是相对唯一的、用以代表对象的int值,他是通过将该对象的某些信息进行转换而生成的。
2.hashCode()是类Object中的方法,因此所有Java对象都能产生散列码。HashMap就是使用对象的hashCode()进行快速查询的,此方法能够显著提高性能。
3.下面是几种基本的Map的实现:

Method 实现描述
HashMap Map基于散列表带你实现(它取代了Hashtable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能
LinkedHashMap 类似于HashMap,但是便利它时,取得“键值对”的顺序是其插入顺序,或是最近最少使用的顺序。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部的次序
TreeMap 基于红黑树的实现。查看“键”或“键值对”时,它们会被排序(由ComparableComparator决定)。TreeMap的特定在于,所得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map。它可以返回一个子树
WeekHashMap 弱键映射,允许释放映射所指向的对象;这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个“键”,则此“键”可以被垃圾回收器回收
ConcurrentHashMap 一种线程安全的Map,它不涉及同步加锁
IdentityHashMap 使用==代替equals()对“键”进行比较的散列映射。

4.对Map中使用的键的要求与对Set中的元素的要求一样。任何键都必须具有一个equals()方法;如果键被用于散列Map,那么它必须还具有恰当的hashCode()方法;如果键被用于TreeMap,那么它必须实现Comparable
5.关联数组中的基本方法是put()get()keySet()方法返回一个由Map的键组成的Set。可以直接通过values()方法获取“值”,该方法会产生一个包含Map中所有“值”的Collection,需要注意的是对该Collection的任何改动都会反映到与之相关联的Map

二.SortedMap

1.使用SortedMapTreeMap是其现阶段的唯一实现),可以确保“键”处于排序状态。
2.SortedMap接口中的一些特殊方法如下:

Method 功能描述
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的子集,由大于或等于fromMap的所有键值对组成
三.LinkedHashMap

1.为了提高速度,LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。
2.可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用算法。于是没有被访问过的元素酒会出现在队列的最前面。对于需要定期清理元素以节省空间的程序来说,此功能使得程序很容易就能实现。

散列与散列码

1.使用标准类库中的类来作为HashMap的键没有问题,因为它具备了键所需的全部性质。
2.当自己创建用作HashMap的键的类,有可能会忘记在其中放置必需的方法,而这是通常会犯的一个错误。
3.可能你会认为,只需编写恰当的hashCode()方法的覆盖版本即可。但是它仍然无法正常运行,除非你同时覆盖equals()方法,它也是Object的一部分。HashMap使用equals()判断当前的键是否与表中的相同。
4.正确的equals()方法必须满足的条件:
(1)自反性。对任意xx.equals(x)一定返回true
(2)对称性。对任意xy,如果y.equals(x)返回true,则x.equals(y)一定返回true
(3)传递性。对任意xyz,如果有x.equals(y)返回truey.equals(z)返回true,则x.equals(z)一定返回true
(4)一致性。对任意xy,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true要么一直是false
(5)对任何不是nullxx.equals(null)一定返回false

一.理解hashCode()

1.使用散列的目的是:想要试用一个对象来查找另外一个对象。

二.为速度而散列

1.散列的价值在于速度:散列使得查询得以快速进行。
2.散列将键保存在某处,以便能够快速找到。存储一组元素最快的数据结构是数组吗所以使用它来表示键的信息
3.数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由hashCode()方法生成。
4.为解决数组容量被固定的问题,不同的键会产生相同的下标。也就是说,可能会有冲突。因此,数组多大就不重要了,任何键总能在数组中找到它的位置。
5.查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够没有冲突,那就有了一个完美的散列函数,但这只是特例。通常。冲突由外部链接处理:数组并不直接保存值,而是保存值的list。然后对list中的值使用equals()方法进行线性查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不会查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。

三.覆盖hashCode()

1.在使用HashMap的时候,你无法控制数组的下标值的产生。这个值依赖于具体的HashMap对象的容量,而容量大改变与容器的充满程度和负载因子有关。
2.设计hashCode()最重要的一个因素:无论何时,对同一个对象调用hashCode()都应该产生同样的值。
3.不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值。因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。
4.hashCode()必须基于对象的内容生成散列码。散列码不鄙视独一无二的,但是通过hashCode()equals(),必须能够完全确定对象的身份。
5.好的hashCode()应该产生分布均匀的散列码。如果散列码都集中在一块,那么HashMap或者HashSet在某些区域的负载会很重,这样就不如分布均匀的散列函数快。
6.产生hashCode()的基本指导:
(1)给int变量result赋予某个非零值常量,例如17。
(2)为对象内每一个有意义的域f(即每个可以做equals()操作的域)计算出一个int散列码c

域类型 计算
boolean c = (f ? 0 : 1)
bytecharshortint c = (int)f
long c = (int) (f ^ (f >>> 32))
float c = Float.floatToBits(f)
double long l = Double.doubleToLongBits(f) c = (int) (l ^ l >>> 32)
Object,其equals()调用这个域的equals() c = f.hashCode()
数组 对每个元素应用上面的规则

(3)合并计算得到的散列码:
result = 37 * result + c;
(4)返回result
(5)检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。
7.compareTo()的创建方式:排序的规则首先按照实际类型排序,然后如果有名字的话,按照name排序,最后安好创建的顺序排序。

你可能感兴趣的:(《Thinking in Java》学习——17章容器深入研究(二))