栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

007Java集合002详解ArrayList

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

007Java集合002详解ArrayList

注意:本文基于JDK1.8进行记录。

1 简介

最常用的集合,允许任何符合规则的元素插入,包括null和重复元素。

底层是数组结构,提供了索引机制,查找效率高,增删效率低。

线程不安全。

2 扩容机制

数组结构会有容量的概念,ArrayList的初始容量默认为10,负载因子是1,表示当插入元素后个数超出原有长度时会进行扩增,扩容增量是0.5,所以扩增后容量为1.5倍,可使用方法手动扩容和缩减。

最好指定初始容量值,避免过多的进行扩容操作而浪费时间和效率。

3 方法说明 3.1 构造方法
// 空参构造器,返回默认容量为10的集合。
public ArrayList();
// 指定长度的构造器,如果长度为0,则返回容量为0的集合。
public ArrayList(int initialCapacity);
// 传入了一个集合的构造器,如果集合长度为0,返回容量为0的集合。
public ArrayList(Collection c);
3.2 常用方法
// 获取个数。
public int size();
// 判断是否为空。
public boolean isEmpty();
// 判断是否包含指定数据。
public boolean contains(Object o);
// 计算指定数据首次出现的下标。
public int indexOf(Object o);
// 计算指定数据最后出现的下标。
public int lastIndexOf(Object o);
// 获取指定下标的元素。
public E get(int index);
// 设置指定下标的指定元素,并返回旧元素。
public E set(int index, E element);
// 添加元素,并返回是否成功。
public boolean add(E e);
// 在指定位置添加指定元素。
public void add(int index, E element);
// 删除指定位置的元素,并返回删除的元素。
public E remove(int index);
// 删除元素,并返回是否成功。
public boolean remove(Object o);
4 源码分析 4.1 属性
// 默认的初始容量为10。
private static final int DEFAULT_CAPACITY = 10;
// 空数组。
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认容量的空数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 数组。
transient Object[] elementData;
// 元素个数。
private int size;
4.2 构造方法
// 空参构造器,返回默认容量为10的集合。
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 指定长度的构造器,如果长度为0,则返回容量为0的集合。
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}
// 传入了一个集合的构造器,如果集合长度为0,返回容量为0的集合。
public ArrayList(Collection c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // Object[]数组里的类型不一定都是Object类型的,有可能是Object的子类,所以要判断一下。
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
4.3 常用方法
// 获取个数。
public int size() {
    return size;
}
// 判断是否为空。
public boolean isEmpty() {
    return size == 0;
}
// 判断是否包含指定数据。
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
// 计算指定数据首次出现的下标。
public int indexOf(Object o) {
    // 如果指定数据为null。
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    // 如果指定数据不为null。
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}
// 计算指定数据最后出现的下标。
public int lastIndexOf(Object o) {
    // 如果指定数据为null。
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    // 如果指定数据不为null。
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}
// 获取指定下标的元素。
public E get(int index) {
    // 检查指定下标是否越界。
    rangeCheck(index);
    // 返回指定下标的元素。
    return elementData(index);
}
// 设置指定下标的指定元素,并返回旧元素。
public E set(int index, E element) {
    // 检查指定下标是否越界。
    rangeCheck(index);
    // 设置指定元素并返回旧元素。
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
// 添加元素,并返回是否成功。
public boolean add(E e) {
    // 扩容并增加操作数。
    ensureCapacityInternal(size + 1);
    // 元素个数增加并设置指定元素。
    elementData[size++] = e;
    // 返回成功。
    return true;
}
// 在指定位置添加指定元素。
public void add(int index, E element) {
    // 检查指定下标是否越界。
    rangeCheckForAdd(index);
    // 扩容并增加操作数。
    ensureCapacityInternal(size + 1);
    // 拷贝数组并设置元素,增加元素个数。
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}
// 删除指定位置的元素,并返回删除的元素。
public E remove(int index) {
    // 检查指定下标是否越界。
    rangeCheck(index);
    // 操作数自增。
    modCount++;
    // 获取指定下标的元素。
    E oldValue = elementData(index);
    // 需要移动的元素个数。
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    // 将最后一个元素设置为null并减少容量大小。
    elementData[--size] = null;
    // 返回指定下标的元素。
    return oldValue;
}
// 删除元素,并返回是否成功。
public boolean remove(Object o) {
    // 删除null对象
    if (o == null) {
        for (int index = 0; index < size; index++)
            // 找到并删除null对象
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    // 删除非null对象
    } else {
        for (int index = 0; index < size; index++)
            // 找到并删除非null对象
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
// 快速删除指定位置元素,不检查下标,不返回数据。
private void fastRemove(int index) {
    // 操作数自增。
    modCount++;
    // 需要移动的元素个数。
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    // 将最后一个元素设置为null并减少容量大小。
    elementData[--size] = null;
}
4.4 扩容方法
// 对集合进行扩容。
public void ensureCapacity(int minCapacity) {
    // 如果集合不为空,则设置扩充值为0,如果为空,则设置扩充值为10。
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
    // 如果指定容量大于扩充值,则进行扩容。
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}
// 对集合进行扩容。
private void ensureCapacityInternal(int minCapacity) {
    // 如果集合为空,则在默认大小和最小值之间取最大的。
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 进行扩容。
    ensureExplicitCapacity(minCapacity);
}
// 对集合进行扩容,实际操作。
private void ensureExplicitCapacity(int minCapacity) {
    // 操作数自增。
    modCount++;
    // 如果最小值大于数组长度,则进行扩容。
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
// 数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容计算。
private void grow(int minCapacity) {
    // 获取原容量大小。
    int oldCapacity = elementData.length;
    // 右移一位,变为原来的0.5倍,然后加上原容量,扩容后变为1.5倍。
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果扩容后小于最小值,则设置容量为最小值。
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果扩容后大于最大值,则进一步设置容量。
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 复制数组。
    elementData = Arrays.copyOf(elementData, newCapacity);
}
// 扩容后容量过大的处理方法。
private static int hugeCapacity(int minCapacity) {
    // 如果最小值小于零,则表示溢出,抛出内存溢出异常。
    if (minCapacity < 0)
        throw new OutOfMemoryError();
    // 如果扩容后的值大于最大值,则使用Integer的最大值,否则就使用最大值。
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
4.5 序列化方法
// 序列化
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    int expectedModCount = modCount;
    s.defaultWriteObject();
    s.writeInt(size);
    for (int i=0; i 0) {
        ensureCapacityInternal(size);
        Object[] a = elementData;
        for (int i=0; i 
4.6 遍历方法 

ArrayList提供了两种迭代器,实现了Iterator接口的Itr类,以及实现了ListIterator接口并继承了Itr类的ListItr类。

ListItr类对Itr类的方法进行了扩展,提供了添加和修改的方法,这里只学习Itr类。

// 获取Iterator迭代器。
public Iterator iterator() {
    return new Itr();
}
// Iterator内部类,实现了Iterator接口。
private class Itr implements Iterator {
    // 下一个元素的位置。
    int cursor;
    // 最后一个元素的位置。
    int lastRet = -1;
    // 预期的操作数。
    int expectedModCount = modCount;
    // 是否存在下一个元素。
    public boolean hasNext() {
        return cursor != size;
    }
    // 获取下一个元素。
    public E next() {
        // 校验预期操作数和实际操作数。
        checkForComodification();
        // 将下一个元素的位置赋值给i。
        int i = cursor;
        // 如果i大于或等于集合大小,说明没有元素了,抛出异常。
        if (i >= size)
            throw new NoSuchElementException();
        // 获取集合数组。
        Object[] elementData = ArrayList.this.elementData;
        // 如果i大于或等于数组长度,说明有其他线程改过了,抛出异常。
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        // 下一个元素位置加一。
        cursor = i + 1;
        // 获取元素并返回,设置最后一个元素位置。
        return (E) elementData[lastRet = i];
    }
    // 删除当前元素。
    public void remove() {
        // 如果当前位置小于零,抛出异常。
        if (lastRet < 0)
            throw new IllegalStateException();
        // 校验预期操作数和实际操作数。
        checkForComodification();
        try {
            // 移除当前位置上的元素。
            ArrayList.this.remove(lastRet);
            // 将当前位置赋值给下一个元素位置。
            cursor = lastRet;
            // 将当前位置设置为-1,表示没有此位置。
            lastRet = -1;
            // 同步预期操作数和操作数。
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    // 循环遍历一次,JDK1.8新增方法。
    @Override
    public void forEachRemaining(Consumer consumer) {
        // 判断非空。
        Objects.requireNonNull(consumer);
        // 集合容量大小。
        final int size = ArrayList.this.size;
        // 将下一个元素的位置赋值给i。
        int i = cursor;
        // 如果i大于或等于集合个数,则返回。
        if (i >= size) {
            return;
        }
        // 获取集合数组。
        final Object[] elementData = ArrayList.this.elementData;
        // 如果i大于或等于数组长度,说明有其他线程改过了,抛出异常。
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        // 循环遍历,当下一个元素不为集合的大小,并且预期操作数和实际操作数相等。
        while (i != size && modCount == expectedModCount) {
            // 执行方法,并且每次循环i加一。
            consumer.accept((E) elementData[i++]);
        }
        // 将循环后的i赋值,导致下个元素的位置和集合长度相等,该迭代器不能再次遍历集合了。
        cursor = i;
        // 设置最后一个元素位置。
        lastRet = i - 1;
        // 校验预期操作数和实际操作数。
        checkForComodification();
    }
    // 校验预期操作数和实际操作数。
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
5 补充说明 5.1 数组没有private

查看ArrayList的源码,发现内部定义的elementData数组并没有像size一样被private修饰,表示访问权限是默认的包权限,这么做的原因被写在了声明后面的注释里,即非私有的访问权限是为了简化嵌套类的访问。

如果想知道如何简化嵌套类的访问,就需要写一个例子并反编译进一步查看。

代码如下:

public class DemoTest {
    private int i = 1;
    public Inner inner = new Inner();
    class Inner {
        public void in() {
            i = 5;
        }
    }
    public static void main(String[] args) {
        DemoTest demoTest = new DemoTest();
        demoTest.inner.in();
        System.out.println(demoTest.i);
    }
}

反编译private修饰的代码:

// 在外部类的汇编代码中,多了静态方法:
static int access$002(com.demo.mp.test.DemoTest, int);
  Code:
     0: aload_0
     1: iload_1
     2: dup_x1
     3: putfield      #1                  // Field i:I
     6: ireturn
  LineNumberTable:
    line 3: 0
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0       7     0    x0   Lcom/demo/mp/test/DemoTest;
        0       7     1    x1   I
// 内部类的汇编代码中,在方法中引用外部类变量的时候,是通过静态方法设置的:
public void in();
  Code:
     0: aload_0
     1: getfield      #1                  // Field this$0:Lcom/demo/mp/test/DemoTest;
     4: iconst_5
     // 通过静态方法设置。
     5: invokestatic  #3                  // Method com/demo/mp/test/DemoTest.access$002:(Lcom/demo/mp/test/DemoTest;I)I
     8: pop
     9: return
  LineNumberTable:
    line 8: 0
    line 9: 9
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0      10     0  this   Lcom/demo/mp/test/DemoTest$Inner;

反编译没有private修饰的代码:

// 内部类的汇编代码中,在方法中引用外部类变量的时候,是通过属性直接设置的:
public void in();
  Code:
     0: aload_0
     1: getfield      #1                  // Field this$0:Lcom/demo/mp/test/DemoTest;
     4: iconst_5
     // 通过属性直接设置。
     5: putfield      #3                  // Field com/demo/mp/test/DemoTest.i:I
     8: return
  LineNumberTable:
    line 8: 0
    line 9: 8
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0       9     0  this   Lcom/demo/mp/test/DemoTest$Inner;

因此,去掉private修饰简化嵌套类的访问。

5.2 数组使用transient

查看ArrayList的源码,发现内部定义的elementData数组使用了transient修饰,transient表示该数组不参与序列化。

那ArrayList是如何实现序列化的呢?

这就涉及到了序列化的基础原理了,之前在学习输入输出流的时候,有一种对象流,主要的类是ObjectOutputStream和ObjectInputStream。

ObjectOutputStream类是用于序列化的输出流,ObjectInputStream类是用于反序列化的输入流。

在序列化和反序列化的时候,JVM会调用对象里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有提供,那就调用ObjectOutputStream类的defaultWriteObject方法和ObjectInputStream类的defaultReadObject方法,进行默认的序列化和反序列化,这时就会忽略transient修饰的成员。

因此,在ArrayList内部自定义了writeObject和readObject方法,自定义序列化和反序列化,这么做是为了处理实际存储的元素,避免处理整个数组。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/273906.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号