Java基础知识扫盲(五)——集合

接口

集合框架的接口.png
  1. 集合有两个基本接口 Collection 和 Map
  2. List 是一个有序集合。元素会增加到容器中的特定位置。
  3. Set<E>集。Set 接口等同于 Collection 接口,不过其方法的行为有更严谨的定义,不能添加重复的对象
  4. Iterator<E>
  • iterator.forEachRemaining(element -> do something with element);
  • iterator.next(),当调用 next 时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。
  • iterator.remove(),将会删除上次调用 next 方法时返回的元素。
it.next(); // skip over the first element
it.remove(); // now remove it
  1. ListIterator 定义了一个方法用于在迭代器位置前面增加一个元素。void add(E element)
  2. RandomAccess 为标记接口,不包含任何方法。可以用来测试一个特定的集合是否支持高效的随机访问。
if (c instanceof RandomAccess){
  use random access algorithm
else{
  use sequential access algorithm
}

集合框架的类.png
  • ArrayList 可以动态增长和缩减的索引序列
  • LinkedList 可以在任何位置进行高效地插人和删除操作的有序序列
  • ArrayDeque 用循环数组实现的双端队列
  • HashSet 没有重复元素的无序集
  • TreeSet 有序集
  • EnumSet 包含枚举类型值的集
  • LinkedHashSet 可以记住元素插人次序的集
  • PriorityQueue 允许高效删除最小元素的集合
  • HashMap 存储键 / 值关联的数据结构
  • TreeMap 键值有序排列的映射表
  • EnumMap 键值属于枚举类型的映射表
  • LinkedHashMap 可以记住键 / 值项添加次序的映射表
  • WeakHashMap 其值无用武之地后可以被垃圾回收器回收的映射表
  • IdentityHashMap 用 = 而不是用 equals 比较键值的映射表

链表
Java 程序设计语言中, 所有链表实际上都是双向链接的。LinkedList

注意iterator的位置(“光标”)

LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("this");
linkedList.add("is");
linkedList.add("a");
linkedList.add("sentence");

ListIterator<String> iterator = linkedList.listIterator();
while (iterator.hasNext()){
  if (iterator.next() == "sentence")
    iterator.add("complete");
}
  • 用“ 光标” 类比时要格外小心。remove 操作与 BACKSPACE 键的工作方式不太一样。在调用 next 之后, remove 方法确实与 BACKSPACE 键一样删除了迭代器左侧的元素。但是, 如果调用 previous 就会将右侧的元素删除掉, 并且不能连续调用两次remove
  • add 方法只依赖于迭代器的位置, 而 remove 方法依赖于迭代器的状态。
  • 如果在某个迭代器修改集合时, 另一个迭代器对其进行遍历, 一定会出现混乱的状况。链表迭代器的设计使它能够检测到这种修改。 如果迭代器发现它的集合被另一个迭代器修改了, 或是被该集合自身的方法修改了,就会抛出一个ConcurrentModificationException 异常。

数组列表
List 接口用于描述一个有序集合,并且集合中每个元素的位置十分重要。 访问元素两种方法:

  • 迭代器。适用于链表
  • get和set方法随机访问。适用于数组

ArrayList 数组列表,封装了一个动态再分配的对象数组。比较 Vector,其方法都是同步的,一个线程访问 Vector, 代码要在同步操作上耗费大量的时间。在不需要同步时使用 ArrayList, 而不要使用 Vector。

散列集
链表和数组可以按照人们的意愿排列元素的次序。但是如果想要査看某个指定的元素, 却又忘记了它的位置, 就需要访问所有元素, 直到找到为止。所以,数据结构散列表上场啦

散列表为每个对象计算一个整数, 称为散列码(hash code),是由对象的实例域产生的一个整数。

在 Java 中,散列表用链表数组实现。每个列表被称为桶 ( bucket ) 。


bucket.png

对象位置的计算:计算它的散列码, 然后与桶的总数取余。

例如, 如果某个对象的散列码为 76268, 并且有 128 个桶, 对象应该保存在第 108 号桶中( 76268除以 128余 108 )直接插入到桶中即可。 不幸的是,当桶被占满时,发生散列冲突( hash collision) ,需要用新对象与桶中的所有对象进行比较,査看这个对象是否已经存在 。

通常, 将桶数设置为预计元素个数的 75% ~ 150%。

如果散列表太满, 就需要再散列 (rehashed)。 如果要对散列表再散列, 就需要创建一个桶数更多的表,并将所有元素插入到这个新表中, 然后丢弃原来的表。装填因子( load factor) 决定何时对散列表进行再散列。如果装填因子为 0.75 (默认值,) 而表中超过 75%的位置已经填人元素, 这个表就会用双倍的桶数自动地进行再散列。

散列表可以用于集Set中,Set的add方法首先在集中查找要添加的对象,如果不存在才添加进去。

散列集迭代器将依次访问所有的桶。 由于散列将元素分散在表的各个位置上,所以访问它们的顺序几乎是随机的。只有不关心集合中元素的顺序时才应该使用 HashSet。
可以看到HashSet实际就是HashMap

public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

树集
TreeSet 比散列集有所改进,是有序集。实现使用的是红黑树。

SortedSet<Item> parts = new TreeSet<>();
parts.add(new Item("Hello", 1));
parts.add(new Item("This", 2));
parts.add(new Item("Is", 3));
NavigableSet<Item> sortByDescription = new TreeSet<>(
  Comparator.comparing(Item::getDescription));
sortByDescription.addAll(parts);

队列与双端队列
支持在头部/尾部添加或删除元素。不支持在队列中间添加元素。ArrayDeque和LinkedList都提供了双端队列

优先级队列
priorityQueue 可以按照任意的顺序插入元素。优先级队列并没有对所有的元素进行排序,却总是按照排序的顺序进行检索。使用的数据结构为。堆是一个可以自我调整的二叉树,对树执行添加 ( add) 和删除(remove) 操作, 可以让最小或最大的元素移动到根,而不必花费时间对元素进行排序。

迭代不是按照元素的排列顺序访问。
而无论何时调用 remove 方法,总会获得当前优先级队列中最小的元素。
可以指定比较器进行排序。

PriorityQueue<Item> itemPriorityQueue = new PriorityQueue<>(Comparator.comparing(Item::getPartNumber));
itemPriorityQueue.addAll(parts);

映射
键-值对。HashMap 和 TreeMap

  • 散列映射对进行散列。
  • 树映射用键的整体顺序对元素进行排序, 并将其组织成搜索树
  • 散列或比较函数只能作用于键。
  • 与集一样, 散列稍微快一些, 如果不需要按照排列顺序访问键, 就最好选择散列
  • 设值时候需要之一NullPointerExp情况
HashMap<String, String> hashMap = new HashMap<>();

hashMap.putIfAbsent("word", "");
hashMap.put("word", hashMap.get("word") + "string");

hashMap.put("word", hashMap.getOrDefault("word", "defaultStr"));

hashMap.merge("word","defaultStr",String::concat);

映射视图
键集、值集合、键/值对集。

Set<String> hashMapKeySet = hashMap.keySet();
Collection<String> hashMapValues = hashMap.values();
Set<Map.Entry<String,String>> hashMapEntrySet = hashMap.entrySet();

遍历键值对集更快速方法:

hashMap.forEach((k,v) -> {
  // do sth. with k v
});

弱散列映射
假定对某个键的最后一次引用已经消亡,不再有任何途径引用这个值的对象了。为什么垃圾回收器不能够删除它呢?答:垃圾回收器跟踪活动的对象。只要映射对象是活动的,其中的所有桶也是活动的, 它们不能被回收。

WeakHashMap 当对键的唯一引用来自散列条目时, 这一数据结构将与垃圾回收器协同工作一起删除键 / 值对。

机制:WeakHashMap 使用弱引用保存键。如果某个对象只能由 WeakReference 引用, 垃圾回收器仍然回收它,但要将引用这个对象的弱引用放入队列中。一个弱引用进人队列意味着这个键不再被他人使用, 并且已经被收集起来。于是, WeakHashMap 将删除对应的条目。

链接散列集与映射
LinkedHashSet LinkedHashMap 记住插入元素的顺序。

访问顺序对于实现高速缓存的“ 最近最少使用” 原则十分重要。

枚举集与映射
EnumSet 枚举类型元素集。
可以使用 Set 接口的常用方法来修改 EnumSet。

EnumSet<Weekday> weekdays = EnumSet.allOf(Weekday.class);//返回一个包含给定枚举类型的所有值的集。
EnumSet<Weekday> none = EnumSet.noneOf(Weekday.class);//返回一个空集,并有足够的空间保存给定的枚举类型所有的值
EnumSet<Weekday> workday = EnumSet.range(MONDAY, FRIDAY);//返回一个包含 from 〜 to 之间的所有值(包括两个边界元素)的集

EnumMap 是一个键类型为枚举类型的映射。

视图

方法返回一个包装了接口的对象。这种对象集合称为视图。

获取不可修改的视图
视图的更改器方法抛出一个 UnsupportedOperationException。
Collections 还有几个方法, 用于产生集合的不可修改视图。运行时执行检查,如果发现试图对集合进行修改, 就抛出一个异常,同时这个集合将保持未修改的状态。

Collections.unmodifiableCollection(parts);
Collections.unmodifiableList(linkedList);
Collections.unmodifiableSet(parts);
Collections.unmodifiableSortedSet(parts);
Collections.unmodifiableNavigableSet(sortByNumber);
Collections.unmodifiableMap(hashMap);
Collections.unmodifiableSortedMap(hashMap);
Collections.unmodifiableNavigableMap(hashMap);

视图只是包装了接口而不是实际的集合对象, 所以只能访问接口中定义的方法。不可修改视图并不是集合本身不可修改。仍然可以通过集合的原始引用(在这里是 hashMap / parts / linkedList)对集合进行修改。

视图 unmodifiableCollection 返回的集合,它的 equals 方法不调用底层集合的 equals 方法,继承的是Object 类的 equals 方法,检测两个对象是否是同一个对象。以同样的方式处理 hashCode 方法。

视图 unmodifiableSet 类和 unmodifiableList 类却使用底层集合的 equals 方法和hashCode 方法。

同步视图
视图的方法同步。如果一个线程试图将元素添加到散列表中,同时另一个线程正在对散列表进行再散列,其结果将是灾难性的。

类库的设计者使用视图机制来确保常规集合的线程安全, 而不是实现线程安全的集合类。

Map<String, Item> map = Collections.synchronizedMap(new HashMap<String,Item>());

便可以多线程访问map对象,get 和 put都是同步操作

受查视图
用来对泛型类型发生问题时提供调试。插入一个错误类型的元素,视图的方法拋出一ClassCastException。

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

推荐阅读更多精彩内容

  • Java集合框架 Java中封装了许多常用的数据结构,称为集合框架,可以有效组织数据,提高程序性能。最初Java只...
    Steven1997阅读 924评论 0 2
  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 3,852评论 1 22
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,268评论 0 9
  • 从五一过后,允许你上学带2--3块备用零钱,省得偶尔急需买东西时,再向午托部老师或同学借了,你心里美美的,感觉自己...
    素面迎风阅读 125评论 1 0
  • 首先,还是感谢财松叔的分享。直播里面叔说到由于你给大家带来困扰,会拉你做参考,你甚至会为此伤心,反思和担心,为你这...
    晓言晓语阅读 273评论 0 4