教妹学 Java:大有可为的集合(2)

简介: 教妹学 Java:大有可为的集合

ArrayList 其实是一个动态数组,来看源码。

public class ArrayList<E>
{
     /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
}


1)elementData 是 Object 类型的数组,用来保存添加到 ArrayList 中的元素。如果通过默认构造参数创建 ArrayList 对象时,elementData 的默认大小是 10。当 ArrayList 容量不足以容纳全部元素时,就会重新设置容量,新的容量 = 原始容量 + (原始容量 >> 1)(参照以下代码)。


private void grow(int minCapacity) {

   // overflow-conscious code

   int oldCapacity = elementData.length;

   int newCapacity = oldCapacity + (oldCapacity >> 1);

   elementData = Arrays.copyOf(elementData, newCapacity);

}


>> 运算符还没有驾驭了。不过,通过代码测试后的结论是,当原始容量为 10 的时候,新的容量为 15;当原始容量为 20 的时候,新的容量为 30。


2) size 是 ArrayList 的元素个数。当往 ArrayList 添加一个元素时,size+1,删除一个元素时,size-1。


由于 LinkedList 和 ArrayList 底层实现的不同(一个双向链表,一个动态数组),它们之间的区别也很一目了然。


关键点1 :LinkedList 在添加(add(E e))、插入(add(int index, E element))、删除(remove(int index))元素的性能上远超 ArrayList。


为什么呢?先来看 ArrayList 的相关源码。


// ensureCapacityInternal() 方法内部会调用 System.arraycopy()
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
public void add(int index, E element) {
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
public E remove(int index) {
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}


观察 ArrayList 的源码,就能够发现,ArrayList 在添加、插入、删除元素的时候,会有意或者无意(扩容)的调用 System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 方法,该方法对性能的损耗是非常严重的。


再来看 LinkedList 的相关源码。


/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
}
/**
 * Unlinks non-null node x.
 */
E unlink(Node<E> x) {
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
    x.item = null;
    return element;
}


LinkedList 不存在扩容的问题,也不需要对原有的元素进行复制;只需要改变节点的数据就好了。


关键点2:LinkedList 在查找元素时要慢于 ArrayList。


为什么呢?先来看 LinkedList 的相关源码。

/**
 * Returns the (non-null) Node at the specified element index.
 */
Node<E> node(int index) {
    // assert isElementIndex(index);
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}


观察 LinkedList 的源码,就能够发现, LinkedList 在定位 index 的时候会先判断位置(是在 1 / 2 的前面还是后面),再从前往后或者从后往前执行 for 循环依次找。


再来看 ArrayList 的相关源码。


@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}


ArrayList 直接根据 index 从数组中取出该位置上的元素,不需要 for 循环遍历啊——这样显然更快!


相关文章
|
15天前
|
安全 Java 开发者
【JAVA】哪些集合类是线程安全的
【JAVA】哪些集合类是线程安全的
|
15天前
|
Java
【JAVA】怎么确保一个集合不能被修改
【JAVA】怎么确保一个集合不能被修改
|
1天前
|
存储 安全 Java
Java一分钟之-集合框架进阶:Set接口与HashSet
【5月更文挑战第10天】本文介绍了Java集合框架中的`Set`接口和`HashSet`类。`Set`接口继承自`Collection`,特征是不允许重复元素,顺序不确定。`HashSet`是`Set`的实现,基于哈希表,提供快速添加、删除和查找操作,但无序且非线程安全。文章讨论了`HashSet`的特性、常见问题(如元素比较规则、非唯一性和线程安全性)以及如何避免这些问题,并提供了代码示例展示基本操作和自定义对象的使用。理解这些概念和注意事项能提升代码效率和可维护性。
9 0
|
1天前
|
存储 安全 算法
Java一分钟之-Java集合框架入门:List接口与ArrayList
【5月更文挑战第10天】本文介绍了Java集合框架中的`List`接口和`ArrayList`实现类。`List`是有序集合,支持元素重复并能按索引访问。核心方法包括添加、删除、获取和设置元素。`ArrayList`基于动态数组,提供高效随机访问和自动扩容,但非线程安全。文章讨论了三个常见问题:索引越界、遍历时修改集合和并发修改,并给出避免策略。通过示例代码展示了基本操作和安全遍历删除。理解并正确使用`List`和`ArrayList`能提升程序效率和稳定性。
7 0
|
3天前
|
存储 安全 算法
掌握Java并发编程:Lock、Condition与并发集合
掌握Java并发编程:Lock、Condition与并发集合
11 0
|
3天前
|
存储 安全 Java
深入理解Java集合框架
深入理解Java集合框架
9 0
|
9天前
|
存储 安全 Java
Java集合的分类有哪些?
Java中的集合就像一个容器,专门用来存储Java对象,这些对象可以是任意的数据类型,并且长度可变。这些集合类都位于java.util包中,在使用时一定要注意导包的问题,否则会出现异常。
36 10
|
12天前
|
安全 Java
循环的时候去删除集合中的元素 java.util.ConcurrentModificationException
循环的时候去删除集合中的元素 java.util.ConcurrentModificationException
|
13天前
|
Java
【专栏】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性
【4月更文挑战第28天】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性。本文介绍了 Streams 的基本概念,如从数据源创建 Stream,以及中间和终端操作。通过过滤、映射、归并、排序、分组等案例,展示了 Streams 的使用,包括并行 Streams 提高效率。学习 Streams 可以提升代码质量和效率,文章鼓励读者在实际开发中探索更多 Streams 功能。
|
15天前
|
Java API 开发者
【专栏】Java 8的Stream API是处理集合数据的新方式,强调简洁和声明式编程
【4月更文挑战第27天】Java 8的Stream API是处理集合数据的新方式,强调简洁和声明式编程。它基于延迟执行和惰性求值,提供创建、中间操作(如filter、map)和终端操作(如forEach、collect)。示例展示了如何通过Stream排序、过滤、映射和聚合数据。