浅析 java ArrayList

简介

容器是java提供的一些列的数据结构,也可以叫语法糖。容器就是用来装在其他类型数据的数据结构。

ArrayList是数组列表所以他继承了数组的优缺点。同时他也是泛型容器可以自定义各种数据解构、对象容纳在其中。

结构浅析

  • 父类

    AbstractList

  • 接口

    List

    Collection

    RandomAccess

    Cloneable

    Serializable

基本用法

创建对象:

ArrayList<Integer> arrList = new ArrayList<Integer>();

表示创建一个支持整数类型的List集合。

添加元素

arrList.add(10);
arrList.add(5, 10); 第一个表示在arrList的尾部添加元素10;
第二个表示在arrList的index = 5的地方添加元素为10,并且会把5之后的数据全部右移

读取元素

arrList.get(int)

获取指定位置的值

删除元素

arrList.remove(int index)
arrList.remove(Object object) 第一种是删除指定位置的元素,删除后该位置之后的元素全部左移动
第二种是按指定对象删除,同样的删除后再其末尾的数据要左移动

如果ArrayList 的类型为Integer, 同时又想按对象删除元素是需要把其转换成对象: arrList.remove((Integer)10).

源码分析

构造函数

ArrayList.class:

 public ArrayList(int paramInt)
{
if (paramInt < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + paramInt);
}
elementData = new Object[paramInt];
} public ArrayList()
{
this(10);
} public ArrayList(Collection<? extends E> paramCollection)
{
elementData = paramCollection.toArray();
size = elementData.length;
if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
}

如上可以看出来,ArrayList是一个Object对象数组结构的,在没有被指定数组长度的情况下默认是为10。Object是所有对象的父类,所以ArrayList支持所有的对象类型。

添加元素

ArrayList.class


public boolean add(E paramE)
{
ensureCapacityInternal(size + 1); // 确保原本的数组容量还能不能容纳新元素, 会被直接按当前大小的1/2 扩大。
elementData[(size++)] = paramE; // 在尾部添加元素,同时让size + 1
return true;
} public void add(int paramInt, E paramE)
{
rangeCheckForAdd(paramInt); // 判断paramInt会不会越界,必须要小于list的长度才能被插入,否则会报异常:IndexOutOfBoundsException(outOfBoundsMsg(paramInt))
ensureCapacityInternal(size + 1); // 确保长度是足够的。
System.arraycopy(elementData, paramInt, elementData, paramInt + 1, size - paramInt);
elementData[paramInt] = paramE;
size += 1;
} private void ensureCapacityInternal(int paramInt)
{
modCount += 1;
// 新长度未超过最大list的size
if (paramInt - elementData.length > 0) {
grow(paramInt); // 对旧的list扩大size
}
} private void grow(int paramInt)
{
int i = elementData.length;
int j = i + (i >> 1); // 等价于 (int)i+i/2, 用位运算效率更高。把长度扩充到原来的1.5倍
if (j - paramInt < 0) { // 如果扩充的元素还是小于新的list的长度,则直接去新的list的size进程扩充
j = paramInt;
}
if (j - 2147483639 > 0) { // (其实这里插入有可能失败了,因为如果当j = Integer.MAX_VALUE时是没有真正的扩充原本的list)
j = hugeCapacity(paramInt);
}
// 跟系统重新分配一块数组空间,并完成拷贝工作
elementData = Arrays.copyOf(elementData, j);
} private static int hugeCapacity(int paramInt)
{
if (paramInt < 0) {
throw new OutOfMemoryError();
}
// Integer.MAX_VALUE = 2147483647, 2^31 -1
return paramInt > 2147483639 ? Integer.MAX_VALUE : 2147483639;
}

Array.class

 public static <T> T[] copyOf(T[] paramArrayOfT, int paramInt)
{
return (Object[])copyOf(paramArrayOfT, paramInt, paramArrayOfT.getClass());
} public static <T, U> T[] copyOf(U[] paramArrayOfU, int paramInt, Class<? extends T[]> paramClass)
{
Object[] arrayOfObject = paramClass == Object[].class ? (Object[])new Object[paramInt] : // 重新分配数组 (Object[])Array.newInstance(paramClass.getComponentType(), paramInt);
System.arraycopy(paramArrayOfU, 0, arrayOfObject, 0, Math.min(paramArrayOfU.length, paramInt)); // 拷贝数据
return arrayOfObject; // 新的数组地址
}

从这块的代码分析过来,可以发现有如下两种行为:

  1. 频繁内存申请和数据拷贝:

    如果list频繁的插入数据会让这个list不断的重新分配数组空间,并重新拷贝数据。这个时候就考虑给一个不错的ArrayList 数组默认长度或者考虑其他更好的更适合场景的数据容器。

  2. 线程安全、并发问题

    观察add(Index, element) 发现ArrayList是非线程安全的,比如线程1 缸取Index的元素,然后做修改因为获取到的是元素的应用,所以修改Index也就修改了ArrayList的值了。在这个期间如果有线程2去把Index的元素给替换了,那么线程1的元素操作就被覆盖了。

读取元素

ArrayList.class

  public E get(int paramInt)
{
rangeCheck(paramInt); // 检查位置的合法性
return (E)elementData(paramInt); // 取出对应值
} private void rangeCheck(int paramInt)
{
if (paramInt >= size) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(paramInt));
}
}
// 从这里也可以看出来,就是按数组的形式来操作的
E elementData(int paramInt)
{
return (E)elementData[paramInt];
}

这块代码浅显易懂就不做论述

删除元素

ArrayList.class

 // 根据指定位置删除元素
public E remove(int paramInt)
{
rangeCheck(paramInt); // 检查删除的元素的位置释放是合法的
modCount += 1;
Object localObject = elementData(paramInt); // 取出对应位置的元素
int i = size - paramInt - 1;
if (i > 0) {
System.arraycopy(elementData, paramInt + 1, elementData, paramInt, i);
}
elementData[(--size)] = null; // 对应位置设置为null值
return (E)localObject;
}
// 根据对象主动删除元素
public boolean remove(Object paramObject)
{
int i;
if (paramObject == null) {
for (i = 0; i < size; i++) {
if (elementData[i] == null)
{
fastRemove(i);
return true;
}
}
} else {
for (i = 0; i < size; i++) {
if (paramObject.equals(elementData[i]))
{
fastRemove(i);
return true;
}
}
}
return false;
}
// 具体的删除操作
private void fastRemove(int paramInt)
{
modCount += 1;
int i = size - paramInt - 1;
if (i > 0) {
System.arraycopy(elementData, paramInt + 1, elementData, paramInt, i);
}
elementData[(--size)] = null;
}
// 检查元素合法性
private void rangeCheck(int paramInt)
{
if (paramInt >= size) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(paramInt));
}
}

从代码中可以得出如下结论:

  1. null值

    ArrayList中允许直接存储null值的

  2. 线程安全、并发问题

    因为他是直接对ArrayList对应位置置为null,如果有多个线程访问可能存在数据安全性问题。(跟上述说的问题是一样的)

  3. 内存问题

    上面说到他的内存随着数据不断插入会不断的去申请内存块,但是从这里的这块代码中可以发现,如果内存已经被分配的情况下是不会随着元素的递减(删除)而收缩内存的。

  4. 按对象删除元素

    如果是按对象从ArrayList中删除元素,会从开始位置找到第一个跟删除对象匹配的值并删除,如果ArrayList中存在多个相同的对象时需要考虑清楚是否是删除你想删除的对象哦。

优缺点

优点:读取速度快

缺点:插入慢,非线程安全

总结

在使用ArrayList的时候,脑海里必须清晰好自己的场景是否会涉及到并发问题。其次要清晰的了解到数组的优缺点。因为它就是数组的实现

浅析 java ArrayList的更多相关文章

  1. jdk 1.8下 java ArrayList 添加元素解析

    转载请注明http://www.cnblogs.com/majianming/p/8006452.html 有人问我,java ArrayList底层是怎么实现的?我就回答数组,他再问我,那它是怎么实 ...

  2. 浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  3. 浅析Java.lang.Process类

    一.概述      Process类是一个抽象类(所有的方法均是抽象的),封装了一个进程(即一个执行程序).      Process 类提供了执行从进程输入.执行输出到进程.等待进程完成.检查进程的 ...

  4. 浅析Java中的访问权限控制

    浅析Java中的访问权限控制 今天我们来一起了解一下Java语言中的访问权限控制.在讨论访问权限控制之前,先来讨论一下为何需要访问权限控制.考虑两个场景: 场景1:工程师A编写了一个类ClassA,但 ...

  5. [转载]浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  6. 浅析JAVA设计模式之工厂模式(一)

    1 工厂模式简单介绍 工厂模式的定义:简单地说,用来实例化对象,取代new操作. 工厂模式专门负责将大量有共同接口的类实例化.工作模式能够动态决定将哪一个类实例化.不用先知道每次要实例化哪一个类. 工 ...

  7. Java ArrayList、Vector和LinkedList等的差别与用法(转)

    Java ArrayList.Vector和LinkedList等的差别与用法(转) ArrayList 和Vector是采取数组体式格式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,都 ...

  8. 浅析java内存管理机制

    内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分.不同的编程语言有不同的内存管理机制,本文在对比C++和Java语言内存管理机制的不同的基础上,浅析java中 ...

  9. 【转】浅析Java中的final关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...

随机推荐

  1. spring reference

    Spring框架概述 Spring可以轻松创建Java企业应用程序.它提供了在企业环境中使用Java语言所需的一切,支持Groovy和Kotlin作为JVM上的替代语言,并可根据应用程序的需要灵活地创 ...

  2. Robot Framework--ride使用说明2

    RIDE创建项目 1.创建项目 1.1File->New Project 注:选择directory原因是,在directory的项目下可以创建测试套件,如果是tpye为file,则只能创建测试 ...

  3. hive的排序,分組练习

    hive的排序,分組练习 数据: 添加表和插入数据(数据在Linux本地中) create table if not exists tab1( IP string, SOURCE string, TY ...

  4. linux服务器上使用find查杀webshell木马方法

    本文转自:http://ju.outofmemory.cn/entry/256317 只要从事互联网web开发的,都会碰上web站点被入侵的情况.这里我把查杀的一些方法采用随记的形式记录一下,一是方便 ...

  5. bind与继承 待研究

    class a { f() { console.log('a') } get f2() { console.log('f2') return (this['f'] = this.f.bind(this ...

  6. week7

    catalog 1.面向对象 2.类的继承(续):直接继承与间接继承 3.类方法.静态方法.属性方法 4.getitem 5.反射 6._new_\_metaclass_ 7.异常处理 1.面向对象 ...

  7. 小白的python之路Linux部分10/27&28

     用户 创建流程模拟 总代码 [root@localhost ~]# vim /etc/passwd #1 [root@localhost ~]# mkdir /home/rose [root@loc ...

  8. 常用正则表达式 c#

    /// <summary> /// 是否手机号 /// </summary> /// <param name="str"></param& ...

  9. webix的Form绑定支持数组Array

    绑定的原理 form.setValues:把树形对象,压平展开成一维的.比如: var data = { id: 11, name: { first: 'alex', last: 'wu' } }; ...

  10. 输入系统:进程间双向通信(socketpair+binder)

    一.双向通信(socketpair) socketpair()函数用于创建一对无名的.相互连接的套接子,如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1]:否则返回-1,错误码保存于e ...