浅谈ArrayList

废话不多说(事实是不会说),让我们直接进入正题

首先讲一讲最基本的ArrayList的初始化,也就是我们常说的构造函数,ArrayList给我们提供了三种构造方式,我们逐个来查看

Arraylist();

无参的构造方法,这种方式的初始化,ArrayList内部会为我们声明一个长度为0的集合,但在我们调用add方法加入一个元素时,它内部会经历add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->grow->调用Arrays.copyOf方法返回新生成的集合(内部最终调用System.arraycopy完成元素的转移)->grow->ensureExplicitCapacity->ensureExplicitCapacity->add方法,该执行流程最终生成一个容量为十(默认值)的集合,并将元素加入到集合中。以下列出部分关键源码

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
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 static int calculateCapacity(Object[] elementData, int minCapacity) {
    //如果为无参的构造
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //该处的DEFAULT_CAPCITY=10,minCapcity=1
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
    //集合结构发生改变的记录值,没发生一次结构改变(扩容、元素增加、元素删除),该值加一
    modCount++;

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private void grow(int minCapacity) {
    ...
    elementData = Arrays.copyOf(elementData, newCapacity);
}

也许你会问,既然有初始默认容量值,为什么不在调用构造函数的时候就初始化一个默认长度的集合呢?

个人意见:我认为在存在一种情况,就是仅声明一个ArrayList的集合对象,但是后面并未使用,在这种情况下,如果在声明是就初始化一个长度为10的集合,会造成空间的浪费,而且在真正使用集合时(即调用add方法),才初始化集合,有一种懒加载的思想在其中,避免了耗资源操作的集中,但也并非所有的构造方法中都不会初始化集合容量,在ArrayList(int capacity)构造方法中,只要输入的初始容量为正整数,那么就会在构造函数中就定义出指定大小的集合对象

ArrayList(int capacity);

该方式的初始化在执行构造函数的时候传入了一个初始容量值,不过要求这个初始容量值必须为正整数,而且如果为0的话等同于无参的构造方法,假定初始化容量为5,那么在调用add方法时,经历的方法依次为add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->add,下面我们来看看部分关键源码:

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        //按指定容量初始化集合,此时elementData.length==initialCapacity
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //传入值等于0,初始化一个容量为0的初始集合
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //传入值为不符合要求,报错
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}
private static final Object[] EMPTY_ELEMENTDATA = {};
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //不满足条件,不会进入该分支
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //直接返回最小容量,即集合当前元素值+1(给新增元素的空间)
    return minCapacity;
}

ArrayList(Collection<? extends E> c);

该方式的初始化需要传入Collection的子类对象,并根据该对象来初始化ArrayList

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        //如果不是Object[]类型的集合,转化为Object[]类型
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

到这里对ArrayList的三个构造函数做了简单的介绍

在继续进行ArrayList中方法的介绍之前,需要注意ArrayList不是线程安全的,所以如果涉及并发操作,建议在初始化的使用调用Collections.synchronizedList(list)来将线程不安全的集合对象转化为线程安全的对象,内部的实现原理:使用代理模式在原来的方法执行之前嵌套了一个同步机制,这里摘取其中的size()方法,源码如下:

public int size() {
    synchronized (mutex) {  //同步代码块
        return c.size();    //c为被代理对象
    }
}

因为给所有的方法加上锁会降低代码的执行效率,而且有些方法是不需要加锁的,如果不想对所有的方法都加锁,可以在需要加锁的特定方法调用之前手动的做同步处理

下面进入ArrayList中的部分方法介绍,首先介绍从List父接口中继承过来的方法,其中使用到的[]代表对应参数可以存在也可以不存在,不过存在与不存在构成了不同的重载方法:

add([int index,] E element);

该方法用于向集合中添加元素,存在单参方法和双参两个重载方法,单参直接往集合末尾添加新元素,双参方法则可以手动指定插入到集合的哪个索引位置。每当集合元素的个数(size)超过集合容量(elementData.length)时,会触发grow方法(扩容方法),每次在老容量的基础上增加一半的容量,然后通过Arrays.copyOf方法进行新集合的创建和拷贝原集合内容

//在grow方法中关于新集合容量定义的关键代码
int newCapacity = oldCapacity + (oldCapacity >> 1);

addAll([int index,] Collection<? extends E> c);

该方法用于将另一个集合中的所有元素加入到当前集合中,该方法也存在单参和双参两个重载方法,单参方法把所有新元素添加到集合末尾,双参方法则在指定索引位置开始插入所有元素,扩容方法的触发和过程同add

set(int index, E element);

该方法用于替换集合中指定索引位置的值,内部会先进行索引范围检查,返回值为被替换下的老元素

isEmpty();

该方法用于判断集合中是否存在元素,返回值是一个布尔类型,源码的返回值为size == 0

get(int index);

该方法用于获取集合中指定位置的值并返回

remove(int index);

该方法用于删除集合中特定索引位置的值,返回值为被删除的老元素

remove(Object o);

该方法用于删除集合中第一个为特定元素的值,可以传入null,代表删除集合中第一个为null的元素,返回值为布尔类型

clear();

该方法用于清空集合中的所有元素,内部对elementData集合对象的所有元素置空,并记录modCount(集合结构记录变量)值,最后将size置0

removeAll(Collection<?> c);

该方法用于删除集合中存在于传入集合中的所有指定元素,要注意的是,该方法为全集合查找删除,不同于remove方法只删除第一次出现的位置

retainAll(Collection<?> c);

该方法用于跟removeAll的作用刚好互补,用于保留传入集合中包含的所有指定元素,即删除指定集合之外的所有元素

size();

该方法返回集合中元素的个数(size),但该值并不代表集合当前的容量(elementData.length)

indexOf(Object o);

该方法用于查询集合中特定元素出现的第一个索引位置,如果未找到则返回-1

lastIndexOf(Object o);

该方法用于查询集合中特定元素出现的最后一个下标位置,如果未找到则返回-1

contains(Object o);

该方法用于判断集合中是否存在指定的元素,返回值为布尔类型,源码的返回值为indexOf(o) >= 0

sort(Comparator<? super E> c);

该方法用于对集合元素按传入的指定排序规则进行排序,以下使用匿名内部类并结合Lambda表达式来作为示例构造一个二级排序规则,可根据需要增加更多层级的排序规则:

list.sort((o1,o2) -> {
    int result = -(o1.getClick() - o2.getClick());// 数值类型的比较写法,点击量降序
    if (result == 0) {// 如果点击量相同,进入二级排序
        result = o1.getDate().compareTo(o2.getDate());// 字符串类型的比较写法,时间升序
    }
    return result;// 返回比较结果
});

subList(int fromIndex,int toIndex);

该方法用于返回一个可操作性的子集合,不过要注意的是,子集合只不过是添加了偏移量的父集合,所以两个集合的对象是一致的,对于子集合中的操作,源码中都是在添加上对应的偏移量之后直接对父集合做对应修改,所以,对子集合的操作实际上就是对父集合的操作

iterator();

该方法用于返回当前集合的一个普通迭代器,提供了hasNext,next,remove和forEachRemaining方法,其中hasNext用于判断是否存在下一个迭代对象,next用于将cursor指向下一个待操作元素,并返回当前元素(由内部的lastRet索引值进行指定),remove用于删除当前元素(该方法不可连续调用多次,因为内部的lastRet索引在一次操作后会被置为-1,可以会在下一次next时重新指向当前元素),forEachRemaining用于对集合未迭代对象执行传入的钩子方法

listIterator([int index]);

该方法用于返回一个加强版的迭代器对象,由于内部继承了iterator的类,所以可以提供iterator的所有功能,除此之外,还提供了自己独有的功能和特性,首先在初始化时就可以指定迭代初始索引,其次还提供了hasPrevious,nextIndex,previousIndex,previous,set和add方法,其中的previous方法可以实现集合的逆序遍历,set可以替换集合的当前迭代对象,add可以在当前迭代位置添加新的集合元素

splitIterator();

该方法用于切割集合元素,可用于并发编程时多线程操作集合,需要先进行切割,方法内部默认对半分,再对切割完成的集合进行并行处理,示例代码如下:

ist list = new ArrayList();
list.add(1);list.add(2);list.add(3);list.add(4);list.add(5);
list.add(6);list.add(7);list.add(8);list.add(9);list.add(0);
//对集合进行切割
Spliterator<Integer> a = list.spliterator();//将集合转化为可拆分集合
Spliterator<Integer> b = a.trySplit();//将可拆分的a集合对半分
Spliterator<Integer> c = a.trySplit();//继续将可拆分的a集合对半分
Spliterator<Integer> d = b.trySplit();//将对半分得到的b集合对半分
//对切割好的集合进行操作
a.forEachRemaining(x -> System.out.print(x + " "));
System.out.println();
b.forEachRemaining(x -> System.out.print(x + " "));
System.out.println();
c.forEachRemaining(x -> System.out.print(x + " "));
System.out.println();
d.forEachRemaining(x -> System.out.print(x + " "));

打印结果为:

8 9 0
3 4 5
6 7
1 2

toArray(T[] a);

该方法用于将集合转化为数组,可以使用参数来指定生成数组的数据类型,返回值为指定类型的数组,以下为代码示例:

String[] strs=list.toArray(new String[0]);// list.toArray(new Object[0]);等同于list.toArray();

下面的方法是ArrayList中特有的方法,如果在你的代码中调用不到,请检查下声明该ArrayList对象的时候对象声明部分是否为ArrayList喔~

clone();

该方法继承自Cloneable接口父类,用于创建并返回一个浅克隆的集合对象

ensureCapacity(int minCapacity);

该方法用于检查集合容量是否已经达到瓶颈,继而判断是否需要进行扩容操作

trimToSize();

该方法用于将集合容量调整至集合的元素总数,可以对不需要继续添加新元素的集合使用该操作用于释放部分资源,使用了该方法的集合,在添加新元素时会进行扩容操作,扩容方法依旧是扩容原容量的一半

forEach(Consumer<? super E> action);

用于对集合中的所有元素执行对应的操作,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:

list.forEach(x -> System.out.println(x.getAge()+1));// 如果只是简单的打印,还可以使用list.forEach(System.out::println);

removeIf(Predicate<? super E> filter);

该方法用于移除集合中符合条件的所有元素,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:

list.removeIf(x -> x.getAge() < 18);

如果对你有帮助,点个赞,或者打个赏吧,嘿嘿

整理不易,请尊重博主的劳动成果

浅谈ArrayList的更多相关文章

  1. 浅谈 ArrayList 及其扩容机制

    浅谈ArrayList ArrayList类又称动态数组,同时实现了Collection和List接口,其内部数据结构由数组实现,因此可对容器内元素实现快速随机访问.但因为ArrayList中插入或删 ...

  2. 浅谈 Java集合

    Java 集合 集合是对象的容器,定义了多个对象进行操作的常用方法,可实现数组的功能. Java集合类库所处位置:java.util.*. 与现代的数据结构类库的常见做法一样,Java集合类库也将接口 ...

  3. 浅谈Android编码规范及命名规范

    前言: 目前工作负责两个医疗APP项目的开发,同时使用LeanCloud进行云端配合开发,完全单挑. 现大框架已经完成,正在进行细节模块上的开发 抽空总结一下Android项目的开发规范:1.编码规范 ...

  4. 浅谈JAVA集合框架

    浅谈JAVA集合框架 Java提供了数种持有对象的方式,包括语言内置的Array,还有就是utilities中提供的容器类(container classes),又称群集类(collection cl ...

  5. 浅谈TabLayout(ViewPager+Tab联动)

    google发布了的Android Support Design库中提供了TabLayout 通过TabLayout+ViewPager实现导航栏效果,点击Tab ,ViewPager跟随变化,滑动V ...

  6. 安卓开发_浅谈ListView(SimpleAdapter数组适配器)

    安卓开发_浅谈ListView(ArrayAdapter数组适配器) 学习使用ListView组件和SimapleAdapter适配器实现一个带图标的ListView列表 总共3部分 一.MainAc ...

  7. 浅谈Java的集合框架

    浅谈Java的集合框架 一.    初识集合 重所周知,Java有四大集合框架群,Set.List.Queue和Map.四种集合的关注点不同,Set 关注事物的唯一性,List 关注事物的索引列表,Q ...

  8. 浅谈java类集框架和数据结构(2)

    继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...

  9. 浅谈 Java 主流开源类库解析 XML

    在大型项目编码推进中,涉及到 XML 解析问题时,大多数程序员都不太会选用底层的解析方式直接编码. 主要存在编码复杂性.难扩展.难复用....,但如果你是 super 程序员或是一个人的项目,也不妨一 ...

随机推荐

  1. pandas 的常用方法

    pandas的常用方法: 1.数据输入 2.数据查看 3.数据清洗 4.数据处理 5.数据提取 6.数据筛选 7.数据汇总 8.数据统计 9.数据输出 详情见: https://blog.csdn.n ...

  2. 四则运算APP版

    (一)四则运算APP版 这这个Demo的目的就是强化一下本周学习的Android的Jetpack里的新内容,接下来我将通过这个Demo来展示我所学到的新知识. 先列出新学到的知识:ViewModel, ...

  3. mysql两表合并,对一列数据进行处理

    加班一时爽,一直加班~一直爽~  欢迎收看http://www.996.icu/ 今天弄了下MySQL中两表合并的并且要处理一列数据,这列数据原来都是小写字母,处理时将这列数据改成驼峰命名的~~ 基本 ...

  4. java物流查询接口测试代码-快递100

    测试代码 返回json格式,xml/html格式自行修改参数 import java.io.IOException; import java.io.InputStream; import java.n ...

  5. 最简单的 TensorFlow 代码,TensorFlow Hello World 。

    # -*- coding:utf-8 -*- from __future__ import print_function ''' HelloWorld example using TensorFlow ...

  6. TensorBoard中HISTOGRAMS和DISTRIBUTIONS图形的含义

    前言 之前我都是用TensorBoard记录训练过程中的Loss.mAP等标量,很容易就知道TensorBoard里的SCALARS(标量)(其中横纵轴的含义.Smoothing等). 最近在尝试模型 ...

  7. 泛型Genericity

    泛型:可以在类或方法中预支地使用未知的类型. 注意: 一般在创建对象时,将未知的类型确定具体的类型.当没有指定泛型时,默认类型为Object类型.           E - Element      ...

  8. [ASP.NET Core MVC] 如何实现运行时动态定义Controller类型?

    昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对ASP.NET Core MVC应用的扩展,比如在程序运行过程中上传一段C#脚本将其中定义的Controller类型注册到应用中,问我是 ...

  9. Python——五分钟理解函数式编程与闭包

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第9篇文章,我们来聊聊Python的函数式编程与闭包. 函数式编程 函数式编程这个概念我们可能或多或少都听说过,刚听 ...

  10. Hadoop入门之hdfs

        大数据技术开篇之Hadoop入门[hdfs] 学习都是从了解到熟悉的过程,而学习一项新的技术的时候都是从这个技术是什么?可以干什么?怎么用?如何优化?这几点开始.今天这篇文章分为两个部分.一. ...