0004.Java学习—集合(List包含源码分析)

0004.Java学习—集合List包含源码分析

概述

由于数组固定长度的局限,在不确定固定尺寸的情况下,可以使用集合来解决,集合可以自动调整大小。
其中基本的类型包括List、Set、Queue、Map,这些类型统称为容器类,用于存储和获取数据。

  • Collection:一个独立元素的序列。List必须以插入的顺序保存元素,Set不能包含重复元素

  • Queue按照排队规则来确定对象产生的顺序

  • Map:一组成对的"键值对"对象,允许使用键来查找值
    在这里插入图片描述

     												上图为集合的简图
    

注:
1.绿色表示接口,橙色表示抽象类,蓝色表示具体类
2.虚线表示实现关系,实线表示继承关系

Iterator集合

public interface Iterator<E> {
    // 如果还有可以迭代的元素, 返回true
    boolean hasNext();
    // 返回当前集合中迭代的下一个元素
    E next();
    // 方法将迭代器最近返回的元素删除, 如正常删除当前元素, 返回true, 否则返回false
    default void remove() {
    throw new UnsupportedOperationException("remove");
    }
}

// 例
public static void main(String[] args) {

    Collection<String> strArr = new ArrayList<>();
    Collection<String> strSet = new HashSet<>();
    strArr.addAll(Arrays.asList("Hadoop", "Spark", "Flink", "Zookeeper"));
    Iterator<String> strArrIterator = strArr.iterator();
    Iterator<String> strSetIterator = strArr.iterator();

    // 1. 使用hasNext()和next()方法
    while (strArrIterator.hasNext()) {
        System.out.print(strArrIterator.next() + "\t");
        strArrIterator.remove();
    }

    // 2. 使用for-in语法遍历
    for (String str : strArr) {
        System.out.print(str + "\t");
        strArr.remove(str);
    }
    
    // 不同集合调用统一方法disPlay()
    // 迭代器能够遍历序列的操作与该序列的底层结构分离
    disPlay(strArrIterator);
    disPlay(strSetIterator);

    System.out.println("");
    System.out.println(strArr.size());
    System.out.println(strArrIterator.hasNext());
}


public static void disPlay(Iterator<String> iterator) {

    while (iterator.hasNext()) {
        System.out.print(iterator.next() + "\t");
        iterator.remove();
    }
}

// Output: 
// Hadoop    Spark    Flink    Zookeeper    
// 0
// false

1.通常情况下当我们去使用集合的时候,必须知道集合的数据类型,但是使用迭代器不需要知道数据类型和数据的数量,
2.上例中
   (1) 使用了 hasNext() 和 next() 两个方法
   (2) 使用了for-in语法
3.Iterator在调用remove()方法之前,必须先调用next()方法
4.迭代器是一个对象,它在一个序列中移动并选择该序列中的每一个对象。
5.迭代器能够遍历序列的操作与该序列的底层结构分离,迭代器统一了对集合的访问方式

列表List

  • List继承了Collection接口, 并添加了一些方法,可以允许在List中间插入和删除元素
  • List的两个特点是
      1、有序(存储和取出元素的顺序相同)
      2、存储元素可以重复
  • List分为两种:
      1、 ArrayList: 优势是随机访问元素快, 但在List中间插入和删除元素速度较慢
      2、LinkedList: 在List中间进行的插入和删除操作快, 但是随机访问较慢

ArrayList

ArrayList的三种创建方法

transient Object[] elementData;
private static final Object[] EMPTY_ELEMENTDATA = {};

// 创建ArrayList的三种方法
// 第一种传入一个初始容量
public ArrayList(int initialCapacity) {
    // 判断初始容量的值
    // 如果init > 0, 则创建init大小的Object数组
    // 如果init == 0, 则创建一个空Object数组
    // 其他情况下报错, 初始容量为负值异常
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}
// 第二种传入一个Collection对象
// 可以传入数据类型为Object类型或者其子类
public ArrayList(Collection<? extends E> c) {
    // 将Collection集合转换成Object数组
    elementData = c.toArray();
    // 分别判断size大小是否 > 0
    // 如果等于0, 则创建一个空Object数组
    // 如果不等于0, 则判断c.toArray()是否生成了Object数组, 如果是, 则生成一个新的数组
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

// 第三种没有参数, new一个无参构造器, 创建一个空的Object数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//下面方法是Arrays.copyOf()
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    // 下面是三元运算
    // 判断newType是否是Object数组
    // 如果是, 则创建一个newLength大小的Object数组
    // 如果不是, 则返回当前newType类型并创建一个Object类型
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    //从指定的源数组——original的指定位置, 开始复制到目标数组的指定位置
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

public static Object newInstance(Class<?> componentType, int length)
    throws NegativeArraySizeException {
    return newArray(componentType, length);
}

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

ArrayList常见的方法解析

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 1. 参数是Object类型
public boolean add(E e) {
    // 判断当前容量与传入的最小容量(默认为10)的大小
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将数据插入到Object数组中
    elementData[size++] = e;
    return true;
}
// 判断最小容量
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 计算容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 判断elementData是否为空Object数组, 如果是, 则返回默认容量(大小为10) 与 传参的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 如果不为空, 则直接返回最小容量的值size + 1 
    return minCapacity;
    } 

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    // 增加容量, 至少能保证为默认最小容量参数指定的元素数量
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 2. 插入固定索引位置
public void add(int index, E element) {
    // 判断index是否在0到size之间, 如不是报错
    rangeCheckForAdd(index);

        // 判断当前容量与传入的最小容量(默认为10)的大小
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 插入到对应的位置上
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    将当前索引内容换成待插入的数据
    elementData[index] = element;
    size++;
}
// 3. 插入Collection对象(与add大同小异)
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    // 如果c不为空, 则返回true, 否则返回false插入失败
    return numNew != 0;
}
// 4. 删除数据
public E remove(int index) {
    // 判断index是否小于等于集合的size
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    // numMoved是需要移动的个数, size - (index + 1)
    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;
}
// 5. 获取数据
public E get(int index) {
    // 判断index是否小于集合大小, 如果大于则报错
    rangeCheck(index);
    // 直接获取集合索引所在数据
    return elementData(index);
}
// 6. 判断集合是否为空, 即为判断size是否为0
public boolean isEmpty() {
    return size == 0;
}
// 7. 获取集合大小
public int size() {
    return size;
}

// 8. 获取数据索引
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}
// 9. 清除集合, 直接将集合中所有内容置空
public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

LinkedList

LinkedList的两种创建方法

//LinkedList 有两种创建方式
// 第一种:创建一个空List
public LinkedList() {
}
// 第二种:传递Collection集合
public LinkedList(Collection<? extends E> c) {
    //同样会先创建一个空List
    this();
    //调用addAll()方法
    addAll(c);
}

// 首先看一下add(E e)方法
public boolean add(E e) {
    linkLast(e);
    return true;
}

// Node节点中包括当前数据, 前一个节点的引用, 后一个节点的引用, 如果是最后一个元素, next是null; 如果是第一个元素,prev是null
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

LinkedList 常见的方法解析


// add方法调用的是linkLast方法
void linkLast(E e) {
    // 新创建的LinkedList中last=null
    final Node<E> l = last;
    // 创建Node节点参数是: 最后一个Node节点(就是新插入的节点的前一个节点)、当前节点数据、next节点为null
    final Node<E> newNode = new Node<>(l, e, null);
    // 此时最后一个节点变为当前插入的节点
    last = newNode;
    // 判断: 如果是空集合, 则第一个节点为当前节点; 如果不是空节点: 最后一个节点的下一个节点就是待插入的节点
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

// add的另一个方法, 在集合中间插入数据
public void add(int index, E element) {
    //1.判断当前索引是不是在0~当前集合的size中间
    checkPositionIndex(index);
    //判断index与集合size的关系, 如果相等是在集合最后插入数据, 等同于add(E e); 如果不相等, 则判断插入位置, 详见下一个方法
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
// 判断插入位置
// 参数是: 需要插入的数据, index索引节点
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //首先获取index索引的前一个节点
    final Node<E> pred = succ.prev;
    //创建包含插入数据的新节点, 新节点的前一个元素是index索引的前一个节点, 后一个几点是index索引节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    //将index索引节点的前一个节点变为新插入的节点
    succ.prev = newNode;
    //判断index节点的前一个节点是否为null, 就是判断index节点是不是第一个元素,first
    //如果是null则第一个元素变为新节点, 如果不是null则index索引的前一个节点的next节点变成当前插入节点
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}
private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

//addAll(Collection<? extends E> c)本质也是调用addAll(int index, Collection<? extends E> c)只不过index索引变成集合的大小size
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
    //1.检查index所以是否>=0 && <=size
    checkPositionIndex(index);

    //2.将传入的集合变成一个Object数组
    Object[] a = c.toArray();
    //3.数组的长度
    int numNew = a.length;
    if (numNew == 0)
        return false;
    //4.创建两个空节点
    Node<E> pred, succ;
    //5.给两个空节点赋值, 即为index索引所在的节点succ, 和index节点的前一个节点prev
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }

    // 6. 遍历数组将数据插入到集合中
    for (Object o : a) {
        // 将Object数据类型强制转换为当前类型E
        @SuppressWarnings("unchecked") E e = (E) o;
        // 创建当前数据的新节点
        Node<E> newNode = new Node<>(pred, e, null);
        //判断pred(index索引所在节点的前一个节点)是否为null,
        //如果为null则新插入节点即为first第一个节点, 
        //如果不是则将index索引所在节点的前一个节点的后一个节点变为新插入节点
        //然后将index索引的前一个节点变成新插入的节点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }
    // 如果succ为null,即index==size, 则最后一个元素节点==newNode新插入节点
    // 如果succ不为null, 即index < size, 则新插入节点的后一个节点是index节点, index节点的前一个节点变为新插入节点
    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    // size ==size + a.length
    size += numNew;
    modCount++;
    return true;
}

// addFirst和addLast同理是插入带集合第一个元素前、最后一个元素后
public void addFirst(E e) {
    linkFirst(e);
}
public void addLast(E e) {
    linkLast(e);
}

// LinkedList中的get方法
public E get(int index) {
    //判断index是否 >= 0 && <= size
    checkElementIndex(index);
    //调用弄得方法, 方法如下
    return node(index).item;
}
Node<E> node(int index) {
    // assert isElementIndex(index);
    // 首先判断index与size/2的大小
    // 如果index<size/2, 则从0~index区间查找
    // 如果index>size/2, 则从index~size-1区间查找
    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;
    }
}
//set方法
public E set(int index, E element) {
    //1.判断index是否>=0&&<=size
    checkElementIndex(index);
    //2.取出index所在的节点
    Node<E> x = node(index);
    //3.将index节点的数据变为新数据并返回老数据
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}

如有任何错误,请不吝赐教。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值