HashMap类
在介绍hashMap之前,有必要介绍下关于散列表的知识。(太久没用到散列表,没想到一时竟然忘记了它的存在的意义了,看来不多做笔记真不行)
散列表:支持以常数时间对任何命名项的检索或删除。为什么能够这么会有这种效果呢?原理是:定义一个空的tablesize大小数组,每个要插入元素根据散列函数取得数组的下标,所以要能根据元素进行线性的检索;
冲突:就是不同的项通过散列函数取得相同的下标(专业术语:相撞),就会造成冲突,我们可以通过线性探测,二次探测等方法来获得新的下标,也可以用hashMap类中的解决方案,如果出现不同的项有相同的下标,将第二项挂在第一项下,(就是第一项的next指向第二项)依次类推;
但是散列表牺牲了元素插入的有序性;
hashMap简单介绍:
hashMap是基于散列表的原理,类中定义了变量table数组来存放数据,数组类型为Entry类型,(Entry是hashMap的一个静态内部类);包含这些基本的元素,capacity(容量),loadFactor(加载因子),size,threshold;Capacity:即数组的长度(初始值为16),loadFactor来决定threshold的大小(默认为0.75),Threshold = loadFactor* Capacity;也就是说如果size>=Threshold,那么我们需要扩充数组的大小capacity,如果此时的capacity的大小已经达到最大值Integer.MAX_VALUE,那么不需改变数据大小,继续添加数组即可;
每组数据,对应一个Entry类和一个table的下标,这个小标的取值跟key的hash值和table的长度有关;所以有一点特别重要,在扩充数组的时候,定义了一个新的数组后,由于table的长度变化,需要对每一个Entry对象重新计算小标值,重新将数据填入数组中;
hashMap的实现不是同步的。
它的部分源代码如下:我们一一分析:
//继承类AbstractMap和接口Map,Cloneable,Serializable;
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
类包含的成员
//散列表
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//元素的个数
transient int size;
//加载因子,就是说如果要存放size=4的数据,根据加载因子,我们定义散列表的大小为4/loadFactor;
//加载因子的默认值是0.75
final float loadFactor;
//值为 (capacity * load factor).
int threshold;
存放一组元素的操作如下:
//向map里头存放数据
public V put(K key, V value) {
//如果数组为空,初始化数组inflateTable(),第一次存放//时出现
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//允许key为空,此时的数组下标为0,(剩下的)操作相同;
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
//这段代码是重点,告诉我们散列表存数的形式,找到下标后,判断该位置是否已经有数据,如果有挂载在已有的数据下;
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//该方法,将这组数据创建一个Entry对象保存,同时存放了hash值和小标值,当然要先判断是否数组达到了上限,具体请看方法的内容
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//用来扩充数组的,如果数组已经达到上限,立刻return;
//如果没有,那么遍历原来的数组,重新定义下标。。。
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//重新定义下标
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//新建一个Entry对象,然后存放在table中
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
接下来,是时候介绍小Entry类了,其实它仅仅就是用来存放一组数据和一些基本信息的结构而已哈;
//内部类 Entry,继承接口Map.Entry<K,V>,简化了部分代码
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
接下来介绍下,如果根据key来取得value值,知道原理后,我们应该知道怎么取值了吧,由key获得hash值,在与table.length计算出下标,从该位置上找出key对应的value。
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
简单的介绍:
LinkedHashMap继承类HashMap,在HashMap的基础上提供了记录输入顺序的方法,也就是用链表的方式,对每一次输入进行记录;
//定义了成员变量,记录链表的头部
private transient Entry<K,V> header;
//覆盖了HashMap的init()的方法,原本在HashMap中它是一个空方法,但是在第一次初始化数组的时候会被调用;
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
//当数组需要调整大小的时候,会调用这个方法,(覆盖的原因仅仅是因为用链表来遍历效率更高一点哈)
@Override
void transfer(HashMap.Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e = header.after; e != header; e = e.after) {
if (rehash)
e.hash = (e.key == null) ? 0 : hash(e.key);
int index = indexFor(e.hash, newCapacity);
e.next = newTable[index];
newTable[index] = e;
}
}
//这是ListHashMap中类Entry<K,V>一个方法,当插入新数据的时候会调用该方法,此方法在HashMap. Entry类中是一个空的方法;在此,该方法是为了维护数据的有序性
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
TreeMap
TreeMap是基于红黑树实现的,红黑树的具体分析在以后的章节会详细介绍,现在大致说下红黑树的特点:
二叉查找树特点:1,父节点的值大于两个儿子的值;2,同一个父亲,右儿子的值大于左儿子的值;二叉查找树有个明显的缺陷,就是不断的操作最终会导致树非常不平衡(也就是树的左右孩子的深度不一样),所以诞生了平衡二叉树,但是平衡二叉树对树的要求比较高;由此产生了红黑树;对于红黑树的操作的时间复杂度,构造树的方法,以后再详细介绍;
针对TreeMap分析:
类成员变量
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
构造函数:
//如果不传入参数,则默认按照自然顺序(就是hashcode的大小)排序
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
如果compare后,返回0那么认为这两个数是一样的,在TreeMap中对同样的数,覆盖原则哈,
测试代码
public class person1{
int height;
int age;
person1(int height,int age){
this.height = height;
this.age = age;
}
public static void main(String args[]){
TreeMap<person1,String> tr = new TreeMap<person1, String>( new personComparator());
person1 p1 = new person1(1,1);
person1 p2 = new person1(1,2);
person1 p3 = new person1(2,3);
tr.put(p1, "p1");
tr.put(p2, "p2");
tr.put(p3, "p3");
System.out.print((tr.get(p1)+tr.get(p2)+tr.get(p3)));
}
}
class personComparator implements Comparator<person1>{
@Override
public int compare(person1 o1, person1 o2) {
// TODO Auto-generated method stub
if(o1.height==o2.height){
return 0;
}else if(o1.height>o2.height){
return 1;
}else{
return -1;
}
}
}
测试结果如下:
p2p2p3