List集合

介绍

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

扩容问题:

  1. 默认集合大小–>10
       默认构造方法创建的集合,默认长度是0,当添加第一个元素的时候,会扩容到默认长度10. (JDK1.7)
  2. 扩容规则
       每次扩容都是使用“>>”移位运算符来实现的,所以每次扩容大小是原来的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);
}

扩容实例:

  1. 新建一个集合,向里面添加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,则必须自己实现访问同步。

(转载)链表的基本使用

链表结构图解

  1. LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下:
    结构图
  2. 既然是双向链表,那么必定存在一种数据结构——我们可以称之为节点,节点实例保存业务数据,前一个节点的位置信息和后一个节点位置信息,如下图所示:
    业务图
    (转载)链表结构详细解析
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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值