五、HashMap、ArrayList、LinkedList源码分析

五、HashMap:HashSet集合大部分方法是调用HashMap实现的。
       
       HashMap变量表:
   //table初始容量,初始容量为16
    static final int DEFAULT_INITIAL_CAPACITY = 16;
   //table最大值的一半 1<<30 = 2^30 = int最大负数值的一半。
    static final int MAXIMUM_CAPACITY = 1 << 30;
   //负载因子 默认0.75f
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
   //哈希表
    transient Entry[] table;        //如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。Java的serialization提供了一种持久化对象实例的机制。transient 则声明对象不包含在序列化中。
   //长度
    transient int size;
   //重构因子
    int threshold;
   //负载因子
    final float loadFactor;
   //记录table的修改次数,只记录改变映射的数量,相同key覆盖不算。
    transient volatile int modCount;

put()方法:
public V put(K key, V value) {
      if (key == null)
            return putForNullKey(value);         //指向table[0]的位置,遍历table[0]桶,如果存在key=null,则替换key的值;如果不存在,则桶指针加1,并把值添加进去。
      int hash = hash(key.hashCode());       //获取key的hashCode()值。
      int i = indexFor(hash, table.length);   //根据位与操作 h & (length-1),获取hash表中的地址
      for (Entry<K,V> e = table[i]; e != null; e = e.next) {      //遍历table[i]
            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;
            }
        }
        //找不到,则指针加1,添加进Hash桶里面。
        modCount++;
        addEntry(hash, key, value, i);      //如果table中的长度已经大于重构因子的长度,则table表长度乘2 ;
        return null;
}  


           问题一:为什么还要判断key值?
                原理:因为HashCode相等,key值不一定相等,key值才是真正的唯一标识符。若key值也相等的话,则该对象为同一个对象。

           问题二:负载因子的作用?
                原理:控制table什么时候开始扩充。负载因子是控制table的容量的。假设table的初始容量为16,负载因子为0.75f,则当容量增加到12的时候,table就会开始扩充。

           问题三:为什么增大负载因子可以减少Hash表所占用的内存空间,但会增加查询数据的时间开销。减少则不然。
                原理:因为增大了负载因子可是让table扩充的次数更少,从而使table占用的内存更少。但是由于table变小,那么冲突(重复性)更大,从而增加了桶的开销。

                          
            
          问题四:重构因子的作用?
                原理:与负载因子相对应。 重构因子=初始容量*负载因子 。决定什么时候开始重构哈希表。(也就是创建一个长度为原Hash表两倍的Hash表,并把原Hash表复制过去)

get()方法:
public V get(Object key) {
        if (key == null)
            return getForNullKey();   //指向table[0]的位置,遍历table[0]桶,如果存在key=null,则替换key的值;如果不存在,则桶指针加1,并把值添加进去。
        int hash = hash(key.hashCode());  //获取key的hashCode()值。
        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.equals(k)))   //查找到对应的对象。
                return e.value;
        }
        return null;
 }


问题五:为什么要将存储数据的数据链表用transient声明?
原理:transient就是表明该数据不参与序列化。因为HashMap中的存储数据的数组数据成员中,数组还有很多的空间没有被使用,没有被使用到的空间被序列化没有意义。
所以需要手动使用writeObject()方法,只序列化实际存储元素的数组。


          问题六:Hashtable与HashMap的不同点,为什么Hashtable是线程安全?
                    原理:Hashtable基本方法都使用了synchronized同步。

二、ArrayList:
ArrayList变量表: 
//数组数据
private transient Object[] elementData;
//数组长度
private int size;

add()方法:
public boolean add(E e) {
   ensureCapacity(size + 1); // 动态增加数组的长度,长度修改为原数的3/2+1。内嵌了统计修改次数
   elementData[size++] = e;
   return true;
}

get()方法:
  ensureCapacity()方法:动态增加数组长度
public void ensureCapacity(int minCapacity) {
  modCount++;                                            // 修改次数
  int oldCapacity = elementData.length;    // 旧数组长度
  if (minCapacity > oldCapacity) {               // 如果 新数组长度 大于旧数组
     Object oldData[] = elementData;
     int newCapacity = (oldCapacity * 3)/2 + 1;  // 动态增加数组的长度,长度修改为原数的3/2+1。
     if (newCapacity < minCapacity)
          newCapacity = minCapacity;          // 确保数组大小大于旧数组+1
     // minCapacity is usually close to size, so this is a win:
     elementData = Arrays.copyOf(elementData, newCapacity);   //复制旧数组
  }
}


remove(int index)方法:
public E remove(int index) {
  RangeCheck(index);                               、//index不能大于size
  modCount++;                                            //修改次数
  E oldValue = (E) elementData[index];      //查找出旧数组的值
  int numMoved = size - index - 1;             //查找出旧数组索引的-1位
  if (numMoved > 0)
       System.arraycopy(elementData, index+1, elementData, index, numMoved);   // 复制index后面的数组填充index,删除index数组。
  elementData[--size] = null;                      // 数组长度-1
  return oldValue;
}


RangeCheck(int index)方法:
private void RangeCheck(int index) {
  if (index >= size)                                        //index不能大于size
     throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}


三、LinkedList:双向链表  双向链表可以实现对半查找,从而提高查找速率

LinkList变量表:
  //链表
    private transient Entry<E> header = new Entry<E>(null, null, null);
  //链表长度
    private transient int size = 0;


add()方法:
public boolean add(E e) {
        addBefore(e, header);
        return true;
}

//添加到头部
private Entry<E> addBefore(E e, Entry<E> entry) {
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);  //元素插入链表表头
    newEntry.previous.next = newEntry;    //设置表头
    newEntry.next.previous = newEntry;    //设置表头
    size++;
    modCount++;
    return newEntry;
}

get()方法:
public E get(int index) {
        return entry(index).element;     //取出实体Entry的element元素
}

//取出指定位置的实体Entry
private Entry<E> entry(int index) { 
        if (index < 0 || index >= size)          //不能超出链表长度
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;     //指向表头,从表头开始索引
        if (index < (size >> 1)) {        //索引小于总长度的一半
            for (int i = 0; i <= index; i++)    //表头索引
                e = e.next;
        } else {                                    //索引大于总长度的一般
            for (int i = size; i > index; i--)    //表尾索引
                e = e.previous;
        }
        return e;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值