集合是Java中非常重要而且基础的内容,因为任何数据必不可少的就是数据的存储。集合的作用就是以一定的方式组织、存储数据。下面说说ArrayList,只捡干货聊。

ArrayList特点

1、ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。

2、ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l),函数返回一个线程安全的ArrayList类,底层方法内使用synchronized同步块进行控制;也可以使用concurrent并发包下的CopyOnWriteArrayList类,底层数组直接使用关键字volatile。Vector则在操作数组的方法上加上了关键字synchronized。

3、ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

4、每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素, 其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前, 应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

ArrayList底层

对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析ArrayList的源代码:

1) 私有属性:

ArrayList定义只定义类两个私有属性:

     /**       * The array buffer into which the elements of the ArrayList are stored.       * The capacity of the ArrayList is the length of this array buffer.       */ private transient Object[] elementData;          /**       * The size of the ArrayList (the number of elements it contains).       *       * @serial       */ private int size;

很容易理解,elementData存储ArrayList内的元素(应该是堆内存中元素的引用,而不是实际的元素 ),size表示它包含的元素的数量。

有个关键字需要解释:transient。

Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。

有点抽象,看个例子应该能明白。

public class UserInfo implements Serializable {       private static final long serialVersionUID = 996890129747019948L;       private String name;       private transient String psw;          public UserInfo(String name, String psw) {           this.name = name;           this.psw = psw;       }          public String toString() {           return "name=" + name + ", psw=" + psw;       }   }      public class TestTransient {       public static void main(String[] args) {           UserInfo userInfo = new UserInfo("张三", "123456");           System.out.println(userInfo);           try {               // 序列化,被设置为transient的属性没有被序列化               ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(                       "UserInfo.out"));               o.writeObject(userInfo);               o.close();           } catch (Exception e) {               // TODO: handle exception               e.printStackTrace();           }           try {               // 重新读取内容               ObjectInputStream in = new ObjectInputStream(new FileInputStream(                       "UserInfo.out"));               UserInfo readUserInfo = (UserInfo) in.readObject();               //读取后psw的内容为null               System.out.println(readUserInfo.toString());           } catch (Exception e) {               // TODO: handle exception               e.printStackTrace();           }       }   }

被标记为transient的属性在对象被序列化的时候不会被保存。ntData数组被序列化。这是为什么?因为序列化ArrayList的时候,ArrayList里面的elementData未必是满的,比方说elementData有10的大小,但是我只用了其中的3个,那么是否有必要序列化整个elementData呢?显然没有这个必要,因此ArrayList中重写了writeObject方法:

    private void writeObject(java.io.ObjectOutputStream s)            throws java.io.IOException{        // Write out element count, and any hidden stuff        int expectedModCount = modCount;        s.defaultWriteObject();        // Write out array length        s.writeInt(elementData.length);        // Write out all elements in the proper order.        for (int i=0; i

每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化那些有的元素,这样:

加快了序列化的速度

减小了序列化之后的文件大小

这种做法也是值得学习、借鉴的一种思路。接着回到ArrayList的分析中......

2) 构造方法:
ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

    // ArrayList带容量大小的构造函数。    public ArrayList(int initialCapacity) {            super();            if (initialCapacity  c) {            elementData = c.toArray();            size = elementData.length;            if (elementData.getClass() != Object[].class)                elementData = Arrays.copyOf(elementData, size, Object[].class);        }

3) 元素存储与扩容:

ArrayList 提供了set(int index, E element)、add(E e)、add(int index, E element)、 addAll(Collection extends E> c)、 addAll(int index, Collection extends E> c)这些添加元素的方法。

20 // 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。21 public E set(int index, E element) {  22    RangeCheck(index);  23 24    E oldValue = (E) elementData[index];  25    elementData[index] = element;  26    return oldValue;  27 }    28 // 将指定的元素添加到此列表的尾部。29 public boolean add(E e) {  30    ensureCapacity(size + 1);   31    elementData[size++] = e;  32    return true;  33 }    34 // 将指定的元素插入此列表中的指定位置。35 // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。36 public void add(int index, E element) {  37    if (index > size || index  c) {  50    Object[] a = c.toArray();  51    int numNew = a.length;  52    ensureCapacity(size + numNew); // Increments modCount  53    System.arraycopy(a, 0, elementData, size, numNew);  54    size += numNew;  55    return numNew != 0;  56 }    57 // 从指定的位置开始,将指定collection中的所有元素插入到此列表中。58 public boolean addAll(int index, Collection extends E> c) {  59    if (index > size || index  0)  69      System.arraycopy(elementData, index, elementData, index + numNew, numMoved);  70 71    System.arraycopy(a, 0, elementData, index, numNew);  72    size += numNew;  73    return numNew != 0;     }

底层数组的大小不够了怎么办?答案就是扩容,这也就是为什么一直说ArrayList的底层是基于动态数组实现的原因,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容,扩容的代码就在ensureCapacity里面:

 public void ensureCapacity(int minCapacity) {    modCount++;    int oldCapacity = elementData.length;    if (minCapacity > oldCapacity) {        Object oldData[] = elementData;        int newCapacity = (oldCapacity * 3)/2 + 1;            if (newCapacity 

看到扩容的时候把元素组大小先乘以3,再除以2,最后加1。可能有些人要问为什么?我们可以想:

如果一次性扩容扩得太大,必然造成内存空间的浪费

如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比价耗费性能的一个操作

所以扩容扩多少,是JDK开发人员在时间、空间上做的一个权衡,提供出来的一个比较合理的数值。最后调用到的是Arrays的copyOf方法,将元素组里面的内容复制到新的数组里面去:

    public static  T[] copyOf(U[] original, int newLength, Class extends T[]> newType) {           T[] copy = ((Object)newType == (Object)Object[].class)               ? (T[]) new Object[newLength]               : (T[]) Array.newInstance(newType.getComponentType(), newLength);           System.arraycopy(original, 0, copy, 0,                            Math.min(original.length, newLength));           return copy;    }

数组复制拷贝尽量使用System.arrayCopy或Arrays.copyof()方法,效率更高。

ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:

public void trimToSize() {      modCount++;      int oldCapacity = elementData.length;      if (size 

由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length和size相同,节省空间。

4) 元素读取:

// 返回此列表中指定位置上的元素。public E get(int index) {      RangeCheck(index);      return (E) elementData[index];  }

5) 元素删除:

ArrayList提供了根据下标或者指定对象两种方式的删除功能。对于ArrayList来说,这两种删除的方法差不多,都是调用的下面一段代码:

    int numMoved = size - index - 1;    if (numMoved > 0)        System.arraycopy(elementData, index+1, elementData, index,                 numMoved);    elementData[--size] = null; // Let gc do its work

把指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一个位置,最后一个位置的元素指定为null,这样让gc可以去回收它。

ArrayList的优缺点

随机访问快。ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快

顺序添加快。ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已

删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

ArrayList和Vector区别

Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。ArrayList需要使用Collections.synchronizedList方法变成一个线程安全的List。

    List synchronizedList = Collections.synchronizedList(list);    synchronizedList.add("aaa");    synchronizedList.add("bbb");    for (int i = 0; i 

Vector是ArrayList的线程安全版本,其实现90%和ArrayList都完全一样

增长因子不同。ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。

Vector提供indexOf(obj, start)接口,ArrayList没有。

ArrayList转静态数组toArray

有两个转化为静态数组的toArray方法。

第一个,调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。

public Object[] toArray() {       return Arrays.copyOf(elementData, size);  }

第二个,如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将 elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。

public  T[] toArray(T[] a) {    if (a.length  size)        a[size] = null;    return a;}

Fail-Fast机制:
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。具体介绍请参考这篇文章HashMap中的Fail-Fast机制。

transient关键字的作用_ArrayList Vector (transient关键字)--JAVA成长之路相关推荐

  1. java static关键字的作用是什么_static关键字有什么作用

    今天主要学习下Java语言中的static关键字. static关键字的含义及使用场景 static是Java50个关键字之一.static关键字可以用来修饰代码块表示静态代码块,修饰成员变量表示全局 ...

  2. java关键字及其作用解释_java 常见关键字及其作用

    finalize:Object的一个方法,一般通过重写或者是显示调用来时实现垃圾回收: finally:异常处理机制中try,catch的从句,不管是否发生异常,其中的代码都要执行,常见的释放相关资源 ...

  3. c语言关键字extern作用,c语言extern关键字详解

    标签: 在c语言中代码的执行顺序是从上往下执行的,如果定义的函数或者变量在调用之后,那么调用的时候编译就会找不到需要使用的变量.这样就会出现错误,如下代码: #include int main(){ ...

  4. java arraylist下标从几开始_ArrayList——JAVA成长之路

    1.1.ArrayList概述 1)ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类. 2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capac ...

  5. 、简述global关键字的作用_详解static inline关键字

    详解static inline关键字 本文章为知乎用户 @徐yang哟 原创,禁止抄袭! 灵感来源 在查stm32的LL库部分函数的API时,有时会查到这种函数: __STATIC_INLINE vo ...

  6. c语言中ANSI标准的关键字,C语言中32个关键字详解

    C语言中32个关键字详解 由 ANSI 标准定义的 C 语言关键字共32个,根据关键字的作用,可以将关键字分为数据类型关键字和流程控制关键字两大类. 一.数据类型关键字 A 基本数据类型(5个) vo ...

  7. c语言的37个关键字,c语言的37个关键字都是什么

    满意答案 qekcs 2017.08.17 采纳率:52%    等级:9 已帮助:964人 关键字就是已被C语言本身使用,不能作其它用途使用的字.例如关键字不能用作变量名.函数名等 由ANSI标准定 ...

  8. c语言系统关键字6,C语言语法之关键字

    由ANSI标准定义的C语言关键字共32个: auto double int struct break else long switch case enum register typedef char ...

  9. 24.volatile关键字的作用、volatile原理、可见性、内存屏障、volatile性能、transient

    24.volatile关键字的作用 24.1.volatile原理 24.2.可见性 24.3.内存屏障 24.4.volatile性能 25.transient 24.volatile关键字的作用 ...

最新文章

  1. 有 Bug 不会调试 ? 这篇文章很详细 !
  2. 光遗传学离诺奖又双叒进一步!10天后谜底将揭晓 (盘点光遗传学近年来获奖记录)...
  3. python比c语言好学吗-学C语言好还是Python好?
  4. 程序员必须掌握的英语单词
  5. 用minGW编译ffmpeg(供替换opencv中引用的ffmpeg库)
  6. 2021 年前端趋势预测
  7. 1101: 逆序数字(函数专题)
  8. 【JEECG_3.7.1】列表多表头的设计
  9. 数据结构学习笔记:实现链表
  10. Java教程:Java continue语句详解
  11. 各种编译环境中如何为C++添加命令行参数(Command-line parameter)
  12. mysql xtrabackup_mysql备份
  13. 【NOIP2015】【Luogu2669】金币(模拟)
  14. Security+ 学习笔记36 嵌入式系统安全
  15. Python:SQLMap的工作流程
  16. 全网独家:LINUX登录桌面后,如何自动运行自己的应用程序
  17. java发送短信功能工具类及思路详解
  18. 【安全牛学习笔记】DNS区域传输、DNS字典爆破、DNS注册信息
  19. 对称、群论与魔术(三)——常见的几何对称性简介
  20. css波浪动画使用图片

热门文章

  1. nodejs 定时 mysql_nodejs 使用 mysql
  2. python wordpress自定义字段获取_WordPress 自定义字段(Custom Fields)详细介绍和使用...
  3. 【GIF动画+完整可运行源代码】C++实现 归并排序——十大经典排序算法之五
  4. 为什么HashMap要树化呢?
  5. html-loader无效,html-loader加载失败
  6. shell脚本详解(一)——Shell编程规范与变量
  7. 数据库下午怎么插入_2020/3/25 下午 数据库第四章 自连接(示例代码)
  8. horizon服务主要模块_OpenStack组件——Horizon Web界面管理服务
  9. filter函数的用法_这几个超牛函数,你的Excel里有没有?
  10. linux 添加用户_linux ---添加普通用户账号