List + Set + Map + Queue

List

  • ArrayList
    平时使用的最多的list,内部基于随机数组实现,set和get的方法效率较高,默认容量为10,
    private static final int DEFAULT_CAPACITY = 10;

当超过容量时,增加一半

        int newCapacity = oldCapacity + (oldCapacity >> 1);
  • LinkedList
    基于链表实现的list,add和remove的方法效率较高,但也仅限于达到一定的数量级才能看得出差异。

  • Vector
    线程安全的list,内部基于数组实现,默认容量为10,容量不够的时候翻倍

int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
 处于线程安全的考虑,本来就会损失一定的效率,在扩容方面又被ArrayList甩开了。
  • CopyOnWriteArrayList
    又是一个线程安全的list,基于数组的实现。每次都以当前数组大小+1,生成一个新的数组。

  • Vector和CopyOnWriteArrayList的抉择问题
    两者都是线程安全,那我们应该如何取舍?
    既然是线程安全,那肯定会从操作列表的方法开始着手,我们就一一来看一下双方的add方法
    CopyOnWriteArrayList.java

public synchronized boolean add(E e) {
        Object[] newElements = new Object[elements.length + 1];
        System.arraycopy(elements, 0, newElements, 0, elements.length);
        newElements[elements.length] = e;
        elements = newElements;
        return true;
    }
同步方法,通过new新的数组出来,native做拷贝。

Vector.java

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
就是纯粹的方法同步。

乍一看都是同步,貌似看不出什么使用区别,那我们再来看一个区别你就明白了:
CopyOnWriteArrayList.java
@Override public ListIterator<E> listIterator(int index) {
            Slice slice = this.slice;
            Object[] snapshot = elements;
            slice.checkPositionIndex(index);
            slice.checkConcurrentModification(snapshot);
            CowIterator<E> result = new CowIterator<E>(snapshot, slice.from, slice.to);
            result.index = slice.from + index;
            return result;
        }
Vector.java
public synchronized ListIterator<E> listIterator(int index) {
        if (index < 0 || index > elementCount)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

一个是同步的,现在是不同步的。那么什么意思呢?

就是说CopyOnWriteArrayList在遍历的时候,你可以进行add和remove操作,但是就像前面说的其实现原理,如果你在遍历的时候对数据进行操作,实际上并不影响遍历的结果。
而Vector需要遍历了之后才可以进行操作,因为遍历是上锁的。

  • 遍历的效率问题
    只要是实现了RadomAccess接口的类,遍历的时候使用
    for( int i = 0 ; i < x ; i++)是效率最高的
    其他的遍历方式包括for(Object object : list)
    list.forEach(new Consumer)
    list.forEach(var->())

Set

  • HashSet
    内部基于HashMap, 线程不安全
    public HashSet() {
        map = new HashMap<>();
    }
   默认的大小为HashMap默认大小4
    static final int DEFAULT_INITIAL_CAPACITY = 4;
 由于基于HashMap的实现,故而存储元素需要重写equals和hashcode方法,来保证相同元素的equals放回true,hashcode相等
  • TreeSet
    内部基于treeMap,非线程安全。
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
  由于跟树有关,存储的元素需要实现comparable
  • LinkedHashSet
    继承了hashSet,基本和hashSet相同,在最新的代码中,我已看不出和HashSet有什么区别,除了初始的initialCapacity有些许差异之外,就是spliterator这个java8最新加入的方法了

  • CopyOnWriteArraySet
    同CopyOnWriteArrayList

  • EnumSet
    专门为枚举设计的set,内部基于enum数组实现

  • 谈谈新出现的spliterator方法
    用于实现并发遍历的方法,我们之前所用的for遍历都是单线程的实现,但是我们现在可以使用spliterator方法趋势线多线程的遍历,大大的提高遍历的效率。使用方式如下

        Spliterator<Employee> spliterator = set.spliterator();
        while (spliterator.tryAdvance(new Consumer<Employee>() {
                public void accept(Employee employee) {
                    employee.name += "new";
                }
            }));
  或者
        Spliterator<Employee> spliterator = set.spliterator();
        spliterator.forEachRemaining (new Consumer<Employee>() {
                public void accept(Employee employee) {
                    employee.name += "new";
                }
            });

Map

  • HashMap
    使用的最多的map,内部基于数组链表实现,需要重写key对象的hashcode和equals方法,先通过hashcode方法找到数组的index,然后通过equals方法去该index的链表中找到这个key,然后取出键值对。
```

final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}

    int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
    for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}
     非线程安全

   * ArrayMap


   * TreeMap
     非线程安全,内部基于红黑树实现,相应的key需要实现comparable接口,需要以此来实现内部树排序

   * LinkedHashMap
     非线程安全,内部会有排序(默认为插入的顺序),或者根据你传入的排序规则LRU等等。在遍历的时候,会按照顺序进行遍历,而非HashMap的乱序

   * HashTable
      线程安全,看到所有的方法都是synchronized的,key和value都不能为null

// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

    // Makes sure the key is not already in the hashtable.
    HashtableEntry tab[] = table;
    int hash = hash(key);
   * ConcurrentHashMap
     结合了HashTable和HashMap的有点,不是所有的方法都上锁,在保证线程安全的情况下摇臂HashTable的性能好。
    
   * WeakHashMap
     和HashMap基本相同,只是key使用的若引用,一旦key的对象呗销毁,就会自动抹去相应的key
    ```
        public K getKey() {
            return (K) WeakHashMap.unmaskNull(get());
        }
        static Object unmaskNull(Object key) {
            return (key == NULL_KEY) ? null : key;
        }
        private static final Object NULL_KEY = new Object();
  • EnumMap
    适用于Enum的map

Queue

  • ArrayBlockingQueue
    线程安全的队列,内部基于数组实现。实现阻塞队列接口,采用外部锁方式进行添加
public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
构造时需要传入队列大小,一旦队列存满,再次存数据将被阻塞
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
  • LinkedBlockingQueue
    依然实现阻塞队列的接口,默认的capacity为无穷大,可以指定,内部一链表形式实现。线程安全

  • PriorityBlockingQueue
    优先级阻塞队列,基于数组实现,默认大小为11

    public PriorityBlockingQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

内部按照传入的Comparator方法来布置队列优先级。线程安全

  • PriorityQueue
    线程不安全的优先级队列。

使用场景的梳理

首先List, Set, Queue都是基于Collection的实现,collection的最基本定义就是一个位置只能存一个值。

List更多强调的是具有一定的顺序,相当于只是对数组做了简单的封装。
Set更强调的是不能存在重复的对象。
Queue更强调的是进出容器的顺序。

Map自成一类,本身的实现基于数组的链表,在数组的index上存了一串链表,链表中按照put的顺序存着许多entry,根据key找到相应的entry。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容