java中简单集合框架(二)

来源:互联网 发布:mac怎么下b站视频 知乎 编辑:程序博客网 时间:2024/06/06 01:07
摘要 HashMap

一.简单的用法

 下面我们在来讨论讨论Map接口,其中Map接口中的实现类为HashMap.

 常用的方法:

       map.clear();                         //清除整个链表

       map.remove();                      //删除指定键对应的Entry节点

       map.put(T k,T v)();               //放数据

       set ketSet();                          //得到键的集合

       set<Entry> entrySet();            //得到Entry集合

      代码:

public class TestMap {
       public static void main(String[] args) {

      
      //生成新闻对象
      Date d=new Date();
      News cars=new News("凯迪拉克", "黄渤", d);
      News sports=new News("乔丹", "黄渤", d);
      News house=new News("SOHO", "潘石屹", d);
      News animal=new News("duck", "唐老鸭", new Date());
      
      //生成HashMap容器
      HashMap map=new HashMap();
      map.put("cars",cars); 
      map.put("sports", sports);
      map.put("house", house);
      map.put("animal", animal);
      
    
      
        /**遍历map的第一种方式

              遍历的方式获得map集合中Key的集合,通过Key获得Value

        **/
      
      Set set=map.keySet();
      Iterator it=set.iterator();

       //这样可以在一定程度上节约内存。在栈中只有一份key,value;
      Object key=null;     
      Object value=null;


      while(it.hasNext()){
        key=it.next();
        System.out.print(key);
        value=map.get(key);
        System.out.println(value);
      }
      



       /**遍历map的第二种方式

            通过map.entrySet()获得键值对的集合   

            然后遍历集合,得到每个节点。    

**/
      
      
      
     
      Set entrys=map.entrySet();  
      Iterator it2=entrys.iterator();
      Object obj;
      while(it2.hasNext()){
      obj=it2.next();
      System.out.println(obj);
      }
}


}



二.对hashMap的初步认识(拷贝的源代码):

HashMap 是我们最常使用的具有映射关系的容器,在HashMap中键值对是一一对应的,也就是一个键只能对应一个值。如果我们通过hashMap中的put(key,value)放入值得时候,如果在HashMap中有此键,此时,将

将覆盖此键对应的值。由于键是唯一的,所以在HashMap中只有唯一一个空键。

HashMap的底层实现为数组链表。我们在开辟容量大小的时候,其实开辟的是对象数组。此时的数组的类型为

Entry类型,下面我们在来看看Entry的类型定义。Entry的属性为key,value,next,hash.

1.下面我们来看看这几个字段:

    key:    键,用final关键字修饰,也就是说这个键一旦确定就不能更改。

    value:  没有用final修饰,所以可以改变,对应的场景就是,你如果通过put()方法往容器中添加键值对时,如果在此数组链表中有含有此键的Entry节点,此时不会生成新的结点,只是将原来

    数组数组链表中结点的value值更改为新的value值。

    next:表示指向下一个节点。这样节点与节点就会串联起来。生成数组链表。

    hash:每个节点对象中都有一个hash值,这个值是用来刻画Entry节点中key(键)的。如果键值没有重写Hashcode()方法,则此键的hashcode()就为object的hashcode()即为对象的地址。

     在此基础上再调用一下Hash()函数来计算的一个值。如果重写了hashcode()方法,就为重写的hashcode()求出的值在调用hash()函数来求的。这样做的目的是为了快速找到结点中相同的键,

      如果hash不同,则说明键肯定不同。这样就会比较下一个节点的key值。可以提高查询效率。

 static int hash(int h) {
     
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
//这个不懂。

  

 static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

         //重写了Equls方法.       
        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {     
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }


2.下面我们来简单描述一下put(key,value)方法的执行过程。

    分为两种情况:

     1.如果键值为null。则会执行putForNullKey方法,由于键值为空所以会在table[0]//这一行链表中

    如果已经存在键值为null的,此时会覆盖value值,如果不存在,就会添加新的Entry节点。

      2.如果键值不为null,则会根据形参传递的key,先求此key的Hash,然后通过此hash定位到此Entry结点在数组的哪一行上的单链表。

      然后依次遍历整条单链表,如果存在就会覆盖,如果没有就会在table[i]的头插入,其他节点在后面。


  1. public V put(K key, V value) {
        if (key == null)                                               

           return putForNullKey(value);
        int hash = hash(key.hashCode());

        //确定在哪行单链表中
        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++;

        //不存在就会新创结点
        addEntry(hash, key, value, i);
        return null;
    }



3.下面看看 函数indexFor()

  static int indexFor(int h, int length) {
        return h & (length-1);
    }

 在看这个函数之前必须看看构造函数,

  这个构造函数保证开辟的数组容量一定为2的n次幂。也就是Entry[]table永远为2的n次幂。length-1表示的二进制数位11111..11,所有位都为1,那么拿h&(length-1),我们可以将节点均匀的

 分布到table[]数组上,,例如:length = 8,则 1&7=1,2&7=2,3&7=3...7&7=7,8&7=0,9&7=1; 其效果和探测散列中hash对数组取余效果一样。

   

  public HashMap(int initialCapacity, float loadFactor) {
    
        // Find a power of 2 >= initialCapacity
        int capacity = 1;

        //找到一个最小的不小于指定容量的数据量,此数据量为2的n次幂。

        //用的为左移
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }


//从头开始遍历所有的数组中的节点,单这样效率不高。

4.  public boolean containsValue(Object value) {
if (value == null)
            return containsNullValue();

Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
return false;
    }


5. final Entry<K,V> getEntry(Object key) {

       //求hash值
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;

            这个地方用了短路与,通过hash可以提高比较速度。
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }



 判定是否包含指定的键,此时调用了getEntry方法,通过键找到Entry节点,看看是否能找到。

6.public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }


7.HashMap的扩容机制

 void resize(int newCapacity) {

        /**如果容量已经达到了最大此时就不再扩容**/
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
     

        /**如果没有达到系统定义的最大容量,先开数组空间,

             然乎在将原来数组中的节点映射到新的数组链表中

      */
        Entry[] newTable = new Entry[newCapacity];

       /*老数组中的节点映射到新的数组中*/
        transfer(newTable);

       /*返回值*/
        table = newTable;

       /**新的临界值,也就是当节点的个数大于此值时,就会重新扩容*/
        threshold = (int)(newCapacity * loadFactor);
    }


    /**
     *  /*老数组中的节点映射到新的数组中*/
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

0 0
原创粉丝点击