Java Collection集合 浅析

java集合框架主要包含Collection和Map。这里主要解析一下collection。collection主要实现包括list、set、queue。



collection中稍微说下iterator,iterator.next返回的值为值传递,即是基本数据类型为返回值,引用类型返回引用值。
下面稍微说下list、set、queue的区别:

  • set:不可重复
  • list:有序可重复,可通过下标随机访问
  • queue:队列,FIFO

Set

不可重复,通过对象的equals()、hashCode()判断唯一性。如果对象没有重写equals()、hashCode()则默认为Object的实现即判断是否为同一个对象引用。

HashSet

HashSet是Set接口的典型实现,实现了Set接口中的所有方法,并没有添加额外的方法。
java1.8内部实现通过HashMap实现,因为HashMap可存储null值,所以HashSet也可以存储空值

private static final Object PRESENT = new Object();

    public boolean add(E var1) {
        return this.map.put(var1, PRESENT) == null;
    }

可以看出,HashSet通过Map的Key值唯一性做为HashSet保证。
所以HashSet有以下特性:

  • 无序,存储与获取时顺序可能不同
  • 非线程安全
  • 元素可以为null
  • 判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等

所以根据HashMap的特性,可以有下面四个特性:

  • equals()、hashCode()都为false,存储在不同位置
  • equals()为true,hashCode()为false,存储在不同位置
  • equals()为false,hashCode()为true,存储在相同位置,通过链表式结构来保存多个对象
  • equals()、hashCode()都为true,不进行保存

LinkedHashSet

继承与HashSet,底层通过LinkedHashMap实现有序,所以其特性和LinkedHashMap保持一致,无序、非线程安全、元素可以为null、通过equals()和hashCode()判断存储对象是否已经存在

---LinkedHashSet.java
public LinkedHashSet() {
        super(16, .75f, true);
}
---HashSet.java
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

TreeSet

TreeSet实现于SortedSet接口。内部实现基于TreeMap(实现NavigableMap接口)。
TreeMap解析可以参考Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例

若TreeSet没指定Comparator则存储对象必须实现Comparable接口,若指定Comparator优先使用Comparator进行排序。

  • first():返回最小元素。可能throw NoSuchElementException
  • last(): 返回最大元素。可能throw NoSuchElementException
  • lower(E e):返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。e元素不可为空
  • higher(E e):返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。e元素不可为空
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

可以看出,和HashSet一样,通过Map的Key特性保证数据"唯一性"。
TreeMap为有序的key-value集合并通过红黑树实现。因为实现了NavigableMap接口,有一系列的导航方法,比如返回有序的key集合等。因为TreeMap为非线程安全集合,所以TreeSet一样是非线程安全
注意的是,存储在Set中数据不要修改关键比较数值,修改后并不会重新排序,而且可能造成删除不成功。

HashSet、LinkedSet、TreeSet性能比较

HashSet>LinkedSet>TreeSet
因为TreeSet有额外的红黑树操作,LinkedSet有额外的链表操作,所以性能以HashSet最高。
所以,查询较多用HashSet,有排序需求用TreeSet,插入删除较多可以考虑用LinkedSet。

List

List判断两个对象相等只要通过equals()方法比较返回true即可

ArrayList、Vector

ArrayList和Vector类都是基于数组实现的List类,封装了一个动态的、允许再分配的Object[]数组。

ArrayList为非线程安全,Vector为线程安全

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    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);
    }

ArrayList在添加数据时,若添加完数据后的长度大于现在数据数组长度,则通过grow方法增加数组长度。新的数组长度 = 旧数组长度 + 旧数组长度/2 。
而Vector可以定义每次增长长度,不定义则新数组长度增长是翻倍。

Stack

Stack继承于Vector,通过提供API模拟LIFO模式。因为继承与Vector,若无需考虑线程安全问题可以使用LinkedList

LinkedList

实现了List<E>, Deque<E>,所以既支持随机访问也支持双端队列访问。所以可以模拟Stack操作。
内部通过链表保存数据

LinkedList的内部节点类定义
    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;
        }
    }

内部只保存了链头和链尾两个节点。所以随机存取效率较慢。而表头表尾的插入删除是指Node.next和Node.prev的指向修改,所以效率很高。

Queue

一般为FIFO操作,有时根据情况可能回使用双端队列或者循环队列

PriorityQueue/PriorityBlockingQueue

前者为非线程安全类,后者为线程安全类。可通过自然排序(实现Comparable接口)或者自定义Comparator,可以参考TreeSet
内部实现类似于ArrayList通过数组实现,且不允许插入null。

//增长数组方法
    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }
//自然排序时,从指定位置向上遍历调整位置
//一般为添加节点时调用
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }
//自然排序时,从指定位置向下遍历调整位置
//一般用于删除节点后从0开始调整位置
    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

PriorityQueue长度小于64时成倍增长,长度超过64后每次增长一半。
PriorityQueue内部数据结构主要为一棵完全二叉树,通过小顶堆方式排序元素。如果需要实现大顶堆可以通过自定义Comparator实现。

Deque接口

实现该接口为提供双端队列实现

ArrayDeque

内部由循环队列的数组实现。通过head(int)、tail(int)确定队头及队尾。
因为是非线程安全的,所以作为栈的话,效率比Stack高。因为仅是通过head(int)、tail(int)确定队头及队尾且数组天生可以随机访问,所以作为队列效率比LinkedList高。

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

推荐阅读更多精彩内容

  • Java集合类可用于存储数量不等的对象,并可以实现常用的数据结构如栈,队列等,Java集合还可以用于保存具有映射关...
    小徐andorid阅读 1,936评论 0 13
  • 一、集合入门总结 集合框架: Java中的集合框架大类可分为Collection和Map;两者的区别: 1、Col...
    程序员欧阳阅读 11,556评论 2 61
  • Java集合 1、集合的由来 程序的需求也许并不能确定创建对象的数量,甚至不知道对象的类型,为了满足普适的编程需要...
    小灰灰_5c75阅读 1,004评论 0 0
  • 前两天我在微博上看到了,关于留学东京的女学生江歌的案卷又一次被舆论推向高潮。看了江歌妈妈和刘鑫的对话,看着江歌妈妈...
    译娴阅读 1,810评论 13 3
  • 16年的最后一天,在微信上遇到了许久未联系的老同学。闲聊几句后她突然说,告诉你一件事,先帮我保密吧,我和老公马上要...
    味味小惟阅读 418评论 1 2