接着昨天的Collection继续:
- Object[] toArray()
将集合转换成数组
public class CollectionDemo1 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");
Object[] objs = c.toArray();
for (int i = 0; i < objs.length; i++) {
String s = (String) objs[i];
System.out.println(s.toUpperCase()); // HELLO
// WORLD
// JAVA
}
objs[1] = "WORLD";
System.out.println(Arrays.toString(objs)); // [hello, WORLD, java]
System.out.println(c); // [hello, world, java]
}
}
由代码可以看出,此方法相当于在遍历的过程中额外复制了一份,objs数组和集合c没有关联,满足不了对集合修改的需求。
因此,出现了Iterator iterator()。
- Iterator iterator()
对 collection 进行迭代的迭代器,提供统一遍历的操作。所有collection都有此方法 - Iterator接口
boolean hasNext()
E next() 返回迭代的下一个元素
void remove() 删除元素
public class CollectionDemo2 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");
// E next()
Iterator it1 = c.iterator();
System.out.println(it1.next());
System.out.println(it1.next());
System.out.println(it1.next());
// System.out.println(it1.next()); // NoSuchElementException异常
// boolean hasNext()
Iterator it2 = c.iterator();
while (it2.hasNext()) {
String s = (String) it2.next();
System.out.println(s);
// void remove() 删除最近返回的元素
Iterator it3 = c.iterator();
// it3.remove(); // IllegalStateException异常,因为还没有返回元素
it3.next();
it3.next();
it3.remove();
// it3.remove(); // IllegalStateException异常
System.out.println(c); // [hello, java]
}
}
ArrayList的迭代器模型
关于并发修改异常的问题:
练习:用迭代器遍历的过程中,删除"java"。
public class CollectionDemo3 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");
// 练习:删除"java"
/*
Iterator it = c.iterator();
while (it.hasNext()) {
String s = (String) it.next();
if ("java".equals(s)) {
it.remove(); // [hello, world]
// c.remove(s); // ConcurrentModificationException并发修改异常
}
}
System.out.println(c);
*/
// 另外一个问题
Iterator it1 = c.iterator();
while (it1.hasNext()) {
String s = (String) it1.next();
System.out.println(s);
}
// ...茫茫多的代码
// ...
// 删除"hello"
Iterator it2 = c.iterator();
while (it2.hasNext()) {
String s = (String) it2.next();
if ("hello".equals(s)) {
it1.remove(); // ConcurrentModificationException并发修改异常
}
System.out.println(c);
}
}
}
我们看到,在练习中使用集合的API进行"java"的删除操作时,会出现并发修改异常。
那么,使用c.remove(s)为什么会出现并发修改异常呢?
因为c.remove(s)是在原集合中直接删除"java",此时迭代器的cursor(光标)依然位于"java"的右边,没有被修改,为非法位置,因此抛出并发修改异常。
由此得出一个结论:
如果用集合的API修改了集合的结构, 那么所有迭代器会失效。
另外一个问题,上面这段代码,在用it2遍历时,不小心把it2.remove();写成it1.remove();,那么就会报错:ConcurrentModificationException 并发修改异常。也就是说,你在用一个迭代器遍历时,只能访问这个迭代器的变量。
由此得出另外一个结论:
如果用某个迭代器修改了集合的结构,那么所有其它迭代器会失效。
所以说,**用迭代器遍历集合的时候,不要使用while循环,可以使用for循环,最好使用foreach循环。**因为for循环中的变量只在本循环体内起作用。
public class CollectionDemo3 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");
/*Iterator it1 = c.iterator();
while (it1.hasNext()) {
String s = (String) it1.next();
System.out.println(s);
}*/
for(Iterator it = c.iterator(); it.hasNext();) {
String s = (String) it.next();
System.out.println(s);
}
// ...茫茫多的代码
// ...
// 删除"hello"
/*Iterator it2 = c.iterator();
while (it2.hasNext()) {
String s = (String) it2.next();
if ("hello".equals(s)) {
it1.remove();
}
}*/
for(Iterator it = c.iterator(); it.hasNext(); ) {
String s = (String) it.next();
if ("hello".equals(s)) {
it.remove();
}
}
System.out.println(c);
}
}
下面我们看一下Iterator接口的实现原理:
List接口
List是Collection的子接口
概述:
-
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
-
与 set 不同,列表通常允许重复的元素。更确切地讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。
API:(以下为List包含的额外的API)
-
增:
boolean add(E e)
向列表的尾部添加指定的元素
void add(int index, E element)
在指定的索引位置添加元素;索引范围:[0,list.size()]
boolean addAll(int index, Collection c)
索引范围:[0,list.size()] -
删:
E remove(int index)
删除指定索引上的元素;索引范围:[0,list.size()-1] -
改:
E set(int index, E element)
用element元素替换指定index位置的元素,并把被替换的元素返回;索引范围:[0,list.size()-1] -
查:
E get(int index)
获取指定索引位置上的元素;索引范围:[0,list.size()-1]
int indexOf(Object o)
获取与指定对象o相等的第一个元素的索引,如果不存在这样的元素,那么返回-1
int lastIndexOf(Object o)
获取与指定对象o相等的最后一个元素的索引,如果不存在这样的元素,那么返回-1 -
遍历:
ListIterator listIterator()
光标后面元素索引为0
ListIterator listIterator(int index)
光标后面元素索引为index
我们先来看一下ListIterator接口,此接口继承自Iterator
概述:
列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置。
API:
- 遍历:
正向遍历
boolean hasNext()
// 以正向遍历列表时,如果列表迭代器有多个元素,则返回 true。
E next()
// 返回列表中的下一个元素。
逆向遍历
boolean hasPrevious()
// 以逆向遍历列表时,如果列表迭代器有多个元素,则返回 true。
E previous()
// 返回列表中的前一个元素。
public class ListDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
// 正向遍历
/*for (ListIterator it = list.listIterator(); it.hasNext();) {
String s = (String) it.next();
System.out.println(s);
}*/
// 逆向遍历
for (ListIterator it = list.listIterator(list.size()); it.hasPrevious();) {
String s = (String) it.previous();
System.out.println(s);
}
}
}
- 获取当前光标位置:
int nextIndex()
// 获取光标后面元素的索引
int previousIndex()
// 获取光标前面元素的索引
public class ListDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
// ListIterator it = list.listIterator();
// System.out.println(it.previousIndex()); // -1
// System.out.println(it.nextIndex()); // 0
// ListIterator it = list.listIterator(list.size());
// System.out.println(it.previousIndex()); // 2
// System.out.println(it.nextIndex());// 3
ListIterator it = list.listIterator(list.size());
it.previous(); // previous会向前移动1个光标
System.out.println(it.previousIndex()); // 1
System.out.println(it.nextIndex());// 2
}
}
- 修改:
void add(E e)
// 在光标后面的位置添加元素
public class ListDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
// void add(E e) 在光标后面\前面添加元素
// 在"hello"后面添加"kitty"
for (ListIterator it = list.listIterator(); it.hasNext();) {
String s = (String) it.next();
if ("hello".equals(s)) {
it.add("kitty");
}
}
System.out.println(list);
// 在"java"前面添加"kitty"
for (ListIterator it = list.listIterator(list.size()); it.hasPrevious();) {
String s = (String) it.previous();
if ("java".equals(s)) {
it.add("kitty");
}
}
System.out.println(list);
// 在"hello"后面添加"kitty", 出现ConcurrentModificationException的情况
// 用集合的API修改了集合的结构, 那么所有迭代器会失效。
for(ListIterator it = list.listIterator(); it.hasNext(); ) {
String s = (String) it.next();
if ("hello".equals(s)) {
// 获取添加元素的索引
int index = it.nextIndex();
list.add(index, "kitty"); // 调用集合的API
}
}
System.out.println(list);
}
}
void remove()
// 删除最近返回的元素
public class ListDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
// void remove()
// IllegalStateException 非法状态异常,因为最近没有返回元素
/*ListIterator it = list.listIterator();
it.remove();
System.out.println(list);*/
//IllegalStateException 非法状态异常 最近返回的元素移除之前不能修改集合的结构
/*ListIterator it = list.listIterator();
it.next();
it.add("Allen");
it.remove();
System.out.println(list);*/
// 删除"hello"
for(ListIterator it = list.listIterator(); it.hasNext(); ) {
String s = (String) it.next();
if ("hello".equals(s)) {
it.remove();
}
}
System.out.println(list);
// ConcurrentModificationException 并发修改异常
// 用集合的API修改了集合的结构, 那么所有迭代器会失效。
for(ListIterator it = list.listIterator(); it.hasNext(); ) {
String s = (String) it.next();
if ("hello".equals(s)) {
// 获取删除元素的索引
int index = it.previousIndex(); // 获取光标前面元素的索引
list.remove(index); // 删除这个索引
}
}
System.out.println(list);
}
}
void set(E e)
// 替换最近返回的元素
public class ListDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
//将"hello"修改成"HELLO"
for (ListIterator it = list.listIterator(); it.hasNext(); ) {
if ("hello".equals(it.next())) {
it.set("HELLO");
}
}
System.out.println(list);
//调用List的set方法并不会报错并发修改异常
//为什么呢?
//只有通过List 修改 集合的结构 的时候才会报错 ConcurrentModificationException
//现在只是改了集合某个位置的元素值,集合结构 并没有增加一个元素或减少一个元素
//所以并不会报错
for (ListIterator it = list.listIterator(); it.hasNext(); ) {
if ("hello".equals(it.next())) {
int index = it.previousIndex();
list.set(index,"HELLO");
}
}
System.out.println(list);
}
}
讲完ListIterator接口,继续来看List接口的最后一个API:
- 截取子串:
List subList(int fromIndex, int toIndex) 包左不包右
// 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。
public class ListDemo3 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
List subList = list.subList(1, 3);
System.out.println(subList); // [world, java]
//subList和list共享一份数据
subList.set(0, "WORLD");
System.out.println(subList); // [WORLD, java]
System.out.println(list); // [hello, WORLD, java]
}
}
为什么sublist和list的值都改变了呢?
因为subList和list共享一份数据。我们把这种技术称之为视图技术。
去看ArrayList中subList的源码:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
subList方法只是返回了一个SubList对象,再找到SubList的源码,发现SubList是一个成员位置内部类。ArrayList底层其实就是一个数组,但是看SubList的四个成员变量,它并没有维护一个数组,说明它的数据就是通过访问外部类的数据而得到的。
利用成员位置内部类取访问外部类,这就是视图技术的原理。
数组
Q1: 数组我们都很熟悉,那你理解的数组是什么样的呢?它的最主要特点是什么呢?
数组的本质是固定大小的连续的内存空间,并且这片连续的内存空间又被分割成等长的小空间。它最主要的特点是随机访问。
- 数组的长度是固定的
- 数组只能存储同一种数据类型的元素
注意:在Java中只有一维数组的内存空间是连续,多维数组的内存空间不一定连续。
那么数组又是如何实现随机访问的呢?
- 寻址公式:i_address = base_address + i * type_length
Q2: 为什么数组的索引是一般都是从0开始的呢?
假设索引不是从0开始的,而是从1开始的,那么我们有两种处理方式:
-
寻址公式变为: i_address = base_address + (i – 1) * type_length
-
浪费开头的一个内存空间,寻址公式不变。
在计算机发展的初期,不管是CPU资源,还是内存资源都极为宝贵,所以在设计编程语言的时候,索引就从0开始了,而我们也一直延续了下来。
扩展:现在的编程语言有些索引是从1开始的,甚至有些编程语言还支持负数索引。
Q3: 为什么数组的效率比链表高?
CPU、内存和IO设备,它们传输数据的速率是存在很大差异的。
CPU一天,内存一年;内存一天,IO十年。
那么根据木桶理论:木桶能装多少水,取决于最短的那块木板。程序的性能主要取决于IO设备的性能?也就是说,我们提升CPU和内存的传输速率收效甚微。
实际是这样的吗?当然不是!那我们是怎么缓解它们之间的速率差异的呢?
CPU 和 内存
-
高速缓存(预读)
CPU不直接从内存中读取数据,而是从高速缓存中读取数据。高速缓存从内存中读取的不只是你想要的数据,而是把和这条数据相邻的数据都读取进来。
时间局部性:刚刚访问过的这条数据很可能下一次还会访问。
空间局部性:刚刚访问过的这条数据,很可能内存中相邻的数据会访问这条数据相邻的一条数据。 -
编译器的指令重排序
加快机器指令执行的效率。
保证在单线程的环境下,执行结果和顺序执行结果一致。
内存和 IO
- 缓存
将磁盘上的数据缓存在内存。
CPU 和 IO
- 中断技术
- DMA
数组可以更好地利用CPU的高速缓存!
因为数组中的数据数连续存放的,链表中的数据不是连续存放的。根据局部性原理,会优先加载到缓存中。(局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中)