介绍
List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许加入重复元素,因为它可以通过索引来访问指定位置的集合元素。List集合默认按元素的添加顺序设置元素的索引,
注意:List中可以有null元素,例如[ tom,null,1 ];
1、List 接口的三个典型实现:
①、List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
②、List list2 = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
③、List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高
LIst 接口源代码:
import java.util.Iterator;
import java.util.ListIterator;
/*
* Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
public interface List<E> extends Collection<E> {
int size(); //集合长度
boolean isEmpty(); // 判断是否为空
boolean contains(Object o); //是否包含
Iterator<E> iterator(); //返回迭代器
Object[] toArray(); //转成数组
<T> T[] toArray(T[] a); //转成指定对象的数组
boolean add(E e); //添加对象
boolean remove(Object o); //删除对象
boolean containsAll(Collection<?> c); //判断是否包含所有
boolean addAll(Collection<? extends E> c); //添加一个结合
boolean addAll(int index, Collection<? extends E> c); //从指定位置添加集合
boolean removeAll(Collection<?> c); //删除集合
boolean retainAll(Collection<?> c); //判断交集(没有验证,感觉是)
void clear(); //清空集合
boolean equals(Object o); //判断是否相等
int hashCode(); //hashcode值
E get(int index); //返回指定位置的对象
E set(int index, E element); //用指定元素替换该列表中指定位置的元素(返回被替换的元素)
void add(int index, E element); //同上
E remove(int index); //删除指定位置对象
int indexOf(Object o); //从前面查询指定对象位置
int lastIndexOf(Object o); //从后面开始查询指定对象位置
ListIterator<E> listIterator(); //返回列表中元素上的列表迭代器。
ListIterator<E> listIterator(int index); //从列表中指定位置返回列表中元素的列表迭代器(以适当的顺序)
List<E> subList(int fromIndex, int toIndex); //截取
}
1、ArrayList
ArrayList是基于数组实现的List类,它封装了一个动态的增长的、允许再分配的Object[]数组。(线程不安全)
源代码:
源代码较多,这里不再贴出
注意:JDK7 和 JDK8 源码的不同:
JDK7源码中在调用空构造器(new ArrayList()
)的时候就分配了10个长度的数组空间。
// JDK7
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}
// add方法不在写了,有兴趣自己去看
JDK8源码中调用空构造器只是赋值了一个空的 Object[]数组。在调用add()方法的时候才会实际分配默认10个长度的数组空间。**
// JDK8
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public boolean add(E e) {
//这个方法里面有判断数组长度,如果是空值,设置为默认值10长度
ensureCapacityInternal(size + 1);
//将elementData的下一份元素赋值为e
elementData[size++] = e;
//返回true
return true;
}
扩容问题:
- 默认集合大小–>10
默认构造方法创建的集合,默认长度是0,当添加第一个元素的时候,会扩容到默认长度10. (JDK1.7) - 扩容规则
每次扩容都是使用“>>”移位运算符来实现的,所以每次扩容大小是原来的1.5倍。
<所以如果能确定集合长度,最好能显示声明,这样避免自动扩容消耗性能>
扩容源代码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 下面这句:原长度+原长度右移一位 (右移一位相当于除以2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//这里一句也相当重要,如果通过上面的扩容还是没有达到存储的长度,
//那么就吧数组长度设置为需要存储数组的长度(比较别扭,下面有实例)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
扩容实例:
- 新建一个集合,向里面添加11个元素,因为默认长度是10, 当添加第11个元素时候,那么就调用了上面源码中的grow方法。
ArrayList<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
list.add("8");
list.add("9");
list.add("10");
list.add("11"); //上面都能满足,当添加到 第 11个元素时候,就会调用grow方法
通过int newCapacity = oldCapacity + (oldCapacity >> 1);
把原数组长度设置为 15,15个长度的数组存放11个元素,可以满足要求
2. 接下来,我们新建一个默认长度为10的集合,当通过addAll(Collection c)
一次性添加16个数据,同上,因为初始长度为10,不满足情况,会通过grow方法中的int newCapacity = oldCapacity + (oldCapacity >> 1);
扩容,但是扩容后的长度 15 还是小于 16. 那么,我们看grow里的一个判断:
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
如果: (扩容后的)newCapacity - (需要存放元素长度)minCapacity < 0 说明库容的长度还不满足要求。那么接下来把:(需要存放元素长度)minCapacity 赋值 给 (扩容后的)newCapacity 。也就是说,把数组长度设置为你需要存放元素的长度。现在需要存放16个元素, 那么就把数组设置为16这么长。
2、Vector
Vector类是在JDK 1.0的时候所推出的最早的实现数据结构支持的类,其最早翻译为向量,但是到了JDK 1.2之后,为了使其可以继续使用,所以让这个类多实现了一个List接口,这样一来,就造成了Vector子类的操作方法比ArrayList更多,但是一般这些多的方法很少考虑。与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。
线程安全,效率慢,用法与ArrayList大体一样,几乎已经淘汰该集合。
扩容:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 这里扩容的容量,与ArrayList有所不用,是原来的2倍,不是1.5倍。
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
虽然这个集合不怎么用了,但是顺便提一下, 因为他的扩容与ArrayList有所不用,主要体现在扩容的容量方面。从上面的源码可以看出,Vector每次扩容都是扩容到原来的2倍。其他的地方都不变
3、LinkedList
同样实现List接口的LinkedList与ArrayList不同,ArrayList是一个动态数组,而LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了insert方法。由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作。与ArrayList一样,LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步。
(转载)链表的基本使用
链表结构图解
- LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下:
- 既然是双向链表,那么必定存在一种数据结构——我们可以称之为节点,节点实例保存业务数据,前一个节点的位置信息和后一个节点位置信息,如下图所示:
(转载)链表结构详细解析
3、Queue
队列:Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接 口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue 继承了Queue接口
- 队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
- 在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。
- 在java5中新增加了java.util.Queue接口,用以支持队列的常见操作。该接口扩展了java.util.Collection接口。-
- Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。
如果要使用前端而不移出该元素,使用element()或者peek()方法。
值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。
package com.ljq.test;
import java.util.LinkedList;
import java.util.Queue;
public class QueueTest {
public static void main(String[] args) {
//add()和remove()方法在失败的时候会抛出异常(不推荐)
Queue<String> queue = new LinkedList<String>();
//添加元素
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
queue.offer("e");
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("poll="+queue.poll()); //返回第一个元素,并在队列中删除
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("element="+queue.element()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("peek="+queue.peek()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
}
}
参考:https://www.cnblogs.com/samjustin/p/5785078.html