Collection对比总结

Collection:

List:

ArrayList:

线性表,底层是数组实现

继承:AbstractList

实现了Seriazable,所以可用于json的序列化操作

Iterator:进行轮训的时候尽量使用轮询的方法,,内部优化了非空判断,以及异常处理,for循环遍历有风险,可读,不要用于其他方面

优点:尾插效率高,支持随机访问

扩容:

在add方法里面了,首先会判断容量大小,如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值,  ensureExplicitCapacity方法可以判断是否需要扩容,如果最小需要空间比elementData的内存空间要大,则需要扩容。

.下面是扩容:先扩容,在检查最小值和容器是否溢出,最后赋值数组到新数组里。

private void grow(int minCapacity) {
          // 获取到ArrayList中elementData数组的内存空间长度
          int oldCapacity = elementData.length;
         // 扩容至原来的1.5倍
         int newCapacity = oldCapacity + (oldCapacity >> 1);
         // 再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,
          // 不够就将数组长度设置为需要的长度
         if (newCapacity - minCapacity < 0)
             newCapacity = minCapacity;
         //若预设值大于默认的最大值检查是否溢出
         if (newCapacity - MAX_ARRAY_SIZE > 0)
             newCapacity = hugeCapacity(minCapacity);
         // 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
         // 并将elementData的数据复制到新的内存空间
         elementData = Arrays.copyOf(elementData, newCapacity);
     }

ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。

两个arraylist相比受否相等,使用equals

     public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof List))
            return false;
 
        ListIterator<E> e1 = listIterator();
        ListIterator<?> e2 = ((List<?>) o).listIterator();
        while (e1.hasNext() && e2.hasNext()) {
            E o1 = e1.next();
            Object o2 = e2.next();
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }
        return !(e1.hasNext() || e2.hasNext());

https://blog.csdn.net/weixin_40247263/article/details/79572636

LinkedList

双链表。是一个直线型的链表结构。

因此其顺序访问的效率非常高,而随机访问的效率就比较低了

继承了AbstractSequentialList抽象类:在遍历LinkedList的时候,官方更推荐使用顺序访问,也就是使用我们的迭代器。(因为LinkedList底层是通过一个链表Node来实现的)(虽然LinkedList也提供了get(int index)方法,但是底层的实现是:每次调用get(int index)方法的时候,都需要从链表的头部或者尾部进行遍历,每一的遍历时间复杂度是O(index),而相对比ArrayList的底层实现,每次遍历的时间复杂度都是O(1)。所以不推荐通过get(int index)遍历LinkedList。至于上面的说从链表的头部后尾部进行遍历:官方源码对遍历进行了优化:通过判断索引index更靠近链表的头部还是尾部来选择遍历的方向)(所以这里遍历LinkedList推荐使用迭代器)。

实现了List接口。(提供List接口中所有方法的实现)

实现了Cloneable接口,它支持克隆(浅克隆),底层实现:LinkedList节点并没有被克隆,只是通过Object的clone()方法得到的Object对象强制转化为了LinkedList,然后把它内部的实例域都置空,然后把被拷贝的LinkedList节点中的每一个值都拷贝到clone中。(后面有源码解析)

实现了Deque双端队列接口。实现了Deque所有的可选的操作

实现了Serializable接口。表明它支持序列化。(和ArrayList一样,底层都提供了两个方法:readObject(ObjectInputStream o)、writeObject(ObjectOutputStream o),用于实现序列化,底层只序列化节点的个数和节点的值)。

Node类:前后节点,双向链表的核心

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

它的容量是随着元素的个数的变化而动态变化的。而ArrayList底层是通过数组来存储新添加的元素的,所以我们可以为ArrayList设置初始容量(实际设置的数组的大小)。

整体分析下来,其实LinkedList还是比较简单的,上面对一些重要的相关源码进行了分析,主要重点如下:

#1.LinkedList底层数据结构为双向链表,非同步。

#2.LinkedList允许null值。

#3.由于双向链表,顺序访问效率高,而随机访问效率较低。

#4.注意源码中的相关操作,主要是构建双向链表。

ArrayList和LinkList区别:

1、存储空间连续与否,逻辑上连续物理上不连续

2、顺序表可以随机访问、尾部插入删除简单。删除插入慢

3、链表不支持随机访问,效率低、删除插入快

Vector

内部数组实现

它和ArrayList的最大的不同是它是线程安全的,这点在Vector源码中也有说明。Vector源码中注释有这么一句“Vector is synchronized. If a thread-safe implementation is not needed, it is recommended to use ArrayList in place of Vector”,意为“Vector 是同步的。如果不需要线程安全的实现,推荐使用ArrayList代替Vector”。

RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。也就是说,实现了这个接口的集合是支持 快速随机访问 策略的。如果是实现了这个接口的 List,那么使用for循环的方式获取数据会优于用迭代器获取数据。

  • Vector<E>:说明它支持泛型
    extends AbstractList<E> implements List<E>:说明它继承了extends AbstractList<E>,并实现了implements List<E>。
    implements RandomAccess:表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。下面是JDK1.8中对RandomAccess的介绍:
  • implements Cloneable:表明其可以调用clone()方法来返回实例的field-for-field拷贝。
  • implements java.io.Serializable:表明该类是可以序列化的。
  • Vector底层是数组。
  • 有序。Vector底层是数组,数组是有序的。
  • 可重复。数组是可重复的。
  • 随机访问效率高,增删效率低。通过索引可以很快的查找到对应元素,而增删元素许多元素的位置都要改变。
  • 线程安全。很多方法都是synchronized的。

stack

又名后进先出表,它是一种运算受限的线性表。 其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。

栈(stack)又名后进先出表,它是一种运算受限的线性表。 其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。

经典应用-逆波兰表达式法

Set

HashSet

①:实现了Serializable接口,表明它支持序列化。
②:实现了Cloneable接口,表明它支持克隆,可以调用超类的clone()方法进行浅拷贝。
③继承了AbstractSet抽象类,和ArrayList和LinkedList一样,在他们的抽象父类中,都提供了equals()方法和hashCode()方法。它们自身并不实现这两个方法,(但是ArrayList和LinkedList的equals()实现不同。你可以看我的关于ArrayList这一块的源码解析)这就意味着诸如和HashSet一样继承自AbstractSet抽象类的TreeSet、LinkedHashSet等,他们只要元素的个数和集合中元素相同,即使他们是AbstractSet不同的子类,他们equals()相互比较的后的结果仍然是true。下面给出的代码是JDK中的equals()代码:
从JDK源码可以看出,底层并没有使用我们常规认为的利用hashcode()方法求的值进行比较,而是通过调用AbstractCollection的containsAll()方法,如果他们中元素完全相同(与顺序无关),则他们的equals()方法的比较结果就为true。arraylist则是要求顺序

 public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection<?> c = (Collection<?>) o;
        //必须保证元素的个数相等。
        if (c.size() != size())
            return false;
        try {
        //调用了AbstractCollection的方法。
            return containsAll(c);
        } catch (ClassCastException unused)   {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
    }

    public boolean containsAll(Collection<?> c) {
    //只需要逐个判断集合是否包含其中的元素。
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }

 

底层数据结构:HashSet底层封装的是HashMap,所以元素添加会放到HashMap的key中,value值使用new Object对象作为value;所以HashSet和HashMap的所具有的特点是类似的;

  •  数据不能重复;
  •  可以存储null值;
  •  数据不能保证插入有序;

hashSet无序,不重复,为啥不重复,因为内部是hashmap,这也是去重的原理

private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;    
public boolean add(E e) { 
            return map.put(e, PRESENT)==null;
}    
...hashmap的put方法...
if (e.hash == hash && ((k = e.key) == key || key.equals(k))){
......//e.hash调用了hashCode()方法获得了e元素的hash值
}

LinkedHashSet

对于 LinkedHashSet 而言,它继承与 HashSet、又基于 LinkedHashMap 来实现的。

LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。LinkedHashSet 的源代码如下:

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {

    private static final long serialVersionUID = -2851667679971038690L;

    /**
     * 构造一个带有指定初始容量和加载因子的新空链接哈希set。
     *
     * 底层会调用父类的构造方法,构造一个有指定初始容量和加载因子的LinkedHashMap实例。
     * @param initialCapacity 初始容量。
     * @param loadFactor 加载因子。
     */
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    /**
     * 构造一个带指定初始容量和默认加载因子0.75的新空链接哈希set。
     *
     * 底层会调用父类的构造方法,构造一个带指定初始容量和默认加载因子0.75的LinkedHashMap实例。
     * @param initialCapacity 初始容量。
     */
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    /**
     * 构造一个带默认初始容量16和加载因子0.75的新空链接哈希set。
     *
     * 底层会调用父类的构造方法,构造一个带默认初始容量16和加载因子0.75的LinkedHashMap实例。
     */
    public LinkedHashSet() {
        super(16, .75f, true);
    }

    /**
     * 构造一个与指定collection中的元素相同的新链接哈希set。
     *
     * 底层会调用父类的构造方法,构造一个足以包含指定collection
     * 中所有元素的初始容量和加载因子为0.75的LinkedHashMap实例。
     * @param c 其中的元素将存放在此set中的collection。
     */
    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
}
/**
     * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。
     * 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。
     *
     * 实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。
     * @param initialCapacity 初始容量。
     * @param loadFactor 加载因子。
     * @param dummy 标记。
     */
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
}

在父类 HashSet 中,专为 LinkedHashSet 提供的构造方法如下,该方法为包访问权限,并未对外公开。

由上述源代码可见,LinkedHashSet 通过继承 HashSet,底层使用 LinkedHashMap,以很简单明了的方式来实现了其自身的所有功能。

TreeSet

实现Serializable接口,即支持序列化。
实现Cloneable接口,即能被克隆。
实现Iterable接口,即能用foreach使用迭代器遍历得到集合元素。
实现了NavigableSet接口,即能支持一系列的导航方法。比如查找与指定目标最匹配项。并且,TreeSet中含有一个”NavigableMap类型的成员变量”m,而m实际上是”TreeMap的实例”。
继承AbstractSet,AbstractSet实现set,所以它是一个Set集合,不包含满足element1.eauqals(element2)的元素树,并且最多包含一个null.

所以,TreeSet的本质是一个”有序的,并且没有重复元素”的集合,而且支持自定义排序。

由此,该方式TreeSet初始化时 底层是map

SparseArray

Android轻量级数据SparseArray详解

  1. SparseArray采用了延迟删除的机制,通过将删除KEY的Value设置DELETED,方便之后对该下标的存储进行复用;
  2. 使用二分查找,时间复杂度为O(LogN),如果没有查找到,那么取反返回左边界,再取反后,左边界即为应该插入的数组下标;
  3. 如果无法直接插入,则根据mGarbage标识(是否有潜在延迟删除的无效数据),进行数据清除,再通过System.arraycopy进行数组后移,将目标元素插入二分查找左边界对应的下标;
  4. mSize 小于等于keys.length,小于的部分为空数据或者是gc后前移的数据的原数据(也是无效数据),因此二分查找的右边界以mSize为准;mSize包含了延迟删除后的元素个数;
  5. 如果遇到频繁删除,不会触发gc机制,导致mSize 远大于有效数组长度,造成性能损耗;
  6. 根据源码,可能触发gc操作的方法有(1、put;2、与index有关的所有操作,setValueAt()等;3、size()方法;)
  7. mGarbage为true不一定有无效元素,因为可能被删除的元素恰好被新添加的元素覆盖;

根据SparseArray的这些特点。我们能分析出其使用场景

  • key为整型;
  • 不需要频繁的删除;
  • 元素个数相对较少;

ArrayMap

面试必备:ArrayMap源码解析

  • 数据量不大
  • 空间比时间重要
  • 需要使用Map
  • 在Android平台,相对来说,内存容量更宝贵。而且数据量不大。所以当需要使用keyObject类型的Map时,可以考虑使用ArrayMap来替换HashMap

CopyOnArrayList

弱一致性

CopyOnWriteArrayList原理解析

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值