【Java集合系列】---ArrayList

简介: 开篇前言--ArrayList中的基本方法 前面的博文中,小编主要简单介绍java集合的总体架构,在接下来的博文中,小编将详细介绍里面的各个类,通过demo、对比,来对java集合类进行更加深入的理解和认识,希望可以帮助有有需要的小伙伴们`(*∩_∩*)′,不足之处,还请小伙伴们多多指教哦`(*∩_∩*)′。

开篇前言--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线程问题之前,我们需要了解一下,什么是线程安全?什么是线程不安全?线程安全就是多线程访问的时候,采用了加锁机制,当一个线程访问该类的某个数据的时候,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可以使用,不会出现数据不一致或者数据污染。

目录
相关文章
|
15天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
33 6
|
15天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
33 3
|
15天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
26 2
|
17天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
25 3
|
3天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
12天前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
41 5
|
13天前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
40 3
|
15天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
【10月更文挑战第16天】Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。通过 hashCode() 和 equals() 方法实现唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 添加和遍历元素,体现了 Set 的高效性和简洁性。
21 4
|
17天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。它通过 hashCode() 和 equals() 方法确保元素唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 实现这一特性。
22 5
|
15天前
|
Java 开发者
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素。通过哈希表和红黑树两种模式,Set能够高效地识别并拒绝重复元素的入侵,确保集合的纯净。无论是HashSet还是TreeSet,都能在不同的场景下发挥出色的表现,成为开发者手中的利器。
24 2