详解Java集合类之List篇

目录
  • 1.集合框架体系
  • 2.Collection接口
  • 3.迭代器
  • 4.List接口
  • 5.ArrayList
    • ArrayList扩容机制
    • ArrayList使用实例
  • 6.Vector
  • 7.LinkedList
    • 增加元素源码分析
    • 删除元素源码分析
    • LinkedList使用Demo
  • 8.ArrayList和LinkedList的选择

1.集合框架体系

集合框架被设计成要满足以下几个目标。

  • 该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。
  • 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
  • 对一个集合的扩展和适应必须是简单的。

为此,整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现,诸如: LinkedList, HashSet, 和 TreeSet 等,除此之外你也可以通过这些接口实现自己的集合。

Collection接口的实现子类

Map接口的实现子类

体系图

从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。

2.Collection接口

该接口定义:

public interface Collection<E> extends Iterable<E>

Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。

Collection 接口存储一组不唯一,无序的对象。

Collection接口的常用方法:

import java.util.ArrayList;
import java.util.List;

/**
 * Collection接口的实现类
 * Java不提供直接继承自Collection的类,只提供继承于的子接口
 * 所以我们以ArrayList为例子演示Collection接口的抽象方法
 */
public class CollectionTest {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        // 添加
        list.add("dahe");
        list.add(521);
        list.add(true);
        System.out.println(list);
        // 删除
        // 删除第一个元素
        list.remove(0);
        // 删除指定的元素
        list.remove(true);
        System.out.println(list);
        // 查找
        System.out.println(list.contains(521));
        // 元素个数
        System.out.println(list.size());
        // 判空
        System.out.println(list.isEmpty());
        // 清空
        list.clear();
        // 添加多个元素(可以添加另一个实现了Collection接口的对象)
        ArrayList arrayList = new ArrayList();
        arrayList.add("tiktok直播");
        arrayList.add("tiktok广告投放");
        list.addAll(arrayList);
        System.out.println(list);
        // 查找多个元素是否都存在
        System.out.println(list.containsAll(arrayList));
        // 删除多个元素
        list.removeAll(arrayList);
        System.out.println(list);
    }
}

输出:

[dahe, 521, true]
[521]
true
1
false
[tiktok直播, tiktok广告投放]
true
[]

3.迭代器

Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。

Iterator 是 Java 迭代器最简单的实现,ListIterator 是 Collection API 中的接口, 它扩展了 Iterator 接口。

迭代器 it 的两个基本操作是 next 、hasNext 和 remove。

  • 调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。
  • 调用 it.hasNext() 用于检测集合中是否还有元素。
  • 调用 it.remove() 将迭代器返回的元素删除。

代码DEMO示例:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
 * 迭代器
 */
public class IteratorTest {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Book("三国演义","罗贯中",52.7));
        col.add(new Book("小李飞刀","古龙",10.2));
        col.add(new Book("红楼梦","曹雪芹",34.6));
        System.out.println(col);
        // 使用迭代器
        Iterator iterator = col.iterator();
        while (iterator.hasNext()) {
            // 返回下一个元素,类型是Object
            Object obj = iterator.next();
            System.out.println(obj);
        }
    }
}

class Book {
    private String name;
    private String author;
    private Double price;

    public Book(String name, String author, Double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

输出:

[Book{name='三国演义', author='罗贯中', price=52.7}, Book{name='小李飞刀', author='古龙', price=10.2}, Book{name='红楼梦', author='曹雪芹', price=34.6}]
Book{name='三国演义', author='罗贯中', price=52.7}
Book{name='小李飞刀', author='古龙', price=10.2}
Book{name='红楼梦', author='曹雪芹', price=34.6}

我们还可以使用增强型for循环来遍历集合:(底层依然是迭代器,可以理解为简化版的迭代器遍历)

for (Object o : col) {
    System.out.println(o);
}

4.List接口

List接口是Collection接口的子接口

List集合中元素有序(添加顺序和取出顺序一致),元素可以重复

List中的每个元素都有其顺序的索引

常用方法代码示例:

/**
 * List接口,Collection接口的子接口
 */

import java.util.ArrayList;
import java.util.List;

public class ListTest {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        List list1 = new ArrayList();
        list1.add("ceshi");
        list1.add("tangseng");
        // 添加元素
        list.add("dahe");
        list.add("tom");
        list.add("dahe"); // 可以重复
        // 指定位置插入元素
        list.add(1,"qian");
        // 加入多个元素,传入一个Collection对象
        list.addAll(1,list1);
        System.out.println(list);
        // 取出指定索引的元素
        System.out.println(list.get(0));
        // 查找元素第一次出现的位置
        System.out.println(list.indexOf("dahe"));
        // 查找元素最后一次出现的位置
        System.out.println(list.lastIndexOf("dahe"));
        // 删除元素
        list.remove(1);
        System.out.println(list);
        // 元素替换
        list.set(1,"marry");
        System.out.println(list);
        // 返回子集合,0到1的元素集合
        List returnList = list.subList(0,2);
        System.out.println(returnList);
    }
}

输出:

[dahe, ceshi, tangseng, qian, tom, dahe]
dahe
0
5
[dahe, tangseng, qian, tom, dahe]
[dahe, marry, qian, tom, dahe]
[dahe, marry]

5.ArrayList

ArrayList是由数组来进行数据存储的,为线程不安全,效率较高多线程场景建议使用vector

例如ArrayList add方法的源码:(并没有synchronized进行修饰)

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

ArrayList扩容机制

使用无参构造器

当我们使用ArrayList的无参构造器的时候,进入源码分析:

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

这里的elementData是ArrayList存放数据的数组,可以看出当我们调用无参构造的时候,数组初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,那这一长串代表什么呢?

继续步入

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
1
该值为空,那么我们可以得出结论,当调用ArrayList的无参构造时,存储数组的默认空间大小为0,也就是空

同时,我们追入elementData数组,可以发现他是Object数组,这也就解释了为什么ArrayList可以加入任意类型的元素,因为Object是万物之父!

transient Object[] elementData; // non-private to simplify nested class access

添加数据之前的准备

接下来,当我们使用add方法,比如操作下面这行代码:

arrayList.add(521);

首先,ArrayList会对521进行装箱操作:

@IntrinsicCandidate
 public static Integer valueOf(int i) {
     if (i >= IntegerCache.low && i <= IntegerCache.high)
         return IntegerCache.cache[i + (-IntegerCache.low)];
     return new Integer(i);
 }

继续步入,进入如下代码:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

我们来仔细看一下这段代码,首先:

modCount++;

在所有的集合实现类中(Collection与Map中),都会有一个 modCount 的变量出现,它的作用就是记录当前集合被修改的次数。此处将修改次数 + 1,防止多线程操作异常

随后,进入真正添加数据的add重载方法:

add(e, elementData, size);

开始添加数据

在真正添加数据的部分,代码如下:

可以看到,首先需要判断elementData数组的容量是否充足,如果容量已经满了的话,就执行grow方法进行扩容,否则就加入数据,size + 1

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

那他是怎么进行扩容的呢?

我们进入grow方法看一下:

private Object[] grow() {
    return grow(size + 1);
}

继续步入到grow的重载方法:

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

获取老的容量

如果一次也没有扩容过,则扩容大小为DEFAULT_CAPACITY和minCapacity较大的一个,DEFAULT_CAPACITY被定义为10,而我们此时的minCapacity为1,所以第一次扩容的大小为10

如果老的容量大于0或者elementData数组不是初始化状态的数组(也就是已经第一次扩容过)

那么通过位运算进行扩容到原容量的1.5倍

注意:IDEA在debug默认情况下显示的数据是简化的,如果需要看完整的数据,需要进行设置

使用指定大小的构造器

原理操作和上面类似,只不过它初始的容量为指定的容量,需要扩容时扩容为原容量的1.5倍

ArrayList使用实例

ArrayList arrayList = new ArrayList();
arrayList.add(521);
arrayList.add(null);
arrayList.add("武松");
arrayList.add(null);
System.out.println(arrayList);

输出:

[521, null, 武松, null]

6.Vector

vector底层同样是一个对象数组

protected Object[] elementData;

和ArrayList不同的是,它是线程同步的,也就是线程安全的

Vector和ArrayList的区别:

7.LinkedList

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。

链表可分为单向链表和双向链表。

一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。

一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。

Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。

与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。

LinkedList的底层是一个双向链表

增加元素源码分析

在LinkedList链表中,存在几个必知的概念 --> First:链表头节点;Last:链表尾节点;next:该节点下一个节点地址;prev:该节点前一个节点地址

LinkedList linkedList = new LinkedList();
// 增
linkedList.add(521);
linkedList.add(1314);
System.out.println(linkedList);

首先:调用LinkedList的无参构造,这里什么也没有捏

public LinkedList() {
}

随后,和ArrayList和Vector一样,进行装箱操作

开始添加操作:

public boolean add(E e) {
    linkLast(e);
    return true;
}

为了方便理解,我们需要知道一下Node的构造器内容:

Node(Node<E> prev, E element, Node<E> next) {
    this.item = element;
    this.next = next;
    this.prev = prev;
}

继续步入:

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

这里是关键,接下来我们来分析一下上面的这段代码:

首先,将last的值赋给l,新建一个Node节点,将last指向新建的Node节点,随后进行分支判断,将first也指向新建的Node节点

最后,链表大小 + 1 ,修改次数 + 1

经过了上面的修改,现在的first和last都指向新建的节点,该节点的next和prev都为null

以上是链表中只有一个元素的情况,那么再次向链表添加元素呢?我们再来浅浅看一下

第二次添加节点,l被置为上一个节点的地址,随后会被添加到新节点的prev属性中:

还需要更新一下last的值为新添加进来的节点的地址:

随后,将上一个节点的next值置为当前新加的节点地址:

最后,更新size和modCount即可!

删除元素源码分析

// 删除最后一个节点
linkedList.remove();

进行源码分析,步入:

public E remove() {
    return removeFirst();
}

继续步入:

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

Java的设计者在这里获取了链表头节点的地址,以备后续进行删除,随后检查了一个头节点为空的异常,在unlinkFirst方法,将会真正执行删除的操作!

继续步入:

private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

开头,获取头节点的值,以备在后续返回:

final E element = f.item;

先获取一下头节点后面的节点的地址,保存下来,然后将头节点的数据和next置空,请求GC进行回收:

final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC

头节点更新为原头节点的下一个节点,并作节点个数判断:

first = next;
if (next == null)
    last = null;
else
    next.prev = null;

最后,更新size和modCount,返回element

LinkedList使用Demo

import java.util.LinkedList;

/**
 * LinkedList
 */
public class LinkedListTest {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        // 增
        linkedList.add(521);
        linkedList.add(1314);
        System.out.println(linkedList);
        // 删除头节点
        linkedList.remove();
        System.out.println(linkedList);
        // 修改
        linkedList.set(0,999);
        System.out.println(linkedList);
        // 查找
        System.out.println(linkedList.get(0));
    }
}

输出:

[521, 1314]
[1314]
[999]
999

8.ArrayList和LinkedList的选择

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

在实际开发中,ArrayList用的最多,因为大部分的业务是查询业务!

另外,ArrayList和LinkedList都是线程不安全的,推荐在单线程场景下使用

到此这篇关于详解Java集合类之List篇的文章就介绍到这了,更多相关Java集合类List内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 学习Java中的List集合

    目录 1.概述 2.List的使用 2.1List的常用方法 3.List的实现类 3.1ArrayList 3.2Vector 3.3LinkedList 3.4ArrayList与Vector的区别 1.概述 List是一个有序集合(也被称为序列).此接口的用户在列表中的每个元素都被插入的地方有精确的控制.用户可以通过它们的整数索引(在列表中的位置)访问元素,并在列表中搜索元素. 说是List集合,其实只是习惯说法,因为它是Collection接口的一个子接口(Collection有很多的子

  • Java中List集合的常用方法详解

    目录 1.void add(int index, E element) 2.boolean addAll(int index, Collection<? extends E> c) 3.E get(int index) 4.int indexOf(Object o) 5.ListIterator<E> listIterator() 6.ListIterator<E> listIterator(int index) 7.E remove(int index) 8.E se

  • 深入解读Java三大集合之map list set的用法

    Map接口和Collection接口是所有集合框架的父接口: Collection接口的子接口包括:Set接口和List接口 Map接口的实现类主要有:HashMap.TreeMap.Hashtable.ConcurrentHashMap以及Properties等 Set接口的实现类主要有:HashSet.TreeSet.LinkedHashSet等 List接口的实现类主要有:ArrayList.LinkedList.Stack以及Vector等 List,Set,Map三者的区别?List.

  • Java中List集合的深入介绍(超级推荐!)

    目录 1,Java集合介绍 2,List介绍 2.1 ArrayList集合 2.2 LinkedList集合 3,List常用方法 3.1 ArrayList 基本操作 3.2 LinkedList 基本操作 4,ArrayList和LinkedList比较 5,ArrayList源码分析 6,LinkedList源码分析 7,小结 1,Java集合介绍 作为一个程序猿,Java集合类可以说是我们在工作中运用最多.最频繁的类.相比于数组(Array)来说,集合类的长度可变,更加方便开发. Ja

  • 详解Java 集合类 List 的那些坑

    现在的一些高级编程语言都会提供各种开箱即用的数据结构的实现,像 Java 编程语言的集合框架中就提供了各种实现,集合类包含 Map 和 Collection 两个大类,其中 Collection 下面的 List 列表是我们经常使用的集合类之一,很多的业务代码都离不开它,今天就来看看 List 列表的一些坑. 第一个坑:Arrays.asList 方法返回的 List 不支持增加.删除操作 例如我们执行以下代码: List<String> strings = Arrays.asList(&qu

  • Java List接口的集合使用详解

    目录 ArrayList集合 LinkedList类 LinkedList与ArrayList的区别: 总结 List接口继承collection接口,此接口可以对列表中的每个元素精确的控制,可以根据元素的索引来获取列表中的值,下面介绍常用的几种,ArrayList,LinkedList. ArrayList集合 ArrayList是一个动态修改的数组,它没有固定的大小,不需要自己分配空间,java本身已经初始化容量为10的大小,可以直接添加删除等一系列操作. ArrayList构造方法: Ar

  • 详解Java集合类之HashSet篇

    目录 1.Set接口方法 2.HashSet 3.HashSet的扩容机制 - 初次添加数据 4.HashSet的扩容机制 - 继续添加数据 5.HashSet的扩容机制 - 添加重复元素 1.Set接口方法 Set接口对象存放的数据是没有重复,且数据是无序存放的(添加顺序和存放顺序不一致,但是这个存放的顺序是固定的,不会随机变化) 代码示例: import java.util.HashSet; import java.util.Iterator; import java.util.Set; /

  • 详解Java集合类之List篇

    目录 1.集合框架体系 2.Collection接口 3.迭代器 4.List接口 5.ArrayList ArrayList扩容机制 ArrayList使用实例 6.Vector 7.LinkedList 增加元素源码分析 删除元素源码分析 LinkedList使用Demo 8.ArrayList和LinkedList的选择 1.集合框架体系 集合框架被设计成要满足以下几个目标. 该框架必须是高性能的.基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的. 该框架允许不同类型的集合,以类

  • 详解Java集合类之Map篇

    目录 1.Map接口介绍 2.Map接口分析 3.Map接口方法 4.Map遍历方式 1.Map接口介绍 Map用于保存具有映射关系的数据:Key - Value 对于Set,底层其实依然是一个Map,但是Set选择不使用Value,也就是Set的Value值始终是一个常量 Map中的Key和Value可以是任何类型的数据,会封装到HashMap$Node对象中 Map中的Key不能重复,但是Value可以重复,当有相同的Key时,等价与替换操作 2.Map接口分析 存放Map键值对是在Hash

  • 详解Java集合类之HashTable,Properties篇

    目录 1.基本介绍 2.HashTable底层 3.HashTable扩容机制 4.HashMap和HashTable的对比 5.Properties 6.集合选型规则 1.基本介绍 HashTable的键和值都不能为空,否则会抛出一个异常 使用方法基本与HashMap一致 HashTable是线程安全的,HashMap是线程不安全的 2.HashTable底层 先上代码: Hashtable hashtable = new Hashtable(); hashtable.put("john&qu

  • 详解Java单元测试之JUnit篇

    单元测试是编写测试代码,应该准确.快速地保证程序基本模块的正确性. JUnit是Java单元测试框架,已经在Eclipse中默认安装. JUnit4 JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @BeforeClass 全局只会执行一次,而且是第一个运行 @Before 在测试方法运行之前运行 @Test 测试方法 @After 在测试方法运行之后允许 @AfterClass 全局只会执行一次,而且是最后一个运行 @Ignore 忽略此方法 下面基于Eclipse介绍JUn

  • 详解Java 包扫描实现和应用(Jar篇)

    如果你曾经使用过 Spring, 那你已经配过 包扫描路径吧,那包扫描是怎么实现的呢?让我们自己写个包扫描 上篇文章中介绍了使用 File 遍历的方式去进行包扫描,这篇主要补充一下jar包的扫描方式,在我们的项目中一般都会去依赖一些其他jar 包, 比如添加 guava 依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <ve

  • 详解java调用python的几种用法(看这篇就够了)

    java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐) 调用python脚本中的函数 准备工作: 创建maven工程,结构如下: 到官网https://www.jython.org/download.html下载Jython的jar包或者在maven的pom.xml文件中加入如下代码: <dependency> <groupId>org

  • 详解Java TCC分布式事务实现原理

    概述 之前网上看到很多写分布式事务的文章,不过大多都是将分布式事务各种技术方案简单介绍一下.很多朋友看了还是不知道分布式事务到底怎么回事,在项目里到底如何使用. 所以这篇文章,就用大白话+手工绘图,并结合一个电商系统的案例实践,来给大家讲清楚到底什么是 TCC 分布式事务. 业务场景介绍 咱们先来看看业务场景,假设你现在有一个电商系统,里面有一个支付订单的场景. 那对一个订单支付之后,我们需要做下面的步骤: 更改订单的状态为"已支付" 扣减商品库存 给会员增加积分 创建销售出库单通知仓

  • 一文详解Java线程中的安全策略

    目录 一.不可变对象 二.线程封闭 三.线程不安全类与写法 四.线程安全-同步容器 1. ArrayList -> Vector, Stack 2. HashMap -> HashTable(Key, Value都不能为null) 3. Collections.synchronizedXXX(List.Set.Map) 五.线程安全-并发容器J.U.C 1. ArrayList -> CopyOnWriteArrayList 2.HashSet.TreeSet -> CopyOnW

随机推荐