源码分析(7)---纯手写JDK不同版本下的List接口(ArrayList和Vector集合)

集合框架介绍

  • 集合框架图
    在这里插入图片描述
  • 对于上面的结构图的说明:
    (1)所有的集合类都是在java.util包的下面。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或者实现类。
    (2)集合接口:6个接口(短虚线表示),表示不同的集合类型,是集合框架的基础。
    (3)抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可以扩展为自定义集合类。
    (4)实现类:8个具体的实现类(实线表示),对接口的具体实现。
    (5)Collection接口是一组允许重复的对象
    (6)Set接口继承Collection,集合元素不重复
    (7)List接口集成Collection,允许重复,有序,维护了元素的插入顺序
  • List集合有序集合,有序有重复,访问集合中的元素可以根据元素的索引来访问
  • Set集合是无序集合,集合中的元素无序无重复,访问集合中的元素只能根据元素本身访问(也是集合元素不允许重复的原因)
  • Map集合用key来访问value
  • ArrayList扩容是1.5倍,Vector扩容是2倍
  • ArrayList的性能好,因为线程不安全,Vector线程安全
  • TreeSet默认是按照自然序排序,可以通过compareTo方法进行排序规则的定制,TreeSet不是按照存入顺序排序的。
  • 集合底层使用数组去实现
  • 数组的扩容技术可以保证集合中存放无限大小的数据
  • 移位操作符
  • <<和>>
    其中<< 表示的是向左移位,>> 表示向位移
    (1) << : 左位移运算符,num<<1,相当于num*2
    (2) >>: 右位移运算符,num>>1,相当于num/2

数组扩容技术

  • Arrays.copyof(原来的数组,新数组的长度):
  • 该API可以把原来的数组复制一份,并扩展成新的数组的长度,返回是新的数组
  • 只会扩充长度,数组的元素并没有改变,原来是几个元素现在还是几个,只不过现在的数组的长度变大了

java代码示例:

/**
 * 数组的动态扩容技术
 * 1. copyof
 */
public class Test001 {
    public static void main(String[] args) {
        copyOfArray1();
    }

    private static void copyOfArray1() {
        // 1. 定义数组长度是2
        Object[] objects = {1, 2};
        System.out.println("原数组长度: " + objects.length);
        // 2. 数组扩容,将objects数组的长度扩大到10,返回一个新的数组,元素与objects一致,长度改变
        Object[] newObjects = Arrays.copyOf(objects, 10);
        System.out.println("新数组长度: " + newObjects.length);
    }
}

结果如下:

原数组长度: 2
新数组长度: 10

Process finished with exit code 0
  • System.arraycopy(src, srcPos, dest, destPos, length)
  • 该函数的作用是拷贝数组。
  • src:原数组
  • srcPos:起始的位置
  • dest:目标数组
  • destPos:目标数组的起始位置
  • length:复制的长度
  • 注意:
    (1)src和dest都必须是同类型或者可以进行转换类型的数组。该函数也可以实现自己复制自己。
    (2)复制的目标数组待复制的元素长度不要超过目标数组的长度
    举例说明

int[] fun = {0,1,2,3,4,5,6};
System.arraycopy(fun, 0 , fun,3,3)
结果是{0,1,2,0,1,2,6}

实现过程是:先生成了一个长度是length的临时数组,将fun数组中srcPos到srcPos+length-1之间的数据拷贝到临时数组中,再执行System.arraycopy(临时数组,0,fun,目标数组的起始位置,临时数组的长度即length);

小结一下
虽然两种方法都可以实现数组扩容,但是如果数组的长度比较大,那么使用System.arraycopy会比较有优势,因为其使用的是内存复制,省区了大量的数组寻址访问等时间。

ArrayList集合框架

  • rt.jar无法用断点调试。
  • jdk1.7之后数组默认的数据初始化大小代码存放在add方法中。
  • ArrayList采用数组实现,数组名称是elementData
  • jdk1.7之后当我们定义了ArrayList之后,起始我们的数组是没有被初始化的。即List list = new ArrayList(); 此时数组elementData没有被初始化,其是在调用add方法的时候会进行初始化;在JDK1.6的时候,默认构造函数就会初始化elementData的大小。
  • ArrayList底层数组elementData默认的初始化长度是10,就是说ArrayList的默认的最小容量是10
  • 如果规定了列表的长度,其数组长度以我们规定的为准。如果 new ArrayList(2); 表示初始化一个长度为2的列表,即底层数组长度是2,然后数组中的值都为null,即[null.null](这种原因是ArrayList的有参构造造成的),有参构造如下:
    在这里插入图片描述
    此时因为数组不为空,就不会进入初始化是10的过程中,如下
    在这里插入图片描述

手写ArrayList集合

  • list即使规定了初始值,但是add的时候即使超过了初始值,也不会报错,而是会进行扩容
  • 每次扩容是以1.5倍进行扩容的
  • ArrayList的源码分析:

(1)List是存在有参和无参两种构造的

  • 无参构造:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

我们可以发现无参构造,我们默认为其创建了一个空的数组,并没有规定其长度

  • 有参构造:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
  * Constructs an empty list with the specified initial capacity.
  *
  * @param  initialCapacity  the initial capacity of the list
  * @throws IllegalArgumentException if the specified initial capacity
  *         is negative
  */
 public ArrayList(int initialCapacity) {
     if (initialCapacity > 0) {
         this.elementData = new Object[initialCapacity];
     } else if (initialCapacity == 0) {
         this.elementData = EMPTY_ELEMENTDATA;
     } else {
         throw new IllegalArgumentException("Illegal Capacity: "+
                                            initialCapacity);
     }
 }

我们发现其有参构造当我们的参数传递一个大于0的数字的时候,我们会创建指定长度的数组;如果长度等于0,创建一个空的数组。注意:这里的指定长度若大于0,则初始化数组的时候会指定数组的长度,其他情况不会指定数组的长度

(2)list的add方法:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

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);
    }

我们可以看到上面的代码:
(1)在add方法的时候会判断是否需要扩容操作,传入的是当前正在添加第几个元素(没有第0个),也就是我们内存扩容的最小大小,最小就应该是数组可以放进去当前的元素。
(2)如果当前要扩容的最小大小大于了数组的长度,则应该对其进行扩容操作。
(3)扩容操作的扩容长度应该是原来数组大小的1.5倍
(4)当扩容大小小于最小的扩容长度,则取最小扩容长度为新数组的长度。
(5)对数组进行扩容
(6)扩容完成后,将新添加的元素放到数组中

(3) list的删除操作(remove操作,根据下标删除,返回删除的元素)

  • 其实也是数组的复制操作,将当前数组从待删除的目标位置的下一个位置开始复制,复制到该数组待删除的位置开始,长度就是删除下标到原数组结束.复制结束后,将数组的最后一个元素复制为空,实际上删除的时候没有缩小数组的长度,只不过是将最后无效数据改为null
  • 源码如下:
System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);

其中elementData是数组,index代表了要被删除的元素的下标,arraycopy(原数组,原数组起始位置,目标数组,目标数组起始位置,复制的长度)

  • 总结就是删除操作就是移动数组,将待删除的后面的元素向前移动。

(4)list的删除操作(根据元素删除)

  • 先根据对象查找出在数组中的具体的位置

  • 再根据以下标进行删除做同样的操作

  • 注意:我们删除元素的时候只可以删除第一个出现的,即使有多个存在,也不会全部删除

  • JAVA的手写ArrayList,我们这里只写其核心方法的实现:
    (1)List接口手写

package com.xiyou.myList;

/**
 * 抽象接口,相当于是List
 * @author 郑一帆
 */
public interface ExList<E> {

    /**
     * 添加元素
     * @param object
     */
    public void add(E object);

    /**
     * 在指定的位置上添加元素
     * @param index
     * @param object
     */
    public void add(int index, E object);

    /**
     * 根据下标移除元素
     * @param index
     * @return
     */
    public E remove(int index);

    /**
     * 移除元素,但是只能移除一个,即使有重复的
     * @param object
     * @return
     */
    public boolean remove(E object);

    /**
     * 返回列表的长度
     * @return
     */
    public int getSize();

    /**
     * 根据下标值取出具体的元素
     * @param index
     * @return
     */
    public E get(int index);
}

(2)ArrayList

package com.xiyou.myList;

import java.util.Arrays;

/**
 * 纯手写ArrayList
 * @author zyf
 */
public class ExtArrayList<E> implements ExList<E>{

    /**
     * 用来保存列表的底层实现,数组实现列表
     */
    private transient Object[] elementData;

    /**
     * 保存列表中实际存储的值
     */
    private int size;


    /**
     * 无参构造
     */
    public ExtArrayList(){
        // 默认创建一个初始值为10的
        this(10);
    }


    /**
     * 有参构造,传入的参数表示的是初始化的数组的长度
     * @param initialCapacity
     */
    public ExtArrayList(int initialCapacity) {
        if (initialCapacity < 0) {
            // 如果构建数组的长度小于0,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }
        // 创建初始化大小的数组
        elementData = new Object[initialCapacity];
    }


    /**
     * 添加一个元素到列表的最后
     * @param object
     */
    @Override
    public void add(E object) {
        // 判断是否需要扩容
        // 传入的是当前正在准备处理的第几个元素
        ensureExplicitCapacity(size+1);
        // 保存到数组中
        elementData[size++] = object;
    }


    /**
     * 判断数组是否足够大 是否需要扩容
     * @param minCapacity 最小的扩容大小,传进来的是准备要处理的第几个元素,我们数组最起码应该有这么大,能存放进去
     */
    private void ensureExplicitCapacity(int minCapacity) {
        // 判断当前存储的元素是否等于数组的大小,等于的话则无法进行新的元素的存储,需要扩容
        if (size == elementData.length) {
            // 获取原来数组长度
            int oldCapacity = elementData.length;
            // 扩容成原来的1.5倍 >>1 相当于/2
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            // 最新的扩容的大小小于最小的扩容大小
            if (newCapacity < minCapacity) {
                // 扩容大小变为最小的扩容大小
                newCapacity = minCapacity;
            }
            // 进行扩容, 将数组elementData扩大到长度是newCapacity
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }


    /**
     * 在指定的位置上添加元素
     * @param index
     * @param object
     */
    @Override
    public void add(int index, E object) {
        // 检查下标是否越界
        rangeCheck(index);
        // 检查是否需要扩容
        ensureExplicitCapacity(size + 1);
        // 进行扩容
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        // 已经复制过了,然后将index上面的元素进行替换
        elementData[index] = object;
        size ++ ;
    }


    /**
     * 判断数组是否越界
     * @param index
     */
    private void rangeCheck(int index) {
        // 这里不能和数组的长度比较,因为数组长度会进行扩容,存了多少并不是数组的长度
        if (index >= size) {
            throw new IndexOutOfBoundsException("数组越界");
        }
    }


    /**
     * 根据下标值进行删除
     * @param index
     * @return
     */
    @Override
    public E remove(int index) {
        // 得到删除的元素,一会要返回
        E object = get(index);
        // 应该复制多少
        int numMoved = size - index -1;
        if (numMoved > 0 ) {
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        }
        // 把最后一位赋值为空
        elementData[--size] = null;
        return object;
    }


    /**
     * 根据元素进行删除
     * @param object
     * @return
     */
    @Override
    public boolean remove(E object) {
        for (int i = 0; i < elementData.length; i++) {
            Object element = elementData[i];
            if (element.equals(object)) {
                remove(i);
                return true;
            }
        }
        return false;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public E get(int index) {
        // 检查数组下标是否越界
        rangeCheck(index);
        return (E) elementData[index];
    }
}

Vector

Vector的集合实现和ArrayList的实现一模一样,只不过是在扩容的时候加了synchronized(扩容是在add方法中实现的,所以也是锁是加在add方法上的)

  • Vector和ArrayList的区别:

(1)Vector是线程安全的,但是性能要比ArrayList低
(2)ArrayList和Vector都是采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加原来的50%,Vector默认增加原来的一倍
(3)Vector可以设置capacityIncrement(扩容时候增大多少),而ArrayList不行,从字面的意思理解就是capacity容量,Increment增加,容量增长的参数。

补充知识点:

  • Java的反射机制是无法获取到泛型的具体类型,泛型是在编译之后才会存在的,运行时期不在,一般情况下用字节码技术来获取泛型的具体类型
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值