java核心技术卷I-具体的集合

具体的集合

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

链表

数组和数组列表都有一个重大的缺陷。这就是从数组的中间位置删除一个元素要付出很大的代价,其原因是数组中处于被删除元素之后的所有元素都要向数组的前端移动。在数组中间的位置上插入一个元素也是如此。
链表( linked list) 解决了这个问题。尽管数组在连续的存储位置上存放对象引用, 但链表却将每个对象存放在独立的结点中。每个结点还存放着序列中下一个结点的引用。在 Java 程序设计语言中,所有链表实际上都是双向链接的(doubly linked)—即每个结点还存放着指向前驱结点的引用
在这里插入图片描述
从链表中间删除一个元素是一个很轻松的操作, 即需要更新被删除元素附近的链接
在这里插入图片描述
链表是 一 个 有 序 集 合( ordered collection), 每个对象的位置十分重要。LinkedList.add 方法将对象添加到链表的尾部。但是,常常需要将元素添加到链表的中间。由于迭代器是描述集合中位置的, 所以这种依赖于位置的 add 方法将由迭代器负责。只有对自然有序的集合使用迭代器添加元素才有实际意义。
在 Iterator 接口中就没有add 方法。相反地,集合类库提供了子接口 Listlterator, 其 中 包 含 add方法

interface ListIterator<E> extends Iterator<E>{
	void add(E element);
	...
}

与 Collection.add 不同, 这个方法不返回 boolean 类型的值, 它假定添加操作总会改变链表。另外,Listlterator 接口有两个方法,可以用来反向遍历链表。与 next 方法一样, previous 方法返回越过的对象。

E previous()
boolean hasPrevious()

LinkedList 类的 listlterator 方法返回一个实现了 Listlterator 接口的迭代器对象

ListIterator<String> iter = staff.listiterator();

Add 方法在迭代器位置之前添加一个新对象。add 方法只依赖于迭代器的位置, 而 remove 方法依赖于迭代器的状态。
可以想象, 如果在某个迭代器修改集合时, 另一个迭代器对其进行遍历,一定会出现混乱的状况。例如,一个迭代器指向另一个迭代器刚刚删除的元素前面,现在这个迭代器就是无效的,并且不应该再使用。链表迭代器的设计使它能够检测到这种修改。如果迭代器发现它的集合被另一个迭代器修改了, 或是被该集合自身的方法修改了, 就会抛出一个ConcurrentModificationException 异常。

List<String> list = . .
ListIterator<String> iterl = list.listiterator();
ListIterator<String> iter2 = list.listiterator();
iterl.next();
iterl.remove();
iter2.next(); // throws ConcurrentModificationException

为了避免发生并发修改的异常,请遵循下述简单规则:可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的迭代器。
链表不支持快速地随机访问。如果要查看链表中第n个元素,就必须从头开始, 越过n-1个元素。没有捷径可走。
尽管如此, LinkedList 类还是提供了一个用来访问某个特定元素的 get 方法:

LinkedList<String> list = . .;
String obj = list.get(n);

数组列表

ArrayList 封装了一个动态再分配的对象数组。
在需要动态数组时, 可能会使用 Vector 类。为什么要用 ArrayList 取代 Vector 呢? 原因很简单:Vector 类的所有方法都是同步的。 可以由两个线程安全地访问一个 Vector 对象。但是, 如果由一个线程访问 Vector, 代码要在同步操作上耗费大量的时间。这种情况还是很常见的。而 ArrayList 方法不是同步的,因此,建议在不需要同步时使用 ArrayList, 而不要使用 Vector。

散列集

散列表为每个对象计算一个整数, 称为散列码(hashcode。) 散列码是由对象的实例域产生的一个整数。更准确地说, 具有不同数据域的对象将产生不同的散列码。
如果自定义类,就要负责实现这个类的 hashCode方法。注意,自己实现的 hashCode方法应该与 equals 方法兼容,即如果 a.equals(b) 为 true, a 与 b 必须具有相同的散列码。
在 Java 中,散列表用链表数组实现。每个列表被称为桶( bucket) 。 要想査找表中对象的位置, 就要先计算它的散列码, 然后与桶的总数取余, 所得到的结果就是保存这个元素的桶的索引。例如, 如果某个对象的散列码为 76268, 并且有 128 个桶, 对象应该保存在第 108 号桶中(76268除以 128余 108 )。或许会很幸运, 在这个桶中没有其他元素,此时将元素直接插人到桶中就可以了。
在这里插入图片描述
当然,有时候会遇到桶被占满的情况, 这也是不可避免的。这种现象被称为散列冲突( hash collision)。这时, 需要用新对象与桶中的所有对象进行比较,査看这个对象是否已经存在。如果散列码是合理且随机分布的, 桶的数目也足够大, 需要比较的次数就会很少。
散列表可以用于实现几个重要的数据结构。 其中最简单的是 set 类型。set 是没有重复元素的元素集合。set 的 add方法首先在集中查找要添加的对象,如果不存在,就将这个对象添加进去。
Java 集合类库提供了一个 HashSet 类,它实现了基于散列表的集。可以用 add 方法添加元素。contains方法已经被重新定义,用来快速地查看是否某个元素已经出现在集中。它只在某个桶中査找元素,而不必查看集合中的所有元素

树集

TreeSet 类与散列集十分类似, 不过, 它比散列集有所改进。树集是一个有序集合( sorted collection) 。 可以以任意顺序将元素插入到集合中。在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。
正如 TreeSet 类名所示,排序是用树结构完成的,当前实现使用的是红黑树(red-black tree)。每次将一个元素添加到树中时,都被放置在正确的排序位置上。因此,迭代器总是以排好序的顺序访问每个元素。要使用树集, 必须能够比较元素。这些元素必须实现 Comparable 接口 或者构造集时必须提供一个 Comparator

队列与双端队列

队列可以让人们有效地在尾部添加一个元素, 在头部删除一个元素。有两个端头的队列, 即双端队列,可以让人们有效地在头部和尾部同时添加或删除元素。不支持在队列中间添加元素。在 Java SE 6中引人了 Deque 接口,并由 ArrayDeque 和LinkedList 类实现。这两个类都提供了双端队列,而且在必要时可以增加队列的长度。

优先级队列

优先级队列(priority queue) 中的元素可以按照任意的顺序插人,却总是按照排序的顺序进行检索。也就是说,无论何时调用 remove 方法,总会获得当前优先级队列中最小的元素。然而,优先级队列并没有对所有的元素进行排序。如果用迭代的方式处理这些元素,并不需要对它们进行排序。优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)。堆是一个可以自我调整的二叉树,对树执行添加( add) 和删除(remore) 操作, 可以让最小的元素移动到根,而不必花费时间对元素进行排序。
与 TreeSet—样,一个优先级队列既可以保存实现了 Comparable 接口的类对象, 也可以保存在构造器中提供的 Comparator 对象。
使用优先级队列的典型示例是任务调度。每一个任务有一个优先级,任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

局外人一枚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值