Java HashMap简介

来源:互联网 发布:淘宝代做课程设计 编辑:程序博客网 时间:2024/05/22 14:40

http://www.cnblogs.com/chengxiao/p/6059914.html

http://blog.csdn.net/vking_wang/article/details/14166593

Java中的存储结构无外乎两种:一种是顺序存储结构,也就是申请一块连续的内存空间:以数组的形式显示。像一般的顺序栈,顺序队列就是这样。另外一种则是链式存储结构,链表,链式栈,链式队列,二叉树(一般不用顺序存储结构实现二叉树,因为极大的浪费空间)。

接下来我们来讨论一下顺序存储和链式存储的优缺点。

顺序存储 ——优点:当知道或者能够估计使用的数据的数量时,可以使用顺序存储,因为存取方便,时间复杂度为                       O(1)。

                       缺点:删除上会极大的不方便,因为需要移动位置,从而影响性能


链式存储 ——优点:不需要事先知道数据量,可以自动申请空间,删除上会方便,因为不需要移动位置,只需改变引用                                  即可。

                       缺点:数据的查询会不方便,因为链式存储的数据结构一般都是通过头节点进行遍历来查询的,时间复杂度即便通过一些经典算法也会比较大。


但是,HashMap这一数据结构实现了顺序存储和链式存储的结合,通过键值对(key,value)的方式实现了数据的快速保存和读取。

第一步:HashMap的保存和读取实际上使用的是对象数组Entry(HashMap中的静态内部类)进行存储,接着通过构造函数实现table的初始化。 

static class MEntry<K,V>{
    int hash; //对key进行hash()函数后得到的值
    K key;
    V value;
    MEntry<K, V> nextEntry;//链表结构
    public MEntry() {
}
public MEntry(int index, K key, V value) {
super();
this.hash = index;
this.key = key;
this.value = value;
}
     }

第二步:通过hash函数和indexfor函数得到key的下标,如果下标的数组为空,直接插入。如果不为空,遍历该下标数组,将相同的key覆盖,不相同的利用链表的插入插到头节点。

第三步:在插入数据时候判断size<=threshold,如果size>threshold,则需要进行扩容。

由于分析的数据量太大,本人讲的可能难以理解。以下是本人实现hashmap的源码(不完整):

package test.map;




public class HashMap<K, V> {
static class MEntry<K,V>{
    int hash; //对key进行hash()函数后得到的值
    K key;
    V value;
    MEntry<K, V> nextEntry;//链表结构
    public MEntry() {
}
public MEntry(int index, K key, V value) {
super();
this.hash = index;
this.key = key;
this.value = value;
}
     }
     final static int DEFAULT_CAPACITY=16;         //默认值,用户没有指定则为16
     final static float DEFAULT_LOAD_FACTOR=0.75f;
     static final int MAXIMUM_CAPACITY = 1 << 30;
public MEntry<K, V>[] table;       //hashmap的内部实际结构
int capacity;                       //hashmap数组的长度
int size;                           //hashmap的长度,不能超过阙值
int threshold;                      //hashmap的阙值 =capacity*loadFactor
     float loadFactor;                   //hashmap的装载因子=size/capacity
     
public HashMap(int capacity,float loadFactor){                   //初始化默认hashmap 
if (capacity < 0)
           throw new IllegalArgumentException("错误的capacity: " +capacity);
    if (capacity > MAXIMUM_CAPACITY)
        capacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
           throw new IllegalArgumentException("错误的装载因子: " + loadFactor);
    this.loadFactor = loadFactor;
    this.capacity=capacity;
    /*this.threshold=(int)(this.capacity*this.loadFactor);
            不在这里设置上一句的原因是需要在初始化table时将capacity赋值为2的幂*/
     }
     public HashMap(){
    this(DEFAULT_CAPACITY,DEFAULT_LOAD_FACTOR); 
     }
     public HashMap(int initialCapacity) {
         this(initialCapacity, DEFAULT_LOAD_FACTOR);
     }
     /**
      * hash函数,将key转化为数组下标
      * @return
      */
     private int hash(Object key){
    int h=0;
         h ^= key.hashCode();  //调用自身的hashcode方法(如:String则调用String.class的hashcode()方法)
         h ^= (h >>> 20) ^ (h >>> 12); //按位的异或
         return h ^ (h >>> 7) ^ (h >>> 4);
     }
     
     private static int roundUpToPowerOf2(int number) {
         // assert number >= 0 : "number must be non-negative";
         int rounded = number >= MAXIMUM_CAPACITY
                 ? MAXIMUM_CAPACITY
                 : (rounded = Integer.highestOneBit(number)) != 0
                     ? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded
                     : 1;
         return rounded;
     }
     /**
      * 通过构造函数得到的capacity初始化table,长度必须是比capacity最接近的2的幂
      * @param capacaity
      */
     private void initTable(int capacaity){
         int capacity = roundUpToPowerOf2(capacaity);
         threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
         table = new MEntry[capacity];
     }
     private void addEntry(int hash,K key,V value,int index){
    table[index]=new MEntry<K,V>(hash,key,value);
    table[index].nextEntry=null;
     }
     int indexFor(int h, int length) {
         return h & (length-1);
     }
     private void resize(int newCapacity){
    MEntry[] oldTable = table;          //将table地址复制给新的引用变量
         int oldCapacity = oldTable.length;  
         if (oldCapacity == MAXIMUM_CAPACITY) {//如果旧的table长度已经到达最大,将不再扩容
             threshold = Integer.MAX_VALUE;
             return;
         }
         MEntry[] newTable = new MEntry[newCapacity];  //创建新的MEntry对象数组,长度为之前的一倍
         transfer(newTable);                           //将旧数组的内容copy到新的数组
         table = newTable;                             
         threshold = (int)(newCapacity * loadFactor);
     }
     
     void transfer(MEntry[] newTable) {
    MEntry[] src = table;
         int newCapacity = newTable.length;
         for (int j = 0; j < src.length; j++) {
        MEntry<K,V> e = src[j];
             if (e != null) {
                 src[j] = null;
                 do {
                MEntry<K,V> next = e.nextEntry;
                     //重新计算index
                     int i = indexFor(e.hash, newCapacity);
                     e.nextEntry = newTable[i];
                     newTable[i] = e;
                     e = next;
                 } while (e != null);
             }
         }
     }
     public void put(K key,V value){
    if(table==null){
    System.out.println("当前table为空,正在初始化数组");
    initTable(this.capacity);    //如果table为空,则初始化table
    }
    int hash=hash(key);              //获取hash函数获取hash值
    int index=indexFor(hash, table.length);//通过与运算得到判断冲突的数组下标
    if(table[index]==null){          //说明当前该下标数组为空,直接插入
             if(size==threshold){ //如果当前size到达阙值,需要扩容
            resize(2 * table.length);
            index=indexFor(hash, table.length);//由于改变了table的长度,需要重新寻找下标
             }
    System.out.println("下标"+index+"数组为空,插入Key:"+key+" value:"+value);
    addEntry(hash, key, value, index);
    size++;
    }
    else{                            //说明当前带下标数组存在链表
    for(MEntry<K, V> e=table[index];e!=null;e=e.nextEntry){  //说明存在链表
        Object k;
                 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  //判断是否存在相同的key
                     System.out.println("下标"+index+"存在冲突key相同,将覆盖");
                V oldValue = e.value;
                     e.value = value;
                 }
                 else{            //不存在相同的key
                if(size==threshold){ //如果当前size到达阙值,需要扩容
                    resize(2 * table.length);
                    index=indexFor(hash, table.length);//由于改变了table的长度,需要重新寻找下标
                     }
                System.out.println("下标"+index+"存在冲突key不同,将插入");
                MEntry<K,V> newEntry= new MEntry<>(hash, key, value);
                newEntry.nextEntry=table[index];
                table[index]=newEntry;
                     size++;
                 }
        }
   
     }
     
     public V get(Object key){
    int hash=hash(key);
    int index=indexFor(hash, table.length);//通过与运算得到判断冲突的数组下标
    V value=null;
    MEntry<K,V> mEntry=table[index];
    while(mEntry!=null){
    if(key==mEntry.key||key.equals(mEntry.key)){
    return value=mEntry.value;
    }
    else mEntry=mEntry.nextEntry;
    }
    return value;
     }
     
     public int getSize(){
    return this.size;
     }
     
}