Java基础学习总结:集合之(一)分类、Collection、List、ArrayList、LinkedList

一、集合分类

Java中容器有单列集合(Collection)和双列集合(Map)两大类,其下又有很多子类,如下:

(1)单例集合:Collection

  • Collection
    • List
      • ArrayList
      • LinkList
      • Vector
      • Stack
    • Set
      • HashSet
      • TreeSet

(2)双列集合:Map

  • Map
    • HashMap
      • LinkedHashMap
    • TreeMap
    • ConcurrentHashMap
    • HashTable

二、Collection 接口

所有单列集合的父接口。

(1)Collection中常用的方法:

方法名描述
add(E e)向集合中添加元素。
addAll(Collection<? extends E> c) 将指定 Collection 中的所有元素都添加到此 Collection
clear()移除此 Collection 中的所有元素
contains(Object o)如果此 Collection 包含指定的元素,则返回true
containsAll(Collection<?> c)如果此 Collection 包含指定 Collection 中的所有元素,则返回 true
equals(Object o)比较此 Collection 与指定对象是否相等
isEmpty()如果此 Collection 不包含元素,则返回true
iterator()返回在此 Collection 的元素上进行迭代的迭代器
remove(Object o)从此 Collection 中移除指定元素,如果存在。不存在时不会报异常
removeAll(Collection<?> c)移除此 collection 中那些也包含在指定 collection 中的所有元素
retainAll(Collection<?> c) 仅保留此 collection 中那些也包含在指定 collection 的元素
size()返回此 collection 中的元素数

toArray()

返回包含此 collection 中所有元素的数组

三、List

List 是 Collection 接口的子接口,集合中元素有序且可重复。

public interface List<E> extends Collection<E> {}

1、ListIterator

List接口中有一个特别的方法,listIterator() 得到一个 ListIterator 接口类型的,专门针对 List 集合设计的迭代器实例,该迭代器实现了双向遍历 List 集合,并能够在遍历过程中实现 集合元素的增删改。ListIterator 针对 List 集合提供了更强大的遍历功能。

ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);

ListIterator 接口源码:

package java.util;


public interface ListIterator<E> extends Iterator<E> {
    // Query Operations

    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();

    // Modification Operations

    void remove();
    void set(E e);
    void add(E e);
}

四、ArrayList

ArrayList 集合继承了 AbstractList 抽象类,并实现了是 List 接口。同时还实现了 RandomAccess(随机存取,一个标记接口,类似的还有 SequenceAccess (顺序访问) )、Cloneable(克隆)、Serializable(序列化)接口。

ArrayList 类中还以私有的方式实现了 Iterator 和 ListIterator 接口。

ArrayList 的实现原理是数组,并允许包括null在内的所有元素。

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

3、ArrayList使用

package basis.CollectionStu;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class StuList {
    public static void main(String[] args) {

        //创建一个ArrayList集合对象
        List<String> list_1 = new ArrayList<>();
        List<String> list_2 = new ArrayList<>();
        //向集合中添加元素,同一个集合中,如果不加泛型约束,可以添加任意类型。
        list_1.add("张三");
        list_1.add("李四");
        list_1.add("王五");
        list_2.add("1");
        list_2.add("2");
        list_2.add("3");
        //把一个集合中的所有元素插入到另一个集合
        list_2.addAll(list_1);
        //移除集合中的所有元素
        //list_1.clear();
        //判断集合中是否包含指定元素
        System.out.println(list_2.contains("张三"));
        //判断集合中是否包含指定集合的所有元素
        System.out.println(list_2.containsAll(list_1));
        //判断两个集合是否相等
        System.out.println(list_2.equals(list_1));
        //判断集合是否为空
        System.out.println(list_1.isEmpty());
        //从集合中移除指定元素,元素不存在时,不会抛异常
        list_2.remove("aaa");
        //从当前集合中移除指定集合中的所有元素
        list_2.removeAll(list_1);
        //仅保留此collection 中的那些也包含在指定集合中的元素
        list_2.retainAll(list_1);
        //返回集合中元素的个数
        System.out.println(list_2.size());
        //把集合转成数组,需要为集合添加泛型
        List<String> list_3 = new ArrayList<>();
        list_3.add("suxing");
        list_3.add("suxing");
        list_3.add("suxing");
        //转成Object类型的数组
        Object[] obj = list_3.toArray();

        //转成指定类型的数组
        /*
            new String[0],告诉程序集合中的元素应该转成什么类型的数组,
            [0],表示数组的实际长度,该数值小于集合元素个数时,数组长度都等于集合元素个数;
            如果该值大于集合中元素的个数,该值为数组的实际长度
        */
        String[] str = list_3.toArray(new String[0]);
        System.out.println(str.length);

        //遍历集合:Iterator
        Iterator<String> iterator = list_3.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
            //可以再遍历中,使用Iteator 中的删除方法,进行元素的删除,不可以使用集合的remove()方法
            iterator.remove();
        }

        //遍历集合:ListIterator
        ListIterator listIterator = list_2.listIterator();
        while (listIterator.hasNext()){
            listIterator.hasPrevious();//是否存在前一个元素
            listIterator.next();//下一个
            listIterator.previous();//前一个
            listIterator.nextIndex();//下一个元素的索引
            listIterator.previousIndex();//前一个元素的索引
            listIterator.add("aaa");//添加元素
            listIterator.set("123");//修改元素
            listIterator.remove();//删除元素
        }
    }
}

4、ArrayList 源码分析

(1)构造方法:

  • ArrayList 集合的默认容量为 10。
private static final int DEFAULT_CAPACITY = 10;
  • ArrayList 集合内部使用数组来实现
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList 有三个构造方法,分别是无参的、int类型参数的、和集合类型参数的。

  • 无参构造:使用预定义的Object[]类型的数组存放数据
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  • int类型参数的构造:使用传入的数字(不为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);
    }
}
  • 集合类型参数的构造 :使用传入的集合中的数据初始化数组(传入集合不为空),同时保证数组必须是Object类型的。
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    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;
    }
}

(2)扩容机制:

  ArrayList扩容发生在add()方法调用的时候,下面是add()方法的源码:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

根据意思可以看出ensureCapacityInternal()是用来扩容的,形参为最小扩容量,进入此方法后:

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

通过方法calculateCapacity(elementData, minCapacity)获取:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

ensureExplicitCapacity方法可以判断是否需要扩容:

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

    // 如果最小需要空间比elementData的内存空间要大,则需要扩容
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        //扩容
        grow(minCapacity);
}

 接下来重点来了,ArrayList扩容的关键方法grow():

private void grow(int minCapacity) {
    // overflow-conscious code
    //获取到ArrayList中elementData数组的内存空间长度
    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);
    // 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
    // 并将elementData的数据复制到新的内存空间
    elementData = Arrays.copyOf(elementData, newCapacity);
}

数组在进行扩容时会进行大量的数组的复制操作,调用了 System.arraycopy() 方法,

System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));

 而,System.arraycopy() 是一个本地方法,因此,复制效率还是相当高的。

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

五、LinkList

通过链表实现的集合,存储结构:循环双向链表。

LinkedList链表由一系列表项(节点)连接而成。一个表项总是包含3个部分:元素内容,前驱表和后驱表,如图所示:

在下图展示了一个包含3个元素的 LinkedList 的各个表项间的连接关系。在 JDK 的实现中,无论 LikedList 是否为空,链表内部都有一个 header 表项,它既表示链表的开始,也表示链表的结尾。表项 header 的后驱表项便是链表中第一个元素,表项header 的前驱表项便是链表中最后一个元素。 

LInkedList 类的声明: 

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{

transient int size = 0;

transient Node<E> first;

transient Node<E> last;
//……

}

LinkedList 继承了 AbstractSequentialList 抽象类,并实现了 List, Deque(双端队列), Cloneable, Serializable 接口。在LinkedList中有三个transient修饰的变量,分别表示该 LinkedList 的元素个数、头结点和尾节点。

transient关键字

java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。

1、常用方法

LinkedList 中的方法和 ArrayList 中的方法基本类似且用法相同。

2、源码分析

(1)无参构造:

public LinkedList() {
}

(2)有参构造:使用传入的集合的数据初始化此集合

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

(3)add() 方法

public boolean add(E e) {
        linkLast(e);
        return true;
    }

add()方法中调用了 linklast() 方法,该方法在 将创建一个新的节点并将此节点插入到链表的末尾。

linklast()方法

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++;
    }

该方法先获取链表的最后一个节点,然后创建一个新的节点,将这个新节点插入到链表中;把当前的最后一个节点作为新节点的前一个节点,把新节点的后一个节点设为 null 。然后更新最后一个节点为新插入的节点。

相似的,还有 linkfirst() 方法。

linkfirst() 方法

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++;
    }

该方法首先获取当前链表的头结点,作为新创建的节点的下一个结点,并将新创建的节点加入到链表中,然后更新头结点。

六、ArrayList 和 LinkedList 比较

ArrayList 集合适用于 查找比较频繁的情况,因为 ArrayList 底层是用数组实现的,查找效率很高,不适用于增加和删除操作比较频繁的情况,因为数组在进行增加和删除时需要移动元素,效率不高。

LinkedList 不适用于查找比较频繁的情况,因为LinkedList 底层使用链表实现,链表的查询操作每次都需要从根结点开始逐个进行遍历;而适用于增加和删除操作比较频繁的情况,因为链表的增加和删除不需要移动元素,效率较高。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值