Java笔记(15) Collection集合–>List集合

集合的理解和好处
数组一旦定义,长度即固定,不能修改。要添加新元素需要新建数组,然后循环拷贝,非常麻烦

  1. 集合可以动态保存任意多个对象,使用比较方便
  2. 提供饿了一系列方便的操作对象的方法:add、remove、set、get等
  3. 使用集合添加、删除新元素的示意代码,简洁明了

集合主要是两组(单列集合,双列集合)
Collection 接口有两个重要的子接口,List 和 Set,他们的实现子类都是单列集合,直接存放值
Map接口的实现子类 是双列集合,存放的是K-V键值对
这是Collection接口下体系的主要接口和类体系:
Collection集合体系
这是Map接口下体系的主要接口和类体系:
Map集合体系

1. Collection接口和常用方法

1.1 Collection接口实现类的特点

public interface Collection<E> extends Iterable<E>
  1. collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类,可以存放重复的元素,有些不可以
  3. 有些Collection的实现类,有些事有序的(List),有些不是有序的(Set)
  4. Collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的

1.2 Collection接口和常用方法

以实现子类ArrayList来演示

void add(E e);        //添加单个元素, E是泛型
E remove(int index);     //删除并返回指定元素
boolean contains(Object o);   //查找某个元素是否存在
int size();       //获取元素个数
boolean isEmpty();    //判断是否为空
void clear();      //清空
boolean addAll(Collection<? extends E> c);   //添加E集合中的所有元素
boolean containsAll(Collection <?> c);  //查找E集合中的元素是否都存在于该集合中
boolean removeAll(Collection<?> c);    //移除该集合中所有同时存在于E集合中的元素

1.3 Collection接口遍历元素方式

1.3.1 使用Iterator(迭代器)

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
  2. 所有实现了Collection接口的集合类多有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即可以返回一个迭代器
  3. Iterator的方法和执行原理:
//hasNext();    判断是否还有下一个元素
//next();       1.指针下移,2.将下移以后集合位置上的元素返回
//remove();     从底层集合中移移除此迭代器返回的最后一个元素,每次调用next()时,只能调用此方法一次。并且如果在迭代过程中,调用了除该方法意外的任何方式修改基础集合,都会破坏迭代器,从而终止迭代,这是为了应对并发问题,因此可以通过实现此方法的时候指定并发修改策略来避免破坏迭代器。

Iterator iterator = coll.iterator();//得到一个集合的迭代器
while(iterator.hasNext()){
    //next():
    System.out.println(iterator.next());
}
  1. Iterator仅用于遍历集合,Iterator本身并不存放对象
    提示:在调用iterator.next()方法之前,必须要调用iterator.hasNext()进行检测。否则当下一条记录无效时,调用iterator.next()会抛出NoSuchElementException异常。如果需要再次遍历,需要重置迭代器:iterator=coll.iterator();

1.3.2 使用增强for循环

增强for循环的底层仍然是迭代器,所以可以理解成简化版的迭代器
增强for循环可以用来遍历数组或者Collection集合

for(element:list){
    System.out.println(element);
}

2. List接口和常用方法

List接口和Set接口都继承了Collection接口,因此Collection接口的所有方法,实现了List接口和Set接口的类都拥有,但List接口和Set接口是平级的,因此List接口的方法,Set接口不一定拥有

2.1 List集合基本介绍

  1. List集合类中元素有序(即添加顺序和取出顺序一致)、且元素可重复。
  2. List集合中每个元素都有器对应的顺序索引,即支持索引。
  3. List容器中的元素都对应一个整数型的序号记载器在容器中的位置,可以根据序号存取容器中的元素
  4. JDK API中List接口的实现类有:AbstractList,AbstractSequentialList , ArrayList , AttributeList , CopyOnWriteArrayList , LinkedList , RoleList , RoleUnresolvedList , Stack , Vector

2.2 List接口的常用方法

List集合里添加了一些根据索引来操作集合元素的方法:

1. void add(int index, E element); //在index位置插入E类型的元素,E是泛型
2. boolean addAll(int index, Collection<? extends E> c); //将c中所有的元素插入index位置,c是实现了Collection接口的E类型的子类
3. E get(int index); //获取并返回index位置的元素
4. int indexOf(Object o);   //返回obj在集合中首次出现的位置
5. int lastIndexOf(Object o);   //返回obj在集合中最后出现的位置
6. boolean remove(Object o);    //移除指定元素
7. E remove(int index); //移除并返回指定位置的元素
8. E set(int index, E element); // 返回index位置的元素,并将此位置元素替换为element
9. List<E> subList(int fromIndex, int toIndex);//返回从fromIndex到toIndex位置的前闭后开子集合[fromIndex,toIdex)

2.3 ArrayList的注意事项

ArrayList类定义说明:

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Aloneable, java.io.Serializable
  1. ArrayList 可以放入null,并且可以放入多个
  2. ArrayList是由数组来实现数据存储的
  3. ArrayList基本等同于Vector,ArrayList的执行效率更高,但是线程不安全的,因此多线程情况下,不建议使用ArrayList
    //ArrayList源码中,没有关于线程控制的代码
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
    //Vector源码中使用Synchronized关键字修饰,是线程安全的
    public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }
    //LinkedList也是线程不安全的
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

2.4 ArrayList的底层操作机制源码分析

  1. ArrayList中维护了一个Object类型的数组elementData
    //transient 关键字表示此数组不会被序列化
    transient Object[] elementData; // non-private to simplify nested class access
  1. 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0.
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  1. 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到适当位置
  2. 如果使用的是无参构造器,则第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍
  3. 如果使用的时指定容量capacity的构造器,则初始elementData的容量为capacity
  4. 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍

2.5 ArrayList无参构造器

1. 使用无参构造器初始化一个数组:
ArrayList<Object> list = new ArrayList<Object>();
-->初始化过程调用过程:
-->ArrayList.java
    这一步涉及的成员变量:
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //首先将elementData初始化为空数组
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
2. 循环添加数据:
for(int i = 1; i <= 10; i++){
    list.add(i);
}
-->调用过程:
-->ArrayList.java
    这一步涉及的成员变量:
    private int size; //成员变量,用来记录数组列表的大小(包含元素的数量)
    private static final int DEFAULT_CAPACITY = 10;
    
    //首先发生int->Integer自动装箱
    //然后调用 boolean add(E e)方法,modCount记录该集合被修改的次数
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);//size = 0
        return true;
    }
        --> private void add(E e, Object[] elementData, int s) {
                if (s == elementData.length)//如果当前数组已满
                    elementData = grow();//调用grow函数扩容至10
                elementData[s] = e;
                size = s + 1;
        }
        --> Object[] grow(){
            return grow(size+1);
        }
        --> Object[] grow(int minCapacity){//将数组拷贝到长度为10的新数组中
            return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
        }
        --> int newCapacity(int minCapacity){    //return 10;
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity>>1);//新容量为旧容量的(1+0.5)倍
            if(newCapacity - minCapacity <= 0){//即如果旧容量为0或1
                //如果当前数组为就是那个空的成员数组,就返回10和(当前容量+1)中较大的一个
                if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDTA){
                    return Math.max(DEFAULT_CAPACITY,minCapacity);
                }
                //block of code unrelated to the operation
            }
        }
3. 再次循环添加数据:
for (int i = 11; i <= 15; i++){
    list.add;
}
-->调用过程:
-->ArrayList.java
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
    --> private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)//此时数组已经存满了
            elementData = grow();//调用grow方法再次扩容至10*(1+0.5)=15
        elementData[s] = e; //在第10位存入这一轮的数据
        size = s + 1;
    }
    --> private Object[] grow() {
        return grow(size + 1);//size = 10
    }
    --> private Object[] grow(int minCapacity) {//minCapacity = 11,即最小也得11个位置才可以,新数组不能小于这个数
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }
    --> private int newCapacity(int minCapacity) {//minCapacity = 11
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//15
        if (newCapacity - minCapacity <= 0) {//false
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow //false
                throw new OutOfMemoryError();
            return minCapacity; //return 15
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
4. 再次添加数据:
list.add(100);//此时由于list.length = 15,容量已经满了,会再次扩容至22
//

2.5 Vector的基本介绍

  1. Vector类的定义说明
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. Vector 底层也是一个对象数组,protected Object[] elementData;
  2. Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
public synchronized void setSize(int newSize) {
    modCount++;
    if (newSize > elementData.length)
        grow(newSize);
    final Object[] es = elementData;
    for (int to = elementCount, i = newSize; i < to; i++)
        es[i] = null;
    elementCount = newSize;
}
  1. 在开发中,需要线程同步安全时,考虑使用Vector

2.5.1 Vector和ArrayList的比较

集合 底层结构 版本 线程安全(同步)效率 扩容
ArrayList 可变数组 jdk1.2 不安全,效率高 如果有参构造,1.5倍;如果是无参构造,第一次10,从第二次开始按1.5倍扩容
Vector 可变数组 jdk1.0 安全,效率不高 如果是无参,默认10,满后按2倍扩容;有参构造则按指定大小创建,并每次按2倍扩容,也可以手动指定增量capacityIncrement,每次增加capacityIncrement

2.6 LinkedList

LinkedList类定义说明

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  1. LinkedList实现了双向链表和双端队列特点
  2. 可以天界任意元素(元素可以重复),包括null
  3. 线程不安全,没有实现同步

2.6.1 LinkedList底层架构

LinkedList的底层操作机制

  1. LinkedList底层维护了一个双向链表。
  2. LinkedLsit中维护了两个属性first和last分别指向首节点和尾节点
  3. 每个节点(Node对象)里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表。
  4. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高

2.6.1 ArrayList和LinkedList比较

集合 底层结构 增删的效率 改查的效率
ArrayList 可变数组 较低 较高
LinkedList 双向链表 较高,通过链表追加 较低

2.6.2 如何选择ArrayList和LinkedList

  1. 如果改查的操作多,选择ArrayList
  2. 如果增删的操作多,选择LinkedList
  3. 一般来说,在程序中,80%~90%的操作都是查询,因此大部分情况下会选择ArrayList
  4. 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另一个模块使用的是LinkedList,要根据业务来选择。
  5. 这两个都是线程不安全的,在单线程程序中使用

原文链接:https://www.cnblogs.com/hiibird/p/17320033.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java笔记(15) Collection集合–>List集合 - Python技术站

(0)
上一篇 2023年4月17日
下一篇 2023年4月17日

相关文章

  • Java实现线程插队的示例代码

    实现线程插队可以使用Java中的join()方法。下面我将提供两个示例说明。 实现线程按照指定顺序执行 示例代码如下: class ThreadSequence implements Runnable { private int value; private Thread preThread; public ThreadSequence(int value,…

    Java 2023年5月18日
    00
  • spring AOP的Around增强实现方法分析

    下面是详细讲解“Spring AOP的Around增强实现方法分析”的完整攻略。 一、介绍 在Spring框架中,AOP(面向切面编程)是实现被广泛使用的一种技术。其中,Around增强是AOP中最复杂的增强类型之一,因此本文将对它的实现方法进行分析。 二、Around增强实现 在Spring框架中,Around增强实现需要使用到 ProceedingJoi…

    Java 2023年5月31日
    00
  • extJs 常用到的增,删,改,查操作代码

    下面我将为您详细讲解 ExtJS 常用到的增、删、改、查操作的完整攻略。这里主要针对 ExtJS 版本 6.2 进行讲解。 概述 在 ExtJS 中,我们常常需要进行数据的增、删、改、查操作。这些操作基本都是基于 Ext.data.Store 和 Ext.data.Model 进行的。 其中,Ext.data.Store 负责连接数据源(可以是远程 URL,…

    Java 2023年6月15日
    00
  • Java concurrency之AtomicLong原子类_动力节点Java学院整理

    Java Concurrency之AtomicLong原子类 在多线程并发编程过程中,避免线程安全问题是很关键的一点。Java提供了一系列的原子类,其中AtomicLong原子类是其中之一。本篇攻略将介绍AtomicLong原子类的使用,包括什么是AtomicLong原子类?什么情况下使用AtomicLong原子类?以及如何使用AtomicLong原子类? …

    Java 2023年5月26日
    00
  • Java实现简单的万年历

    下面就是讲解实现Java简单的万年历的攻略及示例说明: 1. 确定需求和功能 在实现Java简单的万年历之前,我们需要定义该项目的需求和功能,以便能够更好地进行程序设计和编写。以下是常见的需求和功能: 能够查询指定日期的日历; 能够查询制定月份和年份的日历; 能够查询当前日期的日历; 能够显示节假日和纪念日等特殊日期。 2. 时间库的选择 为了实现Java简…

    Java 2023年5月19日
    00
  • js构造函数constructor和原型prototype原理与用法实例分析

    那么让我来详细讲解一下“js构造函数constructor和原型prototype原理与用法实例分析”的完整攻略。 什么是构造函数constructor? 在 JavaScript 中,构造函数是一种用于创建对象并初始化其属性的特殊函数。每个对象都是由一个构造函数生成的,JavaScript 内置了很多构造函数,比如 Array、String 等。 构造函数…

    Java 2023年5月23日
    00
  • java自动生成编号的实现(格式:yyMM+四位流水号)

    Java自动生成编号的实现,通常需要考虑到以下几个方面: 记录上一个流水号。 根据当前时间生成编号的前缀(yyMM)。 每次生成编号时自增流水号,如果流水号超出限定位数则将其归零并增加前缀的年月数。 将新编号保存在数据库中。 下面是一个完整的代码示例和实现攻略。 1. 定义实体类 首先定义一个实体类,包含自动生成编号所需的属性和setter/getter方法…

    Java 2023年5月30日
    00
  • java 遍历Map及Map转化为二维数组的实例

    以下是详细的讲解: 遍历Map 在Java中,我们可以使用 java.util.Map 接口来表示键值对的集合。 Map的常用实现类有 HashMap、TreeMap 和 LinkedHashMap。 要遍历Map中的键值对,我们可以使用 entrySet() 方法来遍历Map中的每一个键值对。 示例代码如下: Map<String, String&g…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部