1集合框架(单列集合)

基本概念:集合是一个存放对象引用的容器

特点:

  • 长度可变:集合的大小可以根据需要进行动态调整。
  • 包含大量方法:集合提供丰富的操作方法,便于进行各种数据处理。
  • 数据类型限制:集合只能存储引用数据类型的数据。
分类:
  • a. 单列集合:每个元素独立构成集合的一部分。
    • 示例:list.add("张三")
  • b. 双列集合:每个元素由两部分组成,即 key 和 value,形成键值对。
    • 示例:map.put("Yegar","19"),其中 "年龄" 为 key"19" 为 value

单列集合的架构:

采自尚硅谷,与数据结构相关

2Collection接口

1. 概述
  • 单列集合的顶级接口:Collection接口是单列集合的顶级接口,所有单列集合(如List、Set)都继承自该接口。
2. 使用
  • 创建
    • 语法:Collection<E> 对象名 = new 实现类对象<E>()
    • 泛型<E> 决定了集合中能存储什么类型的数据,可以统一元素类型。
      • 泛型中只能写引用数据类型,如果不写,默认为Object类型,此时可以存储任何类型的数据。
      • 示例:
        • <int>:错误(int是基本数据类型,不是引用类型)。
        • <Integer>:正确(Integer是引用类型)。
        • <Person>:正确(Person是自定义的引用类型)。
  • 泛型细节
    • 等号前面的泛型必须写,等号后面的泛型可以不写,JVM会根据前面的泛型推导出后面的泛型类型。
3. 常用方法
  • add(E e):将给定的元素添加到当前集合中(调用add时,一般不用boolean接收,因为add一定会成功)。
  • addAll(Collection<? extends E> c):将另一个集合的元素添加到当前集合中(集合合并)。
  • clear():清除集合中所有的元素。
  • contains(Object o):判断当前集合中是否包含指定的元素。
  • isEmpty():判断当前集合中是否有元素(即集合是否为空)。
  • remove(Object o):将指定的元素从集合中删除。
  • size():返回集合中的元素个数。
  • toArray():把集合中的元素存储到数组中。

代码示例:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class Main
{
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
        //boolean add(E e) : 将给定的元素添加到当前集合中 (我们一般调add时,不用boolean接收,因为add一定会成功)
        collection.add("艾伦耶格尔");
        collection.add("三里");
        collection.add("兵长");
        collection.add("猫猫");
        collection.add("狗狗");
        collection.add("利威尔");
        System.out.println(collection);
        //boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
        Collection<String> collection1 = new ArrayList<>();
        collection1.add("花洒");
        collection1.add("吉娃娃");
        collection1.add("猎犬");
        collection1.add("小吴");
        collection1.addAll(collection);
        System.out.println(collection1);

        //void clear() :清除集合中所有的元素
        collection1.clear();
        System.out.println(collection1);
        //boolean contains(Object o) :判断当前集合中是否包含指定的元素
        boolean result01 = collection.contains("Yeagar");
        System.out.println("result01 = " + result01);
        //boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
        System.out.println(collection1.isEmpty());
        //boolean remove(Object o) :将指定的元素从集合中删除
        collection.remove("涛哥");
        System.out.println(collection);
        //int size() :返回集合中的元素个数。
        System.out.println(collection.size());
        //Object[] toArray() : 把集合中的元素,存储到数组中
        Object[] arr = collection.toArray();
        System.out.println(Arrays.toString(arr));
    }
}


测试结果:

3迭代器

  1. 概述:Iterator接口用于遍历集合。
  2. 主要作用:遍历集合。
  3. 获取方式:通过Collection接口中的iterator()方法获取Iterator<E>对象。
  4. 核心方法
    • boolean hasNext():判断集合中是否还有下一个元素。
    • E next():获取集合中的下一个元素。

简要分析

  • Iterator接口:是Java中用于遍历集合的标准接口,提供了一种统一的遍历集合元素的方式。
  • 遍历机制:通过hasNext()方法检查是否还有下一个元素,若有则通过next()方法获取该元素,以此循环实现集合的遍历。
  • 使用场景:适用于需要顺序访问集合中元素且不需要索引的场景,是集合遍历的常用方式之一。

代码示例

import java.util.ArrayList;
import java.util.Iterator;

public class DemoOlIterator {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("艾伦·耶格尔");
        list.add("三笠·阿克曼");
        list.add("阿明·阿诺德");
        list.add("利威尔");
        list.add("韩吉·佐耶");
        
        // 获取迭代器对象
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);
        }
    }
}

测试结果

1. 注意事项
  • next方法的使用:在使用next方法获取元素时,不要连续多次调用,否则会引发NoSuchElementException(没有可操作的元素异常)。

代码:



public class DemoOlIterator {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("艾伦·耶格尔");
        list.add("三笠·阿克曼");
        list.add("阿明·阿诺德");
        list.add("利威尔");
        list.add("韩吉·佐耶");
        
        // 获取迭代器对象
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);
            //String element2 = iterator.next();//判断一个调用两个会在第六个无法访问
            //System.out.println(element2);
        }
    }
}
  • 迭代逻辑
    • 创建一个ArrayList并添加多个字符串元素。
    • 获取该列表的迭代器Iterator
    • 使用while循环和hasNext方法检查是否还有下一个元素。
    • 通过next方法获取并打印当前元素。
    • 错误示例:在一次循环中连续调用next方法(如注释掉的代码),这会导致NoSuchElementException,因为一次next调用已经移动了迭代器的指针,再次调用时指针可能已经超出范围。

迭代过程

next() 方法的逻辑核心是通过 cursor 和 lastRet 两个指针来管理迭代过程:

  1. 检查并发修改:首先调用 checkForComodification() 方法,确保在迭代过程中列表没有被修改(即 expectedModCount 与 modCount 一致)。
  2. 获取当前元素索引:将 cursor 的值赋给 ii 表示当前要返回的元素的索引。
  3. 检查索引有效性
    • 如果 i 大于等于 size,抛出 NoSuchElementException,表示没有更多元素可迭代。
    • 如果 i 大于等于 elementData.length,抛出 ConcurrentModificationException,表示列表结构在迭代过程中被修改。
  4. 移动 cursor 指针:将 cursor 增加 1,指向下一个元素。
  5. 返回当前元素:将 elementData[i] 转换为泛型 E 并返回,同时将 lastRet 设为 i,记录上次返回元素的索引。
  6. 双指针移动原理

代码

public E next() {
    // 检查是否发生并发修改
    checkForComodification();
    
    // 获取当前元素的索引
    int i = cursor;
    
    // 检查索引是否越界
    if (i >= size)
        throw new NoSuchElementException();
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    
    // 移动 cursor 指针到下一个元素
    cursor = i + 1;
    
    // 返回当前元素,并更新 lastRet
    return (E) elementData[lastRet = i];
}

4迭代器源码刨析

迭代器底层原理总结

1. 迭代器的获取方式

迭代器(Iterator)通过集合的 iterator() 方法获取。例如:

Iterator<String> iterator = list.iterator();
2. Iterator 接口的实现
  • Iterator 是一个接口,其具体实现由集合类的内部类负责。
  • ArrayList 的 iterator() 方法 返回其内部类 Itr 的实例,Itr 实现了 Iterator 接口。
3. ArrayList 的 iterator() 方法
public Iterator<E> iterator() {
    return new Itr();
}
  • Itr 是 ArrayList 的一个 私有内部类,实现了 Iterator<E> 接口。
4. 其他集合的迭代器实现
  • HashSet 的 iterator() 方法
public Iterator<E> iterator() {
    return map.keySet().iterator();
}
    • HashSet 内部使用 HashMap 来存储元素,因此其 iterator() 方法实际返回的是 HashMap 的 keySet() 的迭代器。
  • HashMap 的 keySet().iterator() 方法
    public Iterator<K> iterator() {
        return new KeyIterator();
    }
    • KeyIterator 是 HashMap 的一个内部类,继承自 HashIterator 并实现了 Iterator<K> 接口。
5. 总结
  • Iterator 接口的实现依赖于具体集合类的内部类
  • ArrayList 使用 Itr 作为其迭代器的实现类
  • HashSet 通过 HashMap 的 keySet() 来实现迭代器,具体由 KeyIteratorHashMap 的内部类)来实现。
关键点
  • 迭代器的获取:通过集合的 iterator() 方法。
  • 迭代器的实现:由集合类的内部类具体实现 Iterator 接口。
  • 不同集合的迭代器实现不同ArrayList 使用 ItrHashSet 依赖 HashMap 的 KeyIterator

并发修改异常

示例

public class Demo03Iterator {
    public static void main(String[] args) {
        //需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马

        ArrayList<String> list = new ArrayList<>();
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");

        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String element = iterator.next();
            if ("猪八戒".equals(element)){
                list.add("白龙马");
            }
        }

        System.out.println(list);
    }
}

测试结果

1. 问题核心
  • 主要问题:在使用 Iterator 遍历集合时,如果在遍历过程中修改集合(如添加元素),会导致 ConcurrentModificationException(并发修改异常)。
  • 原因分析Iterator 在遍历集合时,会检查 modCount(实际操作次数)和 expectedModCount(预期操作次数)是否相等。如果不相等,就会抛出 ConcurrentModificationException,而add方法会进行modCount++;导致不相等。
public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}
2. 解决方案
  • 使用 ListIteratorListIterator 是 Iterator 的子接口,它不仅支持遍历集合,还支持在遍历过程中安全地修改集合(如添加、删除元素)。
public class Demo03Iterator {
    public static void main(String[] args) {
        // 需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
        ArrayList<String> list = new ArrayList<>();
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");

        ListIterator<String> listIterator = list.listIterator();
        while (listIterator.hasNext()) {
            String element = listIterator.next();
            if ("猪八戒".equals(element)) {
                listIterator.add("白龙马"); // 使用 listIterator.add 安全添加元素
            }
        }
        System.out.println(list);
    }
}

测试结果

5ArryayList

1. 概述
  • ArrayList 是 List 接口的实现类。
2. 特点
  • 元素有序:按照存储顺序存取。
  • 元素可重复:允许存储重复元素。
  • 有索引:支持通过索引操作元素。
  • 线程不安全:在多线程环境下使用需额外同步。
3. 数据结构
  • 数组:底层使用数组实现。
4. 常用方法
  • add(E e):将元素添加到集合尾部(方法调用一定成功,返回值通常忽略)。
  • add(int index, E element):在指定索引位置添加元素。
  • remove(Object o):删除指定元素,成功返回 true,失败返回 false
  • remove(int index):删除指定索引位置的元素,返回被删除的元素。
  • set(int index, E element):将指定索引位置的元素修改为 element
  • get(int index):根据索引获取元素。
  • size():获取集合中元素的个数。

代码方法示例

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        // boolean add(E e) -> 将元素添加到集合中->尾部
        list.add("藏原走");
        list.add("清濑灰二");
        list.add("穆萨");
        list.add("尼古");
        list.add("王子");
        System.out.println(list);

        // void add(int index, E element) ->在指定索引位置上添加元素
        list.add(2, "城太郎");
        System.out.println(list);

        // boolean remove(Object o) ->删除指定的元素,删除成功为true,失败为false
        list.remove("城太郎");
        System.out.println(list);

        // E remove(int index) -> 删除指定索引位置上的元素,返回的是被删除的那个元素
        String element = list.remove(0);
        System.out.println(element);
        System.out.println(list);

        // E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
        String element2 = list.set(0, "神童");
        System.out.println(element2);
        System.out.println(list);

        // E get(int index) -> 根据索引获取元素
        System.out.println(list.get(0));

        // int size() -> 获取集合元素个数
        System.out.println(list.size());
    }
}

测试结果

5遍历
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("藏原走");
        list.add("清濑灰二");
        list.add("穆萨");
        list.add("尼古");
        list.add("王子");

        // 使用迭代器遍历
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("=====================");

        // 使用for循环遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        System.out.println("=====================");

        // 使用fori快捷键生成的循环遍历list.fori
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

测试结果

6ArryayList源码剖析总结

1. 构造方法机制

默认构造方法 ArrayList()

// 静态空数组常量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 实际存储数据的数组
Object[] elementData;

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  • 延迟初始化:创建ArrayList对象时并不会立即分配容量为10的数组

  • 首次添加触发:只有在第一次调用add()方法时才会创建初始容量为10的数组

指定容量构造方法 ArrayList(int initialCapacity)

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity]; // 直接创建指定容量数组
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA; // 使用空数组
    } else {
        throw new IllegalArgumentException("非法容量: " + initialCapacity);
    }
}
  • 立即分配:根据传入的初始容量立即创建相应大小的数组

2. 添加元素与扩容机制

add() 方法调用链

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(); // 执行扩容
    elementData[s] = e; // 放置元素
    size = s + 1; // 更新大小
}

扩容核心逻辑

private Object[] grow() {
    return grow(size + 1); // 至少需要size+1的容量
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 正常扩容:按1.5倍增长
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, // 最小增长量
                oldCapacity >> 1           // 首选增长量:原容量的一半
        );
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        // 首次扩容:取默认容量10和所需容量的最大值
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

关键特性总结

  1. 延迟初始化:默认构造器不会立即创建数组,首次添加时才分配内存

  2. 扩容策略

    • 首次扩容:创建容量为10的数组

    • 后续扩容:按原容量的1.5倍进行扩容

  3. 可变长度实现:通过Arrays.copyOf()创建新数组并复制数据来实现

  4. 线程不安全:没有同步机制,多线程环境下需要外部同步

扩容过程流程图

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐