反射&泛型&容器(ArrayList,LinkedList,HashMap)

反射

正常初始化对象实例是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或者其他呢?

  1. 阈值(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。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值