最近在准备Java实习生面试的过程中发现很多面Java基础时都会设计到集合相关的东西,所以准备总结一下集合相关的东西。
看过很多博客,大家大多以Collection下面的几大接口进行集合总结的,我准备以各个集合底层具体实现的数据结构为主线,附带集合的具体实现接口中的方法来进行总结。
队列:###
通常有两种实现方法:
- 使用循环数组,在Java中对应ArrayDeque类
- 使用链表,在java中对应LinkedList类,同时该类实现了Queue接口
循环数组要比链表更加的高效,一般优先选择循环数组,但循环数组是一个有限集合,容量是有限制的。如果程序当中我们所需要容纳的集合元素是无限的责需要使用LinkedList 类 。后面我们将阐述它们底层的具体实现和具体使用方法,在这之前我们先了解一下集合中最重要的两个接口Collection和Iterator。
Iterator接口#####
iterator()方法用于返回一个实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。该接口中包含了三个方法:
public interface Iterator<E>{
E next(); //迭代引用先后移动,返回此次移动越过的对象
boolean hasNext(); //表示还有没有其他的元素
void remove(); //移除迭代子最后一次越过的元素
}
因为Collection接口扩展了Iterable接口,因此对标准库中的任何集合都可以使用"for each"循环
for(String element : c){
//do something with element
}
在Iterator的使用当中我认为应该理解注意以下几个方面:
- Java集合类库中的迭代器与其它类库中的迭代器在概念上有着重要的区别。其它地方迭代器是根据数组索引建模的。Java迭代器中查找操作与位置变更是紧密相连的。查找一个元素的唯一方法是调用next(),而在执行查找操作的同时,迭代器的位置随之向前移动。调用next(),迭代器就越过下一个元素,并返回刚刚越过的元素的引用;
- next()方法和remove()方法是具有相互依赖性的。如果调用remove之前没有调用next将是不合法的。会抛出一个IllegalStateException异常。
- next方法调用到最后没有元素时将抛出NoSuchElementException,所以我们在调用next方法之前,应当先调用hasNext()方法。
PS:后面还会介绍一个listIterator()方法,是list接口的一个方法,LinkedList实现了这个接口,该接口还会提供向前遍历以及添加元素等一些功能,我们之后再讲。
Collection接口#####
除了Map结尾的集合类外,基本上都实现了Collection接口,它提供了很多实用的方法,后面我们便列出一些常用的方法:
//返回一个迭代器
Iterator<E> iterator();
//返回集合中元素的个数
int size();
//集合中是否包含该元素
boolean contains(Object obj);
//该集合是否包含other集合中的所有元素
boolean containsAll(Collection<?> other);
//想集合中添加一个元素,如果该调用改变了集合,返回true
boolean add(Object element);
//将other中的所有元素添加入集合
boolean addAll(Collection<? extends E> other);
//删除集合中等于obj的对象,如果匹配对象呗删除,返回true
boolean remove(Object obj);
//删除集合中所有other所含元素
boolean removeAll(Collection<?> other);
//删除集合中所有元素
void clear();
//从这个集合中删除所有与other集合中的元素不同的元素。如果由于这个调用改变了集合,返回true
boolean retainAll(Collection<?> other);
//返回这个集合的对象数组
Object[] toArray();
//返回这个集合的对象数组,如果arrayToFill足够大,就将集合中的元素填入这个数组中,剩余空间用null填补,否则分配一个新数组,其成员类型与arrayToFill的成员类型相同,其长度等于集合的大小,并填入集合元素
<T> T[] toArray(T[] arrayToFill);
现在我们对集合有了一个基本的了解,下面我们根据底层数据结构来讲解具体的集合。
链表(Link)来讲解LinkedList###
在Java中,所有链表实际上都是双向链接的(doubly linked),每个节点还存放着指向前驱节点的引用。为了方便我就直接贴上来LinkedList的API。
链表是一个有序集合,每个对象的位置都十分的重要。
LinkedList.add()
方法将对象添加到链表的尾部。但是我们常常需要讲一个元素添加到链表的中间。由于迭代器是描述集合中的位置,集合类库提供了子接口ListIterator,其中是包含add()方法的上面就是ListIterator所包含的方法,次对象可由LinkedList的listIterator()方法获取,该方法是list接口中的,而LinkedList便实现了该接口所有有此方法。我们在使用过程中需要注意:
- add方法只依赖于迭代器的位置,而remove方法依赖于迭代器的状态,如果将我们的迭代器比作光标,则add()方法便将元素插入到光标前,remove方法便是我们的backspace操作。
- 多个迭代器同时访问一个集合,如果迭代器发现它的集合被另一个迭代器修改了,就会抛出一个Concurrent ModificationException异常,为了避免发生并发修改异常,我们可以根据需要给容器附加许多的迭代器,但是这些迭代器智能读取列表。另外,再单独附加一个既能读又能写的迭代器。
- 链表是不支持随机访问的,如果要查找一个元素,就必须从头开始查找,下面有一种效率很低的做法,尽管get()方法做了微小的优化,当索引大于size()/2就从列表尾端开始搜索元素
for(int i = 0 ; i< list.size() ; i++){
do something with list.get(i);//因为做不到缓存位置信息的操作,所以每次get都要从头开始
}
先总结到这儿吧,以后有关List的一些特性放到ArrayList的分析中去