Java中的集合概述
集合是一个容器,用来存放引用类型的数据,在java.util包下。
Java中的集合主要有3种类型:
List接口:
是一个有序集合,可以放重复的数据。
Set接口:
是一个无序集合,不允许放重复的数据。
Map接口:
是一个无序集合,集合中包含一个键对象,一个值对象,键对象不允许重复,值对象可以重复。
Java中Collection集合的继承实现关系
Java中的Collection集合继承关系:
相信这里有很多学习java的朋友,小编整理了一份java方面的学习资料,
有想要学习java的可以加一下我的学习群的哟,60833,4068,欢迎爱学习java的你们!
Iterable接口
此接口只有一个方法声明,此方法返回一个Iterator接口类型的实例。
Iterable接口声明:
Iterator接口:
此接口有三个方法申明。主要用于对集合的迭代。
Iterator接口声明:
Collection接口:
Collection接口很多方法声明。
Collection接口:
List接口:
有序并且元素可以重复,对于List集合,存储的顺序不会改变。存储在List集合中的元素可以重复,如一个String类型的对象可以存储多次。
Set接口:
无顺序并且元素不可重复,相同的对象只能存储一次并且存储的顺序和读取的顺序不一定相同。
SortedSet接口:
无序并且不可重复,另外主要的特点是自动根据大小或字典顺序排序。
ArrayList类:
Arraylist实现了List接口中的方法,同时ArrayList也继承了List接口的特点,存取原素有顺序并且元素可以重复。另外,需要注意的地方是ArrayList底层的实现是数组,所以对于ArrayList集合来说适合查询而不善于频繁的动态增删元素。因为数组根据数组的下标就可以提取相应的元素而不需要从头遍历整个数组中所有的元素,数组的这个特点使ArrayList更适合大量查询的场景。而数组的另一个特点是增删数据代价高,当增加或删除原素时,部分原素要进行移动,使性能下降。
LinkedList类:
LinkedList类实现了List接口中的方法,同时LinkedList也继承了List接口的特点,存取原素有顺序并且元素可以重复。另外需要注意的是LinkedList集合底层的实现是双向链表,所以LinkedList更适合频繁的增删操作,但是查询效率不如ArrayList集合,两种集合类型是互补的。
Vector类:
Vector集合底层的实现与ArrayList集合相同,都是数组的方式实现。另外,Vector集合是线程安全的,对于多线程集合操作适合Vector集合,如果ArrayList集合被多线程操作,同时增删集合中的数据那么读取的数据可能是错误的。但是Vector效率较低,使用较少。通常线程安全是在代码中控制,而不需要利用Vector中多线程安全而降低程序的效率。
HashSet类:
HashSet实现了Set接口并实现了Set接口中的方法,HashSet也同时继承了Set接口的特点,集合中的元素无序并且不可以重复,存储顺序和读取顺序不一定相同。HashSet底层的实现是哈希表数据结构。
TreeSet类:
TreeSet直接实现的接口是SortedSet接口,和HashSet相同是元素不可重复,但是集合中的元素会自动进行排序,根据大小或字典顺序。
ArrayList
ArrayList类是一个特殊的数组。它来自于System.Collections命名空间;通过添加和删除元素,就可以动态改变数组的长度。ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。
继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
size(), isEmpty(), get(), set()方法均能在常数时间内完成,add()方法的时间开销跟插入位置有关,addAll()方法的时间开销跟添加元素的个数成正比。其余方法大都是线性时间。
为追求效率,ArrayList没有实现同步(synchronized),如果需要多个线程并发访问,用户可以手动同步,也可使用Vector替代。
方法剖析
set()
既然底层是一个数组ArrayList的set()方法也就变得非常简单,直接对数组的指定位置赋值即可。
public E set(int index, E element) {
rangeCheck(index);//下标越界检查
E oldValue = elementData(index);
elementData[index] = element;//赋值到指定位置,复制的仅仅是引用
return oldValue;
}
get()
get()方法同样很简单,唯一要注意的是由于底层数组是Object[],得到元素后需要进行类型转换。
public E get(int index) {
rangeCheck(index);
return (E) elementData[index];//注意类型转换
}
add()
对应的方法是add(E e)和add(int index, E e)。这两个方法都是向容器中添加新元素,这可能会导致capacity不足,因此在添加元素之前,都需要进行剩余空间检查,如果需要则自动扩容。扩容操作最终是通过grow()方法完成的。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//原来的3倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//扩展空间并复制
}
由于Java GC自动管理了内存,这里也就不需要考虑源数组释放的问题。
空间的问题解决后,插入过程就显得非常简单。
add(int index, E e)需要先对元素进行移动,然后完成插入操作,也就意味着该方法有着线性的时间复杂度。
addAll()
addAll()方法能够一次添加多个元素,根据位置不同也有两个把本,一个是在末尾添加的addAll(Collection<? extends E> c)方法,一个是从指定位置开始插入的addAll(int index, Collection<? extends E> c)方法。跟add()方法类似,在插入之前也需要进行空间检查,如果需要则自动扩容;如果从指定位置插入,也会存在移动元素的情况。
addAll()的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关。
remove()
remove()方法也有两个版本,一个是remove(int index)删除指定位置的元素,另一个是remove(Object o)删除第一个满足o.equals(elementData[index])的元素。删除操作是add()操作的逆过程,需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC起作用,必须显式的为最后一个位置赋null值。
欢迎关注胖胖程序员,可视化学习java,每天更新文章,让Java学习更加简单。