List/Set/Map的区别

集合

Java集合类存放在java.util包中,是一个用来存放对象的容器

  • 集合只能存放对象。比如你存入一个int型数据66放入集合中,其实它是自动转换成Integer类后存入的,Java中每一种基本数据类型都有对应的引用类型;
  • 集合存放的都是对象的引用,而非对象本身。所以我们称集合中的对象就是集合中对象的引用。对象本身还是放在堆内存中;
  • 集合可以存放不同类型,不限数量的数据类型

Collection接口

Collection接口

从图上的得知:

  • List , Set, Map皆为接口,前两个继承至Collection接口,Map为独立接口
  • Set下分为HashSet,LinkedHashSet,TreeSet
  • List下分为ArrayList,Vector,LinkedList
  • Map下分为Hashtable,LinkedHashMap,HashMap,TreeMap
  • Collection接口下还有个Queue接口,有PriorityQueue类
  • SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的

List(有序,可重复)

ArrayListLinkedListVector
底层数据结构是数组,查询快,增删慢底层数据结构是链表,查询慢,增删快底层数据结构是数组,查询快,增删慢
线程不安全,效率高线程不安全,效率高线程安全,效率低
常用不常用貌似好像被淘汰了,不用

Set(无序,唯一)

HashSetLinkedHashSetTreeSet
底层数据结构是哈希表(无序,唯一)底层数据结构是链哈希表(FIFO插入有序,唯一)底层数据结构是红黑树。(唯一,有序)
如何来保证元素唯一性?如何保证元素排序的呢?
依赖两个方法:hashCode()和equals()由链表保证元素有序/由哈希表保证元素唯一自然排序/比较器排序
如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定
关于Collection的使用

是否唯一

是:Set

是否排序

是:TreeSet或LinkedHashSet
否:HashSet

否:List

是否安全

是:Vector
否:ArrayList或LinkedList

Map接口

Map接口

了解:
Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象。其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素还可以是数组一样。

Map

Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。

  • TreeMap是有序的,HashMap和HashTable是无序的。

  • Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。

  • Hashtable是线程安全的,HashMap不是线程安全的。

  • HashMap效率较高,Hashtable效率较低。
    如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。

  • Hashtable不允许null值,HashMap允许null值(key和value都允许)

  • 父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap

ArrayList

ArrayList接口

看部分ArrayList源码(以下源码来自于JDK1.7)

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{	
	//序列化ID
    private static final long serialVersionUID = 8683452581122892189L;
	//默认的初始容量
    private static final int DEFAULT_CAPACITY = 10;
	//用于空实例的共享空数组实例。
    private static final Object[] EMPTY_ELEMENTDATA = {};
	//ArrayList的容量是此数组缓冲区的长度。
	//添加第一个元素时,任何具有elementData == EMPTY_ELEMENTDATA的空ArrayList都将扩展为DEFAULT CAPACITY
    private transient Object[] elementData;
	//ArrayList的大小(它包含的元素数)。
    private int size;
    //要分配的最大数组大小。一些VMs在数组中保留一些标题字尝试分配更大的数组可能会导致,Out0fMemoryError:请求的数组大小超出WM限制
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	//构造一个具有指定初始容量的空列表。
	//@param  initialCapacity  列表的初始容量
	//@throws IllegalArgumentException 如果指定的初始容量为负
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
	//构造一个初始容量为10的空列表。
    public ArrayList() {
        super();
        this.elementData = EMPTY_ELEMENTDATA;
    }
    //构造一个包含指定集合的​​元素的列表,其顺序由集合的迭代器返回。
    //@param c 将其元素放入此列表的集合
    //@throws NullPointerException 如果指定的集合为null
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }
 }
  1. ArrayList数据结构

底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据,也就是说对ArrayList类的实例的所有的操作底层都是基于数组的;
在这里插入图片描述

  1. ArrayList继承结构
    在这里插入图片描述
    ArrayList implements Serizlizable(接口):
    实现序列化接口,表明该类可以被序列化(让类变成字节流传输,从字节流还原成类)
    ArrayList implements Cloneable(接口):
    实现Cloneable接口,就可以使用Object.Clone()方法,意味着克隆
    ArrayList implements RandomAccess(接口):
    实现RandomAccess接口,即 快速随机访问;标记性接口,用来快速随机存取,有关效率的问题,如果实现该接口,那么用for循环来便利,性能更好,比如:ArrayList;如果没有实现该接口,使用Iterator来迭代,性能更高,比如LinkedList;
    ArrayList implements List(接口)
    ArrayList extends AbstractList(抽象类)
    疑问:为什么父类AbstractList实现了List接口,子类ArrayList又去实现了一遍?

和Vector不同的是,ArrayList中的操作是不安全的,建议在单线程中使用,而在多线程选择Vector和CopyOnWriteArrayList

  1. ArrayList构造函数
  • 无参构造函数
/**
 * Constructs an empty list with an initial capacity of ten.
 * 构造一个初始容量为10的空列表。
 */
public ArrayList() {
    super(); 调用父类中的无参构造方法,父类中的是个空的构造方法
    this.elementData = EMPTY_ELEMENTDATA; EMPTY_ELEMENTDATA:是个空的Object[], 将elementData初始化,elementData也是个Object[]类型。空的Object[]会给默认大小10,等到添加元素的时候会赋值。
}

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

  • 有参构造(容量)
//构造一个具有指定初始容量的空列表。
//@param  initialCapacity  列表的初始容量
//@throws IllegalArgumentException 如果指定的初始容量为负
public ArrayList(int initialCapacity) {
    super();  //调用父类中的构造方法
    if (initialCapacity < 0)    //判断如果自定义大小的容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity]; //将自定义的容量大小当成初始化elementData的大小
}
  • 有参构造(元素)
	//构造一个包含指定集合的​​元素的列表,其顺序由集合的迭代器返回。
    //@param c 将其元素放入此列表的集合
    //@throws NullPointerException 如果指定的集合为null
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();   //转换为数组(将数组的地址的赋给elementData)
        size = elementData.length;   //数组中的数据个数
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)//每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用ArrayList中的方法去改造一下
            elementData = Arrays.copyOf(elementData, size, Object[].class);//深拷贝
    }
  1. ArrayList扩容机制

设计到ArrayList的扩容机制,其实就是add的过程,共涉及到4个方法分别是add(),ensureCapacityInternal(),ensureExplicitCapacity(),grow() 其中grow为最为重要的,也是核心所在(hugeCapacity(),Math.max(),Arrays.copyOf()不算),我们现在来看一下

  • add(E e) //默认直接在末尾添加元素
  • add(int index, E element) //在特定位置添加元素,也就是插入元素
  • addAll(Collection<? extends E> c)
  • addAll(int index, Collection<? extends E> c)

add(E e)

add(E e) //默认直接在末尾添加元素

     /**
     * Appends the specified element to the end of this list.//将指定的元素追加到此列表的末尾。
     *
     * @param e element to be appended to this list//要添加到此列表的元素
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    	//确定内部容量是否足够(size是数组中数据的个数),因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.数组length是否够用
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //在数据中正确的位置上放上元素e,并且size++
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal (int minCapacity) //确定内部容量的方法

private static final Object[] EMPTY_ELEMENTDATA = {};

private transient Object[] elementData;

private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
	//判断初始化的elementData是不是空的数组,也就是没有长度
    if (elementData == EMPTY_ELEMENTDATA) {
    //如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小,但是带这里,还没有真正的初始化这个elementData的大小
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

ensureExplicitCapacity(int minCapacity)

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//minCapacity如果大于了实际elementData的长度,那么就说明elementData数组的长度不够用,不够用那么就要增加elementData的length。
//解释minCapacity:
// 1. 由于elementData初始化时是空的数组,那么第一次add的时候,minCapacity=size+1;也就minCapacity=1,在上一个方法(确定内部容量ensureCapacityInternal)就会判断出是空的数组,就会给将minCapacity=10,到这一步为止,还没有改变elementData的大小。
//2. elementData不是空的数组了,那么在add的时候,minCapacity=size+1;也就是minCapacity代表着elementData中增加之后的实际数据个数,拿着它判断elementData的length是否够用,如果length不够用,那么肯定要扩大容量,不然增加的这个元素就会溢出

grow(int minCapacity) 重点

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length; // //将扩容前的elementData大小给oldCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);newCapacity就是1.5倍的oldCapacity(使用位运算符)
    if (newCapacity - minCapacity < 0)//在elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,所以这个判断成立,在这里就是真正的初始化elementData的大小了,就是为10.前面的工作都是准备工作
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)如果newCapacity超过了最大的容量限制,就调用hugeCapacity,也就是将能给的最大值给newCapacity
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);//确定新的容量,复制copyOf数组,改变容量大小
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
        //如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用minCapacity来判断了
        //Integer.MAX_VALUE:2147483647   MAX_ARRAY_SIZE:2147483639  也就是说最大也就能给到第一个数值。还是超过了这个限制,就要溢出了。相当于arraylist给了两层防护
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

        在grow()方法中,首先通过将element的容量扩容至原来的1.5倍(有时是小于1.5倍)(同时这里也是ArrayList的自动扩容机制),接着判断如果扩容后的容量仍旧小于minCapacity,就把minCapacity作为新容量,否则什么也不做,接着判断如果新容量大于最大数组容量的话,继续调用hugeCapacity()进行扩容(一般调用这个的话会爬出异常),最后通过Arrays.copyOf()进行数组的扩容。

add(int index, E element) //在特定位置添加元素,也就是插入元素

public void add(int index, E element) {
	//检查index也就是插入的位置是否合理。
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //用来在插入元素之后,要将index之后的元素都往后移一位,
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //在目标位置上存放元素
    elementData[index] = element;
    //size增加1
    size++;
}
//add和addAll使用的rangeCheck版本。
private void rangeCheckForAdd(int index) {
	//插入的位置肯定不能大于size 和小于0
    if (index > size || index < 0)
    //如果是,就报这个越界异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

当我们调用add方法时,实际上的函数调用如下:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值