java中常用的几个集合类

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

JAVA SE 8官方文档

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

概览

瞎说几句

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

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

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

tip 1: List中<>的内容表示其中元素的类型,是泛型的一种使用。
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 (implements java.util.Collection) 看起

  • AbstractCollection是一个抽象类,实现了Collection接口,这个抽象类实现了接口中定义的部分方法,同时也有一些抽象方法交由AbstractCollection的子类去实现。
  • 然后看AbstractCollection的子类,共有四个。分别是AbstractListAbstractQueueAbstractSetArrayDeque
  • AbstractList:抽象类,继承于AbstractCollection,实现了List接口。其下有一些具体实现类如LinkedListArrayList等。
  • AbstractQueue:抽象类,继承于AbstractCollection,实现了Queue接口。其下有一个具体实现类PriorityQueue
  • AbstractSet:抽象类,继承于AbstractCollection,实现了Set接口。其下有一些具体实现类如EnumSetHashSetLinkedHashSet(是HashSet的子类),TreeSet
  • ArrayDeque:AbstractCollection的一个具体实现类。是一个双端队列。内部使用数组来进行元素存储,可以高效地进行元素查找和尾部插入取出,可用作队列,双端队列和栈,性能极佳。

接着看 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。进行扩容。

LinkedList

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。

在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

HashMap 的原理
HashMap 的内部可以看做数组+链表的复合结构。数组被分为一个个的桶(bucket)。哈希值决定了键值对在数组中的寻址。具有相同哈希值的键值对会组成链表。需要注意的是当链表长度超过阈值(默认是8)的时候会触发树化,链表会变成树形结构。
把握HashMap的原理需要关注4个方法:hash、put、get、resize。

  • hash方法
    将 key 的 hashCode 值的高位数据移位到低位进行异或运算。这么做的原因是有些 key 的 hashCode 值的差异集中在高位,而哈希寻址是忽略容量以上高位的,这种做法可以有效避免哈希冲突。

  • put 方法
    put 方法主要有以下几个步骤:
    通过 hash 方法获取 hash 值,根据 hash 值寻址。
    如果未发生碰撞,直接放到桶中。
    如果发生碰撞,则以链表形式放在桶后。
    当链表长度大于阈值后会触发树化,将链表转换为红黑树。
    如果数组长度达到阈值,会调用 resize 方法扩展容量。

  • get方法
    get 方法主要有以下几个步骤:
    通过 hash 方法获取 hash 值,根据 hash 值寻址。
    如果与寻址到桶的 key 相等,直接返回对应的 value。
    如果发生冲突,分两种情况。如果是树,则调用 getTreeNode 获取 value;如果是链表则通过循环遍历查找对应的 value。

  • resize 方法
    resize 做了两件事:
    将原数组扩展为原来的 2 倍
    重新计算 index 索引值,将原节点重新放到新的数组中。这一步可以将原先冲突的节点分散到新的桶中。

TreeMap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java常用的集合有以下几种: 1. List: 有序集合,元素可以重复,常用实现类有ArrayList和LinkedList。 2. Set: 无序集合,元素不可重复,常用实现类有HashSet和TreeSet。 3. Map: 键值对集合,键不可重复,常用实现类有HashMap和TreeMap。 4. Queue: 队集合,先进先出,常用实现类有LinkedList和PriorityQueue。 5. Stack: 栈集合,后进先出,只有一个实现类是Stack。 List是一种有序集合,可以允许元素重复,同时提供了很多常用的方法,例如add、remove、get、contains等,常用的实现类是ArrayList和LinkedList。ArrayList是基于数组实现的,适用于访问较多的场景;LinkedList是基于链表实现的,适用于增删较多的场景。 Set是一种无序集合,元素不可重复,常用的实现类有HashSet和TreeSet。HashSet是通过哈希表实现的,可以快速查找元素;TreeSet是基于红黑树实现的,可以自动排序,但是要求元素可比较。 Map是键值对集合,键不可重复,常用实现类有HashMap和TreeMap。HashMap是通过哈希表实现的,可以快速查找键;TreeMap是基于红黑树实现的,键可以自动排序,但是要求键可比较。 Queue是队集合,先进先出,常用实现类有LinkedList和PriorityQueue。LinkedList是基于链表实现的,可以用作普通队和双端队;PriorityQueue是基于堆实现的,可以用作优先队。 Stack是栈集合,后进先出,只有一个实现类是Stack。它继承了Vector,在Java也可以用Deque代替。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值