Java基础篇--集合(上)

198 阅读38分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

一、前言

编码过程中,我们需要容器来装我们的数据或者对象。集合就是用来存放某类对象的,集合类有一个共同特点,就是它们只容纳对象(实际上是对象名,即指向地址的指针)。这一点和数组不同,数组可以容纳对象和简单数据。如果在集合类中既想使用简单数据类型,又想利用集合类的灵活性,就可以把简单数据类型数据变成该数据类型类的对象,然后放入集合中处理(基本数据的装箱与拆箱),但这样执行效率会降低。

1.1数组和集合的比较

数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架类可适用不同场合。如下:

  1. 数组能存放基本数据类型和对象,而集合类存放的都是对象,集合类不能存放基本数据类型。数组和集合存放的对象皆为对象的引用地址。
  2. 数组容易固定无法动态改变,集合类容量动态改变。
  3. 数组无法判断其中实际存有多少元素,length只告诉了数组的容量,而集合的size()可以确切知道元素的个数。
  4. 集合有多种实现方式和不同适用场合,不像数组仅采用顺序表方式。
  5. 集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,大大提高了软件的开发效率

1.2 基础数据类型的装箱与拆箱

Java中有八种基本的数据类型,分别是:byte、short、char、int、long、float、double和boolean。这八种基本类型在Java中都有对应的包装类型:Byte、Short、Character、Integer、Long、Float、Double以及Boolean。

  • java中已经有了基本数据类型,为什么还需要包装类型呢?

这是由Java本身的语言特性决定的,Java是一种面向对象的编程语言,一切皆对象。基本类型不具备Java中对象的某些特征--对象内部可以封装一系列属性和行为,所以对应的包装类型就应运而生了。

装箱与拆箱

装箱和拆箱描述的是Java中这八种基本数据类型和对应的包装类型之间的转换过程。我们把基本数据类型转换成对应的包装类型的过程叫做装箱。反之就是拆箱。在Java中的装箱和拆箱不是人为操作的,是程序在编译的时候编译器完成这项任务的,自动拆装箱是在JDK1.5以后引入的,对于之前版本的Java,需要格外注意格式的转换。

装箱

Integer i = 20;
int j = 2;

将代码反编译之后可以得到:

Integer i = Integer.valueOf((int)20);
int j = 2;

​ 可以看到对于数值类型直接赋值给包装类型,有一个自动装箱的操作,而自动装箱的操作就是利用了Integer中的valueOf方法,这就是前面在节约空间那部分提到的valueOf方法。Integer的valueOf方法中具有缓存的功能,也就是说在数值为-128到127之间的数据,都是被构造成同一个对象,这就是上面提到的享元模式的设计思路:


public static Integer valueOf(int i) {
 //IntegerCache.low = -128, IntegerCache.high = 127
   if (i >= IntegerCache.low && i <= IntegerCache.high)
       return IntegerCache.cache[i + (-IntegerCache.low)];
   return new Integer(i);
}

拆箱

Integer a = 100;
int b = 20;
int c = a + b;

代码反编译后得到:

Integer a = Integer.valueOf((int)100);
int b = 20;
int c = a.intValue() + b;

可以看到第一步进行了自动装箱操作,在第三行中,基本数据类型和包装类型进行运算,需要将包装类型进行拆箱操作,用到了intValue方法。在包装类型中,内部都有一个基本数据类型的字段用于存储对应基本类型的值,这个字段就是value。相应的其他包装类型在进行拆箱的时候,都会调用对应的xxxValue方法,例如:byteValue、shortValue等等方法。其实内部逻辑都是一样,直接返回存储的value值。

自动装箱和拆箱的时机

  • 直接赋值:将一个字面量直接赋值给对应包装类型会触发自动装箱操作。

  • 函数参数

//自动拆箱
public int getNum1(Integer num) {
    return num;
 }
//自动装箱
public Integer getNum2(int num) {
    return num;
}
  • 集合操作:在Java的集合中,泛型只能是包装类型,但是我们在存储数据的时候,一般都是直接存储对应的基本类型数据,这里就有一个自动装箱的过程。
  • 运算符运算:上面在拆箱操作的时候利用的就是这个特性,当基本数据类型和对应的包装类型进行算术运算时,包装类型会首先进行自动拆箱,然后再与基本数据类型的数据进行运算。 说到运算符,这里对于自动拆箱有一个需要注意的地方:
Integer a = null;
int b = a;
int b = a.intValue();

这种情况编译是可以通过的,但是在运行的时候会抛出空指针异常,这就是自动拆箱导致的这种错误。因为自动拆箱会调用intValue方法,但是此时a是null,所以会抛异常。平时在使用的时候,注意非空判断即可。

自动装拆箱带来的问题

==比较

自动装箱的机制,我们不能依赖于操作符,它在一定范围内数值相同为true,但是在更多的空间中,数值相同的包装类型对象比较的结果为false。如果需要比较,可以考虑使用equals比较或者将其转换成对应的基本类型再进行比较可以保证结果的一致性。

空指针

自动拆箱的机制,如果初始的包装类型对象为null,那么在自动拆箱的时候的就会报NullPointerException,在使用时需要格外注意,在使用之前进行非空判定,保证程序的正常运行。

内存浪费

这里有个例子:

Integer sum = 0;
for(int i=1000; i<5000; i++){
 sum+=i;
}

上面代码中的 sum+=i 这个操作其实就是拆箱再装箱的过程,拆箱过程是发生在相加的时候,sum本身是Integer,自动拆箱成int与 i 相加。将得到的结果赋值给sum的时候,又会进行自动装箱,所以上面的for循环体中一句话,在编译后会变为:n = Integer.valueOf((int)(n.intValue() + i));每次调用valueOf方法都会返回一个Integer对象,所以在进行了5000次循环后,会出现大量的无用对象造成内容空间的浪费,同时加重了垃圾回收的工作量,所以在日常编码过程中需要格外注意,避免出现这种浪费现象。

二、Java集合

Java中的集合框架体系如下图:

如上图,我们可以看到Java集合有一个根父类接口Iterable(迭代接口),继承迭代接口中的方法,根据存放的数据结构不同有Collection(单列数据)和Map(双列数据)两种,在Collection的子类中有不同的数据结构实现的子类,在Map的子类有不同数据结构实现的子类,下面对这些子类进行详细介绍:

2.1 Iterable(迭代接口)

在Java中,Iterable接口是Java集合的顶级接口之一。Collection接口和Map接口都继承Iterable接口,所以Collection和Map的所有子类也实现了Iterable接口。由于集合的子类的不同特性,实现Iterable接口时采用了设计模式的迭代器模式。

Iterator,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

2.1.1 Iterable源码


package java.lang;


import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
/**
 * Implementing this interface allows an object to be the target of
 * the "for-each loop" statement. See
 * <strong>
 * <a href="{@docRoot}/../technotes/guides/language/foreach.html">For-each Loop</a>
 * </strong>
 *
 * @param <T> the type of elements returned by the iterator
 *
 * @since 1.5
 * @jls 14.14.2 The enhanced for statement
 */
public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();

    /**
     * Performs the given action for each element of the {@code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{@code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    /**
     * Creates a {@link Spliterator} over the elements described by this
     * {@code Iterable}.
     *
     * @implSpec
     * The default implementation creates an
     * <em><a href="Spliterator.html#binding">early-binding</a></em>
     * spliterator from the iterable's {@code Iterator}.  The spliterator
     * inherits the <em>fail-fast</em> properties of the iterable's iterator.
     *
     * @implNote
     * The default implementation should usually be overridden.  The
     * spliterator returned by the default implementation has poor splitting
     * capabilities, is unsized, and does not report any spliterator
     * characteristics. Implementing classes can nearly always provide a
     * better implementation.
     *
     * @return a {@code Spliterator} over the elements described by this
     * {@code Iterable}.
     * @since 1.8
     */
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

2.1.2 源码解析

  1. package java.lang; -->表示Iterable接口是在Java.lang下的接口
  2. Implementing this interface allows an object to be the target of the "for-each loop" statement. See 这句话是对这个类的一个介绍,大致意思是表示:实现Iterable接口允许对象成为“for each loop”语句的目标.当我们的类实现了Iterable接口后,就可以定义一组可以迭代的Iterable对象,可以迭代其元素。
  3. 在Iterable接口中定义了三个成员方法
  • iterator()方法

在设计之初,设计者并没有让开发人员在实现Iterable接口直接就可以对集合进行迭代操作,而是巧妙地在Iterable接口内部定义了一个iterator()方法,当开发者调用iterator()方法后,就可以返回一个内部元素为T类型的迭代器,拿到迭代器后就可以调用迭代器内部的hasNext()和next()方法,来进行集合元素的遍历,实现了集合元素操作的隔离性。

    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();

Iterator对象的成员方法:
image.png

  • hasNext() 如果有元素则继续迭代,返回true
  • next() 返回迭代的元素
  • remove() 移除返回的元素
    代码示例:
List l = new ArrayList();\
l.add("aa");\
l.add("bb");\
l.add("cc");\
Iterator iterator = l.iterator();\
while (iterator.hasNext()){\
    String str = (String) iterator.next();\
    System.out.println(str);\
}

在后续介绍的集合中,不同的集合对Iterator对象的成员方法有不同的定义。

  • forEach

foreach方法是一个语法糖,在类加载完成后,会转化成迭代器Iterator实现,对每个元素进行特定操作,直到元素被处理完毕或抛出异常,用于循环处理集合中数据。

  default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
  • spliterator

分割迭代器主要是用来对源数据元素进行遍历和分区。

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }

2.2 Collection

2.2.1 Collection接口

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

1.添加元素
(1)add(E obj):添加元素对象到当前集合中
(2)addAll(Collection<? extends E> other):添加other集合中的所有元素对象到当前集合中。

2.删除元素
(1)boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。
(2)boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll集合中相同的元素。\

3.判断元素
(1)boolean isEmpty():判断当前集合是否为空集合。
(2)boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素。
(3)boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前\集合的“子集”。

4.查询
(1)int size():获取当前集合中实际存储的元素个数
(2)Object[] toArray():返回包含当前集合中所有元素的数组\

5.交集
(1)boolean retainAll(Collection<?> coll):当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集,即this = this ∩ coll;

2.2.2 List接口

List接口继承于Collection接口,除继承了collection的成员方法外,还有以下特性:

  • List集合特点:列表索引从0开始,就像数组一样,元素有序的,且可重复。
  • List允许您拥有'null'元素
  • List集合遍历:根据下标,foreach,迭代器遍历数据。
  • Lits集合扩容:Lits集合当我们实例出来,它的默认初始容量为10,当往List集合里面增加的数据超过10个以后,他就会扩容增加0.5倍,扩容以后就是15。即新容量 = 原容量 + 原容量 * 0.5。
  • List常用方法:
    void add(int index, Object element) :添加对象element到位置index上
    boolean addAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素
    Object get(int index) :取出下标为index的位置的元素
    int indexOf(Object element) :查找对象element 在List中第一次出现的位置
    int lastIndexOf(Object element) :查找对象element 在List中最后出现的位置
    Object remove(int index) :删除index位置上的元素
    ListIterator listIterator(int startIndex) :返回一个ListIterator 迭代器,开始位置为startIndex
    List subList(int fromIndex, int toIndex) :返回一个子列表List ,元素存放为从 fromIndex 到toIndex之前的一个元素

元素有序不是指,我们存进Lits集合中的什么1,3,7,6他给我们从小到大,或者从大到小这样子,所谓的有序是我们该集合有下标,下标从0开始,然后我们按照什么顺序增加到list集合的,那么他就是什么样子的顺序)。

既然List的元素是有序可重复的,那我们就可以使用继承自Iterable接口的foreach方法和迭代器的方式遍历集合中的元素。

vector,ArrayList,LinkedList都是继承List,所以和List集合以上特点都是一样的。

2.2.2.1 ArrayList集合

2.2.2.1.1 对象数组

使用学生数组,存储三个学生对象,代码如下:

//学生对象类
public class Student {

    private String name;// 姓名
    private int age;// 年龄
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    publicvoid setName(String name) {
        this.name = name;
    }
    publicint getAge() {
        return age;
    }
    publicvoid setAge(int age) {
        this.age = age;
    }
}

对象数组的测试代码:

public class Test01StudentArray {

    public static void main(String[] args) {
        //创建学生数组
        Student[] students = new Student[3];
        //创建学生对象
        Student s1 = new Student("曹操", 40);
        Student s2 = new Student("刘备", 35);
        Student s3 = new Student("孙权", 30);
        //把学生对象作为元素赋值给学生数组
        students[0] = s1;
        students[1] = s2;
        students[2] = s3;
        //遍历学生数组
        for (int x = 0; x < students.length; x++) {
            Student s = students[x];
            System.out.println(s.getName() + "‐‐‐" + s.getAge());
        }
    }
}

Java中存储对象数据选择的容器有对象数组和集合,由上述代码可以看出,使用数组存储对象元素存在局限性,因为数组的长度是固定的,当数组的初始化以后,长度将不再可变,如果初始化的时候直接初始化一个大数据,后续使用过程中势必会造成内存资源的浪费,无法适应数据变化的需求。为了解决这个问题,Java提供了另一个容器java.util.ArrayList 集合类,让我们可以更便捷的存储和操作对象数据。

2.2.2.1.2 ArrayList集合
  1. ArrayList是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了ListRandomAccessCloneablejava.io.Serializable这些接口。ArrayList集合不适合随机的删除和增加.
  2. ArrayList与Collection的关系如下图,实现代表继承,虚线代表实现接口:

image.png

  1. ArrayList继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  2. ArrayList实现了RandmoAccess接口,即提供了随机访问的功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
  3. ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
  4. ArrayList实现了java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
2.2.2.1.2 ArrayList集合的使用

构造方法
public ArrayList() :构造一个内容为空的集合。

基本格式:
ArrayList<String> list = new ArrayList<String>();

在JDK 7后,右侧泛型的尖括号之内可以留空,但是<>仍然要写。简化格式:

ArrayList<String> list = new ArrayList<>();

成员方法

  • public boolean add(E e) : 将指定的元素添加到此集合的尾部。参数 E e ,在构造ArrayList对象时, 指定了什么数据类型,那么 add(E e) 方法中,只能添加什么数据类型的对象。

    使用ArrayList类,存储三个字符串元素,代码如下:

public class Test02StudentArrayList {

public static void main(String[] args) {
//创建学生数组
ArrayList<String> list = new ArrayList<>();
//创建学生对象
String s1 = "曹操";
String s2 = "刘备";
String s3 = "孙权";
//打印学生ArrayList集合
System.out.println(list);
//把学生对象作为元素添加到集合
list.add(s1);
list.add(s2);
list.add(s3);
//打印学生ArrayList集合
System.out.println(list); 
    }
}
  • public E remove(int index) :移除此集合中指定位置上的元素。返回被删除的元素。
  • public E get(int index) :返回此集合中指定位置上的元素。返回获取的元素。
  • public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。
public class Demo01ArrayListMethod {

public static void main(String[] args) {

//创建集合对象

ArrayList<String> list = new ArrayList<String>();

//添加元素

list.add("hello");

list.add("world");

list.add("java");

//public E get(int index):返回指定索引处的元素

System.out.println("get:"+list.get(0));

System.out.println("get:"+list.get(1));

System.out.println("get:"+list.get(2));

//public int size():返回集合中的元素的个数

System.out.println("size:"+list.size());

//public E remove(int index):删除指定索引处的元素,返回被删除的元素

System.out.println("remove:"+list.remove(0));

//遍历输出

for(int i = 0; i < list.size(); i++){ 
         System.out.println(list.get(i)); 
                           } 
                    }
        }
                             
2.2.2.1.2 ArrayList集合源码分析

核心源码

package java.util;

import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;


public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组(用于空实例)。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

     //用于默认大小空实例的共享空数组实例。
      //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 保存ArrayList数据的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 所包含的元素个数
     */
    private int size;

    /**
     * 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //如果传入的参数大于0,创建initialCapacity大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //如果传入的参数等于0,创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //其他情况,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     *默认无参构造函数
     *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
     */
    public ArrayList(Collection<? extends E> c) {
        //将指定集合转换为数组
        elementData = c.toArray();
        //如果elementData数组的长度不为0
        if ((size = elementData.length) != 0) {
            // 如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)      
            if (elementData.getClass() != Object[].class)
                //将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 其他情况,用空数组代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

    /**
     * 修改这个ArrayList实例的容量是列表的当前大小。 应用程序可以使用此操作来最小化ArrayList实例的存储。 
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
//下面是ArrayList的扩容机制
//ArrayList的扩容机制提高了性能,如果每次只扩充一个,
//那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
    /**
     * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
     * @param   minCapacity   所需的最小容量
     */
    public void ensureCapacity(int minCapacity) {
        //如果是true,minExpand的值为0,如果是false,minExpand的值为10
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : 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++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            //调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
    }

    /**
     * 要分配的最大数组大小
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * ArrayList扩容的核心方法。
     */
    private void grow(int minCapacity) {
        // oldCapacity为旧容量,newCapacity为新容量
        int oldCapacity = elementData.length;
        //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
        //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //再检查新容量是否超出了ArrayList所定义的最大容量,
        //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
        //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
        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);
    }
    //比较minCapacity和 MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

    /**
     *返回此列表中的元素数。 
     */
    public int size() {
        return size;
    }

    /**
     * 如果此列表不包含元素,则返回 true 。
     */
    public boolean isEmpty() {
        //注意=和==的区别
        return size == 0;
    }

    /**
     * 如果此列表包含指定的元素,则返回true 。
     */
    public boolean contains(Object o) {
        //indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 
        return indexOf(o) >= 0;
    }

    /**
     *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 
     */
    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++)
                //equals()方法比较
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    /**
     * 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。.
     */
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    /**
     * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。) 
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            //Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // 这不应该发生,因为我们是可以克隆的
            throw new InternalError(e);
        }
    }

    /**
     *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 
     *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。
     *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    /**
     * 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 
     *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 
     *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 
     *如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。
     *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) 
     */
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // 新建一个运行时类型的数组,但是ArrayList数组的内容
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
            //调用System提供的arraycopy()方法实现数组之间的复制
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    // Positional Access Operations

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

    /**
     * 返回此列表中指定位置的元素。
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    /**
     * 用指定的元素替换此列表中指定位置的元素。 
     */
    public E set(int index, E element) {
        //对index进行界限检查
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        //返回原来在这个位置的元素
        return oldValue;
    }

    /**
     * 将指定的元素追加到此列表的末尾。 
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //这里看到ArrayList添加元素的实质就相当于为数组赋值
        elementData[size++] = e;
        return true;
    }

    /**
     * 在此列表中的指定位置插入指定的元素。 
     *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
     *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
        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);
        elementData[--size] = null; // clear to let GC do its work
      //从列表中删除的元素 
        return oldValue;
    }

    /**
     * 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。
     *返回true,如果此列表包含指定的元素
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        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
    }

    /**
     * 从列表中删除所有元素。 
     */
    public void clear() {
        modCount++;

        // 把数组中所有的元素的值设为null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

    /**
     * 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
     */
    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;
        return numNew != 0;
    }

    /**
     * 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

    /**
     * 从此列表中删除所有索引为fromIndex (含)和toIndex之间的元素。
     *将任何后续元素移动到左侧(减少其索引)。
     */
    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

    /**
     * 检查给定的索引是否在范围内。
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * add和addAll使用的rangeCheck的一个版本
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * 返回IndexOutOfBoundsException细节信息
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    /**
     * 从此列表中删除指定集合中包含的所有元素。 
     */
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        //如果此列表被修改则返回true
        return batchRemove(c, false);
    }

    /**
     * 仅保留此列表中包含在指定集合中的元素。
     *换句话说,从此列表中删除其中不包含在指定集合中的所有元素。 
     */
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }


    /**
     * 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
     *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 
     *返回的列表迭代器是fail-fast 。 
     */
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

    /**
     *返回列表中的列表迭代器(按适当的顺序)。 
     *返回的列表迭代器是fail-fast 。
     */
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

    /**
     *以正确的顺序返回该列表中的元素的迭代器。 
     *返回的迭代器是fail-fast 。 
     */
    public Iterator<E> iterator() {
        return new Itr();
    }


Arraylist的扩容机制
首先以创建无参构造为例,创建后一开始不会马上扩容为默认的大小10,会先是一个空的Object数组,如果使用了add方法后才会扩容为10。因为这时 ensureCapacityInternal(size + 1);  也就是说size=1;

//minCapacity=1
   private void ensureCapacityInternal(int minCapacity) {
   //这时的elementData还是0
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//true
              // 获取“默认的容量”和“传入参数”两者之间的最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//所以这里minCapacity会是10
        }

		//把minCapacity=10传入
        ensureExplicitCapacity(minCapacity);
    }

ensureExplicitCapacity方法:

 //判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        //这时10-0>0,所以执行我们的扩容方法
        if (minCapacity - elementData.length > 0)
            //调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
    }

grow方法

private void grow(int minCapacity) {
        // oldCapacity为旧容量,newCapacity为新容量
        int oldCapacity = elementData.length;//0这里是
        //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
        //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);//这里还是0
        //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity; //这里就变成10了
        //再检查新容量是否超出了ArrayList所定义的最大容量,
        //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
        //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
        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);
    }

接下来继续添加:然后会继续比较 ensureCapacityInternal(size + 1);  也就是说size=2;

//minCapacity=2
   private void ensureCapacityInternal(int minCapacity) {
   //这时的elementData被扩容成了10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//false
              // 获取“默认的容量”和“传入参数”两者之间的最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

		//把minCapacity=2传入
        ensureExplicitCapacity(minCapacity);
    }

所以后面添加直到第11个才会扩容。

扩容机制代码已经做了详细的解释。另外值得注意的是大家很容易忽略的一个运算符:移位运算符。

简介: 移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(带符号右移)和>>>(无符号右移)。   作用:对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源   比如这里:int newCapacity = oldCapacity + (oldCapacity >> 1); 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。

System.arraycopy()Arrays.copyOf()方法 通过上面源码我们发现这两个实现数组复制的方法被广泛使用而且很多地方都特别巧妙。比如下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法让数组自己复制自己实现让index开始之后的所有成员后移一个位置:

 /**
     * 在此列表中的指定位置插入指定的元素。 
     *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大;
     *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //arraycopy()方法实现数组自己复制自己
        //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }

又如toArray()方法中用到了copyOf()方法

   /**
     *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 
     *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。
     *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。
     */
    public Object[] toArray() {
    //elementData:要复制的数组;size:要复制的长度
        return Arrays.copyOf(elementData, size);
    }

2.2.2.2 Vector集合

  • Vector 集合可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。不过,Vector的大小是可以增加或者减小的,以便适应创建Vector后进行添加或者删除操作。

  • Vector实现List接口,继承AbstractList类,所以我们可以将其看做队列,支持相关的添加、删除、修改、遍历等功能。

  • Vector实现RandmoAccess接口,即提供了随机访问功能,提供提供快速访问功能。在Vector我们可以直接访问元素。

  • Vector 实现了Cloneable接口,支持clone()方法,可以被克隆。

  • Vector集合初始化容量是10,每次扩容容量增加一倍,即:10–>20–>40–>80

  • Vector中所有方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率较低,使用较少。

2.2.2.3 LinkedList集合

2.2.2.3.1 LinkList概述
  • LinkedList是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  • LinkedList 实现 List 接口,能进行队列操作。
  • LinkedList 实现Deque接口,即能将LinkedList当作双端队列使用。
  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。 
  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。  - LinkedList 是非同步的。
  • ArrayList底层是由数组支持,而LinkedList是由双向链表实现的,其中的每个对象包含数据的同时还包含指向链表中前一个与后一个元素的引用。
2.2.2.3.2 LinkList常用方法与遍历

常用方法:

  • public E peek():检索但不删除此列表的头(第一个元素)。
  • public E peekFirst():检索但不删除此列表的第一个元素,如果此列表为空,则返回 null 。
  • public E peekLast():检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null 。
  • public E poll():检索并删除此列表的头(第一个元素)。
  • public E pollFirst():检索并删除此列表的第一个元素,如果此列表为空,则返回 null 。
  • public E pollLast():检索并删除此列表的最后一个元素,如果此列表为空,则返回 null 。
  • public boolean offer(E e):将指定的元素添加为此列表的尾部(最后一个元素)。
  • public boolean offerFirst(E e):在此列表的前面插入指定的元素。
  • public boolean offerLast(E e):在该列表的末尾插入指定的元素。

LinkList遍历方式

  • 迭代器遍历 
 Iterator iterator = linkedList.iterator();   
    while(iterator.hasNext()){   
    iterator.next();   
    }
  • for循环get()遍历
for(int i = 0; i < linkedList.size(); i++){   
linkedList.get(i);   
}
  • Foreach循环遍历
for(Integer i : linkedList);
  • 通过pollFirst()或pollLast()遍历
while(linkedList.size() != 0){   
linkedList.pollFirst();   
}
  • 通过removeFirst()或removeLast()遍历
while(linkedList.size() != 0){   
linkedList.removeFirst();   
}
  • 效率测试 测试以上几种遍历方式的效率,部分代码如下:
/**************** 遍历操作 **************/   
System.out.println(“—————————————–”);   
linkedList.clear();   
for(int i = 0; i < 100000; i++){   
linkedList.add(i);   
}   
// 迭代器遍历   
long start = System.currentTimeMillis();   
Iterator iterator = linkedList.iterator();   
while(iterator.hasNext()){   
iterator.next();   
}   
long end = System.currentTimeMillis();   
System.out.println(“Iterator:” + (end - start) +” ms”);

// 顺序遍历(随机遍历)   
start = System.currentTimeMillis();   
for(int i = 0; i < linkedList.size(); i++){   
linkedList.get(i);   
}   
end = System.currentTimeMillis();   
System.out.println(“for:” + (end - start) +” ms”);

// 另一种for循环遍历   
start = System.currentTimeMillis();   
for(Integer i : linkedList);   
end = System.currentTimeMillis();   
System.out.println(“for2:” + (end - start) +” ms”);

// 通过pollFirst()或pollLast()来遍历LinkedList   
LinkedList temp1 = new LinkedList<>();   
temp1.addAll(linkedList);   
start = System.currentTimeMillis();   
while(temp1.size() != 0){   
temp1.pollFirst();   
}   
end = System.currentTimeMillis();   
System.out.println(“pollFirst()或pollLast():” + (end - start) +” ms”);

// 通过removeFirst()或removeLast()来遍历LinkedList   
LinkedList temp2 = new LinkedList<>();   
temp2.addAll(linkedList);   
start = System.currentTimeMillis();   
while(temp2.size() != 0){   
temp2.removeFirst();   
}   
end = System.currentTimeMillis();   
System.out.println(“removeFirst()或removeLast():” + (end - start) +” ms”);

输出:

Iterator:17 ms 
for:8419 ms 
for2:12 ms 
pollFirst()或pollLast():12 ms 
removeFirst()或removeLast():10 ms

由测试结果可以看出,遍历LinkedList时,使用removeFirst()或removeLast()效率最高,而for循环get()效率最低,应避免使用这种方式进行。应当注意的是,使用pollFirst()或pollLast()或removeFirst()或removeLast()遍历时,会删除原始数据,若只单纯的读取,应当选用第一种或第三种方式。

2.2.2.3.3 LinkList源码解析

为了更了解LinkedList的原理,下面对LinkedList源码代码作出分析。 
在阅读源码之前,我们先对LinkedList的整体实现进行大致说明: 
LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低。 
既然LinkedList是通过双向链表的,但是它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”? 
实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。 
这就是“双线链表和索引值联系起来”的方法。 
好了,接下来开始阅读源码(只要理解双向链表,那么LinkedList的源码很容易理解的)。

package java.util;
 
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // 链表的表头,表头不包含任何数据。Entry是个链表类数据结构。
    private transient Entry<E> header = new Entry<E>(null, null, null);
 
    // LinkedList中元素个数
    private transient int size = 0;
 
    // 默认构造函数:创建一个空的链表
    public LinkedList() {
        header.next = header.previous = header;
    }
 
    // 包含“集合”的构造函数:创建一个包含“集合”的LinkedList
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    // 获取LinkedList的第一个元素
    public E getFirst() {
        if (size==0)
            throw new NoSuchElementException();
 
        // 链表的表头header中不包含数据。
        // 这里返回header所指下一个节点所包含的数据。
        return header.next.element;
    }
 
    // 获取LinkedList的最后一个元素
    public E getLast()  {
        if (size==0)
            throw new NoSuchElementException();
 
        // 由于LinkedList是双向链表;而表头header不包含数据。
        // 因而,这里返回表头header的前一个节点所包含的数据。
        return header.previous.element;
    }
 
    // 删除LinkedList的第一个元素
    public E removeFirst() {
        return remove(header.next);
    }
 
    // 删除LinkedList的最后一个元素
    public E removeLast() {
        return remove(header.previous);
    }
 
    // 将元素添加到LinkedList的起始位置
    public void addFirst(E e) {
        addBefore(e, header.next);
    }
 
    // 将元素添加到LinkedList的结束位置
    public void addLast(E e) {
        addBefore(e, header);
    }
 
    // 判断LinkedList是否包含元素(o)
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }
 
    // 返回LinkedList的大小
    public int size() {
        return size;
    }
 
    // 将元素(E)添加到LinkedList中
    public boolean add(E e) {
        // 将节点(节点数据是e)添加到表头(header)之前。
        // 即,将节点添加到双向链表的末端。
        addBefore(e, header);
        return true;
    }
 
    // 从LinkedList中删除元素(o)
    // 从链表开始查找,如存在元素(o)则删除该元素并返回true;
    // 否则,返回false。
    public boolean remove(Object o) {
        if (o==null) {
            // 若o为null的删除情况
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            // 若o不为null的删除情况
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }
 
    // 将“集合(c)”添加到LinkedList中。
    // 实际上,是从双向链表的末尾开始,将“集合(c)”添加到双向链表中。
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
 
    // 从双向链表的index开始,将“集合(c)”添加到双向链表中。
    public boolean addAll(int index, Collection<? extends E> c) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Object[] a = c.toArray();
        // 获取集合的长度
        int numNew = a.length;
        if (numNew==0)
            return false;
        modCount++;
 
        // 设置“当前要插入节点的后一个节点”
        Entry<E> successor = (index==size ? header : entry(index));
        // 设置“当前要插入节点的前一个节点”
        Entry<E> predecessor = successor.previous;
        // 将集合(c)全部插入双向链表中
        for (int i=0; i<numNew; i++) {
            Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
            predecessor.next = e;
            predecessor = e;
        }
        successor.previous = predecessor;
 
        // 调整LinkedList的实际大小
        size += numNew;
        return true;
    }
 
    // 清空双向链表
    public void clear() {
        Entry<E> e = header.next;
        // 从表头开始,逐个向后遍历;对遍历到的节点执行一下操作:
        // (01) 设置前一个节点为null 
        // (02) 设置当前节点的内容为null 
        // (03) 设置后一个节点为“新的当前节点”
        while (e != header) {
            Entry<E> next = e.next;
            e.next = e.previous = null;
            e.element = null;
            e = next;
        }
        header.next = header.previous = header;
        // 设置大小为0
        size = 0;
        modCount++;
    }
 
    // 返回LinkedList指定位置的元素
    public E get(int index) {
        return entry(index).element;
    }
 
    // 设置index位置对应的节点的值为element
    public E set(int index, E element) {
        Entry<E> e = entry(index);
        E oldVal = e.element;
        e.element = element;
        return oldVal;
    }
 
    // 在index前添加节点,且节点的值为element
    public void add(int index, E element) {
        addBefore(element, (index==size ? header : entry(index)));
    }
 
    // 删除index位置的节点
    public E remove(int index) {
        return remove(entry(index));
    }
 
    // 获取双向链表中指定位置的节点
    private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        // 获取index处的节点。
        // 若index < 双向链表长度的1/2,则从前先后查找;
        // 否则,从后向前查找。
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }
 
    // 从前向后查找,返回“值为对象(o)的节点对应的索引”
    // 不存在就返回-1
    public int indexOf(Object o) {
        int index = 0;
        if (o==null) {
            for (Entry e = header.next; e != header; e = e.next) {
                if (e.element==null)
                    return index;
                index++;
            }
        } else {
            for (Entry e = header.next; e != header; e = e.next) {
                if (o.equals(e.element))
                    return index;
                index++;
            }
        }
        return -1;
    }
 
    // 从后向前查找,返回“值为对象(o)的节点对应的索引”
    // 不存在就返回-1
    public int lastIndexOf(Object o) {
        int index = size;
        if (o==null) {
            for (Entry e = header.previous; e != header; e = e.previous) {
                index--;
                if (e.element==null)
                    return index;
            }
        } else {
            for (Entry e = header.previous; e != header; e = e.previous) {
                index--;
                if (o.equals(e.element))
                    return index;
            }
        }
        return -1;
    }
 
    // 返回第一个节点
    // 若LinkedList的大小为0,则返回null
    public E peek() {
        if (size==0)
            return null;
        return getFirst();
    }
 
    // 返回第一个节点
    // 若LinkedList的大小为0,则抛出异常
    public E element() {
        return getFirst();
    }
 
    // 删除并返回第一个节点
    // 若LinkedList的大小为0,则返回null
    public E poll() {
        if (size==0)
            return null;
        return removeFirst();
    }
 
    // 将e添加双向链表末尾
    public boolean offer(E e) {
        return add(e);
    }
 
    // 将e添加双向链表开头
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }
 
    // 将e添加双向链表末尾
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }
 
    // 返回第一个节点
    // 若LinkedList的大小为0,则返回null
    public E peekFirst() {
        if (size==0)
            return null;
        return getFirst();
    }
 
    // 返回最后一个节点
    // 若LinkedList的大小为0,则返回null
    public E peekLast() {
        if (size==0)
            return null;
        return getLast();
    }
 
    // 删除并返回第一个节点
    // 若LinkedList的大小为0,则返回null
    public E pollFirst() {
        if (size==0)
            return null;
        return removeFirst();
    }
 
    // 删除并返回最后一个节点
    // 若LinkedList的大小为0,则返回null
    public E pollLast() {
        if (size==0)
            return null;
        return removeLast();
    }
 
    // 将e插入到双向链表开头
    public void push(E e) {
        addFirst(e);
    }
 
    // 删除并返回第一个节点
    public E pop() {
        return removeFirst();
    }
 
    // 从LinkedList开始向后查找,删除第一个值为元素(o)的节点
    // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }
 
    // 从LinkedList末尾向前查找,删除第一个值为元素(o)的节点
    // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
    public boolean removeLastOccurrence(Object o) {
        if (o==null) {
            for (Entry<E> e = header.previous; e != header; e = e.previous) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            for (Entry<E> e = header.previous; e != header; e = e.previous) {
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }
 
    // 返回“index到末尾的全部节点”对应的ListIterator对象(List迭代器)
    public ListIterator<E> listIterator(int index) {
        return new ListItr(index);
    }
 
    // List迭代器
    private class ListItr implements ListIterator<E> {
        // 上一次返回的节点
        private Entry<E> lastReturned = header;
        // 下一个节点
        private Entry<E> next;
        // 下一个节点对应的索引值
        private int nextIndex;
        // 期望的改变计数。用来实现fail-fast机制。
        private int expectedModCount = modCount;
 
        // 构造函数。
        // 从index位置开始进行迭代
        ListItr(int index) {
            // index的有效性处理
            if (index < 0 || index > size)
                throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
            // 若 “index 小于 ‘双向链表长度的一半’”,则从第一个元素开始往后查找;
            // 否则,从最后一个元素往前查找。
            if (index < (size >> 1)) {
                next = header.next;
                for (nextIndex=0; nextIndex<index; nextIndex++)
                    next = next.next;
            } else {
                next = header;
                for (nextIndex=size; nextIndex>index; nextIndex--)
                    next = next.previous;
            }
        }
 
        // 是否存在下一个元素
        public boolean hasNext() {
            // 通过元素索引是否等于“双向链表大小”来判断是否达到最后。
            return nextIndex != size;
        }
 
        // 获取下一个元素
        public E next() {
            checkForComodification();
            if (nextIndex == size)
                throw new NoSuchElementException();
 
            lastReturned = next;
            // next指向链表的下一个元素
            next = next.next;
            nextIndex++;
            return lastReturned.element;
        }
 
        // 是否存在上一个元素
        public boolean hasPrevious() {
            // 通过元素索引是否等于0,来判断是否达到开头。
            return nextIndex != 0;
        }
 
        // 获取上一个元素
        public E previous() {
            if (nextIndex == 0)
            throw new NoSuchElementException();
 
            // next指向链表的上一个元素
            lastReturned = next = next.previous;
            nextIndex--;
            checkForComodification();
            return lastReturned.element;
        }
 
        // 获取下一个元素的索引
        public int nextIndex() {
            return nextIndex;
        }
 
        // 获取上一个元素的索引
        public int previousIndex() {
            return nextIndex-1;
        }
 
        // 删除当前元素。
        // 删除双向链表中的当前节点
        public void remove() {
            checkForComodification();
            Entry<E> lastNext = lastReturned.next;
            try {
                LinkedList.this.remove(lastReturned);
            } catch (NoSuchElementException e) {
                throw new IllegalStateException();
            }
            if (next==lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = header;
            expectedModCount++;
        }
 
        // 设置当前节点为e
        public void set(E e) {
            if (lastReturned == header)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.element = e;
        }
 
        // 将e添加到当前节点的前面
        public void add(E e) {
            checkForComodification();
            lastReturned = header;
            addBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }
 
        // 判断 “modCount和expectedModCount是否相等”,依次来实现fail-fast机制。
        final void checkForComodification() {
            if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        }
    }
 
    // 双向链表的节点所对应的数据结构。
    // 包含3部分:上一节点,下一节点,当前节点值。
    private static class Entry<E> {
        // 当前节点所包含的值
        E element;
        // 下一个节点
        Entry<E> next;
        // 上一个节点
        Entry<E> previous;
 
        /**
         * 链表节点的构造函数。
         * 参数说明:
         *   element  —— 节点所包含的数据
         *   next      —— 下一个节点
         *   previous —— 上一个节点
         */
        Entry(E element, Entry<E> next, Entry<E> previous) {
            this.element = element;
            this.next = next;
            this.previous = previous;
        }
    }
 
    // 将节点(节点数据是e)添加到entry节点之前。
    private Entry<E> addBefore(E e, Entry<E> entry) {
        // 新建节点newEntry,将newEntry插入到节点e之前;并且设置newEntry的数据是e
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
        newEntry.previous.next = newEntry;
        newEntry.next.previous = newEntry;
        // 修改LinkedList大小
        size++;
        // 修改LinkedList的修改统计数:用来实现fail-fast机制。
        modCount++;
        return newEntry;
    }
 
    // 将节点从链表中删除
    private E remove(Entry<E> e) {
        if (e == header)
            throw new NoSuchElementException();
 
        E result = e.element;
        e.previous.next = e.next;
        e.next.previous = e.previous;
        e.next = e.previous = null;
        e.element = null;
        size--;
        modCount++;
        return result;
    }
 
    // 反向迭代器
    public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }
 
    // 反向迭代器实现类。
    private class DescendingIterator implements Iterator {
        final ListItr itr = new ListItr(size());
        // 反向迭代器是否下一个元素。
        // 实际上是判断双向链表的当前节点是否达到开头
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        // 反向迭代器获取下一个元素。
        // 实际上是获取双向链表的前一个节点
        public E next() {
            return itr.previous();
        }
        // 删除当前节点
        public void remove() {
            itr.remove();
        }
    }
 
 
    // 返回LinkedList的Object[]数组
    public Object[] toArray() {
    // 新建Object[]数组
    Object[] result = new Object[size];
        int i = 0;
        // 将链表中所有节点的数据都添加到Object[]数组中
        for (Entry<E> e = header.next; e != header; e = e.next)
            result[i++] = e.element;
    return result;
    }
 
    // 返回LinkedList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
    public <T> T[] toArray(T[] a) {
        // 若数组a的大小 < LinkedList的元素个数(意味着数组a不能容纳LinkedList中全部元素)
        // 则新建一个T[]数组,T[]的大小为LinkedList大小,并将该T[]赋值给a。
        if (a.length < size)
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size);
        // 将链表中所有节点的数据都添加到数组a中
        int i = 0;
        Object[] result = a;
        for (Entry<E> e = header.next; e != header; e = e.next)
            result[i++] = e.element;
 
        if (a.length > size)
            a[size] = null;
 
        return a;
    }
 
 
    // 克隆函数。返回LinkedList的克隆对象。
    public Object clone() {
        LinkedList<E> clone = null;
        // 克隆一个LinkedList克隆对象
        try {
            clone = (LinkedList<E>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
 
        // 新建LinkedList表头节点
        clone.header = new Entry<E>(null, null, null);
        clone.header.next = clone.header.previous = clone.header;
        clone.size = 0;
        clone.modCount = 0;
 
        // 将链表中所有节点的数据都添加到克隆对象中
        for (Entry<E> e = header.next; e != header; e = e.next)
            clone.add(e.element);
 
        return clone;
    }
 
    // java.io.Serializable的写入函数
    // 将LinkedList的“容量,所有的元素值”都写入到输出流中
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();
 
        // 写入“容量”
        s.writeInt(size);
 
        // 将链表中所有节点的数据都写入到输出流中
        for (Entry e = header.next; e != header; e = e.next)
            s.writeObject(e.element);
    }
 
    // java.io.Serializable的读取函数:根据写入方式反向读出
    // 先将LinkedList的“容量”读出,然后将“所有的元素值”读出
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();
 
        // 从输入流中读取“容量”
        int size = s.readInt();
 
        // 新建链表表头节点
        header = new Entry<E>(null, null, null);
        header.next = header.previous = header;
 
        // 从输入流中将“所有的元素值”并逐个添加到链表中
        for (int i=0; i<size; i++)
            addBefore((E)s.readObject(), header);
    }
}
  1. LinkedList 实际上是通过双向链表去实现的。 
    它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。 
  2. 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。 
  3. LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。 
  4. LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。 
  5. 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。 
    总结起来如下表格:
       第一个元素(头部)                 最后一个元素(尾部)
        抛出异常        特殊值            抛出异常           特殊值
插入    addFirst(e)    offerFirst(e)    addLast(e)        offerLast(e)
移除    removeFirst()  pollFirst()      removeLast()      pollLast()
检查    getFirst()     peekFirst()      getLast()         peekLast()
  1. LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:
队列方法       等效方法
add(e)        addLast(e)
offer(e)      offerLast(e)
remove()      removeFirst()
poll()        pollFirst()
element()     getFirst()
peek()        peekFirst()

7.LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:

栈方法        等效方法
push(e)      addFirst(e)
pop()        removeFirst()
peek()       peekFirst()
  1. 无论如何,千万不要通过随机访问去遍历LinkedList!
2.2.2.3.4 LinkedList示例
import java.util.LinkedList;

/*
 * @desc LinkedList测试程序。
 */
public class LinkedListTest {
    public static void main(String[] args) {
        // 测试LinkedList的API
        testLinkedListAPIs();

        // 将LinkedList当作 LIFO(后进先出)的堆栈
        useLinkedListAsLIFO();

        // 将LinkedList当作 FIFO(先进先出)的队列
        useLinkedListAsFIFO();
    }

    /**
     * 将LinkedList当作 FIFO(先进先出)的队列
     */
    private static void useLinkedListAsFIFO() {
        System.out.println("\nuseLinkedListAsFIFO");
        // 新建一个LinkedList
        LinkedList queue = new LinkedList();

        // 将10,20,30,40添加到队列。每次都是插入到末尾
        queue.add("10");
        queue.add("20");
        queue.add("30");
        queue.add("40");
        // 打印“队列”
        System.out.println("queue:" + queue);

        // 删除(队列的第一个元素)
        System.out.println("queue.remove():" + queue.remove());

        // 读取(队列的第一个元素)
        System.out.println("queue.element():" + queue.element());

        // 打印“队列”
        System.out.println("queue:" + queue);
    }

    /**
     * 将LinkedList当作 LIFO(后进先出)的堆栈
     */
    private static void useLinkedListAsLIFO() {
        System.out.println("\nuseLinkedListAsLIFO");
        // 新建一个LinkedList
        LinkedList stack = new LinkedList();

        // 将1,2,3,4添加到堆栈中
        stack.push("1");
        stack.push("2");
        stack.push("3");
        stack.push("4");
        // 打印“栈”
        System.out.println("stack:" + stack);
        // 删除“栈顶元素”
        System.out.println("stack.pop():" + stack.pop());

        // 取出“栈顶元素”
        System.out.println("stack.peek():" + stack.peek());

        // 打印“栈”
        System.out.println("stack:" + stack);
    }

    /*
     * 测试LinkedList中部分API
     */
    private static void testLinkedListAPIs() {
        String val = null;
        //LinkedList llist;
        //llist.offer("10");
        // 新建一个LinkedList
        LinkedList llist = new LinkedList();
        //---- 添加操作 ----
        // 依次添加1,2,3
        llist.add("1");
        llist.add("2");
        llist.add("3");

        // 将“4”添加到第一个位置
        llist.add(1, "4");


        System.out.println("\nTest ", llist.addFirst("10"), llist.removeFirst(), llist.getFirst()"");
        // (01) 将“10”添加到第一个位置。  失败的话,抛出异常!
        llist.addFirst("10");
        System.out.println("llist:" + llist);
        // (02) 将第一个元素删除。        失败的话,抛出异常!
        System.out.println("llist.removeFirst():" + llist.removeFirst());
        System.out.println("llist:" + llist);
        // (03) 获取第一个元素。          失败的话,抛出异常!
        System.out.println("llist.getFirst():" + llist.getFirst());


        //System.out.println("\nTest ",llist.offerFirst( ), pollFirst(), peekFirst()+"");
        // (01) 将“10”添加到第一个位置。  返回true。
        llist.offerFirst("10");
        System.out.println("llist:" + llist);
        // (02) 将第一个元素删除。        失败的话,返回null。
        System.out.println("llist.pollFirst():" + llist.pollFirst());
        System.out.println("llist:" + llist);
        // (03) 获取第一个元素。          失败的话,返回null。
        System.out.println("llist.peekFirst():" + llist.peekFirst());


        //System.out.println("\nTest "addLast(), removeLast(), getLast()"");
        // (01) 将“20”添加到最后一个位置。  失败的话,抛出异常!
        llist.addLast("20");
        System.out.println("llist:" + llist);
        // (02) 将最后一个元素删除。        失败的话,抛出异常!
        System.out.println("llist.removeLast():" + llist.removeLast());
        System.out.println("llist:" + llist);
        // (03) 获取最后一个元素。          失败的话,抛出异常!
        System.out.println("llist.getLast():" + llist.getLast());


        //System.out.println("\nTest "offerLast(), pollLast(), peekLast()"");
        // (01) 将“20”添加到第一个位置。  返回true。
        llist.offerLast("20");
        System.out.println("llist:" + llist);
        // (02) 将第一个元素删除。        失败的话,返回null。
        System.out.println("llist.pollLast():" + llist.pollLast());
        System.out.println("llist:" + llist);
        // (03) 获取第一个元素。          失败的话,返回null。
        System.out.println("llist.peekLast():" + llist.peekLast());


        // 将第3个元素设置300。不建议在LinkedList中使用此操作,因为效率低!
        llist.set(2, "300");
        // 获取第3个元素。不建议在LinkedList中使用此操作,因为效率低!
        System.out.println("\nget(3):" + llist.get(2));

        // ---- toArray(T[] a) ----
        // 将LinkedList转行为数组
        String[] arr = (String[]) llist.toArray(new String[0]);
        for (String str : arr) {
            System.out.println("str:" + str);

            // 输出大小
            System.out.println("size:" + llist.size());
            // 清空LinkedList
            llist.clear();
            // 判断LinkedList是否为空
            System.out.println("isEmpty():" + llist.isEmpty() + "\n");
        }
    }
}

2.2.2.4 List集合相关的面试题

  • ArrayList中的操作不是线程安全的吗?

ArrayList中的操作不是线程安全的!所以,建议在单线程中使用,多线程情况下可以选择CopyOnWriteArrayList或者使用Collections中的synchronizedList方法将其包装成一个线程安全的List。

  • ArrayList、Vector、Linklist集合的异同点 相同点:

Vector、ArrayList和LinkedList都是List的接口的实现类,所以它们三个都有List集合身上的特点.List是有序的(元素存入集合的顺序和取出的顺序一致).ArrayList和Vector都是基于动态的Object数组,他们两个的底层实现其实是类似的。

不同点:

  • ArrayList类主要是实现类,虽然效率高,但是线程不安全。底层用的是Object[]数组存储。从查找的时间复杂度来说它属于O(I)。删除元素的时间复杂度O(n)。

  • LinkedList类主要是用于频繁插入,删除操作,当然它的效率比ArrayList高,所以它的线程安全程度比ArrayList更低,底层使用的是双向链表存储。从查找的时间复杂度来说属于O(n)。插入删除的时间复杂度属于O(I)。

  • Vector属于比较古老的一种类,效率比较低,反之线程比较安全,底层使用的是Object[]数组存储。

  • ArrayList和Vector,两个其中ArrayList是非线程安全的而Vector是线程安全的,但是ArrayList的效率比Vector快的多,并且我们一般常用的都ArrayList,而出现多线程时我们采用Vector, ArrayList的add方法的扩容原理是扩为原来的1.5倍而Vector则是扩为原来的2倍。

  • ArrayList和LinkedList相比,ArrayList是动态基于动态的Object数组而LinkedList是链表,ArrayList在指定增加位置、删除和修改没有LinkedList快,在指定位置增加时候很麻烦需要先判断数组是否已满,如果满了还要扩容,然后将指点位置的元素和它后面的元素全部往后移一位,而LinkedList则直接将指定位置的链表断开将元素插入让指针指向对应的地方则可以,删除和修改ArrayList也可能要涉及到元素移动问题除非是最后一位,而LinkedList因为是链表则不涉及这个问题。但是ArrayList的查找速度比LinkedList快,因为ArrayList是连续的内存而LinkedList因为是链表所以它是连续的内存块。 ArrayList:数组实现,查询快,增删慢,轻量级;(线程不安全)
    LinkedList:双向链表实现,增删快,查询慢 (线程不安全)
    底层是一种双向循环链表。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。

双向循环链表的查询效率低但是增删效率高。

  • Vector和LinkedList 第一个不同就是Vector是线程安全的而LinkedList是非线程安全的因为Vector和ArrayList底层实现基本类似,所以增删改上ArrayList有的问题Vector也有,并且Vector的查找速度也是比LinkedList快因为他也是连续的内存。

2.2.3 Set

HashSet

HashSet常用方法: public boolean contains(Object o) :如果set包含指定元素,返回true
public Iterator iterator()返回set中元素的迭代器
public Object[] toArray() :返回包含set中所有元素的数组public Object[] toArray(Object[] a) :返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型
public boolean add(Object o) :如果set中不存在指定元素,则向set加入
public boolean remove(Object o) :如果set中存在指定元素,则从set中删除
public boolean removeAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true 实现Set接口的HashSet,依靠HashMap来实现的。 我们应该为要存放到散列表的各个对象定义hashCode()和equals()。

LinkedHashSet

TreeSet

扩展Collection接口

无序集合,不允许存放重复的元素;

允许使用null元素
对 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的实现 Set -->hashSet-->linkedHashSet SortedSet TreeSet HashSet 的后台有一个HashMap; 初始化后台容量; 只不过生成一个HashSet的话,系统只提供key的访问; 如果有两个Key重复,那么会覆盖之前的; 实现类 : HashSet:equals返回true,hashCode返回相同的整数;哈希表;存储的数据是无序的。
LinkedHashSet:此实现与HashSet的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。存储的数据是有序的。 HashSet类 HashSet类直接实现了Set接口,其底层其实是包装了一个HashMap去实现的。HashSet采用HashCode算法来存取集合中的元素,因此具有比较好的读取和查找性能。 HashSet的特征: 不仅不能保证元素插入的顺序,而且在元素在以后的顺序中也可能变化(这是由HashSet按HashCode存储对象(元素)决定的,对象变化则可能导致HashCode变化) **HashSet是线程非安全的
**HashSet元素值可以为NULL

HashSet的equals和HashCode:

前面说过,Set集合是不允许重复元素的,否则将会引发各种奇怪的问题。那么HashSet如何判断元素重复呢?
HashSet需要同时通过equals和HashCode来判断两个元素是否相等,具体规则是,如果两个元素通过equals为true,并且两个元素的hashCode相等,则这两个元素相等(即重复)。
所以如果要重写保存在HashSet中的对象的equals方法,也要重写hashCode方法,重写前后hashCode返回的结果相等(即保证保存在同一个位置)。所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
试想如果重写了equals方法但不重写hashCode方法,即相同equals结果的两个对象将会被HashSet当作两个元素保存起来,这与我们设计HashSet的初衷不符(元素不重复)。
另外如果两个元素哈市Code相等但equals结果不为true,HashSet会将这两个元素保存在同一个位置,并将超过一个的元素以链表方式保存,这将影响HashSet的效率。
如果重写了equals方法但没有重写hashCode方法,则HashSet可能无法正常工作,比如下面的例子。

LinkedHashSet的特征
LinkedHashSet是HashSet的一个子类,LinkedHashSet也根据HashCode的值来决定元素的存储位置,但同时它还用一个链表来维护元素的插入顺序,插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。查看LinkedHashSet的源码发现它是样的

各种Set集合性能分析

HashSet和TreeSet是Set集合中用得最多的I集合。HashSet总是比TreeSet集合性能好,因为HashSet不需要额维护元素的顺序。 LinkedHashSet需要用额外的链表维护元素的插入顺序,因此在插入时性能比HashSet低,但在迭代访问(遍历)时性能更高。因为插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。 EnumSet元素是所有Set元素中性能最好的,但是它只能保存Enum类型的元素