JAVA容器

容器

普通容器

集合类存放于 Java.util 包中, 主要有 3 种: set(集)、 list(列表包含 Queue)和 map(映射)。java

Collection: Collection 是集合 List、 Set、 Queue 的最基本的接口。算法

Iterator:迭代器,能够经过迭代器遍历集合中的数据。数组

Map:是映射表的基础接口。安全

Collection

List

元素有序,可重复。数据结构

ArrayList(数组,初始容量10,负载因子1,扩容增量1.5)

扩容新建数组,复制原数组中元素,开销很大。多线程

删除元素将后面元素向前移一位,复杂度On并发

for循环遍历数据较快高并发

//遍历方式
public class ArrayListTraversal {
    public void arrayListTraversal(List<Integer> lists){
        /* 第一种遍历方式 */
        System.out.print("for循环的遍历方式:");
        for (int i = 0; i < lists.size(); i++) {
            System.out.print(lists.get(i));
        }
        System.out.println();
        
        /* 第二种遍历方式 */
        System.out.print("foreach的遍历方式:");
        for (Integer list : lists) {
            System.out.print(list);
        }
        System.out.println();
        
        /* 第三种遍历方式 */
        System.out.print("Iterator的遍历方式:");
        for (Iterator<Integer> list = lists.iterator(); list.hasNext();) {
            System.out.print(list.next());
        }
    }
    public static void main(String[] args) {
        List<Integer> lists = new ArrayList<Integer>();
        /* 添加元素 */
        for (int i = 0; i < 10; i++) {
            lists.add(i);
        }
        new ArrayListTraversal().arrayListTraversal(lists);
    }
}
LinkedList(链表)

基于双向链表实现,使用 Node 存储链表节点信息。线程

在列表中插入和删除速度快,可是查找须要遍历整个链表设计

foreach遍历数据较快

(1) 不管ArrayList仍是LinkedList,遍历建议使用foreach,尤为是数据量较大时LinkedList避免使用get遍历。
(2) List使用首选ArrayList。对于个别插入删除很是多的可使用LinkedList。
(3) 可能在遍历List循环内部须要使用到下标,这时综合考虑下是使用foreach和自增count仍是get方式。

Set

元素无序,不可重复

对象的相等性本质是对象 hashCode 值判断(java 是依据对象的内存地址计算出的此序号)。

若是想要让两个不一样的对象视为相等,必须覆盖 Object 的 hashCode 方法和 equals 方法

HashSet(Hash表)

基于哈希表实现,支持快速查找,但不支持有序性操做,经过HashMap实现

TreeSet(二叉树)

TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增长一个对象都会进行排序,将对象插入的二叉树指定的位置。

LinkedHashSet(HashSet+LinkedHashMap)

继承 HashSet、基于 LinkedHashMap 实现。

底层使用 LinkedHashMap 来保存全部元素,全部方法操做与 HashSet 相同。

提供了四个构造方法,并经过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操做上与父类 HashSet 的操做相同,直接调用父类 HashSet 的方法便可。

Map

HashMap(数组+链表+红黑树,初始容量16,负载因子0.75,扩容增量1)

基于哈希表实现。

键能够是null,并且键值不能够重复,若是重复了之后就会对第一个进行键值进行覆盖。

大方向上, HashMap 里面是一个数组,而后数组中每一个元素是一个单向链表。上图中,每一个绿色的实体是嵌套类 Entry 的实例, Entry 包含四个属性: key, value, hash 值和用于单向链表的 next。

1. capacity:当前数组容量,始终保持 2^n,能够扩容,扩容后数组大小为当前的 2 倍。
2. loadFactor:负载因子,默认为 0.75。
3. threshold:扩容的阈值,等于 capacity * loadFactor。

Java8 对 HashMap 进行了一些修改, 最大的不一样就是利用了红黑树,因此其由 数组+链表+红黑树 组成。

根据 Java7 HashMap 的介绍,咱们知道,查找的时候,根据 hash 值咱们可以快速定位到数组的具体下标,可是以后的话, 须要顺着链表一个个比较下去才能找到咱们须要的,时间复杂度取决于链表的长度,为 O(n)。为了下降这部分的开销,在 Java8 中, 当链表中的元素超过了 8 个之后,会将链表转换为红黑树,在这些位置进行查找的时候能够下降时间复杂度为 O(logN)。

HashMap

HashMap

HashMap特性总结
1. HashMap的底层是个Node数组(Node<K,V>[] table),在数组的具体索引位置,若是存在多个节点,则多是以链表或红黑树的形式存在。
2. 增长、删除、查找键值对时,定位到哈希桶数组的位置是很关键的一步,源码中是经过下面3个操做来完成这一步:1)拿到key的hashCode值;
2)将hashCode的高位参与运算,从新计算hash值;
3)将计算出来的hash值与(table.length - 1)进行&运算。
3. HashMap的默认初始容量(capacity)是16,capacity必须为2的幂次方;默认负载因子(load factor)是0.75;实际能存放的节点个数(threshold,即触发扩容的阈值)= capacity * load factor。
4. HashMap在触发扩容后,阈值会变为原来的2倍,而且会进行重hash,重hash后索引位置index的节点的新分布位置最多只有两个:原索引位置或原索引+oldCap位置。例如capacity为16,索引位置5的节点扩容后,只可能分布在新报索引位置5和索引位置21(5+16)。
5. 致使HashMap扩容后,同一个索引位置的节点重hash最多分布在两个位置的根本缘由是:1)table的长度始终为2的n次方;2)索引位置的计算方法为“(table.length - 1) & hash”。HashMap扩容是一个比较耗时的操做,定义HashMap时尽可能给个接近的初始容量值。
6. HashMap有threshold属性和loadFactor属性,可是没有capacity属性。初始化时,若是传了初始化容量值,该值是存在threshold变量,而且Node数组是在第一次put时才会进行初始化,初始化时会将此时的threshold值做为新表的capacity值,而后用capacity和loadFactor计算新表的真正threshold值。
7. 当同一个索引位置的节点在增长后达到9个时,会触发链表节点(Node)转红黑树节点(TreeNode,间接继承Node),转成红黑树节点后,其实链表的结构还存在,经过next属性维持。链表节点转红黑树节点的具体方法为源码中的treeifyBin(Node<K,V>[] tab, int hash)方法。
8. 当同一个索引位置的节点在移除后达到6个时,而且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。红黑树节点转链表节点的具体方法为源码中的untreeify(HashMap<K,V> map)方法。
9. HashMap在JDK1.8以后再也不有死循环的问题,JDK1.8以前存在死循环的根本缘由是在扩容后同一索引位置的节点顺序会反掉。
10. HashMap是非线程安全的,在并发场景下使用ConcurrentHashMap来代替。
//遍历map
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class TestMap {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(1, "a");
        map.put(2, "b");
        map.put(3, "ab");
        map.put(4, "ab");
        map.put(4, "ab");// 和上面相同 , 会本身筛选
        System.out.println(map.size());
        // 第一种:
        /*
         * Set<Integer> set = map.keySet(); //获得全部key的集合
         * 
         * for (Integer in : set) { String str = map.get(in);
         * System.out.println(in + "     " + str); }
         */
        System.out.println("第一种:经过Map.keySet遍历key和value:");
        for (Integer in : map.keySet()) {
            //map.keySet()返回的是全部key的值
            String str = map.get(in);//获得每一个key多对用value的值
            System.out.println(in + "     " + str);
        }
        // 第二种:
        System.out.println("第二种:经过Map.entrySet使用iterator遍历key和value:");
        Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
        while (it.hasNext()) {
             Map.Entry<Integer, String> entry = it.next();
               System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
        }
        // 第三种:推荐,尤为是容量大时
        System.out.println("第三种:经过Map.entrySet遍历key和value");
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            //Map.entry<Integer,String> 映射项(键-值对)  有几个方法:用上面的名字entry
            //entry.getKey() ;entry.getValue(); entry.setValue();
            //map.entrySet()  返回此映射中包含的映射关系的 Set视图。
            System.out.println("key= " + entry.getKey() + " and value= "
                    + entry.getValue());
        }
        // 第四种:
        System.out.println("第四种:经过Map.values()遍历全部的value,但不能遍历key");
        for (String v : map.values()) {
            System.out.println("value= " + v);
        }
    }
}
TreeMap(可排序)

TreeMap 实现 SortedMap 接口,可以把它保存的记录根据键排序,默认是按键值的升序排序,也能够指定排序的比较器,当用 Iterator 遍历 TreeMap 时,获得的记录是排过序的。

若是使用排序的映射,建议使用 TreeMap。

在使用 TreeMap 时, key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,不然会在运行时抛出 ava.lang.ClassCastException 类型的异常。

LinkedHashMap(记录插入顺序)

LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先获得的记录确定是先插入的,也能够在构造时带参数,按照访问次序排序。

Queue

public interface Queue<E> extends Collection<E> {
    boolean add(E var1);
    boolean offer(E var1);
    E remove();
    E poll();
    E element();
    E peek();
}
FIFO队列
Queue<Integer> q=new LinkedList<Integer>();
优先队列

默认升序,底层为堆,初始容量11

传入对象时须要指定比较器

Queue<Integer> q=new PriorityQueue<Integer>();
Queue<Student> q=new PriorityQueue<Student>((e1,e2)->(e1.id-e2.id));
阻塞队列

阻塞队列,在java.util.concurrent包下。

同步容器

能够简单地理解为经过synchronized来实现同步的容器

Vector(数组实现,线程同步)

实现与 ArrayList 相似,使用 synchronized 进行同步,访问速度慢。

线程安全,底层数组,初始10,默认扩容2倍

Stack(继承Vector类,底层实现是数组)

栈,继承Vector类,底层实现是数组,先进后出

HashTable(线程安全)

Hashtable 是遗留类,不少映射的经常使用功能与 HashMap 相似。

Collections.synchronized方法生成

这些容器实现线程安全的方式就是将它们的状态封装起来,并在须要同步的方法上加上关键字synchronized。
削弱了并发性,当多个线程共同竞争容器级的锁时,吞吐量就会下降。

并发容器

采用CAS算法,部分代码使用synchronized锁保证线程安全**。java.util.concurrent包中提供了多种并发类容器。

CopyOnWriteArrayList

对应的非并发容器:ArrayList。

目标:代替Vector、synchronizedList。

原理:利用高并发每每是读多写少的特性,对读操做不加锁对写操做,先复制一份新的集合,在新的集合上面修改,而后将新集合赋值给旧的引用,并经过volatile 保证可见性,固然写操做的锁是必不可少的

CopyOnWriteArraySet

对应的非并发容器:HashSet。

目标:代替synchronizedSet。

原理:基于CopyOnWriteArrayList实现,其惟一的不一样是在add时调用 CopyOnWriteArrayList 的addIfAbsent方法,其遍历当前Object数组,若是Object数组中已有当前元素,则直接返回,若是没有则放入Object数组的尾部并返回

ConcurrentHashMap

对应的非并发容器:HashMap。

目标:代替Hashtable、synchronizedMap,支持复合操做。

原理:JDK6采用更加细粒度的加锁机制Segment“分段锁”JDK8采用CAS无锁算法

ConcurrentSkipListMap

对应的非并发容器:TreeMap。

目标:代替synchronizedSortedMap(TreeMap)。

原理:Skip list(跳表)是一种能够代替平衡树的数据结构默认按Key值升序

ConcurrentSkipListSet

对应的非并发容器:TreeSet。

目标:代替synchronizedSortedSet。

原理:内部基于ConcurrentSkipListMap实现

ConcurrentLinkedQueue

不会阻塞的队列。

对应的非并发容器:Queue。

原理:基于链表实现的FIFO队列(LinkedList的并发版本)

LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue

对应的非并发容器:BlockingQueue。

特色:拓展了Queue,增长了可阻塞的插入和获取等操做。

原理:经过ReentrantLock实现线程安全,经过Condition实现阻塞和唤醒

实现类:
LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列
ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列
PriorityBlockingQueue:按优先级排序的队列
并发容器是专门针对多线程并发设计的,使用了锁分段技术,只对操做的位置进行同步操做,可是其余没有操做的位置其余线程仍然能够访问,提升了程序的吞吐量。
相关文章
相关标签/搜索