浅析 java ArrayList
浅析 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; // 新的数组地址
}
从这块的代码分析过来,可以发现有如下两种行为:
频繁内存申请和数据拷贝:
如果list频繁的插入数据会让这个list不断的重新分配数组空间,并重新拷贝数据。这个时候就考虑给一个不错的ArrayList 数组默认长度或者考虑其他更好的更适合场景的数据容器。
线程安全、并发问题
观察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));
}
}
从代码中可以得出如下结论:
null值
ArrayList中允许直接存储null值的
线程安全、并发问题
因为他是直接对ArrayList对应位置置为null,如果有多个线程访问可能存在数据安全性问题。(跟上述说的问题是一样的)
内存问题
上面说到他的内存随着数据不断插入会不断的去申请内存块,但是从这里的这块代码中可以发现,如果内存已经被分配的情况下是不会随着元素的递减(删除)而收缩内存的。
按对象删除元素
如果是按对象从ArrayList中删除元素,会从开始位置找到第一个跟删除对象匹配的值并删除,如果ArrayList中存在多个相同的对象时需要考虑清楚是否是删除你想删除的对象哦。
优缺点
优点:读取速度快
缺点:插入慢,非线程安全
总结
在使用ArrayList的时候,脑海里必须清晰好自己的场景是否会涉及到并发问题。其次要清晰的了解到数组的优缺点。因为它就是数组的实现
浅析 java ArrayList的更多相关文章
- jdk 1.8下 java ArrayList 添加元素解析
转载请注明http://www.cnblogs.com/majianming/p/8006452.html 有人问我,java ArrayList底层是怎么实现的?我就回答数组,他再问我,那它是怎么实 ...
- 浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 浅析Java.lang.Process类
一.概述 Process类是一个抽象类(所有的方法均是抽象的),封装了一个进程(即一个执行程序). Process 类提供了执行从进程输入.执行输出到进程.等待进程完成.检查进程的 ...
- 浅析Java中的访问权限控制
浅析Java中的访问权限控制 今天我们来一起了解一下Java语言中的访问权限控制.在讨论访问权限控制之前,先来讨论一下为何需要访问权限控制.考虑两个场景: 场景1:工程师A编写了一个类ClassA,但 ...
- [转载]浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 浅析JAVA设计模式之工厂模式(一)
1 工厂模式简单介绍 工厂模式的定义:简单地说,用来实例化对象,取代new操作. 工厂模式专门负责将大量有共同接口的类实例化.工作模式能够动态决定将哪一个类实例化.不用先知道每次要实例化哪一个类. 工 ...
- Java ArrayList、Vector和LinkedList等的差别与用法(转)
Java ArrayList.Vector和LinkedList等的差别与用法(转) ArrayList 和Vector是采取数组体式格式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,都 ...
- 浅析java内存管理机制
内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分.不同的编程语言有不同的内存管理机制,本文在对比C++和Java语言内存管理机制的不同的基础上,浅析java中 ...
- 【转】浅析Java中的final关键字
谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...
随机推荐
- linux上docker安装centos7.2
1.安装 docker pull centos:7.2.1511 2.启动镜像 docker run -d -i -t <IMAGE ID> /bin/bash 3.进入容器 docker ...
- Layui追加合计
parseData: function(res) { //将原始数据解析成table组件所规定的数据 admin.restest(res); var list = new Array(); var t ...
- js将手机号中间四位变成*号
方法一.利用数组splice,split,join方法 var tel = 18810399133; tel = "" + tel; var ary = tel.split(&qu ...
- C#UDP广域网,局域网通信-原理分析
一.UDP局域网通信. 这个比较简单,关于局域网中的2台或者更多的计算机之间的UDP通信,网络上一大把,直接复制粘贴就可以使用,原理也非常简单.所以,本文不做详细介绍. 二.UDP广域通信(包括路由器 ...
- Redis-安装篇
Redis Cluster搭建 需求:创建6个节点,3主3从127.0.0.1:6379127.0.0.1:6380127.0.0.1:6381127.0.0.1:6382127.0.0.1:6383 ...
- win10系统开机输入密码黑屏解决方法
方法一: 第一步:首先打开笔记本电脑开机,输入开机密码进入 第二步:输入密码后显示黑屏,按"Ctrl+Alt+Del"组合键打开任务管理器 第三步:在打开的任务管理器中,点击&qu ...
- Shell编程实战
Shell编程实战 为什么要学习Shell编程 Shell脚本语言是实现Linux/Unix系统管理及自动化运维所必须的重要工具,Linux系统的底层以及基础应用软件的核心大都涉及Shell脚本的 ...
- Vscode下的Markdown的基本使用
1.Vscode默认支持Markdown语法,只需要安装相应的扩展插件,Markdown Preview enhanced. 2.安装完插件后,在Vscode上新建一个文件,然后将文件的语言模式设置为 ...
- Java_jsp.jstl.Function函数标签库.记录
JSTL Functions标签库 本书第12章的12.3节(定义和使用EL函数)介绍了EL函数的创建和使用方法.在JSTL Functions标签库中提供了一组常用的EL函数,主要用于处理字符串,在 ...
- java实现链栈
package linkstack; /** * Created by Administrator on 2019/4/18. */ public class LinkStack { private ...