Java 集合学习--ArrayList
一、ArrayList 定义
ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。

①、实现 List 接口
List接口继承Collection接口,是List类的顶层接口,定义了大量方法,子类可进行个性化实现

②、实现RandomAccess接口
RandomAccess 接口是一个标记接口,类似我们熟悉的Serializable接口,表明支持随机访问,在工具类Collections中有发挥其作用。

③、实现 Cloneable 接口
能否调用Object.clone() 方法的关键,如果未实现,调用clone则抛出CloneNoSupportException异常。
④、实现 Serializable 接口
这个接口没什么好说的,能都进行序列化的关键。
二、字段属性
ArrayList类的主要属性如下:
//Arraylist默认初始大小
private static final int DEFAULT_CAPACITY = 10;
//空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//这也是一个空的数组实例,与上面空数组的区别在于,当第一个元素添加的时候,需要膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储 ArrayList集合的元素的数组
transient Object[] elementData;
//ArrayList集合元素个数
private int size;
三、构造函数
1.无参构造函数,构造一个空的数组。

2.有参构造函数,参数指定数组的初始大小。构造给定的初始大小的数组。

3.有参构造函数,参数是一个集合,最终是将给定的集合复制到数组中。

四、主要方法
1.添加元素
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}
在添加元素之前,需要调用ensureCapacityInternal()方法来确定数组的大小,保证数组有足够的大小来容量新增的元素。如果数组已经满了,则进行数组增大,主要是借助Arrays.copyOf()方法实现的。
 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);
 }
①、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
②、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
③、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
数组的最大长度为 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.删除元素
①、通过数组索引删除元素
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;
}
首先判断索引的值是否大于等于size,超过集合的大小则抛出IndexOutOfBoundsException异常,再通过System.arraycopy()方法对数组进行复制操作,最终形成的数组是删除指定位置后的数组,达到了移动数组中元素位置的效果。
②、直接删除指定元素
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;
}
删除指定元素的逻辑非常简单,即首先遍历数组,找到指定元素在数组中的位置后,最终的逻辑和通过索引删除元素一致。
3.查找元素
 public E get(int index) {
        rangeCheck(index);
        return elementData(index);
}
查找元素是根据索引查找,即通过索引获取数组中的值。
4.集合遍历
①、普通 for 循环遍历,通过循环,在借助get方法即可遍历ArrayList集合。
②、通过terator遍历
首先获取迭代器,其返回的是ArrayList类中自定义的内部类Itr。
public Iterator<E> iterator() {
        return new Itr();
}
 private class Itr implements Iterator<E> {
         int cursor;       // index of next element to return
         int lastRet = -1; // index of last element returned; -1 if no such
         int expectedModCount = modCount;
         Itr() {}
         public boolean hasNext() {
             return cursor != size;
         }
         @SuppressWarnings("unchecked")
         public E next() {
             checkForComodification();
             int i = cursor;
             if (i >= size)
                 throw new NoSuchElementException();
             Object[] elementData = ArrayList.this.elementData;
             if (i >= elementData.length)
                 throw new ConcurrentModificationException();
             cursor = i + 1;
             return (E) elementData[lastRet = i];
         }
         public void remove() {
             if (lastRet < 0)
                 throw new IllegalStateException();
             checkForComodification();
             try {
                 ArrayList.this.remove(lastRet);
                 cursor = lastRet;
                 lastRet = -1;
                 expectedModCount = modCount;
             } catch (IndexOutOfBoundsException ex) {
                 throw new ConcurrentModificationException();
             }
         }
         @Override
         @SuppressWarnings("unchecked")
         public void forEachRemaining(Consumer<? super E> consumer) {
             Objects.requireNonNull(consumer);
             final int size = ArrayList.this.size;
             int i = cursor;
             if (i >= size) {
                 return;
             }
             final Object[] elementData = ArrayList.this.elementData;
             if (i >= elementData.length) {
                 throw new ConcurrentModificationException();
             }
             while (i != size && modCount == expectedModCount) {
                 consumer.accept((E) elementData[i++]);
             }
             // update once at end of iteration to reduce heap write traffic
             cursor = i;
             lastRet = i - 1;
             checkForComodification();
         }
         final void checkForComodification() {
             if (modCount != expectedModCount)
                 throw new ConcurrentModificationException();
         }
     }
③、forEach遍历ArrayList本质即通过迭代器遍历。
5.toArray,ArrayList集合转换成数组
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
}
6.trimToSize()
public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
}
这个方法主要是释放内存,将ArrayList集合中数组的大小调整为size的大小。
7.subList方法
这个方法往往引起我们犯错,subList返回的并不是一个ArrayList。经常我们容易使用subList后,再使用add方法,然后报异常。具体原因源码显示的很清楚。
public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
}
其他方法的学习省略,感兴趣的可以参考源码。
五、总结
- ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
- ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
- ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法
- ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
- ArrayList中允许元素为null,且可以重复。
Java 集合学习--ArrayList的更多相关文章
- 2019/3/4  java集合学习(二)
		java集合学习(二) 在学完ArrayList 和 LinkedList之后,基本已经掌握了最基本的java常用数据结构,但是为了提高程序的效率,还有很多种特点各异的数据结构等着我们去运用,类如可以 ... 
- 【源码阅读】Java集合之一 - ArrayList源码深度解读
		Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ... 
- Java集合学习(9):集合对比
		一.HashMap与HashTable的区别 HashMap和Hashtable的比较是Java面试中的常见问题,用来考验程序员是否能够正确使用集合类以及是否可以随机应变使用多种思路解决问题.Hash ... 
- 从源码看Java集合之ArrayList
		Java集合之ArrayList - 吃透增删查改 从源码看初始化以及增删查改,学习ArrayList. 先来看下ArrayList定义的几个属性: private static final int ... 
- 转:深入Java集合学习系列:HashSet的实现原理
		0.参考文献 深入Java集合学习系列:HashSet的实现原理 1.HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特 ... 
- 2019/3/2周末 java集合学习(一)
		Java集合学习(一) ArraysList ArraysList集合就像C++中的vector容器,它可以不考虑其容器的长度,就像一个大染缸一 样,无穷无尽的丢进去也没问题.Java的数据结构和C有 ... 
- java集合学习(2):Map和HashMap
		Map接口 java.util 中的集合类包含 Java 中某些最常用的类.最常用的集合类是 List 和 Map. Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含 ... 
- Java集合学习(7):ArrayList
		一.概述 ArrayList可以理解为动态数组,就是Array的复杂版本.与Java中的数组相比,它的容量能动态增长.ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包 ... 
- Java集合(六)--ArrayList、LinkedList和Vector对比
		在前两篇博客,学习了ArrayList和LinkedList的源码,地址在这: Java集合(五)--LinkedList源码解读 Java集合(四)--基于JDK1.8的ArrayList源码解读 ... 
随机推荐
- 【luogu P3950 部落冲突】 题解
			题目连接:https://www.luogu.org/problemnew/show/P3950 1.像我这种学数据结构学傻了的 2.边权化点权 所有点权初始化0 3.对于战争 将深度较深的-1,对于 ... 
- Android学习笔记_56_应用Tween动画 (渐变、缩放、位移、旋转)
			1.实现listview每个项先向右移动,再向左移动(回到原来位置) TranslateAnimation ta = new TranslateAnimation( Animation.RELATIV ... 
- redis的事务、主从复制、持久化
			redis事务 和其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制.在Redis中, MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事务的基石.Redis中事务 ... 
- TDD: 解除依赖
			1 A类依赖B 类,可以把B类提取成IB接口,解除AB 之间的依赖关系. 通过创建实现了IB接口的BStub 装代码,可以模拟B类进行测试. 这是针对接口编程的典型.适合构造代价大,变化多的情况.应 ... 
- 课时59.体验css(理解)
			我们想做这样一个样式,应该怎么做? 分析: 有一个标题(h1),还有一些段落(p) 标题是居中的,段落也是居中的,所以我们可以设置h标签和p标签居的align属性等于center来实现 标题和段落都有 ... 
- SpringBoot非官方教程 | 第十七篇:上传文件
			转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springboot/2017/07/11/springboot14-upload/ 本文出自方志朋的博客 这篇文 ... 
- ndk-build 学习笔记
			# 必须以local_path 开头# 定位源文件LOCAL_PATH := $(call my-dir) #引入clear-vars.mk文件,清除除local_path以外的其他local_< ... 
- java中静态代理和动态代理
			一.概述 代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象.如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目标对象功能 ... 
- iOS 通用缓存:HanekeSwift
			iOS 通用缓存:HanekeSwift Haneke 是个采用 Swift 编写的轻量级 iOS 通用缓存.示例: 初始化一个数据缓存: let cache = Cache<NSData> ... 
- 使用Python读取Dbf文件
			DBF:一种特殊的文件格式!表示数据库文件,Foxbase,Dbase,Visual FoxPro等数据库处理系统所产生的数据库文件! DBF 数据库是常用的桌面型数据库,它曾经被各企业.事业单位广泛 ... 
