提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
集合介绍
集合的理解和好处
我们保存数据经常使用数组进行,保存,但是数组有以下特点:
- 数组
1)长度开始时必须指定,而一旦指定,不能更改
2)保存的必须为同一类型的元素
3)使用数组增加或者删除元素比较麻烦
比如:
Person[] pers = new Person[1];//我们创建了一个大小为1的Person数组
添加元素:pers[0] = new Person();
如果我们想再添加一个元素,因为数组不能扩容,只能再创建一个新的数组,并把之前的元素复制过去。
所以就得有了集合,集合有一下特点:
- 集合
1)可以动态保存多个对象,使用比较方便(动态扩容,关于各个集合怎么实现动态扩容的,我们下面会一一讲解,这个是集合的难点也是重点,功利点说面试也是必问的)
2)提供了一系列方便的操作对象的方法:add 、remove、set、get等
集合体系架构
我们今天就对上述集合一个一个剖析
首先看一下继承关系图
选中collection,然后ctrl+alt+B
一个个添加进去就好了,最终得到这么一个继承关系图
单例集合和双列集合
- 单例集合:集合里放的都是单个的元素,比如List、Set
- 双列集合:一个完整的数据,需要两个元素来描述,比如:Map,里面存放的是entry (k-v)
public class Collection_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("tom");
HashMap hashMap = new HashMap();
hashMap.put("NO1","北京");
hashMap.put("NO2","上海");
}
}
collection接口
Collection接口实现类的特点
常用方法
遍历方式
迭代器
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
- Iterator 的结构
- Iterator 仅用于遍历集合,Iterator 本身并不存放对象。
public interface Collection<E> extends Iterable<E>
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator()
迭代的执行原理
示例:
//得到一个集合的迭代器
Iterator iterator = coll.iterator();
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next(): 返回迭代中的下一个元素
System.out.printIn(iterator.next());
}
在调用it.next()方法之前必须调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next会抛出 NoSuchElementException异常
public class CollectionIterator {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new book("三国演义","罗贯中",20.2));
col.add(new book("红楼梦","曹雪芹",150.3));
col.add(new book("小李飞刀","古龙",25.3));
System.out.println(col);
// 遍历集合
// 1. 得到集合对应的迭代器
Iterator iterator = col.iterator();
// 2. 使用while循环遍历
while (iterator.hasNext()){ //判断是否还有数据
// 返回下一个元素,类型是Object
Object object = iterator.next();
System.out.println("onject="+object);
}
// 提示: 快速生成while循环快捷键 => itit
// 显示快捷提示键 => ctrl+J
}
}
class book{
public String name;
public String author;
public double price;
public book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
增强for
增强for循环,可以替代iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。
for(元素类型 元素名 : 集合名或数组名){
访问元素
}
public class CollectionFor {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new book("三国演义","罗贯中",20.2));
col.add(new book("红楼梦","曹雪芹",150.3));
col.add(new book("小李飞刀","古龙",25.3));
System.out.println(col);
// 使用增强for循环遍历
// 增强for循环底层依然是迭代器
// 提示:快捷键 => col.for
for(Object book : col){
System.out.println("book="+book);
}
// 增强for也可以用在数组上
int[] nums = {1,25,13,8,5};
for(int i : nums){
System.out.println("i="+i);
}
}
}
List接口
List接口基本介绍
- List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复。
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- List接口常用的实现类有:ArrayList、LinkedList 和 Vector。
接口常用方法
public class ListMethod {
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom'");
list.add("张三");
// void add (int index,Object ele)在index位置插入ele元素
list.add(1,"zhagsan");
System.out.println(list);
// boolean addAll(int index,Collection eles)从index位置开始将eles中的所有元素添加进来
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1,list2);
System.out.println(list);
// Object get(int index)获取指定index位置的元素
Object o = list.get(1);
System.out.println(o);
// int indexOf(Object obj)返回obj在集合中首次出现的位置
System.out.println(list.indexOf("tom"));
// int lastIndexOf(Object obj)返回obj在当前集合中末次出现的位置
list.add("jack");
System.out.println(list.lastIndexOf("jack"));
// Object remove(int index)移除指定index位置的元素,并返回此元素
Object remove = list.remove(5);
System.out.println(remove);
// Object set(int index,Object ele)设置指定index位置的元素为ele,相当于是替换
list.set(1,"mary");
System.out.println(list);
// List subList(int fromeIndex,int toIndex)返回从fromIndex 到 toIndex 位置的子集合
// 子集合范围为前闭后开 fromeIndex <= subList < toIndex
List subList = list.subList(2, 4);
System.out.println(subList);
}
}
List的三种遍历方式
- 使用iterator
- 使用增强for
- 使用普通for
public class ListFor {
public static void main(String[] args) {
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("marry");
list.add("bob");
// 遍历
System.out.println("=======迭代器遍历");
// 1. 迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
System.out.println("=======增强for循环");
// 2. 增强for循环
for (Object o : list) {
System.out.println("o="+o);
}
System.out.println("=======普通for循环");
// 3. 普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println("对象="+list.get(i));
}
}
}
ArrayList
注意事项
- ArrayList 可以加入null,并且多个
- ArrayList是由数组来实现的
- ArrayList基本等同于Vector,除了 ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrrayList。
ArrayList底层结构和源码分析
先说结论
- ArrayList 中维护了一个 Object类型的数组 elementData。
transient Object[] elementData; // transient 标识瞬间,短暂的,表示该属性不会被序列化 - 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData 容量为0,第一次添加,则扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍。
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
关键源码
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
` transient Object[] elementData; // non-private to simplify nested class access``
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList扩容机制
我们以下面这个例子解读一下ArrayList的扩容机制
public class ArrayListSource {
public static void main(String[] args) {
//注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据
//需要做设置.
//使用无参构造器创建ArrayList对象
//ArrayList list = new ArrayList();
ArrayList list = new ArrayList(8);
//使用for给list集合添加 1-10数据
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//使用for给list集合添加 11-15数据
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
}
}
一、首先使用无参构造器创建一个list
ArrayList list = new ArrayList();
我们之前也说过,使用无参构造器会创建一个空数组,下面是关机键代码
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
因此elementData 就是一个空数组——{}。
往list中添加第一个元素
进入add().
执行add操作的时候,每次都先执行ensureCapacityInternal,才执行赋值操作elementData[size++] = e;
ensureCapacityInternal(size + 1);
//size表示当前数组的容量,+1是因为此时向list中添加了一个元素,size+1大小表示list需要的最小容量
ensureCapacityInternal就是ArrayList的扩容机制。继续debug,进入此方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
我们发现ensureExplicitCapacity里还有一个方法calculateCapacity
calculateCapacity(elementData, minCapacity)
//左边参数就是之前传进来的数组list,创建的时候是空的{},右边是当前list需要的最小容量
我们进入calculateCapacity()方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
解读:
1、if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
表示如果当前数组是默认的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就执行
return Math.max(DEFAULT_CAPACITY, minCapacity);
2、否则直接返回minCapacity
3、我们知道elementData, 之前没有对它进行任何赋值,所以它就是我们创建list的空数组。
4、所以执行
return Math.max(DEFAULT_CAPACITY, minCapacity);
返回DEFAULT_CAPACITY和minCapacity的最大的那个
而 private static final int DEFAULT_CAPACITY = 10;
5、因此,calculateCapacity的返回值是10
calculateCapacity,见名知意,意思就是计算容量,确切的说是此次添加元素,所需的数组的容量。
再进入ensureExplicitCapacity()
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//记录修改次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
minCapacity 就是当前所需要的容量,我们由上面知道第一次添加元素的时候,
执行calculateCapacity返回的是10,就是说当前需要的数组容量是10,
而elementData.length是目前数组真正的容量,我们之前没任何添加动作,
所以是0,因此 grow(minCapacity)是要执行的,这是真正的扩容方法。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
1、 int oldCapacity = elementData.length;
把当前数组的容量给oldCapacity ,就是原始容量,此时为0
2、int newCapacity = oldCapacity + (oldCapacity >> 1);//右移1位就相当于除以2
直接 0+0/2,也就是说上来就是1.5倍
(这个和之前说的结论不冲突,结论是宏观的角度说的效果,这个是细节,继续看分析)
3、if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
如果newCapacity - minCapacity < 0,意思是,1.5扩容的容量(0)小于所需的容量(10),
就把minCapacity赋值给newCapacity ,也就是说扩容容量是10
综上,第一次添加元素的时候,数组element扩容为10
4、 if (newCapacity - MAX_ARRAY_SIZE > 0)
//10小于MAX_ARRAY_SIZE 所以下面不执行先忽略后面说
newCapacity = hugeCapacity(minCapacity);
5、 elementData = Arrays.copyOf(elementData, newCapacity);
把原来的数组,复制到一个新的大小为newCapacity的数组上
记住Arrays.copyOf其实每次都创建一个新的数组。
这也就解释了为什么不频繁的扩容,第一次添加元素的时候就给初始容量10.
前面也说了,数组是不能动态扩容的,每次用完想再添加元素,只能创建一个新的。
会占用更多的堆内存
继续debug
到此,第一个元素,添加完毕!
当添加下一个元素时,因为数组一开始就给10容量,所以再继续添加,再10容量范围内也不会再扩容,直到10用完。
我们不停的往list中添加10个元素
当前数组容量用完时,添加第11个元素,继续分析之前的方法
因为数组已经有元素了,所以所需的大小就不是10了,而是11
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
现在所需的容量是11(minCapacity ),而当前数组大小是10(elementData.length)
所以继续扩容
进入grow(minCapacity)
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //10
//直接1.5倍。所以newCapacity为15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//扩容后的容量(newCapacity )是15,大于所需容量(minCapacity )10
//所以下面方法不执行
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:
//把原来数组的复制到一个大小为newCapacity(15)的新数组上,思想是一样的
elementData = Arrays.copyOf(elementData, newCapacity);
}
至此,数组由10扩容为1.5倍,也就是15.
当容量15再用完时,还是会按照上述进行继续扩容1.5倍。。。。。
二、当使用有参的构造器创建ArrayList
ArrayList list = new ArrayList(8);
@SuppressWarnings({"all"})
public class ArrayListSource {
public static void main(String[] args) {
ArrayList list = new ArrayList(8);
//使用for给list集合添加 1-10数据
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//使用for给list集合添加 11-15数据
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
}
}
构造器
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//8>0
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
因此对于有参构造器,直接创建一个new Object[initialCapacity]的数组,并赋值给elementData 。所以此时elementData 容量为8
第一次添加元素时,继续判断是否要扩容
elementData创建的时候,就给了容量8, 不是空数组了,因为所需容量为1(minCapacity)
所需容量为1(minCapacity),当前容量为8(elementData),因此不需要扩容。
继续往list中添加元素,当初始容量8用完后,再添加元素。
同样进入add()方法
根据之前的分析,所需容量是9,当前数组容量是8,需要扩容
同样直接1.5倍,将原数组的元素复制到容量为12的新数组上
以后再用完,再按照上面扩容1.5倍。
Vector
基本介绍
Vector与ArrayList的比较
Vector的扩容机制
无参构造
public class Vector_ {
public static void main(String[] args) {
//无参构造器
Vector vector = new Vector();
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector=" + vector);
}
}
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
它是在空参构造器Vector()中,调用一个参数的有参构造器Vector(int initialCapacity),再调用两个参数的有参构造器Vector(int initialCapacity, int capacityIncrement),
最终执行this.elementData = new Object[initialCapacity];
所以对于Vector,如果使用无参构造器创建的话,会直接创建一个容量为10的数组。
添加第一个元素
进入add(),synchronized修饰,线程安全
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
还是一样先判断要不要扩容
比较所需容量大小(minCapacity)和当前数组容量大小(elementData.length),判断是否需要扩容,因为当前容量10大于所需容量1,所以不扩容
当容量10用完,再向里面添加元素
判断是否进行扩容,所需容量11大于当前容量10,所以扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//原容量10
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);
}
capacityIncrement为0,所以直接2倍
当以后容量再不够时,直接2倍扩容
有参构造器
public class Vector_ {
public static void main(String[] args) {
//无参构造器
//有参数的构造
Vector vector = new Vector(8);
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector=" + vector);
}
}
构造器
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
直接创建一个容量为8的数组,当容量用完了,直接2倍扩容
LinkedList
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
LinkedList底层操作机制
-
LinkedList 底层维护了一个双向链表
-
LinkedList中维护了两个属性 first 和 last 分别指向 首节点和尾节点
-
每个节点(Node对象),里面又维护了prev、next、item、三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表。
-
LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
一个元素就是一个Node节点
Node其实是LinkedList的一个内部类
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
源码分析
添加元素
@SuppressWarnings({"all"})
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList=" + linkedList);
}
}
构造器:
public LinkedList() {
}
添加第一个元素
进入add()
public boolean add(E e) {
linkLast(e);
return true;
}
进入 linkLast(e);
transient Node<E> last;
void linkLast(E e) {
final Node<E> l = last; //此时last还是null,因为还未添加任何元素
final Node<E> newNode = new Node<>(l, e, null);//创建一个新的节点
last = newNode;//尾节点指向新Node
if (l == null)//此时l为空,因为是第一个元素
first = newNode;//头节点也指向新Node
else
l.next = newNode;
size++;//大小加1
modCount++;//记录被修改的次数
}
//这里额外给出Node的构造器
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;//元素
this.next = next;//指向下一个节点
this.prev = prev;//指向上一个节点
}
经过上述分析,第一个节点添加进来后,就是这个样子
再添加元素
void linkLast(E e) {
final Node<E> l = last;//此时last是上一个添加的Node(这里是1那个Node)
final Node<E> newNode = new Node<>(l, e, null);//再创建一个新Node
last = newNode;//last指向新Node
if (l == null)//不是第一次添加,下面不执行
first = newNode;
else
l.next = newNode;//把上一个节点的next执行新node
size++;//大小加1
modCount++;//记录被修改的次数
}
//这里额外给出Node的构造器
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;//元素
this.next = next;//指向下一个节点
this.prev = prev;//指向上一个节点
}
经过分析,节点添加进来后,就是这个样子
再添加元素时,继续按照上述规则,最终形成一个双向链表
删除元素
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList=" + linkedList);
//演示一个删除结点的
linkedList.remove(); // 这里默认删除的是第一个结点
}
}
进入remove()
所以默认删除的是第一个节点
public E removeFirst() {
final Node<E> f = first;//第一个节点为ox7028
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
进入unlinkFirst(f)
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;//取出第一个Node的值,也就是1
final Node<E> next = f.next;//取被删除节点的下一个节点
f.item = null;//node属性值置为null
f.next = null; // help GC // 断掉被删除节点执向下一个节点的引用
first = next;//链表的头节点改为指向下一个节点
if (next == null)//如果当前节点不存在下一个节点了,当前节点就是最后一个
last = null;//断掉尾节点指向它的引用
else//否则
next.prev = null;//下一个节点作为第一个元素
size--;//大小减1
modCount++;//修改次数加1
return element;//返回1,也就是说删除操作是可以知道删除的是哪一个的。
}
我们也可以删除指定的元素
linkedList.remove(2);
操作是差不多,就是改变各种执行该节点的引用,让它孤立出来等待垃圾回收,然后重新形成一个双向链表。
其实这种孤立出来的节点叫做不可触及对象,想学习JVM,可以看我关于JVM的博客
遍历LinkedList
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
System.out.println("===LinkeList遍历增强for====");
for (Object o1 : linkedList) {
System.out.println("o1=" + o1);
}
System.out.println("===LinkeList遍历普通for====");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
ArrayList 和 LinkedList 比较
如何选择ArrayList和LinkedList:
- 如果改查的操作较多,选择ArrayList
- 如果增删的操作较多,选择LinkedList
- 一般来说,程序中,80%-90%都是查询,因此大部分情况下选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList。根据业务合理选择。
- 如果想要线程安全就有Vector
总结
-
ArrayList底层数据结构和源码分析:
1、ArrayList可以加入null,并可以为多个
2、ArrayList由数组来实现存储的
3、ArrayList基本等同于Vector,但是ArrayList是线程不安全的 -
ArrayList的扩容机制
1、ArrayList维护了一个Object类型的数组elementData
2、当创建对象的时,如果使用的是无参构造器,则初始化elementData容量为0,
3、当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到适合的位置
4、如果使用的是无参构造,如果第一次添加,需要扩容的话,则扩容为10,如果需要再次扩容的话,则扩容为1.5倍
5、如果使用的是指定容量capacity的构造器,初始数组容量为capacity,如果需要扩容,则直接扩容为1.5倍 -
Vector:
1、Vector的底层也是一个对象数组(同ArrayList)
2、Vector是线程同步的,即线程安全,Vector的操作方法带有synchronized
3、在开发中,需要线程同步安全时,需要考虑使用Vector -
Vector和ArrayList的比较
底层结构 | 线程安全 | 效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | 不安全 | 效率高 | 如果有参1.5。如果无参:1、第一次10;2、从第二次开 始按1.5倍扩 |
Vector | 可变数组 | 安全 | 效率低 | 如果无参,默认10,满后按照2倍扩如果指定大小,每次直接按2倍扩 |
注意对于空参的方式,verctor是创建的时候,直接初始化数组容量为10,而ArrayList是在第一次添加元素的时候,才初始化容量为10
-
LinkedList
1、LinkedList实现了双向链表和双端队列的特点
2、可以添加任意元素,包括null
3、线程不安全,没有实现同步 -
LinkedList底层操作机制
1、LinkedList底层维护了一个双向链表
2、LinkedList中维护了两个属性first和last,分别指向首节点和尾节点
3、每个节点(node),里面维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
-Array List和LinkedList的比较