java集合List

Java集合概况

Java集合一直理解的都是片面的,整理一下,将知识组织成面,更便于理解。上图来自Java 集合系列01之 总体框架 - 如果天空不死 - 博客园,虽然博主是基于java1.6整理的,但也不碍于我们学习。理解了上图,对于学习java集合会事半功倍,为了便于理解,我要再复述一下,哈哈。

Java集合框架主要包含三部分,Collection、Map映射、工具类(Iterator迭代器、Enumeration枚举类、Arrays和Collections)。

Collection是一个接口,包含了集合的基本操作和属性。为了方便编码,又抽象出了AbstractCollection抽象类,我们可以看到List和Set实现类都间接继承自AbstractCollection。Collection主要包含List和Set接口,List和Set的区别在于,List是基于数组,元素有序可重复;Set的实现类是基于Map,元素无序不可重复。

Map是一个映射接口,主要实现类包括HashMap、TreeMap、HashTable等。

Iterator是遍历集合的工具,Collection实现了iterator()函数,所以Collection依赖于Iterator。Enumcration也是遍历集合的工具,只不过仅用于HashTable、Vector、Stack。

Arrays和Collections。它们是操作数组、集合的两个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

ArrayList

可以参考这个博客:Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 - 如果天空不死 - 博客园;下边理理ArrayList扩容。


ArrayList扩容机制

ArrayList包含了两个重要的对象:elementData 和 size。

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     * 存储ArrayList元素的数组缓冲区,也就是数组的大小,是一个动态数据,如果用无参
     * 构造函数创建,list的大小为默认值10;如果用有参构造函数,可以定义list大小
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     * 动态数组的实际大小
     * @serial
     */
    private int size;

再看下ArrayList的三种创建方式

    /**
     * 默认初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;
    

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     *默认构造函数,使用初始容量10构造一个空列表(无参数构造)
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    /**
     * 带初始容量参数的构造函数。(用户自己指定容量)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//初始容量大于0
            //创建initialCapacity大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//初始容量等于0
            //创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//初始容量小于0,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }


   /**
    *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
    *如果指定的集合为null,throws NullPointerException。 
    */
     public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

由上可知, 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。

接着看扩容源码

    /**
     * Appends the specified element to the end of this list.
     * 增加数据元素到集合得末尾
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        // 判断是否扩容,如果原来的元素个数是size,那么增加一个元素之后的元素个数为size + 1,所以需要的最小容量就为size + 1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        // 获取数组最小容量,如果elementData为空,且minCapacity <= 10,都会以DEFAULT_CAPACITY作为最小容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

        // 如果minCapacity大于elementData的长度,使用grow方法进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * 扩容方法
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // 原有数组容量
        int oldCapacity = elementData.length;
        // 新的数组容量,下面位运算相当于newCapacity = oldCapacity * 1.5 向下取整
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果新的数组容量小于需要的最小容量,即假设新的数组容量是15,最小需要16的容量,则会将16赋予newCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        /*变量MAX_ARRAY_SIZE = 2147483639 [0x7ffffff7],如果扩容后的新容量大于这个值则会使用hugeCapacity方法
         * 判断最小容量minCapacity是否大于MAX_ARRAY_SIZE,如果需要最小容量的也大于MAX_ARRAY_SIZE,则会以
         * Integer.MAX_VALUE = 2147483647 [0x7fffffff]的值最为数组的最大容量,如果没有则会以MAX_ARRAY_SIZE最为最大容量
         * MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,为什么用MAX_ARRAY_SIZE ,源码的中的说法是一些虚拟机中会对数组保留一些标题字段
         * 使用Integer.MAX_VALUE会造成内存溢出错误
         * */
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 确定数组最终的容量newCapacity之后,将原有ArrayList的元素全部拷贝到一个新的ArrayList中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE作为最大长度,而多余的元素就会被舍弃掉
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

ArrayList的扩容机制总结成一句话,就是在第一个添加元素时,创建一个长度为10的数组,之后随着元素的增加,以1.5倍原数组的长度创建一个新数组,即10, 15, 22, 33,。。这样序列建立,将原来的元素拷贝到新数组之中,如果数组长度达到上限,则会以MAX_ARRAY_SIZE 或者 Integer.MAX_VALUE作为最大长度,而多余的元素就会被舍弃掉。

ArrayList的两个remove方法

    //参数是Object包装类型,本质上是寻找集合中是否有该元素,有则删除。返回值是布尔类型,有该对象返回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;
    }


    //参数是基本类型int,实现是直接删除下标,返回泛型类型,有则返回该对象,因为是根据下标删除,所以不存在没有的情况,除非下标越界
    public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

LinkedList

可以参考:Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例 - 如果天空不死 - 博客园

双向链接实现。LinkedList 同时实现了 List 接口和 Deque 接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。这样看来,LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用 LinkedList,一方面是因为 Java 官方已经声明不建议使用 Stack 类,更遗憾的是,Java 里根本没有一个叫做 Queue 的类(它是个接口名字)。关于栈或队列,现在的首选是 ArrayDeque,它有着比 LinkedList(当作栈或队列使用时)有着更好的性能。

ArrayList和LinkedList

这两的对比,我们常常可以总结成一句话:

  • ArrayList底层是数组,查询快,增删慢;
  • LinkedList底层是链表,查询慢,增删快;

但是,任何东西好坏都是没有绝对的,ArrayList和LinkedList的快慢也是要看场景的,ArrayList查询快,也只是对于索引查询……LinkedList增删快,但是必须要先找到元素。

查询比较

ArrayList底层是数组,存储空间是连续的,根据索引查询,时间复杂度是O(1);但是如果通过元素去查,需要遍历,时间复杂度是O(n);

LinkedList底层是链表,存储空间是不连续的,查询时,需要遍历列表,不断跳转新的地址,所以时间复杂度是O(n);

增删比较

ArrayList增删慢,一是因为ArrayList可以扩容,一旦扩容就会有数组拷贝,扩容和拷贝都是耗时的,如果初始分配恰当容量,扩容也是可以避免的;二是因为如果不是头插或尾插,而是把元素插入到中间,这样就会涉及到元素的移动,也是耗时的;

LinkedList增删快,重在说明的是,元素插入的时候,不涉及元素的移动,只是指针的改变;但是进行增删前,有个前提是需要找到对应的位置,而查找是需要遍历的。所以综合考虑的话,LinkedList增删也不一定快,只是在理想的情况下确实是O(1),理想情况就是头插和头删。

两者哪个更占空间呢?

LinkedList每个Node中包含了三个成员,分别是存储数据的item,指向前一个存储单元的点 prev 和指向后一个存储单元的节点 next这么一琢磨,如果存储相同的数据,似乎就是LinkdeList更占空间。。但这只是表面因素。

ArrayList虽然每个节点没有多余信息,但是ArrayList是可以扩容的,如果刚好数据量超过ArrayList默认的临时值时,那么会有将近原来一半大小的数组空间被浪费了,ArrayList占用的空间也是不小的。不过,因为ArrayList的数组变量是用transient关键字修饰的,如果集合本身需要做序列化操作的话,ArrayList这部分多余的空间不会被序列化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子松的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值