JAVA集合框架之List

1.先介绍集合框架:

Java,是由Sun公司于1995年推出的编程语言。用Java实现的HotJava浏览器(支持Java applet)显示了Java的魅力:跨平台、动态的Web、Internet计算。从此,Java被广泛接受并推动了Web的迅速发展,常用的浏览器现在均支持Java applet。集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法

框架的益处

减少设计辛劳

集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而不是为了让程序能正常运转而将注意力于低层设计上。通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以便联合这些API而去写大量的代码。

提高速度质量

集合框架通过提供对有用的数据结构和算法的高性能和高质量的实现使你的程序速度和质量得到提高。因为每个接口的实现是可互换的,所以你的程序可以很容易的通过改变一个实现而进行调整。另外,你将可以从写你自己的数据结构的苦差事中解脱出来,从而有更多时间关注于程序其它部分的质量和性能。

API缺点

许多API天生的有对集合的存储和获取。在过去,这样的API都有一些子API帮助操纵它的集合内容,因此在那些特殊的子API之间就会缺乏一致性,你也不得不从零开始学习,并且在使用时也很容易犯错。而标准集合框架接口的出现使这个问题迎刃而解。

集合框架的复用

对于遵照标准集合框架接口的新的数据结构天生即是可复用的。同样对于操作一个实现了这些接口的对象的算法也是如此。

有了这些优点,并通过合理的使用,它就会成为程序员的一种强大的工具。不过,从历史上来看,集合大多其结构相当复杂,也就给它们一个造成极不合理的学习曲线的坏名声。但是,希望Java2的集合框架能缩短你的学习曲线,从而快速掌握它。
在许多高级语言中的数组其实也是集合的一种简单实现,比如C,C++,Pascal和Java。数组保存着相同类型的多个值,它的长度在数组被创建时就固定下来,建立之后就无法改变。如果你需要一种大小能动态改变的存储结构,数组就不适合了,这时集合框架就有了用武之地了

2.List

Collection

Collection接口提供了一组操作成批对象的方法
它提供了基本操作如添加、删除。它也支持查询操作如是否为空isEmpty()方法等。为了支持对Collection进行独立操作,Java的集合框架给出了一个Iterator,它使得你可以泛型操作一个Collection,而不需知道这个Collection的具体实现类型是什么。它的功能与Java1中的Enumeration类似,只是更易掌握和使用,功能也更强大。在建立集合框架时,Sun的开发团队考虑到需要提供一些灵活的接口,用来操作成批的元素,又为了设计的简便,就把那些对集合进行可选操作的方法与基本方法放到了一起。因为一个接口的实现者必须提供对接口中定义的所有方法的实现,这就需要一种途径让调用者知道它正在调用的可选方法当前不支持。最后开发团队选择使用一种信号,也即抛出一种不支持操作例外(UnsupportedOperationException),如果你在使用一个Collection中遇到一个上述的例外,那就意味着你的操作失败,比如你对一个只读Collection添加一个元素时,你就会得到一个不支持操作例外。在你实现一个集合接口时,你可以很容易的在你不想让用户使用的方法中抛出UnsupportOperationException来告诉使用者这个方法当前没有实现,UnsupportOperationException是RuntimeException的一个扩展。
另外Java2的容器类库还有一种Fail fast的机制。比如你正在用一个Iterator遍历一个容器中的对象,这时另外一个线程或进程对那个容器进行了修改,那么再用next()方法时可能会有灾难性的后果,而这是你不愿看到的,这时就会引发一个ConcurrentModificationException例外。这就是fail-fast。Collection的功能
下面这张表给出了Collection的所有功能,也就是你能用Set和List做什么事(不包括从Object自动继承过来的方法)。(List还有一些额外的功能。)Map不是继承Collection的,所以我们会区别对待。
boolean add(Object):确保容器能持有你传给它的那个参数。如果没有把它加进去,就返回false。(这是个“可选”的方法,本章稍后会再作解释。)
boolean addAll(Collection):加入参数Collection所含的所有元素。只要加了元素,就返回true。
void clear():清除容器所保存的所有元素。(“可选”)
boolean contains(Object):如果容器持有参数Object,就返回true。
boolean containsAll(Collection):如果容器持有参数Collection所含的全部元素,就返回true。 boolean isEmpty():如果容器里面没有保存任何元素,就返回true。
Iterator iterator():返回一个可以在容器的各元素之间移动的Iterator。
boolean removeAll(Collection):删除容器里面所有参数Collection所包含的元素。只要删过东西,就返回true。(“可选”)
boolean retainAll(Collection):只保存参数Collection所包括的元素(集合论中“交集”的概念)。如果发生过变化,则返回true。(“可选”)
int size():返回容器所含元素的数量。
Object[] toArray():返回一个包含容器中所有元素的数组。
Object[] toArray(Object[] a):返回一个包含容器中所有元素的数组,且这个数组不是普通的Object数组,它的类型应该同参数数组a的类型相同(要做类型转换)。
注意,这里没有能进行随机访问的get()方法。这是因为Collection还包括Set。而Set有它自己的内部顺序(因此随机访问是毫无意义的)。所以如果你要检查Collection的元素,你就必须使用迭代器。

接下来讲List, Set和Map的各种实现了,每讲一种容器,我都会(用星号)告诉你默认情况下应该选用哪种实现。

List接口对Collection进行了简单的扩充
它的具体实现类常用的有ArrayList和LinkedList。你可以将任何东西放到一个List容器中,并在需要时从中取出。ArrayList从其命名中可以看出它是一种类似数组的形式进行存储,因此它的随机访问速度极快,而LinkedList的内部实现是链表它适合于在链表中间需要频繁进行插入和删除操作。在具体应用时可以根据需要自由选择。

List的基本用法是相当简单的。虽然绝大多数时候,你只是用add()加对象,用get()取对象,用iterator()获取这个序列的Iterator,但List还有一些别的很有用的方法。

List(接口)

:List的最重要的特征就是有序;它会确保以一定的顺序保存元素。(注意:这里的顺序是指储存顺序和读取顺序一致,而不是按照大小排序)List在Collection的基础上添加了大量方法,使之能在序列中间插入和删除元素。(只对LinkedList推荐使用。)List可以制造ListIterator对象,你除了能用它在List的中间插入和删除元素之外,还能用它沿两个方向遍历List。

ArrayList*:

一个用数组实现的List。能进行快速的随机访问,但是往列表中间插入和删除元素的时候比较慢。ListIterator只能用在反向遍历ArrayList的场合,不要用它来插入和删除元素,因为相比LinkedList,在ArrayList里面用ListIterator的系统开销比较高。

示例代码:

        ArrayList List = new ArrayList();
        for (int i = 0; i < 10; i++) //给数组增加10个Int元素
            List.add(i);
        //..程序做一些处理
        List.remove(5);//将第6个元素移除
        for (int i = 0; i < 3; i++) //再增加3个元素
            List.add(i + 9);

接下来我们看看Arraylist的源代码

 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 transient Object[] elementData;//transient 修饰不会进行序列化
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

默认构造是创建 一个大小为0的数组,可以存放任意类型元素。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果目前的ArrayList 是默认空的ELEMENTDATA就给minCapacity 赋值DEFAULT_CAPACITY=10
            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);
    }

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }   

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

可以看出add的时候会对ArrayList内部的数组进行 扩充默认是10,内部数组最大为AX_ARRAY_SIZE = Integer.MAX_VALUE - 8

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

删除元素会把后面的元素向前移动并把最后一个元素置null进行回收。

Vector

Vector的操作很简单,通过addElement()加入一个对象,用elementAt()取出它,还可以查询当前所保存的对象的个数size();另外还有一个Enumeration类提供了连续操作Vector中元素的方法,这可以通过Vector中的elements()方法来获取一个Enumeration类的对象,可以用一个While循环来遍历其中的元素。用hasMoreElements()检查其中是否还有更多的元素。用nextElement()获得下一个元素。Enumeration的用意在于使你能完全不用理会你要遍历的容器的基础结构,只关注你的遍历方法,这也就使得遍历方法的重用成为可能。由于这种思想的强大功能,所以在Java2中被保留下来,不过具体实现,方法名和内部算法都改变了,这就是Java2中的Iterator以及ListIterator类。然而Enumeration的功能却十分有限,比如只能朝一个方向进行,只能读取而不能更改等。

示例代码

import java.util.Vector;
import java.util.List;
import java.util.Iterator;
import java.util.Enumeration;
/**
 * @desc Vector测试函数:遍历Vector和常用API 
 *
 * @author skywang
 */
public class VectorTest {
    public static void main(String[] args) {
        // 新建Vector
        Vector vec = new Vector();

        // 添加元素
        vec.add("1");
        vec.add("2");
        vec.add("3");
        vec.add("4");
        vec.add("5");
        // 设置第一个元素为100
        vec.set(0, "100");
        // 将“500”插入到第3个位置
        vec.add(2, "300");
        System.out.println("vec:"+vec);
        // (顺序查找)获取100的索引
        System.out.println("vec.indexOf(100):"+vec.indexOf("100"));
        // (倒序查找)获取100的索引
        System.out.println("vec.lastIndexOf(100):"+vec.lastIndexOf("100"));
        // 获取第一个元素
        System.out.println("vec.firstElement():"+vec.firstElement());
        // 获取第3个元素
        System.out.println("vec.elementAt(2):"+vec.elementAt(2));
        // 获取最后一个元素
        System.out.println("vec.lastElement():"+vec.lastElement());
        // 获取Vector的大小
        System.out.println("size:"+vec.size());
        // 获取Vector的总的容量
        System.out.println("capacity:"+vec.capacity());
        // 获取vector的“第2”到“第4”个元素
        System.out.println("vec 2 to 4:"+vec.subList(1, 4));
        // 通过Enumeration遍历Vector
        Enumeration enu = vec.elements();
        while(enu.hasMoreElements())
            System.out.println("nextElement():"+enu.nextElement());

        Vector retainVec = new Vector();
        retainVec.add("100");
        retainVec.add("300");
        // 获取“vec”中包含在“retainVec中的元素”的集合
        System.out.println("vec.retain():"+vec.retainAll(retainVec));
        System.out.println("vec:"+vec);

        // 获取vec对应的String数组
        String[] arr = (String[]) vec.toArray(new String[0]);
        for (String str:arr)
            System.out.println("str:"+str);
        // 清空Vector。clear()和removeAllElements()一样!
        vec.clear();
//        vec.removeAllElements();
        // 判断Vector是否为空
        System.out.println("vec.isEmpty():"+vec.isEmpty());
    }   
}

Vector 除了提供了同步外, 它和 ArrayList 没有任何区别, 因为它是线程安全的, 所以效率比较差, Java 推荐我们尽量使用 ArrayList.

Stack

单元素容器Stack,它最常用的操作便是压入和弹出,最后压入的元素最先被弹出。你可以想象一个只上面开口的书箱,最后放进去的书一定是最先被拿到,而最先放进去的只有在全部书拿出后才能取出,这种特性被称为后进先出(LIFO)。在Java中Stack的的用法也很简单,有push()压入一个元素,用pop()弹出一个元素。然而它的设计却无法让人理解,Stack继承了Vector而不用Vector作为其中一个元素类型来实现其功能,这样造成的结果是Stack也拥有Vector的行为,也就是说你可以把Stack当作一个Vector来用,而这与Stack的用意毫无关系。这应该算为Java1(1.0/1.1)中容器类库设计者的一大失误吧,还好,这些在Java2中都有了相当大的改变观。

示例代码:

import java.util.Stack;
import java.util.Iterator;
import java.util.List;
/**
 * @desc Stack的测试程序。测试常用API的用法
 *
 * @author skywang
 */
public class StackTest {
    public static void main(String[] args) {
        Stack stack = new Stack();
        // 将1,2,3,4,5添加到栈中
        for(int i=1; i<6; i++) {
            stack.push(String.valueOf(i));
        }
        // 遍历并打印出该栈
        iteratorThroughRandomAccess(stack) ;
        // 查找“2”在栈中的位置,并输出该方法返回距离~栈顶最近的出现位置到栈顶的距离;栈中最上端项的距离为1。
        int pos = stack.search("2");
        System.out.println("the postion of 2 is:"+pos);
        // pup栈顶元素之后,遍历栈
        stack.pop();
        iteratorThroughRandomAccess(stack) ;
        // peek栈顶元素之后,遍历栈
        String val = (String)stack.peek();
        System.out.println("peek:"+val);
        iteratorThroughRandomAccess(stack) ;
        // 通过Iterator去遍历Stack
        iteratorThroughIterator(stack) ;
    }
    /**
     * 通过快速访问遍历Stack
     */
    public static void iteratorThroughRandomAccess(List list) {
        String val = null;
        for (int i=0; i<list.size(); i++) {
            val = (String)list.get(i);
            System.out.print(val+" ");
        }
        System.out.println();
    }
    /**
     * 通过迭代器遍历Stack
     */
    public static void iteratorThroughIterator(List list) {
        String val = null;
        for(Iterator iter = list.iterator(); iter.hasNext(); ) {
            val = (String)iter.next();
            System.out.print(val+" ");
        }
        System.out.println();
    }
}

源代码

    public Stack() {
    }

这里看不出什么,无参构造函数默认继承父类的构造函数

    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;
    }   

内部还是通过数组实现的。

LinkedList的用途

用LinkedList做一个栈
“栈(stack)”有时也被称为“后进先出”(LIFO)的容器。就是说,最后一个被“压”进栈中的东西,会第一个“弹”出来。同其他Java容器一样,压进去和弹出来的东西都是Object,所以除非你只用Object的功能,否则就必须对弹起来的东西进行类型转换。
LinkedList的方法能直接实现栈的功能,所以你完全可以不写Stack而直接使用LinkedList。
如果你只想要栈的功能,那么继承就不太合适了,因为继承出来的是一个拥有LinkedList的所有方法的类。
用LinkedList做一个队列
队列(queue)是一个“先进先出”(FIFO)容器。也就是,你把一端把东西放进去,从另一端把东西取出来。所以你放东西的顺序也就是取东西的顺序。LinkedList有支持队列的功能的方法,所以它也能被当作Queue来用。
还能很轻易地用LinkedList做一个deque(双向队列)。它很像队列,只是你可以从任意一端添加和删除元素。

public boolean add(E e) {
        linkLast(e);
        return true;
 void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

添加是往最后一个添加,元素放在Node对象中,fist和last都指向第一个node。等下一个添加的时候fist不变,第一个node的next指针指向新的node,新的prev指向第一个node,last指向新的node。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值