Java集合框架(二)List

List

    看下list的类图:


1.list介绍

     list,java中的有序集合,内部用数组实现,每个元素都有对应的索引,可以存放重复数据。

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。






猜你喜欢

转载自blog.csdn.net/qq_23585245/article/details/80855734