面经连接池 - 集合框架(一)List 篇

List

List是Java非常常用的数据集合框架。List是有序的Collection(对映下标与插入顺序有关)

Java List一共有三个实现类:ArrayList、LinkedList、Vector(不常用,渐渐被取代)

 


一.ArrayList(数组、随机访问、不同步 线程不安全)

ArrayList 是最常用的List实现类,底层通过数组(是一串连续的内存地址)实现,所以它允许对元素通过下标快速随机访问get(int index)时间复杂度为O(1)(注意不是随机查找二分查找都需要O(logn)的时间复杂度),add(E e)、addAll(Collection<? extends E> c) 默认添加到数组末尾(时间复杂度为O(1))、remove(Object o)从列表中删除指定元素的第一个出现(如果存在)。时间复杂度为O(1)

数组的缺点就是每个元素之间不能有间隔,当从ArrayList 的中通过下标的方式插入或者删除,都需要对数组进行复制、移动代价比较高(时间复杂度为O(n))

源码解析:

    
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ...
     /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
    ...
}

默认初始容量:10

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

扩容机制:ArrayList比数组好用的重要原因时:数组大小不可变,ArrayList是可以扩容的。当数组不满足时需要将已有数组的数据复制到新的存储空间中(Array.copyOf)。

public void ensureCapacity(int minCapacity) 手动扩容

int newCapacity = (oldCapacity * 3)/2 + 1;(Java 1.8 以前)

int newCapacity = oldCapacity + (oldCapacity >> 1)(Java 1.8 以后)近似以1.5被扩容;

特别解析:

1. 为什么要以 1.5 倍扩容而不是 2.5、3倍呢 ?

我记得这是面试官有问过一个很有意思的问题 当时也没想出具体答案,当天查询也没得到个具体结果,后面渐渐有了思绪

因为一次扩容太大可能会浪费更多的内存(1.5倍最多浪费33%,而2.5倍最多会浪费60%,3.5倍则会浪费71%……)但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。所以1.5倍是个经验值,它既能满足性能需求,也不会造成很大的内存消耗。最大次数呢 扩容实验:

	public static void main(String[] args) {
	    int a = 10;
	    int count = 0;
	    while ((a = grow(a)) < Integer.MAX_VALUE && a > 0) {
	        count++;
	        System.out.println(a);
	    }
	    System.out.println(count);
	}

	private static int grow(int capacity) {
	    return capacity + (capacity >> 1);
	}
15
22
33
49
73
109
...
70091070
105136605
157704907
236557360
354836040
532254060
798381090
1197571635
1796357452
47

结论:大概是47次

2.add(E e)到扩容阈值 时间复杂度应该是O(n)那为何说其时间复杂度为O(1)呢?

这是因为到 扩容阈值时发生时间复杂度震荡 会有O(1)变为O(n)但是如果将单次的变化进行时间复杂度的均摊 平均到每次add(E e) 又会变成O(1)

3.为什么很多编程语言数组都从 0 开始? 

数组存储模型中 " 下标 " 最确切的定义是 " 偏移 ( offset ) " 如果用数组名 a 来表示首地址,a[0] 就代表偏移为0的位置 ,a[k] 就是偏移量为 k 个 type_size 的位置,所以计算 a[k] 的内存地址只需要用如下公式:                                                                             a[k]_address = base_address + k * type_size


但是,如果数组从1开始计数,那我们计算数组元素a[k]的内存地址就会变为:
a[k]_address = base_address + (k-1)*type_size

每次访问都多了一次减法操作,对于 CPU 而言 就是多了一次减法指令(当然更多的是由于历史原因 Java 等语言都效仿了 C 语言的设计)

二. Vector(数组、随机访问、同步线程安全)

Vector 与 ArrayList一样是通过数组实现,不同的是它支持线程同步,某一时刻只有一个线程能够写Vector(通常认为读操作不会引起线程安全),避免多线程同时写而引起的不一致,但同样因为有加锁的操作,所以它的访问速度比ArrayList慢。

源码解析:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    ...
     public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

    /**
     * Constructs an empty vector so that its internal data array
     * has size {@code 10} and its standard capacity increment is
     * zero.
     */
    public Vector() {
        this(10);
    }

    ...
    public synchronized void setSize(int newSize) {
        modCount++;
        if (newSize > elementCount) {
            ensureCapacityHelper(newSize);
        } else {
            for (int i = newSize ; i < elementCount ; i++) {
                elementData[i] = null;
            }
        }
        elementCount = newSize;
    }

    /**
     * Returns the current capacity of this vector.
     *
     * @return  the current capacity (the length of its internal
     *          data array, kept in the field {@code elementData}
     *          of this vector)
     */
    public synchronized int capacity() {
        return elementData.length;
    }

    /**
     * Returns the number of components in this vector.
     *
     * @return  the number of components in this vector
     */
    public synchronized int size() {
        return elementCount;
    }

    /**
     * Tests if this vector has no components.
     *
     * @return  {@code true} if and only if this vector has
     *          no components, that is, its size is zero;
     *          {@code false} otherwise.
     */
    public synchronized boolean isEmpty() {
        return elementCount == 0;
    }
    ...
}

三. LinkedList(双向链表)

LinkedList是用双向链表(可以是不连续的内存地址 通过引用指向即可)结构存储数据的,很适合数据的动态插入和删除(通常用于操作表头和表位元素 时间复杂度为O(1)),由于链表的性质没有下标 所以随机访问和遍历速度比较慢

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     * 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;
        size++;
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }
...
}

特别解析:

其实细心从源码上不知有没有发现ArrayList 和 Vector 都实现了RandomAccess接口而LinkedList却没有

public interface RandomAccess {
}

代码实现为空 它只是作为随机访问的标识而没有特殊作用

 

 

 

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值