java中常用的几个集合类

//TODO

//未完成
//不过先发了吧

//包含Collection(集合),List,Set,Map(图),以及其Iterator,Comparator ,Cloneable,还有常用的具体实现类
//List<List<String>>集合的嵌套使用
//1、是否允许空
//2、是否允许重复数据
//3、是否有序,有序的意思是读取数据的顺序和存放数据的顺序是否一致
//4、是否线程安全

JAVA SE 8官方文档 

JAVA官方文档中集合类的关系

目录

概览

瞎说几句

对第一张图(集合类的关系)的解析

具体的几个集合类(List,Set,Map)

List 

ArrayList

LinkedList

Set

HashSet

TreeSet

Map

HashMap

TreeMap


概览

瞎说几句

JAVA中有许多的集合,常用的有List,Set,Queue,Map。

其中List,Set,Queue都是Collection(集合),其每个元素都是单独的一个对象,如List<String>,Set<Integer>等,String和Integer就是单独的一个对象。

而Map是一种图,其每个元素都是两个对象一一对应,如Map<Integer, String>中的Integer是键 (key),String是这个键所对应的值(value)。每个元素都是一对Integer和String

tip 1: List<String>中<>的内容表示其中元素的类型,是泛型的一种使用。

tip 2: Integer是一个对象(可从首字母大写的命名方式中看出),可以粗略地将其视作int。

tip 3: 由于这些集合类的元素必须是对象或者由对象构成,所以不能直接使用int这种简单数据类型将Map定义为Map<int, String>,而应该使用Integer将Map定义为Map<Integer, String>。

tip 4: 不能直接使用简单数据类型的更深层次的原因在于:

         集合类(比如Set)在进行各种 "操作" ( 如contains()) 时都会调用元素本身提供的 "方法" ( 如hashCode()和equals()),而不是由集合类自身去实现这些 "方法"。这就要求如果某人想要用这个集合执行某些 "操作",那就必须在要加入集合的元素中实现相应的 "方法"。

         由于简单数据类型 (如int),只是单纯的一个数值,而无法在其中实现方法,所以应该使用实现了各种所需"方法"的类 (如Integer) 作为元素。

 

对第一张图(集合类的关系)的解析

从第二行 java.util.AbstractCollection<E> (implements java.util.Collection<E>) 看起

 

接着看 java.util.AbstractMap<K,V> (implements java.util.Map<K,V>) 这一行

AbstractMap<K,V>是一个抽象类,实现了java.util.Map<K,V>接口,具有图的功能(即key和value的一一对应)。其下有几个具体的实现类,分别是:EnumMap<K,V>,HashMap<K,V>,LinkedHashMap<K,V>,IdentityHashMap<K,V>,TreeMap<K,V>,WeakHashMap<K,V>

 

具体的几个集合类(List,Set,Map)

List 

List(列表),相当于数组。长度可变,元素存放有一定的顺序,下标从0开始。

tip

在JDK中,List作为接口,本身已经声明好了所有的方法(比如add(), contains()......),所以不管是选择ArrayList还是LinkedList,完成各种操作的时候依然是使用List中已经声明过的这一套方法,对使用者来说没有区别。

二者只是内部实现逻辑不同,所以在不同的应用场景下会有不同的效率。

ArrayList  

先回答四个问题:

是否允许空元素(即null)
是否允许重复数据
是否有序
是否线程安全
//如何使用ArrayList创建一个List
//不指定存放的元素类型

//默认容量为10
List list = new ArrayList();

//容量为6
List list = new ArrayList(6);

 Array即数组,顾名思义,ArrayList是用数组实现的一个List(列表)。它实现了List接口声明的的所有方法,而且允许加入包括null在内的所有元素。除了实现列表接口之外,这个类还提供了一些方法来操作内部用于存储列表的数组的大小。

在每一个ArrayList实例中,都有专门保存容量的capacity属性,我在jdk1.8中找到了它的默认容量大小是

DEFAULT_CAPACITY = 10,如下是jdk1.8中对DEFAULT_CAPACITY的定义

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    /*
        some code
    */

    private static final int DEFAULT_CAPACITY = 10;

    /*
        some code
    */
}

接下来讨论ArrayList中对capacity的控制

举个栗子,ArrayList的add()方法,首先需要说明几个变量的含义,我从jdk1.8中找来了这些变量的声明语句包括其注释:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    /*
        some code
    */

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 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;

    /*
        some code
    */
}

其中各个变量的含义如下:

private static final int DEFAULT_CAPACITY = 10;

是默认的容量大小。当不指定容量大小的时候,就是用默认容量10

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

这两个作用类似,在创建一个新的ArrayList对象的时候引用。初始化ArrayList中的数组(即elementData变量)。

两个Object数组本身并没有区别,定义这样没区别的两个是为了区分使用的是哪种构造方法。在ArrayList()方法中用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而在ArrayList(int initialCapacity)和ArrayList(Collection<? extends E> c)方法中用的是EMPTY_ELEMENTDATA。

transient Object[] elementData;

tip:这里有一个transient关键字,是关于序列化和反序列化的。简单说一下,序列化就是把对象转换成字节序列的形式,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写到数据库或文件中,也可以用于网络传输。一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。序列化之后再反序列化就能得到原来的java对象。而transient修饰的数据将不会被自动序列化。但是,elementData会被writeObject方法手动序列化,这个不多说了。

这个变量就是ArrayList中的Array,ArrayList中的元素就是保存在这里。

private int size;

数组中元素的个数,当我们调用size()方法的时候返回的就是这个size。跟容量(capacity)是两个不同的概念,不要混淆。

protected transient int modCount = 0;

还有一个继承自其父类AbstractList的属性,modCount,表示发生结构化改变的次数。关于结构化改变(Structural modifications),AbstractList中是这样子解释的

The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.

意即当size改变了,这个modCount就会+1。

测试代码是这样的:

public class Test{
  public static void main(String[] args) {
    List<String> list = new ArrayList();
    for(int i = 0; i <= 10; i++){
        //向list中依次添加“ii”。
        //例如,当i为0时,向list添加"00";当i为1时,向list添加"11"。
        StringBuilder sb = new StringBuilder();
        sb.append(i)
          .append(i);
        list.add(sb.toString());
    }
  }
}

运行结果是ArrayList的size为11,容量为15

for循环结束之后的ArrayList​​​​

 

下面是ArrayList在执行add()方法的时候所涉及的方法,我给放在一个页面里方便查找。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    /*
        some code
    */

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /*
        some code
    */

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

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

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    /*
        some code
    */

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

    /*
        some code
    */

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

    /*
        some code
    */
}

当i = 0时,此时是第一次向list中添加元素,elementData容量不足,需要加大elementData的容量。初始的list是一个空的Object[],依次调用add(), ensureCapacityInternal(), ensureExplicitCapacity(), grow(), calculateCapacity()方法。

在calculateCapacity()方法中,由于list是由ArrayList的无参构造方法构造的,所以elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的结果为true,执行语句:return Math.max(DEFAULT_CAPACITY, minCapacity);会得到DEFAULT_CAPACITY即10。

返回的结果交给ensureExplicitCapacity(),modCount先自增一次,表示ist必然会做一次结构性修改。此时minCapacity - elementData.length > 0结果为true(nimCapacity = 10, elementData.length = 0),执行grow(minCapacity);

在grow()方法中,oldCapacity = 0,newCapacity = 0,minCapacity = 10。(oldCapacity >> 1相当于oldCapacity / 2)故会进入if (newCapacity - minCapacity < 0)条件判断体中的语句,执行newCapacity = minCapacity,令newCapacity = 10,再执行Array.copyOf()方法,将elementData扩充到容量为10。

之后,回到add()方法体中,elementData[size++] = e; 将elemtntData[0]的值赋为"00",然后size再将赋值为1。

可见,扩容发生在grow()方法中,而在ensureExplicitCapacity()方法中决定是否要扩容。

i = 1~9的过程中,elementData的容量都没有发生改变。不做叙述。

当i = 10时,容量为10的elementData已经被填充满了,需要再次扩容,经过之前提到的方法调用顺序,得到新的容量应该为15。进行扩容。

 

Set

不包含重复元素的集合。更确切地说,是不同时包含使得e1.equals(e2)成立的e1和e2(因为e1与e2的equals()逻辑可以由使用者自己定义)。并且最多包含一个空元素。这个接口是数学集合的一个抽象建模。
注意:如果可变的(mutable)对象用作集合元素,则必须非常小心。当一个对象是集合中的元素时,以影响equals()比较结果的方式更改对象的值,则无法预测集合的行为。有一个特殊情况是,不允许一个集合作为一个元素来包含它自己。所以还是尽量使用immutable的对象作为Set的元素。

HashSet

先回答四个问题:

是否允许空元素(即null)
是否允许重复数据
是否有序
是否线程安全
//如何使用HashSet创建一个Set
//不指定存放的元素类型

//默认容量为16
Set set = new HashSet();

//容量为6
Set set = new HashSet(6);

HashSet是基于HashMap实现的,所以需要先理解HashMap。但是我希望这篇文章是List,Set,Map的顺序,并且并不打算换序,所以还不理解HashMap的点这里跳转到后面。

在HashSet中,保存数据的变量名为map:

private transient HashMap<E,Object> map; 

一个HashMap,map的key是E类型,即HashSet中的元素;每个key的value是Object类型,这是HashSet类中定义的一个常量,名为PRESENT

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

这只是为了使用HashMap而创建的一个Object对象,用来占掉value的位置,无其他意义 。

HashSet不保证集合的迭代顺序;具体来说,它不保证顺序将随时间保持不变(除非你自己实现一个迭代器去保证迭代顺序)。它的一些基本操作(add()、remove()、contains()和size())的时间复杂度是常量级的,前提是散列函数在存储桶中正确地分散元素(即要把hashCode()写好,让equals()方法判定为相等的两个对象的hashCode也相等,尽量让equals()方法判定为不等的两个对象的hashCode不等)。

迭代此集合需要与哈希集实例的大小(即HashSet中元素的个数)加上支持哈希映射实例的“容量”(存储桶数)之和成比例的时间。因此,如果迭代性能很重要,那么不要将初始容量设置得太高(或者负载系数设置得太低)。

TreeSet

先回答四个问题:

是否允许空元素(即null)
是否允许重复数据
是否有序
是否线程安全
//如何使用TreeSet创建一个Set
//不指定存放的元素类型

//无参构造方法
Set set = new TreeSet();

同样是借助Map实现的,不过这次是TreeMap。还不理解TreeMap的点这里跳转到后面。

基于TreeMap的NavigableSet实现。元素是使用它们的自然顺序来排序的,或者通过在设置的创建时提供的比较器来排序的,这取决于使用的是哪个构造函数。

此实现为基本操作(add()、remove()和contains())提供了保证的log(n)时间成本。

注意,如果要正确实现集合接口,集合维护的顺序(无论是否提供显式比较器)必须与equals一致。(请参阅Comparable或Comparator以获得与equals一致的精确定义。)这是因为集合接口是根据equals操作定义的,但TreeSet实例使用其CompareTo(或Compare)方法执行所有元素比较,因此从TreeSet的角度来看,该TreeSet认为相等的两个元素应该是equals()方法返回true的两个元素。这个TreeSet的行为是清晰的,即使其中元素的顺序与equals()方法不一致;它只是不遵守Set接口的总规约(即spec)。

注意,这个实现是不同步的。如果多个线程同时访问一个树集,并且至少有一个线程修改了该集,则必须在外部对其进行同步。这通常是通过在自然封装集合的某个对象上进行同步来完成的。如果不存在此类对象,则应使用collections.synchronizedSortedSet方法“包装”集合。这最好在创建时完成,以防止意外的对集合的非同步访问:

SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));

Map

将key映射到value的对象。Map不能包含重复的key;每个key最多可以映射到一个value

HashMap

TreeMap

 

 

 

  • 32
    点赞
  • 145
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值