List
看下list的类图:
1.list介绍
2.list的几种主要实现
list的主要实现有ArrayList,Vectory,Stack和LinkedList。我们还是通过具体实例来学习。
ArrayList:也被称为动态数组,可以动态改变数组的大小。
测试类:
package com.ljw.ColleactionAndMap;
import java.util.ArrayList;
import java.util.UUID;
/**
* Created by liujiawei on 2018/6/28.
*/
public class TestArrayList {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("jack");
arrayList.add("tom");
arrayList.add("jerry");
arrayList.add("null");
System.out.println(arrayList); //有序 可以保存重复数据 可以插入null
arrayList.set(0,"tony");
System.out.println(arrayList); //通过索引修改对应位置的数据
arrayList.add(0,"pony");
System.out.println(arrayList); //在执行索引出增加数据,索引之后的数据顺延
arrayList.ensureCapacity(20); //执行生成的arraylist 的大小
arrayList.trimToSize(); //将arraylist的大小设置为实际大小,释放空间
}
}
运行结果:
通过上面例子,可以看到arraylist按照插入数据的顺序保存数据,同时也支持通过索引直接修改/增加数据,在指定索引处增加数据时,这个索引后面的数据的索引依次顺延。除此以外,arraylist还有自己独有的两个方法:
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != EMPTY_ELEMENTDATA)
// any size if real element table
? 0
// larger than default for empty table. It's already supposed to be
// at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
这个支持自己手动设置arraylist的默认容量.
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = Arrays.copyOf(elementData, size);
}
}
这个方法可以将arraylist的大小设置为实际占用的大小,可以用来释放多占用的空间。
Java中set,list和queue都实现了iterable接口,代表这些集合对象都可以用迭代器来对集合中的数据进行迭代。在list中,他有一个自己独有的迭代器,listiterator,和iterator相比,它还支持往前迭代。
Arraylist中判断数据是否相同的依据是equals()方法是否返回true。
ArrayList总结:
(1)arraylist内部用数组实现,是一个动态数组,默认分配一个容量为10的空间,每次扩容1.5倍;(2)arraylist有序,通过索引可以直接操作数据;
(3)arraylist是线程不安全的。
Vector:
Vector的用法和arraylist基本一致,从jdk1.0开始就有的一个集合。*Vector是线程安全的,因此性能上没有arraylist好。
Stack:
Stack是Vector的一个子类,模拟了栈的数据结构,栈主要的特点就是后进先出(FILO),可以将它理解一个瓶子,先放进去的东西在最下面,最后放进去的在最上面,离瓶口最近。
通过代码认识下stack:
package com.ljw.ColleactionAndMap;
import java.util.Stack;
/**
* Created by liujiawei on 2018/6/29.
*/
public class TestStack {
public static void main(String[] args) {
Stack stack = new Stack();
stack.add("jack");
stack.add("jack");
stack.add("tom");
stack.add("jerry");
stack.add(null);
System.out.println(stack);
System.out.println(stack.peek());
System.out.println(stack.push("pony"));
System.out.println(stack);
System.out.println(stack.pop());
System.out.println(stack);
}
}
运行结果:
Stack大部分方法都是和list的用法差不多,主要介绍下这三个方法:
- Object peek(); 返回栈的第一个元素。
- Object pop(); 将栈的第一个元素出栈并返回。
- void push(Object item) 将元素入栈,这个元素位于栈顶
LinkedList:
LinkedList既实现了list接口,又实现了queue接口,我们在queue中在介绍它。
3.arraylist,vector底层实现分析
先看下arraylist下的构造方法:
他有三个构造方法:
private static final Object[] EMPTY_ELEMENTDATA = {};
private transient Object[] elementData;
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
可以看到如果我们使用无参构造方法来实例化一个arraylist,他是会分配一个空的Object [],这个解释了为什么arraylist可以放不同类型的对象,因为Object是所有对象的父类。
当我们在arraylist中执行add()操作时,其实arraylist每次都是一个新的对象,我们写一个测试类看下:
package com.ljw.ColleactionAndMap;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Created by liujiawei on 2018/6/26.
*/
public class Test {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
ArrayList arrayList2 = new ArrayList();
ArrayList arrayList3 = new ArrayList(20);
System.out.println(arrayList.hashCode() + "===" + arrayList2.hashCode() + "===" + arrayList3.hashCode());
arrayList.add("jack");
System.out.println(arrayList.hashCode());
arrayList.add("jack");
System.out.println(arrayList.hashCode());
arrayList.add("tom");
System.out.println(arrayList.hashCode());
}
}
看一下上面的代码,想一下结果。
运行结果:
结果和你想的是一样的么,从结果中我们可以看到,通过构造方法实例出的arraylist对象,他们的hashcode都是一样的,也就是说每次new的都是同一个对象,但执行完add()操作以后,每次add完的arraylist的hashcode都不同,这说明每次的arraylist都是不同的对象,我们看下arraylist的add()源代码来分析下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - 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);
}
在调用的最后一个方法中,我们看到了Arrays.copyOf()方法,在之前介绍数组的时候,曾经说过,数组在初始化的时候要声明大小,不可扩容。但是可以通过arrays工具类中的copyof生成一个由原数组数据的新数组来变相的达到扩容的目的。在add()中,每次最后都会通过Arrays.copyOf()生成一个新的数组存放数据,所以我们可以看到arraylist的hashcode在一直变化,那么为什么每次new的都是同一个对象呢,看一下上面贴出来的arraylist的构造方法,可以看到,它最终使用的都是一个定义好的静态常量Object[]对象,因为对象没有发生过变化,所以它的hashcode也不会发生变化。
再说下arraylist的扩容,还是看下add()方法,在最后调用的方法中,会对数组目前的实际大小做判断,
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);
}
也就是这个方法实现扩容,会比较数据实际的大小,当超过默认容量是,会增加0.5倍的容量,最后复制一个新的容量的数组。
通过这个方法,我们也就可以理解arraylist中ensureCapacity(int minCapacity)和trimToSize()这两个方法的作用了,当arraylist中要插入很多数据时,自己手动设置容量,可以避免多次扩容带来的性能影响,再通过trimToSize()释放掉多余的空间。
再看下Vector里面源码:
protected Object[] elementData;
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;
}
可以看到和arraylist一样,vector的默认容量也是10,看下add()方法的源码:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
可以看到,它最后也是使用的Arrays.copyOf(),有区别的是他扩容的方式和arraylist不同:
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
它每次都会在原来的基础扩展一倍。
4.小结
1.list中放的都是对象,不会存放基本数据类型,我们看到的基本数据类型的数据,在添加的时候都会被它的包装类进行包装;2.arraylist和vector内部使用数组实现,所以随机访问数据方便较好,linkedlist内部内部使用双向链表实现,随机访问数据较差,但是插入、删除性能较好;
3.arraylist和linkedlist都是线程不安全的,vector是线程安全的。
使用注意:
(1)需要迭代集合时,arraylist和vector使用get方式来便利较好,linkedlist通过iterator来迭代较好;(2)如果需要频繁的插入,删除数据,使用linkedlist较好,因为它使用链表实现,而vectory和arraylist需要不断对数组进行扩容,耗费时间。
(3)arraylist和linkedlist都是线程不安全的,vector是线程安全的,如果要求线程安全,可以使用vector。