五、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就会开始扩充。
原理:因为增大了负载因子可是让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中的存储数据的数组数据成员中,数组还有很多的空间没有被使用,没有被使用到的空间被序列化没有意义。
问题六:Hashtable与HashMap的不同点,为什么Hashtable是线程安全?所以需要手动使用writeObject()方法,只序列化实际存储元素的数组。
原理:Hashtable基本方法都使用了synchronized同步。
二、ArrayList:
get()方法:
ensureCapacity()方法:动态增加数组长度
get()方法:
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)方法:
RangeCheck(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;
}
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;
}
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;
}