反射
正常初始化对象实例是new()一个实例。这个初始化对象实例是在类加载期就开始加载的。而反射是在运行时才进行的加载的。反射可以破坏单例模式。并且通过反射可以获取对象的属性,方法,变量等。
对于实现反射机制的实现步骤,看下图:
/**
* 示例:创建Class对象的3种方式
*/
@Test
public void testCreatInstance() throws ClassNotFoundException{
//1.通过,class方法
Class personClass= com.mhy.Person.class;
//2.new
com.mhy.Person person=new Person();
Class personClass1=person.getClass();
//3.通过forname()方法
Class personClass2=Class.forName("com.mhy.Person");
System.out.println(personClass==personClass1);
System.out.println(personClass==personClass2);
System.out.println(personClass1==personClass2);
}
@Test
/**
* 通过有参,无参创建实例
* 破坏单例
*/
public void testConstructorInstance() throws Throwable{
Class personClass=Class.forName("com.mhy.Person");
//无参创建实例
Constructor noArgsCon=personClass.getConstructor();
Person noArgPerson=(Person) noArgsCon.newInstance();
//有参创建实例
Constructor argCon=personClass.getConstructor(String.class, Integer.class, Byte.class, Boolean.class);//有参需要传入参数的类型
Person argPerson=(Person)argCon.newInstance("hhhhh",24,(byte)2,Boolean.FALSE);
//测试打印结果
noArgPerson.setName("hollo word!");
System.out.println("无参创建实例:"+noArgPerson);
System.out.println("有参创建实例"+argPerson);
//破坏单例
System.out.println("-------------反射破坏单例----------------");
//1.使用反射获取单例私有构造,创建实例
Class singletonClass=Class.forName("com.mhy.SingletonDto");
Constructor singletCon=singletonClass.getDeclaredConstructor();//获取所有修饰类型的构造函数(非public)
singletCon.setAccessible(true);//设置类似超管的全权限,负责无法使用Declared修饰的方法
SingletonDto singletonInstance=(SingletonDto)singletCon.newInstance();
//获取原单例
SingletonDto singletonDemo=SingletonDto.getInstance();
SingletonDto singletonDemo2=SingletonDto.getInstance();
//测试实例是否相同
System.out.println(singletonInstance==singletonDemo);
System.out.println(singletonInstance==singletonDemo2);
System.out.println(singletonDemo==singletonDemo2);
}
如何通过反射获取属性,方法等,直接上代码:
- getField 只能获取public的,包括从父类继承来的字段。
- getDeclaredField可以获取本类所有的字段,包括private的,但是不能获取继来的字段。 (注:这里只能获取到private的字段,但并不能访问该private字段值,除非加上setAccessible(true))
- 无法直接获取父类非public的属性,方法等,需要先获得父类实例再去获取属性方法等
/**
* 获得private属性和方法
*
* @throws Throwable
*/
@Test
public void getPrivateField() throws Throwable {
/** 首先:获得Person的字节码 */
Class personClazz = Person.class;
/** 其次:获得Person对象(由于非静态非private的属性,访问使用 对象.属性方式访问,所以反射必须先获得对象实例)*/
Person person = (Person) personClazz.getConstructor().newInstance();
/** 第三:通过Class对象,获得Field对象 */
// Field sexField = personClazz.getField("sex"); // 不能使用getField,否则报错:java.lang.NoSuchFieldException: sex
Field sexField = personClazz.getDeclaredField("sex");
sexField.setAccessible(true); // 必须设置为true
/** 最后:获取字段的类型 */
Byte sex = (Byte) sexField.get(person);
System.out.println("private属性:sex=" + sex);
/** 补充内容:获取private类型的方法 */
// Method method = personClazz.getMethod("privateMethod");
// 不能使用getMethod,否则报错:java.lang.NoSuchMethodException: com.muse.reflect.Person.privateMethod()
Method method = personClazz.getDeclaredMethod("privateMethod");
method.setAccessible(true); // 必须设置为true
System.out.println("private方法:privateMethod()=" + method.invoke(person));
}
泛型
泛型类
泛 型 类 就 是 把 泛 型 定 义 在 类 上 ,用 户 使 用 该 类 的 时 候 , 才 把 类 型 明 确 下 来。
/**
* 泛型类
*
* @author mhy
*/
public class ClassGenericity {
public static void main(String[] args) {
/** 创建ObjectTool对象并指定元素类型为String */
ObjectTool<String> stringTool = new ObjectTool<>();
stringTool.setObj("mhy");
System.out.println(stringTool.getObj());
/** 创建ObjectTool对象并指定元素类型为Integer */
ObjectTool<Integer> integerTool = new ObjectTool<>();
// integerTool.setObj("muse"); // 编译报错
integerTool.setObj(20);
System.out.println(integerTool.getObj());
}
/**
* 构建可以存储任何类型对象的工具类
*/
static class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
}
泛型方法
除 了 在 类 上 使 用 泛 型 , 我 们 可 能 就 仅 仅 在 某 个 方 法 上 需 要 使 用 泛 型 , 外 界仅 仅 是 关 心 该 方 法 , 不 关 心 类 其 他 的 属 性。
/**
* 泛型方法
*
* @author mhy
*/
public class MethodGenericity<T> {
public static void main(String[] args) {
//创建对象
ObjectTool tool = new ObjectTool();
/** 调用方法,传入的参数是什么类型,T就是什么类型 */
tool.show("hhhhh");
tool.show(22);
tool.show(14.6f);
}
static class ObjectTool {
//定义泛型方法
public <T> void show(T t) {
System.out.println(t);
}
}
}
泛型类派生出的子类
泛 型 类 是 拥 有 泛 型 这 个 特 性 的 类 , 它 本 质 上还 是 一 个 J a v a 类 , 那 么 它 就 可 以 被 继 承 或 实 现这 里 分 两 种 情 况 :
① 子 类 明 确 泛 型 类 的 类 型 参 数 变
② 子 类 不 明 确 泛 型 类 的 类 型 参 数 变 量
先定义一个泛型类接口
第一种实现泛型接口时明确泛型类型
第二种实现泛型接口时不明确类型还用泛型
类型通配符
L i s t < ? > 表 示 元 素 类 型 未 知 的 L i s t , 它 可 以 匹 配 任 何 类 型 的 元 素 。 声 明L i s t < ? > l i s t 后 , 不 能 向 集 合 中 添 加 元 素 , 因 为 无 法 确 定 集 合 的 元 素 类 型 ,唯 一 例 外 的 是 null。
泛型的上限和下限
泛型的上限:
格式:类型名称 <? extends 类> 对象名称
含义:只能接收该类型及其子类
泛型的下限:
格式:类型名称 <? super 类> 对象名称
含义:只能接收该类型及其父类
类型擦除
泛型是提供给javac编译器使用的,它可以作为类型的限制,让编译器在源代码级别上,挡住非法类型的数据。但是在JDK1.5之前没有泛型的概念,为了能够与之前版本代码兼容,编译器编译完带有泛型的java程序后,生成的class字节码文件中将不再带有泛型信息,这个过程称之为“擦除”。
//使用反射获取编译后class文件里的方法名称以及参数类型
public static void main(String[] args) {
Method[] methods=Cat.class.getDeclaredMethods();
for (Method me:methods) {
String name=me.getName();
Class<?>[] parameterTypes = me.getParameterTypes();
System.out.println(name+ "(" + Arrays.toString(parameterTypes) + ")");
}
}
桥接方法
桥接方法是jdk1.5引入泛型后,为使java泛型方法生成的字节码与jdk1.5版本之前的字节码兼容由编译器自动生成的。可用 method.isBridge() 判断method是否是桥接方法。
ArrayLisk
ArrayList就是 动态数组,它提供了
①动态的增加和减少元素
②实现了ICollection和IList接口
③灵活的设置数组的大小
ArrayList是一个其容量能够动态增长的动态数组。它继承了AbstractList,实现了List、RandomAccess, Cloneable, java.io.Serializable。
基本的ArrayList,长于随机访问元素,但是在List中间插入和移除元素时较慢。同时,ArrayList的操作不是线程安全的!
一般在单线程中才使用ArrayList,而在多线程中一般使用Vector或者CopyOnWriteArrayList。
源码解析:
package java.util;
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 序列版本号
private static final long serialVersionUID = 8683452581122892189L;
// 默认容量大小
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 用于保存ArrayList中数据的数组
private transient Object[] elementData;
// ArrayList中所包含元素的个数
private int size;
// 带初始容量参数的构造函数
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
// 默认构造函数,其默认初始容量为10
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
// 带Collection参数的构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
// 将此 ArrayList 实例的容量调整为列表的当前大小(实际元素个数)
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = Arrays.copyOf(elementData, size);
}
}
// 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所
// 指定的元素数
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != EMPTY_ELEMENTDATA)
// any size if real element table
? 0
// larger than default for empty table. It's already supposed to be
// at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
// 返回ArrayList中的元素个数
public int size() {
return size;
}
// 判断ArrayList是否为空
public boolean isEmpty() {
return size == 0;
}
// 判断ArrayList是否包含Object(o)
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
// 返回ArrayList中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
// 返回ArrayList中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
// 返回此 ArrayList 实例的浅表副本
public Object clone() {
try {
@SuppressWarnings("unchecked")
ArrayList<E> v = (ArrayList<E>) super.clone();
// 将当前ArrayList的全部元素拷贝到v中
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
// 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
// 返回ArrayList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
// 位置访问操作
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
// 返回ArrayList中指定位置上的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
// 用指定的元素替代ArrayList中指定位置上的元素,并返回替代前的元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
// 将指定的元素添加到ArrayList的尾部
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 将指定的元素插入ArrayList中的指定位置
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
// 移除ArrayList中指定位置上的元素,并返回该位置上的元素
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
// 移除ArrayList中首次出现的指定元素(如果存在则移除并返回true,否则返回false)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 私有方法,用于快速移除
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
// 移除ArrayList中的所有元素
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
// 按照指定 collection 的迭代器所返回的元素顺序,
// 将该 collection 中的所有元素添加到ArrayList的尾部
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
// 从指定的位置开始,将指定 collection 中的所有元素插入到ArrayList中
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
// 移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
// 私有方法,用于范围检测
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 私有方法,用于add和addAll
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
// 移除ArrayList中Collection所包含的所有元素
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);
}
// 保留所有ArrayList和Collection共有的元素
public boolean retainAll(Collection<?> c) {
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
// java.io.Serializable的写入函数
// 将ArrayList的“容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
// java.io.Serializable的读取函数:根据写入方式读出
// 先将ArrayList的“容量”读出,然后将“所有的元素值”读出
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
// 返回一个从指定位置开始遍历的ListIterator迭代器
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
// 返回一个ListIterator迭代器
public ListIterator<E> listIterator() {
return new ListItr(0);
}
// 返回一个Iterator迭代器
public Iterator<E> iterator() {
return new Itr();
}
// 返回一个指定范围的子List列表
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
}
总结:ArrayList 底层基于数组实现容量大小动态可变。 扩容机制为首先扩容为原始容量的 1.5 倍。如果1.5倍太小的话,则将我们所需的容量大小赋值给 newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容。 扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。 ArrayList 的最大存储能力:Integer.MAX_VALUE。 size 为集合中存储的元素的个数。elementData.length 为数组长度,表示最多可以存储多少个元素。 如果需要边遍历边 remove ,必须使用 iterator。且 remove 之前必须先 next,next 之后只能用一次 remove。
LinkedList
LinkedList是双向链表实现的List
LinkedList是非线程安全的
LinkedList元素允许为null,允许重复元素
LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)
LinkedList是基于链表实现的,因此不存在容量不足的问题,所以没有扩容的方法
LinkedList还实现了栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用
LinkedList和ArrayList的比较
顺序插入速度ArrayList会比较快,因为ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置 塞一个数据就好了
LinkedList则不同,每次顺序插入的时候LinkedList将new一个对象出来,如果对象比较大,那么new的时间 势必会长一点,再加上一些引用赋值的操作,所以顺序插入LinkedList必然慢于ArrayList
ArrayList的遍历效率会比LinkedList的遍历效率高一些
LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Node的引用地址
ArrayList做插入、删除的时候,慢在数组元素的批量copy,快在寻址
如果确定插入、删除的元素是在前半段,那么就使用LinkedList
如果确定插入、删除的元素在比较靠后的位置,那么可以考虑使用ArrayList
如果不能确定插入、删除是在哪儿呢?建议使用LinkedList,
一来LinkedList整体插入、删除的执行效率比较稳定,没有ArrayList这种越往后越快的情况
二来插入元素的时候,弄得不好ArrayList就要进行一次扩容,而ArrayList底层数组扩容是一个既消 耗时间又消耗空间的操作。
HashMap
特性:
- HashMap是无序且不安全的数据结构。
- HashMap是以key–value对的形式存储的,key值是唯一的(可以为null),一个key只能对应着一个value,但是value是可以重复的。
- HashMap如果再次添加相同的key值,它会覆盖key值所对应的内容,这也是与HashSet不同的一点,Set通过add添加相同的对象,不会再添加到Set中去。
- HashMap 提供了get方法,通过key值取对应的value值,但是HashSet只能通过迭代器Iterator来遍历数据,找对象。
版本差异
JDK7与JDK8及以后的HashMap结构与存储原理有所不同:
Jdk1.7:数组 + 链表 ( 当数组下标相同,则会在该下标下使用链表)
Jdk1.8:数组 + 链表 + 红黑树 (预值为8 如果链表长度<=8则会把链表变成红黑树 )
Jdk1.7中链表新元素添加到链表的头结点,先加到链表的头节点,再移到数组下标位置
Jdk1.8中链表新元素添加到链表的尾结点
(数组通过下标索引查询,所以查询效率非常高,链表只能挨个遍历,效率非常低。jdk1.8及以
上版本引入了红黑树,当链表的长度大于或等于8的时候则会把链表变成红黑树,以提高查询效率
HashMap的默认负载因子:
/**
* The load factor used when none specified in constructor.
/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/*
*默认的负载因子是0.75f,也就是75% 负载因子的作用就是计算扩容阈值用,比如说使用
*无参构造方法创建的HashMap 对象,他初始长度默认是16 阈值 = 当前长度 * 0.75 就
*能算出阈值,当当前长度大于等于阈值的时候HashMap就会进行自动扩容
*/
为什么HashMap的默认负载因子是0.75,而不是0.5或者其他呢?
- 阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 根据HashMap的扩容机制,他会保证容量(capacity)的值永远都是2的幂 为了保证负载因子x容量的结果是一个整数,这个值是0.75(4/3)比较合理,因为这个数和任何2的次幂乘积结果都是整数。
2.理论上来讲,负载因子越大,导致哈希冲突的概率也就越大,负载因子越小,费的空间也就越大,这是一个无法避免的利弊关系,所以通过一个简单的数学推理–泊松分布,可以测算出这个数值在0.75左右是比较合理的
HashMap的扩容机制
阈值(threshold) = 负载因子(loadFactor) x 容量(capacity)
当HashMap中table数组(也称为桶)长度 >= 阈值(threshold) 就会自动进行扩容。
扩容的规则是这样的,因为table数组长度必须是2的次方数,扩容其实每次都是按照上一次tableSize位运算得到的就是做一次左移1位运算,假设当前tableSize是16的话 16转为二进制再向左移一位就得到了32 即 16 << 1 == 32 即扩容后的容量,也就是说扩容后的容量是当前容量的两倍,但记住HashMap的扩容是采用当前容量向左位移一位(newtableSize = tableSize << 1),得到的扩容后容量,而不是当前容量x2
解决哈希冲突的方法
1. 开放定址法
Hi = (H(key) + di) MOD m,其中i=1,2,…,k(k<=m-1)
H(key)为哈希函数,m为哈希表表长,di为增量序列,i为已发生冲突的次数。其中,开放定址法根据步长不同可以分为3种:
1.1 线性探查法(Linear Probing):di = 1,2,3,…,m-1
简单地说,就是以当前冲突位置为起点,步长为1循环查找,直到找到一个空的位置,如果循环完了都占不到位置,就说明容器已经满了。举个栗子,就像你在饭点去街上吃饭,挨家去看是否有位置一样。
1.2 平方探测法(Quadratic Probing):di = ±12, ±22,±32,…,±k2(k≤m/2)
相对于线性探查法,这就相当于的步长为di = i2来循环查找,直到找到空的位置。以上面那个例子来看,现在你不是挨家去看有没有位置了,而是拿手机算去第i2家店,然后去问这家店有没有位置。
1.3 伪随机探测法:di = 伪随机数序列
这个就是取随机数来作为步长。还是用上面的例子,这次就是完全按心情去选一家店问有没有位置了。
但开放定址法有这些缺点:
这种方法建立起来的哈希表,当冲突多的时候数据容易堆集在一起,这时候对查找不友好;
删除结点的时候不能简单将结点的空间置空,否则将截断在它填入散列表之后的同义词结点查找路径。因此如果要删除结点,只能在被删结点上添加删除标记,而不能真正删除结点;
如果哈希表的空间已经满了,还需要建立一个溢出表,来存入多出来的元素。
2. 再哈希法
Hi = RHi(key), 其中i=1,2,…,k
RHi()函数是不同于H()的哈希函数,用于同义词发生地址冲突时,计算出另一个哈希函数地址,直到不发生冲突位置。这种方法不容易产生堆集,但是会增加计算时间。
所以再哈希法的缺点是:增加了计算时间。
3. 建立一个公共溢出区
假设哈希函数的值域为[0, m-1],设向量HashTable[0,…,m-1]为基本表,每个分量存放一个记录,另外还设置了向量OverTable[0,…,v]为溢出表。基本表中存储的是关键字的记录,一旦发生冲突,不管他们哈希函数得到的哈希地址是什么,都填入溢出表。
但这个方法的缺点在于:查找冲突数据的时候,需要遍历溢出表才能得到数据。
4. 链地址法(拉链法)
将冲突位置的元素构造成链表。在添加数据的时候,如果哈希地址与哈希表上的元素冲突,就放在这个位置的链表上。
拉链法的优点:
处理冲突的方式简单,且无堆集现象,非同义词绝不会发生冲突,因此平均查找长度较短;
由于拉链法中各链表上的结点空间是动态申请的,所以它更适合造表前无法确定表长的情况;
删除结点操作易于实现,只要简单地删除链表上的相应的结点即可。
拉链法的缺点:需要额外的存储空间。
从HashMap的底层结构中我们可以看到,HashMap采用是数组+链表/红黑树的组合来作为底层结构,也就是开放地址法+链地址法的方式来实现HashMap。