javaSE——集合(二)

一、List接口

1、List接口

List接口是Collection接口的子接口,其中

  • List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复

  • List集合中每个元素都有其对应的顺序索引,即支持索引

  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素

2、List接口常用方法

list接口继承了Collcetion接口,包含Collcetion中的所有方法。

方法

功能描述

void add(int index,Object element)

将元素elemet插入到List集合的index处

boolean addAll(int index,Collection c)

将集合c所包含的所有元素都插入到List集合的index处

Object get(int index)

返回集合index索引处的元素

int indexOf(Object o)

返回兑现那个o在List集合中第一次出现的位置索引

int lastIndexOf(Object o)

返回对象o在list集合中最后一次出现的位置索引

Object remove(int index)

返回并删除index索引处的元素

Object set(int index,Object element)

将index索引处的元素替换成element对象,返回被替换的旧元素

List subList(int forIndex,int toIndex)

返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合

void replaceAll(UnaryOperator operator)

根据operator指定的计算机规则重新设置List集合的所有元素

void sort(Comparator c)

根据Comparator参数对List集合的元素排序

此外,list还定义了两种方法

  • get(int int index):获得指定索引位置的元素

  • set(int inedx,Object obj):缉拿该集合中指定索引位置的对象修改为指定的对象

3、List三种遍历方式

(1)、使用Iterator

Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
    Object next =  iterator.next();
}

(2)、使用增强for

for (Object o : collection) {
}

(3)、使用普通for

for (int i=0;i<list.size();i++){
    Object object = list.get(i);
    System.out.println(object);
}

4、ArrayList

(1)简介

实现了可变的数组,允许保存所有元素,包括null,并可以根据索引位置对集合进行快速的随机访问;缺点是向指定的索引位置插入对象或删除对象的速度较慢

优点: 底层数据结构是数组,查询快,增删慢。

缺点: 线程不安全,效率高

(2)、ArrayList扩容机制

ArrayList中维护了一个Object类型的数据elementData;当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加时,扩容elementData为10,如需要再次扩容,则扩容elementData为原来的1.5倍;如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为原来的1.5倍

public static void main(String[] args) {
    List list = new ArrayList();
    for (int i=1;i<=5;i++){
        list.add(i);
    }        
    for (int i=5;i<=10;i++){
        list.add(i);
    }
    list.add(100);
    list.add(200);
    list.add(null);
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        Object obj =  iterator.next();
        System.out.println("List="+obj);
    }
}

在执行for循环时,会把int装箱为Integer

点开ArrayList()方法:

调用此构造函数,返回了一个空的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,点开此数组可看到长度为0

ArrayList()中主要的几个变量

//定义一个空元素数据
private static final Object[] EMPTY_ELEMENTDATA = {};

//也是一个空数组,跟上边的空数组不同之处在于,这个是在默认构造器时返回的,扩容时需要用到这个作判断
final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 *transient用来表示一个域不是该对象序行化的一部分,当一个对象被序行化的时候,transient修饰的变量的值是不包括在序行化的表示中的。
 * ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。
 * elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。
 */
transient Object[] elementData;

//数组的长度,此参数是数组中实际的参数,区别于elementData.length
private int size;

ArrayList有三个构造函数,不同的构造函数会影响后边的扩容机制判断:

1.默认的无参构造

调用此构造函数,返回了一个空的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此数组长度为0.

2.给定初始容量的构造函数

构造一个具有指定长度的空数组,当initialCapacity为0时,返回EMPTY_ELEMENTDATA

3.包含特定集合元素的构造函数

把传入的集合转换为数组,然后通过Arrays.copyOf方法把集合中的元素拷贝到elementData中。同样,若传入的集合长度为0,返回EMPTY_ELEMENTDATA

(3)、ArrayList扩容原理

扩容开始于集合添加元素方法,添加元素有两种方法

其中两个方法都调用了ensureCapacityInternal(size + 1)方法,把数组长度加1以确保能存下下一个数据,此方法会先判断elementData是不是一个空数组,如果是则赋值给他一个最小容量,最小容量则为 DEFAULT_CAPACITY 和 minCapacity 的最大值

可以看到DEFAULT_CAPACITY为10,minCapacity为1

若创建ArrayList时调用的是无参构造ArrayList(),此方法会返回DEFAULT_CAPACITY(值为10)和minCapacity的最大值,因此,最终会返回固定值10;若创建ArrayList时调用了有参构造ArrayList(6),则此方法会返回1,注意这个

minCapacity变量只是第一次调用add方法时值为1,此后的调用需要根据实际的数组长度size+1。

然后调用ensureExplicitCapacity方法,

modCount++用来记录当前集合被修改的次数,此用到了快速失败机制:当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值),这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

最小容量减去数组实际的大小,如果大于零则会调用grow方法进行扩容,比如当前容量为10,数组实际大小为0,则无法进行添加操作。即如果ArrayList给定了特定初始容量,则此处需要根据实际情况确定是否调用grow方法,即有可能不需要扩容。如果没有指定初始容量,第一次调用add则此处一定需要调用grow方法。

int newCapacity = oldCapacity + (oldCapacity >> 1)意为原数据大小加原先数组大小的1.5倍,oldCapacity为原来的容量,(oldCapacity >> 1)右移一位,即除以2

当第一次进行添加时,oldCapacity为0,所以进入if判断,判断newCapacity如果小于传入的minCapacity,则直接让newCapacity等于minCapacity,即不需要扩容计算(当无参构造时,elementData.length为0,所以oldCapacity也为0,minCapacity为10,因此最终newCapacity为10)。

然后判断newCapacity是否大于设定的MAX_ARRAY_SIZE,此处如果大于,则调用hugeCapacity方法

Arrays.copyOf即为数组的复制,把newCapacity复制给elementData

5、Vector

(1)、简介

底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素

protected Object[] elementData;

优点: 底层数据结构是数组,查询快,增删慢。

缺点: 线程安全,效率低

(2)、Vector扩容机制

public static void main(String[] args) {
    Vector vector = new Vector();
    for (int i=0;i<5;i++){
        vector.add(i);
    }
}

点击Vector()方法,如果是无参的,默认大小为10

在执行for循环时,会把int装箱为Integer

添加数据到Vector集合,和ArrayList一样有修改计数器

确定是否需要扩容

按实际情况进行扩容,

当第一次进行添加时,oldCapacity为10,capacityIncrement为0,所以直接让newCapacity等于oldCapacity+oldCapacity,即为原来的两倍。

6、LinkedList

(1)、简介

LinkedList采用双向链表结构保存对象。优点是便于像集合中插入和删除对象;但对于随机访问集合中的对象,使用LinkedList类实现List集合的效率较低

优点: 底层数据结构是链表,查询慢,增删快。

缺点: 线程不安全,效率高

(2)、简单双向链表实现

public class LinkedMethod {
    public static void main(String[] args) {
        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node jun = new Node("jun");

        jack.next=tom;
        tom.next=jun;

        jun.pre=tom;
        tom.pre=jack;

        //头尾结点
        Node first = jack;
        Node last = jun;

        System.out.println("==从头到尾进行遍历==");
        while(true){
            if(first==null){
                break;
            }
            //输出first信息
            System.out.println(first);
            first = first.next;
        }

        System.out.println("==从尾到头进行遍历==");
        while(true){
            if(last==null){
                break;
            }
            //输出first信息
            System.out.println(last);
            last = last.pre;
        }

    }
}

class Node {
    //Node节点对象
    public Object item;
    //指向后一个节点
    public Node next;
    //pre指向前一个节点
    public Node pre;

    public Node(Object name) {
        this.item = name;
    }

    public String toString() {
        return "Node name=" + item;
    }
}

(3)、LinkedList底层源码

add()方法

public static void main(String[] args) {
    LinkedList linkedList = new LinkedList();
    for (int i=1;i<=5;i++){
        linkedList.add(i);
    }
    System.out.print("LinkedList="+linkedList);
}

创建一个空数组

依旧是对int类型的装箱

add()方法

将新的结点加入到双向链表的最后;

new Node<>(l, e, null)意为上一个节点、本体、下一个节点;

当第一次添加时,此时last为空,first也为空,但都指向item,item为1;

当第二次添加元素时,要创建一个新的节点,此时l指向前一个元素,下一个结点指向新节点

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DF10F-0001A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值