【Java集合系列】---ArrayList
开篇前言--ArrayList中的基本方法
前面的博文中,小编主要简单介绍java集合的总体架构,在接下来的博文中,小编将详细介绍里面的各个类,通过demo、对比,来对java集合类进行更加深入的理解和认识,希望可以帮助有有需要的小伙伴们`(*∩_∩*)′,不足之处,还请小伙伴们多多指教哦`(*∩_∩*)′。今天这篇博文,小编主要介绍List接口中的ArrayList集合,ArrayList即数组列表,so,她肯定和数组有一定的关系,我们知道List集合的特征有两个,一个是有序;第二个List里面的集合可以重复,既然ArrayList实现了List接口,那么毫无疑问,她肯定也存在这两个特征,我们来看一个简单的demo。新建一个class,命名为ArrayListTest,编写相关代码,如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
运行效果如下所示:
以上是ArrayList的基本用法,接着,我们来修改代码,ArrayList可以添加重复的元素,我们来看下面的代码部分:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
String s4=(String)arrayList.get(3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
}
}
运行效果如下所示:
通过运行结果我们知道,说明第二个“java”已经添加进去了,我们发现,ArrayList是通过add进行添加的操作,通过get方法取出来;我们接着来看,size这个方法,返回列表中元素的个数;可是呢,取数据的时候一个一个取,非常的麻烦,所以,我们可以写一个循环,如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
String s4=(String)arrayList.get(3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
}
}
运行效果如下所示:
size方法,用于获取集合中元素的个数,接着我们来看Clear()方法,编写相关代码,如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
String s4=(String)arrayList.get(3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
arrayList.clear();
System.out.println(arrayList.size());
}
}
运行效果如下所示:
接着,我们来看一下isEmpty这个方法,这个方法是用来判断集合中是否有内容的一个方法,编写相关代码,如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
String s4=(String)arrayList.get(3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
arrayList.clear();
System.out.println(arrayList.isEmpty());
}
}
运行效果如下所示:
接着看,如何删除一个元素呢,remove,删除一个根据元素,我们可以根据索引删除,还可以根据具体的对象进行删除,编写相关代码,如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
String s4=(String)arrayList.get(3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
// arrayList.clear();
// System.out.println(arrayList.isEmpty());
arrayList.remove(0);
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
}
}
运行,如下所示:
除此之外,我们还可以根据索引进行删除,编写相关代码,如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
String s4=(String)arrayList.get(3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
// arrayList.clear();
// System.out.println(arrayList.isEmpty());
arrayList.remove(0);
arrayList.remove("java");
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
}
}
效果如下所示:
接着,我们添加两个元素,打印,indexOf某个对象的索引在哪个位置上,代码如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
arrayList.add("java");
String s1=(String) arrayList.get(0);
String s2=(String)arrayList.get(1);
String s3=(String)arrayList.get(2);
String s4=(String)arrayList.get(3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
// arrayList.clear();
// System.out.println(arrayList.isEmpty());
arrayList.remove(0);
arrayList.remove("java");
System.out.println("----------");
for(int i = 0 ;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
System.out.println("----------");
arrayList.add("aaa");
arrayList.add("bbb");
System.out.println(arrayList.indexOf("aaa"));
}
}
运行,效果如下所示:
接着,我们新建一个类ArrayListTest1,编写相关代码:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest1 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("hello");
list.add(new Integer(2));
String str=(String)list.get(0);
Integer in = (Integer)list.get(1);
System.out.println(str);
System.out.println(in);
}
}
运行效果如下所示:
ArrayList本身接收的是对象,取出来的时候我们需要把他转换成我们放进去的相应的类型。再来新建一个类ArrayListTest2,如何把集合转成数组呢?编写代码,如下所示:
package j2se.demo;
import java.util.ArrayList;
public class ArrayListTest2 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Integer(1));
list.add(new Integer(2));
list.add(new Integer(3));
list.add(new Integer(4));
list.add(new Integer(5));
list.add(new Integer(6));
/**
* 不能将Object[]转换成Integer[]
*/
Object[] in = list.toArray();
for(int i = 0 ;i<in.length;i++){
System.out.println(((Integer)in[i]).intValue());
}
}
}
运行如下所示:
ArrayList底层部分源码实现
前面介绍的都是ArrayList的基本方法,小伙伴们可以查询API文档,接着,我们来看ArrayList她自己本身是如何实现的,首先:
a、对于任何一个集合来说,集合中存放的是对象的引用,而不是对象本身。
b、ArrayList底层采用数组实现,当使用不带参数的构成方法生成ArrayList对象的时候,实际上会在底层生成一个长度为10的Object类型数组。简单来说ArrayList内部实现是数组。
c、如果增加的元素个数超过了10个,那么ArrayList底层会新生成一个数组,长度为原来数组的1.5倍,然后将原数组的内容复制到新数组中,并且后续增加的内容都会放到新数组当中,当新数组无法容纳增加的元素时,重复该过程。集合中不能放入原生数据类型,只能放置对象的引用,我们需要使用原生数据类型的包装类才能加入到集合当中。我们来看看ArrayList底层的部分源码:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
//设置arrayList默认容量
private static final int DEFAULT_CAPACITY = 10;
//空数组,当调用无参数构造函数的时候默认给个空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//这才是真正保存数据的数组
private transient Object[] elementData;
//arrayList的实际元素数量
private int size;
//构造方法传入默认的capacity 设置默认数组大小
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
//无参数构造方法默认为空数组
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
//构造方法传入一个Collection, 则将Collection里面的值copy到arrayList
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
//下面主要看看ArrayList 是如何将数组进行动态扩充实现add 和 remove
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//超出了数组可容纳的长度,需要进行动态扩展
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//这才是动态扩展的精髓,看到这个方法,ArrayList瞬间被打回原形
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//设置新数组的容量扩展为原来数组的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,
//不够就将数组长度设置为需要的长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//判断有没超过最大限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//将原来数组的值copy新数组中去, ArrayList的引用指向新数组
//这儿会新创建数组,如果数据量很大,重复的创建的数组,那么还是会影响效率,
//因此鼓励在合适的时候通过构造方法指定默认的capaticy大小
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
}
so,小伙伴们发现了没有,ArrayList 她骨子里面的本质就是数组, ArrayList就是对数组进行动态的扩展,其add, get , remove 等等操作就是对数组的操作。 ArrayList的一些特性都来源于数组:有序、元素可重复、插入慢、 索引快 等等一系列神马所谓的属性, 有没有一种被欺骗了的赶脚`(*∩_∩*)′。
ArrayList中的遍历
我们来看一下ArrayList中的遍历,ArrayList支持三种遍历方式。
第一种:通过迭代器遍历,即通过Iterator去遍历
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
第二种:随机访问,通过索引值去遍历,因为ArrayList实现了RandomAccess接口,so,她支持通过索引值去随机访问元素:
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}
第三种:for循环遍历,如下所示:
Integer value = null;
for (Integer integ:list) {
value = integ;
}
接着,我们通过一个demo比较一下这三种遍历方式:
package j2se.demo;
import java.util.*;
import java.util.concurrent.*;
/*
* @desc ArrayList遍历方式和效率的测试程序。
*
* @author 丁国华
*/
public class ArrayListRandomAccessTest {
public static void main(String[] args) {
List list = new ArrayList();
for (int i=0; i<100000; i++)
list.add(i);
iteratorThroughRandomAccess(list) ;
iteratorThroughIterator(list) ;
iteratorThroughFor2(list) ;
}
private static void isRandomAccessSupported(List list) {
if (list instanceof RandomAccess) {
System.out.println("RandomAccess implemented!");
} else {
System.out.println("RandomAccess not implemented!");
}
}
public static void iteratorThroughRandomAccess(List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for (int i=0; i<list.size(); i++) {
list.get(i);
}
endTime = System.currentTimeMillis();
long interval = endTime - startTime;
System.out.println("iteratorThroughRandomAccess:" + interval+" ms");
}
public static void iteratorThroughIterator(List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for(Iterator iter = list.iterator(); iter.hasNext(); ) {
iter.next();
}
endTime = System.currentTimeMillis();
long interval = endTime - startTime;
System.out.println("iteratorThroughIterator:" + interval+" ms");
}
public static void iteratorThroughFor2(List list) {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
for(Object obj:list);
endTime = System.currentTimeMillis();
long interval = endTime - startTime;
System.out.println("iteratorThroughFor2:" + interval+" ms");
}
}
运行效果,如下所示:
由此可见,遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低!
ArrayList的优缺点
接着,我们来看一下ArrayList的优缺点:
从上面的几个过程总结一下ArrayList的优缺点。ArrayList的优点如下:
a、ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找速度快;
b、ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已;
ArrayList的缺点:
a、删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
b、插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
因此,ArrayList比较适合顺序添加、随机访问的场景。在后面的博文中,小编将介绍LinkedList,等介绍完LinkedList之后,我们把她们两个放在一起进行对比。
ArrayList线程问题
再说ArrayList线程问题之前,我们需要了解一下,什么是线程安全?什么是线程不安全?线程安全就是多线程访问的时候,采用了加锁机制,当一个线程访问该类的某个数据的时候,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可以使用,不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。那么为什么ArrayList为什么线程不安全呢?我们知道ArrayList在添加一个元素的时候,需要两个步骤,第一步在Items[Size]的位置存放此元素,第二步增大Size的值,在单线程运行的情况,比如两个苹果两个人吃,一个人吃一个,这个是并发的,互不影响,但是如果一个人吃两个苹果,要保证同时吃完,就需要这个苹果吃一点,停下来再去吃一点另一个苹果,既然Add方法内部是需要分两步走,那就是线程A在0的位置赋了一个值,然后停下来去B线程ArrayList 0的位置又赋了一个值,其实是重复在一个位置赋值,然后回到A线程,执行Size增加,也就是ArrayList的大小增加了,原来Size是1,现在变成2,然后停下来继续执行线程B,又增加了一个空间位置,size大小就变成了3,结果就是0的位置有值,1和2的索引位置都没有值实际大小是3,跟想要的结果0和1赋不同的值,结果不对,so线程不安全。那么我们如何解决ArrayList中线程不安全的问题呢?
a、继承Arraylist,然后重写或按需求编写自己的方法,这些方法要写成synchronized,在这些synchronized的方法中调用ArrayList的方法。
b、List list = Collections.synchronizedList(new ArrayList());
小编寄语:该博文,小编主要介绍了List接口实现中的ArrayList集合,通过讲解ArrayList基本方法,加上相关的demo进行联系,接着介绍ArrayList的底层相关源码,满满的都是套路,有么有`(*∩_∩*)′,接着介绍了ArrayList集合中的遍历,ArrayList中的优缺点以及线程问题,ArrayList支持自动改变大小的功能,灵活的插入和删除元素,但是ArrayList跟一般的数组比起来,速度上稍逊风骚,今天的arrayList介绍到这里就要跟小伙伴们再见了,在下篇博文中小编就介绍LinkedList,尽请期待`(*∩_∩*)′!
【Java集合系列】---ArrayList的更多相关文章
- java集合系列—ArrayList
ArrayList是工作以来使用频率最高的集合类.以前上课老师说不知道用什么集合类就用ArrayList,好吧,后面就这样了. public class ArrayList<E> exte ...
- Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)
概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例 ...
- Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)
概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...
- Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和性能分析)
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- java集合系列——List集合之ArrayList介绍(二)
一:List概述 List是 java.util包下面的类,从<a href="http://blog.csdn.net/u010648555/article/details/5604 ...
- java集合系列之ArrayList源码分析
java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...
- 【转】Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)
概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...
- Java 集合系列目录(Category)
下面是最近总结的Java集合(JDK1.6.0_45)相关文章的目录. 01. Java 集合系列01之 总体框架 02. Java 集合系列02之 Collection架构 03. Java 集合系 ...
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
随机推荐
- Centos MySQL数据库迁移详细步骤
其实迁移数据库,一般用sql文件就行,把A服务器数据库的表结构和数据等等导出,然后导入到B服务器数据库, 但是这次数据文件过大,大约有40个G,使用命令行导入,效果不是很好,经常在执行过程中报错.卡死 ...
- Java高级篇(三)——JDBC数据库编程
JDBC是连接数据库和Java程序的桥梁,通过JDBC API可以方便地实现对各种主流数据库的操作.本篇将介绍一下如何使用JDBC操作数据库(以MySQL为例). 一.JDBC JDBC制定了统一访问 ...
- [ZooKeeper] 2 环境搭建
上一篇中我们介绍了 ZooKeeper 的一些基本概念,这篇我们讲一下 ZooKeeper 的环境搭建. ZooKeeper 安装模式 单机模式:ZooKeeper 运行在一台服务器上,适合测试环境: ...
- SSO-单点统一登录系统的设计与实现
本文主要基于web类应用展开讨论,提供的是一种通用机制和方法,所以不论何种技术栈都可进行相应的具体实现. 实现目标 可以在相同或跨域环境下完成各应用的统一登录/注销 方案原理 本质上是采用了web应用 ...
- C#中string的相关方法
下面的方法一般都有很多重载形式,作为初学者的我先把我用过的记录下来吧...以后用到其他的可以一点点添加: 直接上例子吧.先定义两个字符串str1,str2(不要吐槽命名==) string str1, ...
- Headless Chrome:服务端渲染JS站点的一个方案【中篇】【翻译】
接上篇 防止重新渲染 其实说不对客户端代码做任何修改是忽悠人的.在我们的Express 应用中,通过Puppteer加载页面,提供给客户端响应,但是这个过程是有一些问题的. js脚本在服务端的Head ...
- spring源码阅读(1)bean解析
public class Test { public static void main(String[] args) throws Exception { BeanFactory beanFactor ...
- 【NOIP2016】换教室
题目描述 对于刚上大学的牛牛来说, 他面临的第一个问题是如何根据实际情况中情合适的课程. 在可以选择的课程中,有2n节课程安排在n个时间段上.在第 i ( 1≤ i≤n)个时同段上, 两节内容相同的课 ...
- ubuntu16.04安装eclipse后启动栏图标为问号
ubuntu创建eclipse快捷方式图标. cd /usr/share/applications sudo touch eclipse.desktop sudo gedit eclipse.desk ...
- Python中模块之logging & subprocess的讲解
subprocess & logging模块的介绍 1. subprocess 该模块替代了os.system & os.pawn*所实现的功能. 2. logging 1. 日志五大 ...