当前位置: 首页 > news >正文

深入解析Java Spliterator(Stream延迟、并行计算核心)

Spliterator 接口概述

Spliterator (Splitable Iterator,可分割迭代器) 的核心设计目标是支持高效的并行遍历和元素分解。与传统的 Iterator相比,它提供了更强大的功能:

  1. 支持并行处理:通过 trySplit() 方法,可以将数据源分割成多个部分,交由不同的线程独立处理。
  2. 更细粒度的遍历控制tryAdvance() 方法一次处理一个元素,并返回是否还有更多元素,避免了 Iterator 中 hasNext() 和 next() 分离可能导致的竞态条件。
  3. 元素特性报告:通过 characteristics() 方法,Spliterator 可以报告其数据源的特性(如是否有序、元素是否唯一等),供 Stream API 进行优化。
  4. 大小估算estimateSize() 方法提供对剩余元素数量的估算,有助于并行任务的负载均衡。

Spliterator 接口的主要方法和结构

我们来看一下 Spliterator<T> 接口定义的核心方法:

public interface Spliterator<T> {// ... (常量定义)boolean tryAdvance(Consumer<? super T> action);default void forEachRemaining(Consumer<? super T> action) {do { } while (tryAdvance(action));}Spliterator<T> trySplit();long estimateSize();default long getExactSizeIfKnown() {return (characteristics() & SIZED) == 0 ? -1L : estimateSize();}int characteristics();default boolean hasCharacteristics(int characteristics) {return (characteristics() & characteristics) == characteristics;}default Comparator<? super T> getComparator() {throw new IllegalStateException();}// ... (嵌套接口 OfPrimitive, OfInt, OfLong, OfDouble)
}
  1. boolean tryAdvance(Consumer<? super T> action):

    • 作用: 如果存在剩余元素,则对下一个元素执行给定的 action,并返回 true;否则返回 false
    • 核心: 这是逐个元素遍历的核心方法。如果 Spliterator 具有 ORDERED 特性,则按顺序处理元素。
    • 实现: 具体类必须实现此方法。
  2. default void forEachRemaining(Consumer<? super T> action):

    • 作用: 对当前线程中所有剩余元素按顺序执行给定的 action,直到所有元素都被处理或 action 抛出异常。
    • 默认实现:

      java

      default void forEachRemaining(Consumer<? super T> action) {do { } while (tryAdvance(action));
      }
      
      它通过循环调用 tryAdvance() 来实现。实现类通常应覆盖此方法以提供更高效的批量处理。
    • 实现: 这是一个 default 方法,但强烈建议具体实现类覆盖它以获得更好的性能,例如直接遍历底层数据结构。
  3. Spliterator<T> trySplit():

    • 作用: 尝试将当前 Spliterator 覆盖的元素集分割成两部分。如果成功分割,此方法返回一个新的 Spliterator,覆盖元素集的前一部分,而当前 Spliterator 则覆盖剩余部分。如果无法分割(例如元素太少或数据结构不支持),则返回 null
    • 核心: 这是实现并行处理的关键。Stream 框架会调用此方法来获取可以并行处理的数据块。
    • 特性:
      • 如果原 Spliterator 是 ORDERED 的,返回的 Spliterator 必须覆盖一个严格的前缀。
      • 重复调用最终必须返回 null(除非是无限流)。
    • 实现: 具体类必须实现此方法。一个理想的 trySplit 应该能高效地将元素大致均分。
  4. long estimateSize():

    • 作用: 估算通过 forEachRemaining 遍历会遇到的剩余元素数量。如果元素数量是无限的、未知的或计算成本过高,则返回 Long.MAX_VALUE
    • 特性:
      • 如果 Spliterator 具有 SIZED 特性且尚未部分遍历或分割,则此估算必须是精确的。
      • 如果具有 SUBSIZED 特性且尚未部分遍历,也必须是精确的。
      • 否则,估算可能不准确,但必须在 trySplit 调用后相应减少。
    • 实现: 具体类必须实现此方法。
  5. default long getExactSizeIfKnown():

    • 作用: 如果 Spliterator 具有 SIZED 特性,则返回 estimateSize() 的结果;否则返回 -1
    • 默认实现:
      default long getExactSizeIfKnown() {return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
      }
      
    • 实现: 这是一个 default 方法,通常不需要覆盖。
  6. int characteristics():

    • 作用: 返回一个表示此 Spliterator 及其元素特性的整数(位掩码)。这些特性用于指导 Stream API 的优化。
    • 特性常量:
      • ORDERED (0x00000010): 元素有确定的遇到顺序。
      • DISTINCT (0x00000001): 每个遇到的元素都是唯一的 (!x.equals(y))。
      • SORTED (0x00000004): 元素按定义的排序顺序排列。如果报告 SORTED,也必须报告 ORDERED
      • SIZED (0x00000040): estimateSize() 返回的是精确的大小(在遍历或分割前)。
      • NONNULL (0x00000100): 保证遇到的元素不会是 null
      • IMMUTABLE (0x00000200): 元素源不能被结构性修改(添加、替换、删除)。
      • CONCURRENT (0x00001000): 元素源可以安全地并发修改,而不会抛出 ConcurrentModificationException,并且 Spliterator 能反映这些修改。
      • SUBSIZED (0x00004000): trySplit() 返回的 Spliterator 也都是 SIZED 和 SUBSIZED 的,并且分割前后的 estimateSize() 总和保持不变。
      • SIZED 仅保证当前 Spliterator 的 estimateSize() 返回精确大小,但无法保证其子分割器(通过 trySplit() 获得)是否也能保持精确大小。例如:

        树形结构:一个平衡二叉树的 Spliterator 可能知道总元素数量(SIZED),但分割后的子树大小可能无法精确计算(尤其是非完全二叉树)。
        动态数据源:某些数据源可能在分割后丢失大小信息。

    • 实现: 具体类必须实现此方法,并准确报告其数据源的特性。
  7. default boolean hasCharacteristics(int characteristics):

    • 作用: 检查此 Spliterator 是否包含所有给定的特性。
    • 默认实现:
      default boolean hasCharacteristics(int characteristics) {return (characteristics() & characteristics) == characteristics;
      }
      
    • 实现: 这是一个 default 方法,通常不需要覆盖。
  8. default Comparator<? super T> getComparator():

    • 作用: 如果源是按特定 Comparator 排序的(即具有 SORTED 特性),则返回该 Comparator。如果源是按自然顺序排序的,则返回 null。如果源未排序,则抛出 IllegalStateException
    • 默认实现:
      default Comparator<? super T> getComparator() {throw new IllegalStateException();
      }
      
    • 实现: 如果 Spliterator 报告了 SORTED 特性,则应覆盖此方法以返回正确的比较器或 null

重要的概念(来自 Javadoc)

  • 绑定 (Binding):
    • 晚期绑定 (Late-binding)Spliterator 在首次遍历、首次分割或首次查询估算大小时才绑定到元素源。在此之前对源的修改会反映在遍历结果中。许多 JDK 集合(如 ArrayList)提供晚期绑定的 Spliterator
    • 非晚期绑定: 在构造时或首次调用任何方法时即绑定到元素源。
  • 结构性干扰 (Structural Interference): 在 Spliterator 绑定到数据源之后、遍历结束之前,对源进行结构性修改(添加、删除、替换元素)。
  • 快速失败 (Fail-fast): 在检测到结构性干扰后,尽最大努力抛出 ConcurrentModificationException
  • 线程安全Spliterator 本身不要求是线程安全的。使用 Spliterator 的并行算法应确保一个 Spliterator 实例在同一时间只被一个线程操作。通常通过串行线程限制(serial thread-confinement)来实现,即一个线程调用 trySplit() 后,可以将返回的新 Spliterator 交给另一个线程处理。

原始类型特化

Spliterator 接口有针对原始数据类型的特化子接口,以避免自动装箱/拆箱带来的性能开销:

interface OfPrimitive<T, T_CONS, T_SPLITR extends Spliterator.OfPrimitive<T, T_CONS, T_SPLITR>>extends Spliterator<T>
  • Spliterator.OfPrimitive<T, T_CONS, T_SPLITR extends Spliterator.OfPrimitive<T, T_CONS, T_SPLITR>>
    • Spliterator.OfInt extends Spliterator.OfPrimitive<Integer, IntConsumer, Spliterator.OfInt>
    • Spliterator.OfLong extends Spliterator.OfPrimitive<Long, LongConsumer, Spliterator.OfLong>
    • Spliterator.OfDouble extends Spliterator.OfPrimitive<Double, DoubleConsumer, Spliterator.OfDouble>

这些特化接口定义了接受相应原始类型 Consumer (如 IntConsumer) 的 tryAdvance 和 forEachRemaining 方法。

Spliterator.OfPrimitive的范型是为了兼容接口

  1. T:

    • 含义: 代表原始类型对应的包装类 (Wrapper Type)。
    • 原因: Java 的泛型不支持直接使用原始类型(如 intlong)。因此,即使我们处理的是原始数据流,泛型签名中也必须使用对应的包装类。例如,对于 int 类型的流,T 会是 Integer
    • 作用: 尽管底层的操作可能是针对原始类型的,但 OfPrimitive 仍然继承自 Spliterator<T>,这意味着它需要符合 Spliterator 接口的一般契约,其中涉及到对象类型 T。例如,Spliterator<T> 中的 tryAdvance(Consumer<? super T> action) 方法接受的是一个处理对象 T 的 Consumer
  2. T_CONS:

    • 含义: 代表针对特定原始类型的 Consumer 接口。
    • 原因: 为了避免在处理原始类型时发生自动装箱(autoboxing)和拆箱(unboxing)带来的性能开销,Java 8 引入了针对原始类型的函数式接口,如 IntConsumerLongConsumerDoubleConsumer。这个泛型参数 T_CONS 就是用来代表这些特定的原始类型消费者。
    • 作用OfPrimitive 接口会定义一些接受 T_CONS 类型参数的方法,例如 boolean tryAdvance(T_CONS action);。这样,当处理原始类型流时,可以直接传递原始类型的消费者,避免了不必要的对象创建和转换。
  3. T_SPLITR extends Spliterator.OfPrimitive<T, T_CONS, T_SPLITR>:

    • 含义: 代表 OfPrimitive 接口自身的具体实现子类型。这是一种常见的递归泛型模式,有时也被称为 F-bounded polymorphism。
    • 原因: 这种模式允许在父接口中定义返回其具体子类型的方法。
    • 作用: 在 OfPrimitive 接口中,有一个 trySplit() 方法,它需要返回一个与当前 Spliterator 类型相同的新的 Spliterator。通过 T_SPLITR 这个泛型参数,可以确保 trySplit() 方法的返回类型是正确的具体原始类型 Spliterator。例如,在 OfInt 中,trySplit() 会返回 OfInt。【因为 T_SPLITR 是 Spliterator.OfPrimitive<T, T_CONS, T_SPLITR>子类,也就是extends Spliterator<T>,因此返回类型是严格子类型,满足重写条件

OfInt 优先使用 IntConsumer 来处理 int 值,以获得最佳性能。同时,为了保持与 Spliterator<Integer> 的兼容性,它也提供了接受 Consumer<Integer> 的方法,并在必要时进行适配

实现源码分析

由于 Spliterator 是一个接口,其“源码”主要是方法签名、default 方法的实现以及 Javadoc 中对行为契约的详细描述。

default 方法的实现

  • forEachRemaining: 简单地循环调用 tryAdvance。实现者应尽可能覆盖它以提高效率。
  • getExactSizeIfKnown: 根据 SIZED 特性返回 estimateSize() 或 -1
  • hasCharacteristics: 通过位运算检查特性。
  • getComparator: 默认抛出异常,需要 SORTED 的 Spliterator 实现来覆盖。

Javadoc 中的示例实现 (TaggedArraySpliterator):

Javadoc 中提供了一个 TaggedArraySpliterator 的示例,这是一个很好的学习如何实现自定义 Spliterator 的起点。

// (部分代码,来自 Spliterator.java Javadoc)
static class TaggedArraySpliterator<T> implements Spliterator<T> {private final Object[] array;private int origin; // current index, advanced on split or traversalprivate final int fence; // one past the greatest indexTaggedArraySpliterator(Object[] array, int origin, int fence) {this.array = array; this.origin = origin; this.fence = fence;}public void forEachRemaining(Consumer<? super T> action) { // 覆盖了默认实现for (; origin < fence; origin += 2) // 跳过 tagaction.accept((T) array[origin]);}public boolean tryAdvance(Consumer<? super T> action) {if (origin < fence) {action.accept((T) array[origin]);origin += 2; // 跳过 tagreturn true;}else return false;}public Spliterator<T> trySplit() {int lo = origin;int mid = ((lo + fence) >>> 1) & ~1; // 确保 mid 是偶数索引if (lo < mid) { // 如果可以分割origin = mid; // 当前 spliterator 处理后半部分return new TaggedArraySpliterator<>(array, lo, mid); // 返回新的 spliterator 处理前半部分}else return null; // 太小无法分割}public long estimateSize() {return (long)((fence - origin) / 2); // 因为每两个元素有一个是数据}public int characteristics() {return ORDERED | SIZED | IMMUTABLE | SUBSIZED;}
}

这个例子清晰地展示了:

  • 如何根据数据结构特点实现 tryAdvance 和 forEachRemaining
  • trySplit 如何将范围分割,并更新当前 Spliterator 的范围。
  • estimateSize 如何根据当前范围计算大小。
  • characteristics 如何报告数据源的特性。

总结

Spliterator 接口是 Java Stream API 实现高效顺序和并行处理的基石。它通过定义一套清晰的契约(包括遍历、分割、大小估算和特性报告),使得各种数据源都能以统一的方式接入 Stream 处理管道。理解 Spliterator 的设计和各个方法的语义对于深入理解 Stream API 的工作原理至关重要,也是实现自定义数据源以支持 Stream 操作的基础。

要深入理解其“实现”,除了阅读接口定义和 default 方法,更重要的是去阅读 JDK 中各种集合类(如 ArrayListHashMapLinkedListArrayDeque 等)是如何实现它们各自的 spliterator() 方法的,以及 java.util.Spliterators 工具类中提供的各种 Spliterator 实现。这些具体的实现会展示在不同数据结构下如何有效地实现分割和遍历逻辑。

Spliterators

Spliterators 类的主要功能是提供静态方法,用于操作或创建 Spliterator 及其原始类型特化(Spliterator.OfIntSpliterator.OfLongSpliterator.OfDouble)的实例。

其结构可以主要分为以下几个部分:

  1. 空 Spliterator 的工厂方法:

    • emptySpliterator(): 创建一个泛型为空的 Spliterator<T>
    • emptyIntSpliterator(): 创建一个空的 Spliterator.OfInt
    • emptyLongSpliterator(): 创建一个空的 Spliterator.OfLong
    • emptyDoubleSpliterator(): 创建一个空的 Spliterator.OfDouble。 这些空的 Spliterator 都报告 Spliterator.SIZED 和 Spliterator.SUBSIZED特性,并且它们的 trySplit() 方法总是返回 null。它们通常是通过内部类 EmptySpliterator 的静态常量实现的。
  2. 基于数组的 Spliterator 工厂方法: 这类方法用于从数组创建 Spliterator。它们通常接受一个数组、一个可选的起止索引范围以及一个 additionalCharacteristics 参数,用于指定除了 SIZED 和 SUBSIZED(这两个特性总是会被报告)之外的额外特性(例如 IMMUTABLEORDERED)。

    • 针对 Object[] (泛型):
      • spliterator(Object[] array, int additionalCharacteristics)
      • spliterator(Object[] array, int fromIndex, int toIndex, int additionalCharacteristics)
    • 针对 int[]:
      • spliterator(int[] array, int additionalCharacteristics)
      • spliterator(int[] array, int fromIndex, int toIndex, int additionalCharacteristics)
    • 针对 long[]double[]类似
  3. 基于迭代器 (Iterator) 的 Spliterator 工厂方法: 这类方法用于从 Collection 或 Iterator 创建 Spliterator。

    • 从 Collection 创建:
      • spliterator(Collection<? extends T> c, int characteristics): 使用集合的迭代器和大小。
    • 从 Iterator 创建 (泛型和原始类型):
      • spliterator(Iterator<? extends T> iterator, long size, int characteristics): 提供迭代器和已知大小。
      • spliteratorUnknownSize(Iterator<? extends T> iterator, int characteristics): 提供迭代器,大小未知。
      • 类似地,为 PrimitiveIterator.OfIntPrimitiveIterator.OfLongPrimitiveIterator.OfDouble 提供了带大小和未知大小的版本。 这些方法通常会实例化如 IteratorSpliteratorIntIteratorSpliterator 等内部或包级私有的 Spliterator 实现。
  4. 从 Spliterator 创建 Iterator 的工厂方法: 这类方法提供了将 Spliterator 转换回 Iterator 的功能。

    • iterator(Spliterator<? extends T> spliterator): 将泛型 Spliterator 转换为 Iterator<T>
    • iterator(Spliterator.OfInt spliterator): 将 Spliterator.OfInt 转换为 PrimitiveIterator.OfInt
    • iterator(Spliterator.OfLong spliterator): 将 Spliterator.OfLong 转换为 PrimitiveIterator.OfLong
    • iterator(Spliterator.OfDouble spliterator): 将 Spliterator.OfDouble 转换为 PrimitiveIterator.OfDouble
    • 这些方法内部定义了一个名为 Adapter 的局部内部类,该类同时实现了对应的 Iterator 接口和 Consumer 接口(或其原始类型特化版本),用于适配 Spliterator 的 tryAdvance 和 forEachRemaining 方法。
  5. 私有辅助方法:

    • checkFromToBounds(int arrayLength, int origin, int fence): 这是一个私有的静态方法,用于校验数组的起始索引(inclusive)和结束索引(exclusive)是否有效,如果无效则抛出 ArrayIndexOutOfBoundsException

总结Spliterators 类是一个核心的工具类,它通过提供各种静态工厂方法,极大地简化了为不同数据源(如数组、集合、迭代器)创建不同类型(泛型、int、long、double)和不同特性组合的 Spliterator 对象的过程。

同时,它也提供了从 Spliterator 反向创建 Iterator 的能力。该类的设计体现了对不同场景和性能需求的考虑,例如区分已知大小和未知大小的数据源,以及允许用户指定额外的 Spliterator 特性。内部实现细节被很好地封装起来,对外提供清晰易用的API。

iterator

Spliterators.iterator(Spliterator<? extends T> spliterator) 这个静态方法,它用于从一个 Spliterator 创建一个 Iterator

PrimitiveIterator.OfInt 等是类似的,不过对原始类型进行操作。

    /*** Creates an {@code Iterator} from a {@code Spliterator}.** <p>Traversal of elements should be accomplished through the iterator.* The behaviour of traversal is undefined if the spliterator is operated* after the iterator is returned.** @param <T> Type of elements* @param spliterator The spliterator* @return An iterator* @throws NullPointerException if the given spliterator is {@code null}*/public static<T> Iterator<T> iterator(Spliterator<? extends T> spliterator) {// ... 实现 ...}
  • public static<T> Iterator<T> iterator(Spliterator<? extends T> spliterator):
    • 它接受一个 Spliterator<? extends T> 类型的参数,返回一个 Iterator<T>,这个迭代器将用于遍历 Spliterator 中的元素。
  • 文档:
    • 重要警告: "Traversal of elements should be accomplished through the iterator. The behaviour of traversal is undefined if the spliterator is operated after the iterator is returned."
    • 这意味着一旦通过这个方法从 Spliterator 创建了 Iterator,就不应该再直接操作原来的 Spliterator。否则,行为是未定义的,可能会导致迭代不一致或错误。这是因为 Iterator 的状态依赖于对底层 Spliterator 的操作。
    • 如果传入的 spliterator 为 null,会抛出 NullPointerException

方法实现

Spliterators.java

        Objects.requireNonNull(spliterator);class Adapter implements Iterator<T>, Consumer<T> {boolean valueReady = false;T nextElement;// ... Adapter 类的其他方法 ...}return new Adapter();
  1. Objects.requireNonNull(spliterator);:

    • 这是一个防御性编程措施,确保传入的 spliterator 不为 null。如果为 null,会立即抛出 NullPointerException,而不是在后续操作中出现更难以追踪的错误。
  2. class Adapter implements Iterator<T>, Consumer<T>:

    • 这里定义了一个局部内部类 Adapter。这个类是实现从 Spliterator 到 Iterator 转换的核心。
    • 它同时实现了两个接口:
      • Iterator<T>: 这是它作为迭代器必须实现的接口,提供了 hasNext()next() 和 forEachRemaining() (Java 8 默认方法,这里也覆盖了) 方法。
      • Consumer<T>: 这个接口的实现 (accept(T t)) 用于配合 Spliterator.tryAdvance(Consumer<? super T> action) 方法。当 tryAdvance 成功获取一个元素时,它会调用 Adapter 实例的 accept 方法,将元素传递过来。
  3. Adapter 类的成员变量:

    • boolean valueReady = false;:
      • 这是一个状态标志。当 Spliterator.tryAdvance() 成功获取了一个元素并且该元素已经被 Adapter 的 accept 方法接收并存储在 nextElement 中时,valueReady 会被设置为 true。这表示下一个元素已经准备好,可以被 next() 方法返回。
    • T nextElement;:
      • 用于临时存储从 Spliterator 获取到的下一个元素。当 valueReady 为 true 时,nextElement 持有这个待返回的元素。

Adapter 类的方法:

  • public void accept(T t):

    Spliterators.java

                @Overridepublic void accept(T t) {valueReady = true;nextElement = t;}
    
    • 这是 Consumer<T> 接口的实现。
    • 当 spliterator.tryAdvance(this) 被调用时,如果 spliterator 成功获取一个元素,它会将该元素传递给这个 accept 方法。
    • valueReady = true;: 将标志设置为 true,表示有一个元素已经准备好了。
    • nextElement = t;: 将获取到的元素 t 存储起来。
  • public boolean hasNext():

                @Overridepublic boolean hasNext() {if (!valueReady)spliterator.tryAdvance(this);return valueReady;}
    
    • 这是 Iterator<T> 接口的核心方法之一,用于检查是否还有更多元素。
    • if (!valueReady): 如果 valueReady 为 false,意味着当前没有预先加载好的元素(可能是第一次调用 hasNext(),或者上一个元素已经被 next() 消耗掉了)。
    • spliterator.tryAdvance(this);: 在这种情况下,它会尝试从底层的 spliterator 获取下一个元素。注意,这里传递的 Consumer 是 this,即 Adapter 实例本身。
    • 如果 tryAdvance 成功,它会回调 Adapter 的 accept 方法,从而设置 valueReady = true 和 nextElement
    • return valueReady;: 返回 valueReady 的当前状态。如果 tryAdvance 成功了,valueReady 会是 true;如果 tryAdvance 失败(没有更多元素了),valueReady 会保持 false(或者在 accept 中没有被设置为 true)。
  • public T next():

    Spliterators.java

                @Overridepublic T next() {if (!valueReady && !hasNext())throw new NoSuchElementException();else {valueReady = false;T t = nextElement;nextElement = null; // Help GCreturn t;}}
    
    • 这是 Iterator<T> 接口的另一个核心方法,用于返回下一个元素。
    • if (!valueReady && !hasNext()): 这是一个双重检查。
      • 首先检查 !valueReady:如果当前没有预加载的元素。
      • 然后调用 !hasNext():尝试加载下一个元素。如果 hasNext() 返回 false(意味着 tryAdvance 失败,没有更多元素),则整个条件为真。
      • 如果条件为真,说明确实没有下一个元素了,所以 throw new NoSuchElementException();,这符合 Iterator.next() 的规范。
    • else: 如果 valueReady 为 true(由上一次 hasNext() 调用或当前 hasNext() 调用成功设置),或者 hasNext() 在上面的条件中返回 true
      • valueReady = false;: 将标志重置为 false,因为即将返回的元素已经被消耗了。下一次调用 hasNext() 时需要重新尝试获取。
      • T t = nextElement;: 获取存储的元素。
      • nextElement = null;: 将 nextElement 设置为 null,以帮助垃圾回收器回收该对象(如果它是最后一个引用)。
      • return t;: 返回元素。
  • public void forEachRemaining(Consumer<? super T> action):

    Spliterators.java

                @Overridepublic void forEachRemaining(Consumer<? super T> action) {Objects.requireNonNull(action);if (valueReady) {valueReady = false;T t = nextElement;nextElement = null;action.accept(t);}spliterator.forEachRemaining(action);}
    
    • 这是 Iterator<T> 接口的方法 (Java 8 中添加为默认方法,这里进行了覆盖以优化)。它对迭代器中的每个剩余元素执行给定操作。
    • Objects.requireNonNull(action);: 检查传入的 action 是否为 null
    • if (valueReady): 如果有一个元素已经被 hasNext() 预加载并准备好了 (valueReady == true)。
      • valueReady = false;: 消耗这个预加载的元素。
      • T t = nextElement; nextElement = null;: 获取并清空预加载的元素。
      • action.accept(t);: 对这个预加载的元素执行操作。
    • spliterator.forEachRemaining(action);: 然后,调用底层 spliterator 的 forEachRemaining 方法来处理所有再往后的剩余元素。这通常比通过循环调用 hasNext() 和 next() 更高效,因为 Spliterator 可能有更优化的内部批量处理方式。
  1. return new Adapter();:

    • 最后,该方法创建并返回一个新的 Adapter 实例。这个 Adapter 对象就充当了基于原始 Spliterator 的 Iterator

总结与使用场景

这个 Spliterators.iterator() 方法提供了一种将 Spliterator 适配(Adapt)为传统 Iterator 的机制。

核心思想:

  • Iterator 的 hasNext() 和 next() 是拉取(pull-based)模型:调用者请求下一个元素。
  • Spliterator 的 tryAdvance(Consumer) 是推送(push-based)或协作模型:你提供一个 ConsumerSpliterator 尝试获取一个元素并“推送”给这个 Consumer
  • Adapter 类通过内部状态 (valueReadynextElement) 和同时实现 Iterator 与 Consumer 来桥接这两种模型。hasNext() 尝试通过 tryAdvance 预取一个元素到 nextElementnext() 则消耗这个预取的元素。

什么时候会用到这个方法?

  1. 与期望 Iterator 的旧版 API 交互:

    • 在 Java 8 引入 Stream 和 Spliterator 之前,Java 集合的迭代主要通过 Iterator 接口。如果有一个 Spliterator(可能来自一个 Stream,或者是一个自定义的 Spliterator),但需要将其传递给一个只接受 Iterator 的旧方法或库,那么这个 Spliterators.iterator() 方法就非常有用。
    • 例如,某个库函数 processItems(Iterator<String> items),而你有一个 Stream<String> stream,你可以这样做:
      Stream<String> myStream = ... ;
      Iterator<String> iterator = Spliterators.iterator(myStream.spliterator());
      legacyLibrary.processItems(iterator);
      
  2. 需要 Iterator 的特定行为或接口:

    • 虽然 Spliterator 更强大(支持并行、更好的分割特性),但在某些情况下,你可能确实需要 Iterator 接口提供的简单 hasNext()/next() 模式,或者某个特定的 Iterator 子接口。
  3. 调试或简单的顺序遍历:

    • 有时,为了调试或者进行非常简单的、明确的顺序元素处理,直接使用 Iterator 的 hasNext()/next() 循环可能比构建一个完整的 Stream 流水线更直观或简单。如果你已经有了一个 Spliterator,可以方便地转换为 Iterator 来进行这种操作。
  4. 逐步迁移:

    • 当一个代码库从旧的基于 Iterator 的代码逐步迁移到使用 Stream API 时,这个方法可以作为过渡,允许新旧代码共存和交互。

与 Java Stream 的关系:

  • Java Stream API 在内部广泛使用 Spliterator 作为其数据源的抽象。当你调用 stream.iterator() 时,实际上它内部可能就会使用类似 Spliterators.iterator(this.spliterator()) 的逻辑(具体实现可能因 Stream 类型而异,但概念是相通的)。

    java

    // Stream.java (概念性示例)default Iterator<T> iterator() {return Spliterators.iterator(spliterator());
    }
    
  • 所以,当你从一个 Stream 获取 Iterator 时,你间接或直接地使用了这种从 Spliterator 到 Iterator 的转换机制。

注意事项:

  • 如文档所述,一旦创建了 Iterator,就不应再直接操作原始的 Spliterator,因为 Adapter 的状态与 Spliterator 的状态是耦合的。
  • Iterator 通常不支持并行处理,而 Spliterator 的核心优势之一就是其可分割性以支持并行。将 Spliterator 转换为 Iterator 通常意味着放弃了并行处理该数据源的能力(至少通过这个 Iterator 是这样)。

总的来说,Spliterators.iterator() 是一个实用的工具方法,它在 Spliterator 和传统的 Iterator 之间架起了一座桥梁,增强了 Java 集合框架和 Stream API 的互操作性。

ArraySpliterator

ArraySpliterator 是 java.util.Spliterators 类中的一个静态 final 内部类,它实现了 Spliterator<T> 接口。它的主要设计目标是为不可修改的 Object[] 数组提供一个高效的遍历和分割机制。

IntArraySpliterator 之类 也是类似的,不过对原始类型操作

Spliterators.java

// ... (其他 Spliterators 代码) ...// Array-based spliterators/*** A Spliterator designed for use by sources that traverse and split* elements maintained in an unmodifiable {@code Object[]} array.*/static final class ArraySpliterator<T> implements Spliterator<T> {// ... (成员变量和方法) ...}// ... (其他 Spliterators 代码) ...

成员变量

        /*** The array, explicitly typed as Object[]. Unlike in some other* classes (see for example CR 6260652), we do not need to* screen arguments to ensure they are exactly of type Object[]* so long as no methods write into the array or serialize it,* which we ensure here by defining this class as final.*/private final Object[] array;private int index;        // current index, modified on advance/splitprivate final int fence;  // one past last indexprivate final int characteristics;private long estimatedSize; // if >= 0, the estimated size, to help to split evenly// if -1, exact size is known to be fence - index
  • private final Object[] array;
    • 作用: 这是 ArraySpliterator 实际操作的底层数组。它被声明为 final,意味着一旦在构造时赋值,就不能再指向其他数组对象。
    • 类型Object[]。注释中提到,不需要严格检查传入的数组是否真的是 Object[] 类型(例如,可以是 String[]),只要后续操作不向数组中写入或序列化它即可。由于 ArraySpliterator 被声明为 final,可以保证其内部方法不会破坏这个约定。
  • private int index;
    • 作用: 表示当前迭代器指向的数组元素的索引。当调用 tryAdvance 成功处理一个元素后,或者当 trySplit 发生时,这个 index 会被修改。
  • private final int fence;
    • 作用: 表示迭代的结束边界。它指的是最后一个要处理的元素的下一个索引。也就是说,迭代的范围是 [index, fence)。它被声明为 final
  • private final int characteristics;
    • 作用: 存储了这个 Spliterator 的特性。这些特性是通过位掩码表示的整数,例如 Spliterator.SIZED (表示大小已知)、Spliterator.SUBSIZED (表示分裂后的子 Spliterator 大小也已知)、Spliterator.ORDERED (表示元素是有序的) 等。它被声明为 final
  • private long estimatedSize;
    • 作用: 用于存储对剩余元素数量的估计。
      • 如果 estimatedSize 的值为 -1,表示 Spliterator 的大小是精确已知的,可以通过 fence - index 计算得出。这通常是初始创建或从具有精确大小的 Spliterator 分裂出来的情况。
      • 如果 estimatedSize 的值 >= 0,表示这是一个估计的大小。这种情况通常发生在对一个非 SIZED 的 Spliterator 进行 trySplit 操作后,或者当 Spliterator 的大小不能精确确定时,分裂出的子 Spliterator 会有一个估计的大小,以帮助更均匀地进行并行处理。

构造函数

ArraySpliterator 提供了几个构造函数来满足不同的初始化需求。

        /*** Creates a spliterator covering all of the given array.* Its size is known exactly and it is SIZED and SUBSIZED.* @param array the array, assumed to be unmodified during use* @param additionalCharacteristics Additional spliterator characteristics* of this spliterator's source or elements beyond {@code SIZED} and* {@code SUBSIZED} which are always reported*/public ArraySpliterator(Object[] array, int additionalCharacteristics) {this(array, 0, array.length, additionalCharacteristics);}
  • public ArraySpliterator(Object[] array, int additionalCharacteristics)
    • 作用: 创建一个覆盖整个给定数组的 Spliterator
    • 参数:
      • array: 要迭代的数组。
      • additionalCharacteristics: 除了默认的 SIZED 和 SUBSIZED 之外,用户可以指定的其他特性。
    • 行为: 它内部调用了更详细的构造函数 this(array, 0, array.length, additionalCharacteristics),将 origin 设为 0fence 设为 array.length
        /*** Creates a spliterator covering the given array and range.* Its size is known exactly and it is SIZED and SUBSIZED.* @param array the array, assumed to be unmodified during use* @param origin the least index (inclusive) to cover* @param fence one past the greatest index to cover* @param additionalCharacteristics Additional spliterator characteristics* of this spliterator's source or elements beyond {@code SIZED} and* {@code SUBSIZED} which are always reported*/public ArraySpliterator(Object[] array, int origin, int fence, int additionalCharacteristics) {this.array = array;this.index = origin;this.fence = fence;this.characteristics = additionalCharacteristics | Spliterator.SIZED | Spliterator.SUBSIZED;this.estimatedSize = -1;}
  • public ArraySpliterator(Object[] array, int origin, int fence, int additionalCharacteristics)
    • 作用: 创建一个覆盖数组指定范围 [origin, fence) 的 Spliterator
    • 参数:
      • array: 要迭代的数组。
      • origin: 开始迭代的索引(包含)。
      • fence: 结束迭代的索引(不包含)。
      • additionalCharacteristics: 用户可以指定的其他特性。
    • 行为:
      • 初始化 arrayindex (设为 origin), fence
      • characteristics 被设置为 additionalCharacteristics 与 Spliterator.SIZED | Spliterator.SUBSIZED 的按位或操作结果。这意味着通过这个构造函数创建的 Spliterator 总是具有 SIZED 和 SUBSIZED 特性,表示其大小是精确已知的,并且分裂后的子 Spliterator 大小也是精确已知的。
      • estimatedSize 被设置为 -1,表明大小是精确的。
        /*** Creates a spliterator covering the given array and range but that is* not SIZED or SUBSIZED. This case occurs as a result of splitting another* spliterator that is not sized, so it's inappropriate for one of its* sub-spliterators to be sized.* @param array the array, assumed to be unmodified during use* @param origin the least index (inclusive) to cover* @param fence one past the greatest index to cover* @param characteristics characteristics of this spliterator's source; {@code SIZED} and*        {@code SUBSIZED} are removed if present* @param estimatedSize the size estimate; should always be nonnegative*/private ArraySpliterator(Object[] array, int origin, int fence, int characteristics, long estimatedSize) {this.array = array;this.index = origin;this.fence = fence;this.characteristics = characteristics & ~(Spliterator.SIZED | Spliterator.SUBSIZED);this.estimatedSize = estimatedSize;}
  • private ArraySpliterator(Object[] array, int origin, int fence, int characteristics, long estimatedSize)
    • 作用: 这是一个私有构造函数,主要由 trySplit() 方法内部调用,用于创建子 Spliterator
    • 参数:
      • arrayoriginfence: 与公共构造函数类似。
      • characteristics: 从父 Spliterator 继承的特性。
      • estimatedSize: 估计的大小,通常是非负数。
    • 行为:
      • 初始化 arrayindexfence
      • characteristics 被设置为传入的 characteristics 与 ~(Spliterator.SIZED | Spliterator.SUBSIZED) 的按位与操作结果。这意味着这个构造函数会移除 SIZED 和 SUBSIZED 特性(如果存在的话)。这是因为当一个 Spliterator 的大小不是精确已知时(例如,它的 estimatedSize >= 0),它分裂出来的子 Spliterator 的大小通常也是估计的,因此不应该标记为 SIZED 或 SUBSIZED
      • estimatedSize 被设置为传入的 estimatedSize

trySplit()

        @Overridepublic Spliterator<T> trySplit() {int lo = index, mid = (lo + fence) >>> 1;if (lo >= mid) return null;if (estimatedSize == -1) { // Case 1: Exact size is knownreturn new ArraySpliterator<>(array, lo, index = mid, characteristics);}// Case 2: Size is estimatedlong prefixEstimatedSize = estimatedSize >>> 1;estimatedSize -= prefixEstimatedSize;return new ArraySpliterator<>(array, lo, index = mid, characteristics, prefixEstimatedSize);}
  • 作用: 尝试将当前的 Spliterator 分割成两个。如果成功,此方法返回一个新的 Spliterator,它覆盖当前 Spliterator 范围的前半部分,而当前的 Spliterator 则修改其范围以覆盖后半部分。如果无法再分割(例如,剩余元素太少),则返回 null
  • 逻辑:
    1. int lo = index, mid = (lo + fence) >>> 1;:
      • lo 保存当前的起始索引 index
    2. if (lo >= mid) return null;: 如果起始索引 lo 大于或等于中间点 mid,意味着剩余的元素数量不足以进行分割(通常只剩一个元素或没有元素),则返回 null
    3. 情况 1: estimatedSize == -1 (大小精确已知)
      • return new ArraySpliterator<>(array, lo, index = mid, characteristics);
      • 创建一个新的 ArraySpliterator 来处理前半部分 [lo, mid)
      • index = mid: 当前 Spliterator 的 index(起始索引)被更新为 mid,使其负责处理后半部分 [mid, fence)
      • 新的 Spliterator 继承了原有的 characteristics。由于父 Spliterator 的大小是精确的,新分裂出的子 Spliterator 的大小也是精确的,所以它会通过公共构造函数(间接或直接)被标记为 SIZED 和 SUBSIZED(因为 characteristics 已经包含了这些)。
    4. 情况 2: estimatedSize != -1 (大小是估计的)
      • long prefixEstimatedSize = estimatedSize >>> 1;: 估算前半部分的元素数量,通常是当前估计大小的一半。
      • estimatedSize -= prefixEstimatedSize;: 更新当前 Spliterator 的估计大小,减去分配给新 Spliterator 的部分。
      • return new ArraySpliterator<>(array, lo, index = mid, characteristics, prefixEstimatedSize);
      • 创建一个新的 ArraySpliterator 来处理前半部分 [lo, mid),使用私有构造函数。
      • 当前 Spliterator 的 index 更新为 mid
      • 新的 Spliterator 继承原有的 characteristics(但私有构造函数会移除 SIZED 和 SUBSIZED),并被赋予计算出的 prefixEstimatedSize

forEachRemaining(Consumer<? super T> action)

        @SuppressWarnings("unchecked")@Overridepublic void forEachRemaining(Consumer<? super T> action) {Object[] a; int i, hi; // hoist accesses and checks from loopif (action == null)throw new NullPointerException();if ((a = array).length >= (hi = fence) &&(i = index) >= 0 && i < (index = hi)) {do { action.accept((T)a[i]); } while (++i < hi);}}
  • 作用: 对 Spliterator 中所有剩余的元素执行给定的 action
  • 逻辑:
    1. Object[] a; int i, hi;: 声明局部变量,并将对 arrayfenceindex 的访问提升到循环外部,这是一种微优化,可以减少循环内部的字段访问开销。
    2. if (action == null) throw new NullPointerException();: 检查 action 是否为 null
    3. if ((a = array).length >= (hi = fence) && (i = index) >= 0 && i < (index = hi)):
      • 这是一个关键的检查和赋值语句。
      • a = array: 将成员变量 array 赋值给局部变量 a
      • hi = fence: 将成员变量 fence 赋值给局部变量 hi
      • i = index: 将成员变量 index 赋值给局部变量 i
      • a.length >= hi: 检查数组的实际长度是否至少与 fence 一样大,确保不会越界。
      • i >= 0: 检查当前索引是否有效。
      • i < (index = hi): 这是一个复合操作。首先检查当前索引 i 是否小于 hi(即 fence)。如果是,则将成员变量 index 更新为 hi。这意味着在 forEachRemaining 执行后,Spliterator 的 index 会移动到 fence,表示所有元素都已被处理。
    4. do { action.accept((T)a[i]); } while (++i < hi);: 如果上述条件都满足,则进入一个 do-while 循环。
      • action.accept((T)a[i]): 对当前索引 i 处的元素执行 action。这里有一个 @SuppressWarnings("unchecked"),因为 array 是 Object[] 类型,需要强制转换为 T
      • ++i < hi: 递增索引 i,并检查是否仍然小于 hi

tryAdvance(Consumer<? super T> action)

        @Overridepublic boolean tryAdvance(Consumer<? super T> action) {if (action == null)throw new NullPointerException();if (index >= 0 && index < fence) {@SuppressWarnings("unchecked") T e = (T) array[index++];action.accept(e);return true;}return false;}
  • 作用: 如果存在下一个元素,则对其执行给定的 action 并返回 true;否则返回 false
  • 逻辑:
    1. if (action == null) throw new NullPointerException();: 检查 action 是否为 null
    2. if (index >= 0 && index < fence): 检查当前 index 是否在有效范围 [0, fence) 内。
      • 如果条件为真,表示还有元素可以处理:
        • @SuppressWarnings("unchecked") T e = (T) array[index++];: 获取 index处的元素,并将其赋值给 e。然后,index 自增,为下一次调用做准备。
        • action.accept(e);: 对获取到的元素 e 执行 action
        • return true;: 返回 true 表示成功处理了一个元素。
    3. 如果 index 不在有效范围内,则直接 return false;,表示没有更多元素可供处理。

estimateSize()

        @Overridepublic long estimateSize() {return estimatedSize >= 0 ? estimatedSize : (long)(fence - index);}
  • 作用: 返回对剩余元素数量的估计值。
  • 逻辑:
    • estimatedSize >= 0 ? estimatedSize : (long)(fence - index):
      • 如果 estimatedSize 大于或等于 0,说明它存储的是一个估计值,直接返回这个估计值。
      • 否则(即 estimatedSize == -1),说明大小是精确已知的,通过 (long)(fence - index) 计算并返回剩余元素的精确数量。强制转换为 long 以匹配返回类型。

getComparator()

        @Overridepublic Comparator<? super T> getComparator() {if (hasCharacteristics(Spliterator.SORTED))return null;throw new IllegalStateException();}} // End of ArraySpliterator class
  • 作用: 如果此 Spliterator 的元素是 SORTED(有序的)并且定义了排序比较器,则返回该比较器。如果元素是自然排序的,则返回 null。如果元素不是 SORTED 的,则抛出 IllegalStateException
  • 逻辑:
    • if (hasCharacteristics(Spliterator.SORTED)): 检查 Spliterator 是否具有 SORTED 特性。hasCharacteristics 是 Spliterator 接口的默认方法,用于检查是否包含某个特定特性。
      • 如果为 true,则返回 null。对于 ArraySpliterator,它本身不强制或存储特定的比较器。如果数据源(数组)本身是有序的(例如,通过 Arrays.sort() 排序过),并且在创建 Spliterator 时指定了 SORTED 特性,那么这里返回 null 表示它是按自然顺序排序的,或者排序规则未由此 Spliterator 直接定义。
    • 如果 Spliterator 不具有 SORTED 特性,则 throw new IllegalStateException();,因为在这种情况下调用 getComparator() 是没有意义的。

AbstractSpliterator<T>

AbstractSpliterator<T> 是 java.util.Spliterators 类中的一个公共静态抽象内部类,它实现了 Spliterator<T> 接口。它的主要目的是为创建自定义 Spliterator 提供一个便利的基类,特别是当源数据不容易或很难被高效地、均衡地分割以进行并行计算时

AbstractIntSpliterator是类似的

它通过实现一个通用的 trySplit() 方法来提供有限的并行能力,这个 trySplit() 方法会尝试将元素分批(batch)处理。

类声明和文档注释

    // ... (其他 Spliterators 代码) .../*** An abstract {@code Spliterator} that implements {@code trySplit} to* permit limited parallelism.** <p>An extending class need only* implement {@link #tryAdvance(java.util.function.Consumer) tryAdvance}.* The extending class should override* {@link #forEachRemaining(java.util.function.Consumer) forEachRemaining}* if it can provide a more performant implementation.** @apiNote* This class is a useful aid for creating a spliterator when it is not* possible or difficult to efficiently partition elements in a manner* allowing balanced parallel computation.** <p>An alternative to using this class, that also permits limited* parallelism, is to create a spliterator from an iterator* (see {@link #spliterator(Iterator, long, int)}.  Depending on the* circumstances using an iterator may be easier or more convenient than* extending this class, such as when there is already an iterator* available to use.** @param <T> the type of elements returned by this Spliterator** @see #spliterator(Iterator, long, int)* @since 1.8*/public abstract static class AbstractSpliterator<T> implements Spliterator<T> {// ... (成员和方法) ...}
  • public abstract static class AbstractSpliterator<T> implements Spliterator<T>:
    • implements Spliterator<T>: 实现了 Spliterator 接口,因此子类需要实现 Spliterator 接口中未被 AbstractSpliterator 实现的方法(主要是 tryAdvance)。
  • 文档注释:
    • 核心功能: 提供了一个实现了 trySplit 的 Spliterator,允许有限的并行性。
    • 子类责任: 继承此类通常只需要实现 tryAdvance(Consumer<? super T> action) 方法。如果子类能提供更高性能的实现,也应该覆盖 forEachRemaining(Consumer<? super T> action)
    • 使用场景 (API Note): 当无法或难以高效地、均衡地分割元素以进行并行计算时,此类非常有用。它提供了一种“尽力而为”的分割策略。
    • 替代方案: 从 Iterator 创建 Spliterator (使用 Spliterators.spliterator(Iterator, long, int)) 也是一种获得有限并行性的方法,有时可能更简单。

成员

        static final int BATCH_UNIT = 1 << 10;  // batch array size increment (1024)static final int MAX_BATCH = 1 << 25;  // max batch array size (33,554,432)
  • static final int BATCH_UNIT = 1 << 10; (即 1024)
    • 作用: 批处理大小的增量单位。在 trySplit 时,每次分割出的批次大小会尝试在这个单位的基础上增加。
  • static final int MAX_BATCH = 1 << 25; (即 33,554,432)
    • 作用: 批处理数组的最大大小。trySplit 创建的批次大小不会超过这个限制。
        private final int characteristics;private long est;             // size estimateprivate int batch;            // batch size for splits
  • private final int characteristics;
    • 作用: 存储此 Spliterator 的特性(例如 ORDEREDSIZED 等)。一旦在构造时设置,就不会改变。
  • private long est;
    • 作用: 存储对 Spliterator 中剩余元素数量的估计值。这个值会在 trySplit 操作后被更新。
  • private int batch;
    • 作用: 用于 trySplit 的当前批处理大小。这个值会动态调整,初始通常较小,然后逐渐增大,但不会超过 MAX_BATCH

构造函数

        /*** Creates a spliterator reporting the given estimated size and* additionalCharacteristics.** @param est the estimated size of this spliterator if known, otherwise*        {@code Long.MAX_VALUE}.* @param additionalCharacteristics properties of this spliterator's*        source or elements.  If {@code SIZED} is reported then this*        spliterator will additionally report {@code SUBSIZED}.*/protected AbstractSpliterator(long est, int additionalCharacteristics) {this.est = est;this.characteristics = ((additionalCharacteristics & Spliterator.SIZED) != 0)? additionalCharacteristics | Spliterator.SUBSIZED: additionalCharacteristics;}
  • protected AbstractSpliterator(long est, int additionalCharacteristics):
    • protected: 构造函数是受保护的,意味着只有 Spliterators 包内的类或其子类可以调用。
    • 参数:
      • est: 初始的估计大小。如果大小未知,通常传入 Long.MAX_VALUE
      • additionalCharacteristics: 用户指定的附加特性。
    • 逻辑:
      • this.est = est;: 初始化估计大小。
      • this.characteristics = ...: 初始化特性。这里有一个逻辑:如果传入的 additionalCharacteristics 中包含了 Spliterator.SIZED 特性,那么会自动添加 Spliterator.SUBSIZED 特性。这是因为如果一个 Spliterator 的大小是已知的,那么通过 AbstractSpliterator 的 trySplit 机制分割出来的子 Spliterator(通常是 ArraySpliterator)的大小也是已知的。

内部辅助类 HoldingConsumer<T>

        static final class HoldingConsumer<T> implements Consumer<T> {Object value;@Overridepublic void accept(T value) {this.value = value;}}
  • 作用: 这是一个非常简单的 Consumer 实现,它的唯一目的是在 tryAdvance 调用时“持有”或捕获传递给它的单个元素。
  • Object value;: 用于存储通过 accept 方法接收到的值。注意这里用的是 Object 类型,因为泛型 T 在运行时会被擦除。
  • public void accept(T value): 当 tryAdvance(consumer) 被调用时,如果成功获取一个元素,该元素会被传递到这个 accept 方法,并存储在 this.value 字段中。

这个类在 trySplit() 方法中用于从 tryAdvance() 中获取元素,以便将它们收集到一个批处理数组中。

trySplit() 方法

这是 AbstractSpliterator 的核心方法,它提供了一种通用的、基于批处理的分割策略。

        @Overridepublic Spliterator<T> trySplit() {/** Split into arrays of arithmetically increasing batch* sizes.  This will only improve parallel performance if* per-element Consumer actions are more costly than* transferring them into an array.  The use of an* arithmetic progression in split sizes provides overhead* vs parallelism bounds that do not particularly favor or* penalize cases of lightweight vs heavyweight element* operations, across combinations of #elements vs #cores,* whether or not either are known.  We generate* O(sqrt(#elements)) splits, allowing O(sqrt(#cores))* potential speedup.*/HoldingConsumer<T> holder = new HoldingConsumer<>();long s = est;if (s > 1 && tryAdvance(holder)) { // (1)int n = batch + BATCH_UNIT;    // (2)if (n > s)n = (int) s;if (n > MAX_BATCH)n = MAX_BATCH;             // (3)Object[] a = new Object[n];    // (4)int j = 0;do { a[j] = holder.value; } while (++j < n && tryAdvance(holder)); // (5)batch = j;                     // (6)if (est != Long.MAX_VALUE) {   // (7)est -= j;return new ArraySpliterator<>(a, 0, j, characteristics);}// If original est was Long.MAX_VALUE, the new one is also effectively unbounded// but try to provide a somewhat reasonable estimate for the split part.return new ArraySpliterator<>(a, 0, j, characteristics, Long.MAX_VALUE / 2); // (8)}return null; // (9)}
  • 注释解释:

    • 分割策略是创建大小算术递增的批处理数组。
    • 这种策略只有在处理单个元素的 Consumer 操作比将元素转移到数组中的成本更高时,才能提高并行性能。
    • 算术级数增长的分割大小提供了一种在开销和并行度之间的平衡,对于轻量级和重量级元素操作都有一定的适应性。
    • 目标是产生大约 O(sqrt(元素数量)) 个分割,从而可能实现 O(sqrt(核心数量)) 的加速。
  • 逻辑步骤:

    1. HoldingConsumer<T> holder = new HoldingConsumer<>();: 创建一个 HoldingConsumer 实例来捕获由 tryAdvance 提供的元素。
    2. long s = est;: 获取当前的估计大小。
    3. if (s > 1 && tryAdvance(holder))(1)
      • 检查条件:估计大小 s 是否大于1(即至少有两个元素才可能分割),并且调用(子类实现的)tryAdvance(holder) 是否成功获取了第一个元素。如果 tryAdvance 成功,获取到的元素会存储在 holder.value 中。
      • 如果此条件不满足(例如,元素少于2个,或者 tryAdvance 无法获取任何元素),则无法分割,直接跳到步骤 (9) 返回 null
    4. int n = batch + BATCH_UNIT;(2)
      • 计算下一次批处理的目标大小 n。它是在当前 batch 大小(初始可能为0)的基础上增加 BATCH_UNIT (1024)。
    5. if (n > s) n = (int) s;: 如果计算出的 n 超过了总的估计大小 s,则将 n 限制为 s
    6. if (n > MAX_BATCH) n = MAX_BATCH;(3)
      • 如果 n 超过了 MAX_BATCH,则将 n 限制为 MAX_BATCH
    7. Object[] a = new Object[n];(4)
      • 创建一个大小为 n 的 Object 数组 a,用于存储这一批次的元素。
    8. int j = 0; do { a[j] = holder.value; } while (++j < n && tryAdvance(holder));(5)
      • 这是一个 do-while 循环,用于填充批处理数组 a
      • a[j] = holder.value;: 将之前由 tryAdvance(holder)(在步骤 (1) 或循环的上一轮)获取的元素存入数组 a
      • ++j < n && tryAdvance(holder): 递增计数器 j。只要 j 仍然小于批处理大小 n 并且 tryAdvance(holder) 能够成功获取下一个元素,循环就继续。
      • 循环结束后,j 的值就是实际填充到数组 a 中的元素数量。
    9. batch = j;(6)
      • 更新当前 Spliterator 的 batch 成员变量为这次实际处理的元素数量 j。这会影响下一次 trySplit 时的批处理大小计算。
    10. if (est != Long.MAX_VALUE)(7)
      • 如果原始的估计大小 est 不是 Long.MAX_VALUE(即大小是“已知”或有限估计的):
        • est -= j;: 从当前 Spliterator 的估计大小中减去刚刚分割出去的元素数量 j
        • return new ArraySpliterator<>(a, 0, j, characteristics);: 创建一个新的 ArraySpliterator 来包装刚刚填充的数组 a(范围是从索引0到j)。这个新的 ArraySpliterator 继承了原始的 characteristics。由于 ArraySpliterator 的构造函数(当大小精确时)会设置 estimatedSize = -1 并包含 SIZED 和 SUBSIZED 特性,所以这个返回的 Spliterator 是大小精确的。
    11. return new ArraySpliterator<>(a, 0, j, characteristics, Long.MAX_VALUE / 2);(8)
      • 如果原始的 est 是 Long.MAX_VALUE(表示大小未知或非常大):
        • 当前 Spliterator 的 est 保持 Long.MAX_VALUE
        • 为新创建的 ArraySpliterator 提供一个估计大小 Long.MAX_VALUE / 2。这是一个启发式的估计,因为原始大小未知。这里使用了 ArraySpliterator 的一个私有构造函数,该构造函数允许指定估计大小并会移除 SIZED 和 SUBSIZED 特性。
    12. return null;(9)
      • 如果初始条件 (1) 不满足,则返回 null,表示无法分割。

estimateSize() 方法

        @Overridepublic long estimateSize() {return est;}
  • 作用: 返回对剩余元素数量的估计。
  • 逻辑: 直接返回成员变量 est 的值。这个值在构造时初始化,并在每次成功的 trySplit 后(如果原始大小不是 Long.MAX_VALUE)被更新。

总结与使用

AbstractSpliterator 的设计哲学是:子类只需要关注如何逐个提供元素(通过实现 tryAdvance),而将相对复杂的、通用的分割逻辑(trySplit)交给基类处理。

如何使用 AbstractSpliterator

  1. 继承: 创建一个类继承自 AbstractSpliterator<T>
  2. 构造函数: 在子类的构造函数中调用 super(estimatedSize, characteristics) 来初始化估计大小和特性。
    • 如果源的大小已知,传入实际大小和包含 Spliterator.SIZED 的特性。
    • 如果源的大小未知或难以计算,传入 Long.MAX_VALUE 作为 estimatedSize
    • 根据源的特性(如是否有序 ORDERED,元素是否非空 NONNULL 等)设置 characteristics
  3. 实现 tryAdvance(Consumer<? super T> action): 这是必须实现的核心方法。在此方法中,你需要:
    • 检查是否还有下一个元素。
    • 如果有,获取下一个元素,并通过 action.accept(element) 将其传递给消费者。
    • 返回 true 表示成功处理了一个元素。
    • 如果没有更多元素,返回 false
  4. 可选地覆盖 forEachRemaining(Consumer<? super T> action): 如果你的数据源可以更高效地批量处理剩余元素(而不是逐个通过 tryAdvance),则可以覆盖此方法以提供优化实现。默认实现通常是循环调用 tryAdvance

示例场景 (概念性的):

假设你有一个自定义的数据结构,你想为它提供一个 Spliterator,但这个数据结构不容易直接进行高效的并行分割。

import java.util.Spliterators;
import java.util.Spliterator;
import java.util.function.Consumer;class MyCustomDataSource<T> {private java.util.Iterator<T> internalIterator; // 假设内部用迭代器private long estimatedSize;public MyCustomDataSource(java.util.Collection<T> data) {this.internalIterator = data.iterator();this.estimatedSize = data.size();}public Spliterator<T> spliterator() {return new MySpliterator<>(internalIterator, estimatedSize);}private static class MySpliterator<T> extends Spliterators.AbstractSpliterator<T> {private java.util.Iterator<T> it;protected MySpliterator(java.util.Iterator<T> iterator, long estSize) {// 假设元素是有序的,并且大小已知super(estSize, Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED);this.it = iterator;}@Overridepublic boolean tryAdvance(Consumer<? super T> action) {if (it.hasNext()) {action.accept(it.next());return true;}return false;}// 可以选择性地覆盖 forEachRemaining// @Override// public void forEachRemaining(Consumer<? super T> action) {//     it.forEachRemaining(action);// }}
}

在这个例子中,MySpliterator 继承了 AbstractSpliterator,只需要提供 tryAdvance 的实现。trySplit 的逻辑由 AbstractSpliterator 提供,它会尝试从 MySpliterator 中批量拉取元素并创建 ArraySpliterator

AbstractIntSpliteratorAbstractLongSpliterator 和 AbstractDoubleSpliterator 是针对基本数据类型 intlong 和 double 的类似抽象基类,它们也遵循相同的模式:子类实现 tryAdvance(针对相应的原始类型消费者,如 IntConsumer),基类提供 trySplit。这些原始类型的抽象类内部也会使用对应的 IntArraySpliterator 等来创建分割后的批次。

例如,在测试代码中: class IntSpliteratorFromArray extends Spliterators.AbstractIntSpliterator 这个测试类就是通过继承 AbstractIntSpliterator 并实现其 tryAdvance(IntConsumer action) 方法来创建一个用于 int[] 数组的 Spliterator。这展示了 AbstractSpliterator (及其原始类型特化版本) 的典型用法。

IteratorSpliterator

IteratorSpliterator<T> 是 java.util.Spliterators 类中的一个公共静态内部类,它实现了 Spliterator<T> 接口。其核心目的是将一个传统的 java.util.Iterator 适配(Adapt)成一个 Spliterator

这在很多场景下都非常有用,特别是当你有一个基于 Iterator 的旧代码或数据源,但又想利用 Stream API 或其他需要 Spliterator 的现代 Java 特性时。

类声明和文档注释

Spliterators.java

    // ... (其他 Spliterators 代码) .../*** A Spliterator using a given Iterator for element* operations. The spliterator implements {@code trySplit} to* permit limited parallelism.*/static class IteratorSpliterator<T> implements Spliterator<T> {// ... (成员和方法) ...}
  • static class IteratorSpliterator<T> implements Spliterator<T>:
    • implements Spliterator<T>: 实现了 Spliterator 接口,因此它必须提供该接口所有方法的实现,如 tryAdvancetrySplitestimateSizecharacteristics 等。
  • 文档注释:
    • 核心功能: 这是一个使用给定的 Iterator 进行元素操作的 Spliterator
    • 并行性: 它实现了 trySplit 方法,以允许有限的并行处理能力。这意味着即使原始的 Iterator 本身不支持并行,IteratorSpliterator 也会尝试通过批处理的方式来分割任务。

成员

        static final int BATCH_UNIT = 1 << 10;  // batch array size increment (1024)static final int MAX_BATCH = 1 << 25;  // max batch array size; (33,554,432)

这些常量与 AbstractSpliterator 中的定义和用途相同:

  • BATCH_UNIT: 批处理大小的增量单位,用于 trySplit
  • MAX_BATCH: 批处理数组的最大大小,用于 trySplit
        private final Collection<? extends T> collection; // null OKprivate Iterator<? extends T> it;private final int characteristics;private long est;             // size estimateprivate int batch;            // batch size for splits
  • private final Collection<? extends T> collection; // null OK
    • 作用: 可选地持有对原始 Collection 的引用。如果 IteratorSpliterator 是通过一个 Collection 创建的,这个字段会被设置。这允许在需要时(例如,如果迭代器尚未初始化)从集合中重新获取迭代器和大小。如果直接通过 Iterator 创建,则此字段为 null
  • private Iterator<? extends T> it;
    • 作用: 存储底层的 Iterator,实际的元素遍历将通过这个迭代器进行。它可以被延迟初始化(如果 collection 字段存在)。
  • private final int characteristics;
    • 作用: 存储此 Spliterator 的特性。在构造时设置。
  • private long est;
    • 作用: 存储对 Spliterator 中剩余元素数量的估计值。
  • private int batch;
    • 作用: 用于 trySplit 的当前批处理大小,会动态调整。

构造函数

IteratorSpliterator 提供了多个构造函数以适应不同的创建场景:

  • public IteratorSpliterator(Collection<? extends T> collection, int characteristics):

            public IteratorSpliterator(Collection<? extends T> collection, int characteristics) {this.collection = collection;this.it = null; // 迭代器延迟初始化this.characteristics = (characteristics & Spliterator.CONCURRENT) == 0? characteristics | Spliterator.SIZED | Spliterator.SUBSIZED: characteristics;}
    
    • 用途: 从一个 Collection 创建 Spliterator
    • this.collection = collection;: 保存对集合的引用。
    • this.it = null;: 迭代器 it 初始为 null,它将在第一次需要时(如调用 tryAdvance 或 trySplit)通过 collection.iterator() 获取。
    • this.est 也会在迭代器第一次被获取时通过 collection.size() 初始化。
    • 特性处理:
      • 如果传入的 characteristics 不包含 Spliterator.CONCURRENT(意味着集合在迭代期间不会被并发修改),则会自动添加 Spliterator.SIZED 和 Spliterator.SUBSIZED 特性。这是因为可以安全地从集合获取大小,并且分割出的部分(通常是 ArraySpliterator)也将是 SIZED 和 SUBSIZED 的。
      • 如果包含 CONCURRENT,则直接使用传入的特性,因为集合大小可能在迭代期间变化,不能保证 SIZED
  • public IteratorSpliterator(Iterator<? extends T> iterator, long size, int characteristics):

            public IteratorSpliterator(Iterator<? extends T> iterator, long size, int characteristics) {this.collection = null;this.it = iterator;this.est = size;this.characteristics = (characteristics & Spliterator.CONCURRENT) == 0? characteristics | Spliterator.SIZED | Spliterator.SUBSIZED: characteristics;}
    
    • 用途: 从一个已有的 Iterator 和已知的元素数量 (size) 创建 Spliterator
    • this.collection = null;: 没有关联的集合。
    • this.it = iterator;: 直接使用传入的迭代器。
    • this.est = size;: 使用传入的大小作为初始估计。
    • 特性处理与上一个构造函数类似,基于 CONCURRENT 标志。
  • public IteratorSpliterator(Iterator<? extends T> iterator, int characteristics):

            public IteratorSpliterator(Iterator<? extends T> iterator, int characteristics) {this.collection = null;this.it = iterator;this.est = Long.MAX_VALUE; // 大小未知this.characteristics = characteristics & ~(Spliterator.SIZED | Spliterator.SUBSIZED);}
    
    • 用途: 从一个已有的 Iterator 创建 Spliterator,但源的大小未知。
    • this.collection = null;
    • this.it = iterator;
    • this.est = Long.MAX_VALUE;: 因为大小未知,所以估计大小设为最大长整型。
    • 特性处理: 从传入的 characteristics 中移除 SIZED 和 SUBSIZED 特性,因为大小是未知的。

方法实现

  • private void init() (非实际方法,但逻辑分散在各方法中): 许多方法(如 trySplitforEachRemainingtryAdvanceestimateSize)在开始时都有类似的逻辑来确保迭代器 it 和估计大小 est 被初始化(如果它们尚未初始化且 collection 存在):

    // 概念性逻辑
    if (it == null) {it = collection.iterator();est = (long) collection.size();
    }
    

  • public long estimateSize():

            @Overridepublic long estimateSize() {if (it == null) { // 如果迭代器未初始化,从集合获取大小it = collection.iterator();return est = (long)collection.size();}return est; // 返回当前估计大小}
    
    • 如果迭代器 it 尚未初始化(意味着是通过 collection 构造的),则初始化它并从 collection.size() 获取初始估计大小。
    • 返回当前的估计大小 est
  • public Comparator<? super T> getComparator():

            @Overridepublic Comparator<? super T> getComparator() {if (hasCharacteristics(Spliterator.SORTED))return null; // 如果是 SORTED 但没有具体的 Comparator,返回 nullthrow new IllegalStateException();}
    
    • 如果 Spliterator 具有 SORTED 特性,此方法应该返回用于排序的 Comparator,或者如果元素是自然排序的,则返回 null
    • IteratorSpliterator 的这个实现比较简单:如果它被标记为 SORTED,它就假设是自然排序或排序比较器未知,因此返回 null
    • 如果未标记为 SORTED,调用此方法会抛出 IllegalStateException,这符合 Spliterator.getComparator() 的规范。

Spliterator<T> trySplit()

        @Overridepublic Spliterator<T> trySplit() {Iterator<? extends T> i;long s;if ((i = it) == null) { // (1) 确保迭代器已初始化i = it = collection.iterator();s = est = (long) collection.size();}elses = est;if (s > 1 && i.hasNext()) { // (2) 检查是否可以分割int n = batch + BATCH_UNIT;if (n > s)n = (int) s;if (n > MAX_BATCH)n = MAX_BATCH;Object[] a = new Object[n]; // (3) 创建批处理数组int j = 0;do { a[j] = i.next(); } while (++j < n && i.hasNext()); // (4) 从迭代器填充数组batch = j; // (5) 更新批处理大小if (est != Long.MAX_VALUE) { // (6) 更新估计大小并返回 ArraySpliteratorest -= j;return new ArraySpliterator<>(a, 0, j, characteristics);}// (7) 如果原始大小未知,返回带有启发式估计大小的 ArraySpliteratorreturn new ArraySpliterator<>(a, 0, j, characteristics, Long.MAX_VALUE / 2);}return null; // (8) 无法分割}
  • 逻辑与 AbstractSpliterator.trySplit() 非常相似:
    1. (1) 确保迭代器 it 和估计大小 est 已初始化(如果它们是通过 collection 构造的且尚未初始化)。
    2. (2) 检查估计大小 s 是否大于1,并且迭代器 i 是否还有下一个元素 (i.hasNext())。
    3. (3) 计算批处理大小 n(基于 batchBATCH_UNITsMAX_BATCH),并创建批处理数组 a
    4. (4) 使用 do { a[j] = i.next(); } while (...) 循环从迭代器 i 中获取元素并填充到数组 a 中,直到数组满或迭代器耗尽。
    5. (5) 更新成员变量 batch 为实际填充的元素数量 j
    6. (6) 如果原始估计大小 est 不是 Long.MAX_VALUE,则从中减去 j,并创建一个新的 ArraySpliterator 来包装数组 a
    7. (7) 如果原始 est 是 Long.MAX_VALUE,则创建一个 ArraySpliterator,并为其提供一个启发式的估计大小 (Long.MAX_VALUE / 2)。
    8. (8) 如果不满足分割条件 (2),则返回 null
  • 关键区别AbstractSpliterator 依赖子类实现的 tryAdvance(Consumer) 来获取元素,而 IteratorSpliterator 直接使用其内部的 Iterator.next() 来获取元素。

boolean tryAdvance(Consumer<? super T> action):

        @Overridepublic boolean tryAdvance(Consumer<? super T> action) {if (action == null) throw new NullPointerException();if (it == null) { // 确保迭代器已初始化it = collection.iterator();est = (long) collection.size();}if (it.hasNext()) {action.accept(it.next());return true;}return false;}
  • 检查 action 是否为 null
  • 确保迭代器 it 已初始化。
  • 如果 it.hasNext() 为 true,则调用 it.next() 获取元素,并通过 action.accept() 传递给消费者,然后返回 true
  • 否则,返回 false

forEachRemaining(Consumer<? super T> action):

        @Overridepublic void forEachRemaining(Consumer<? super T> action) {if (action == null) throw new NullPointerException();Iterator<? extends T> i;if ((i = it) == null) { // 确保迭代器已初始化i = it = collection.iterator();est = (long)collection.size();}i.forEachRemaining(action); // 委托给 Iterator 的 forEachRemaining}
  • 检查 action 是否为 null
  • 确保迭代器 it 已初始化。
  • 直接调用底层迭代器 i 的 forEachRemaining(action) 方法。这通常比循环调用 tryAdvance 更高效。

为什么需要 IteratorSpliterator

  1. 桥接旧 API 与新 API:

    • Java 8 引入了 Stream API 和 Spliterator,它们提供了更强大和灵活的数据处理方式,特别是对于并行处理。然而,大量的现有代码和库仍然使用 Iterator
    • IteratorSpliterator 充当了一个桥梁,允许你轻松地将基于 Iterator 的数据源包装成 Spliterator,从而可以在 Stream API 中使用它们。
    • 例如,如果你有一个返回 Iterator 的旧方法 getLegacyDataIterator(),你可以这样做:

      java

      Iterator<String> legacyIterator = getLegacyDataIterator();
      Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(legacyIterator, 0); // 假设大小未知,无特定特性
      Stream<String> stream = StreamSupport.stream(spliterator, false);
      stream.forEach(System.out::println);
      
      或者,如果迭代器来自一个集合:
      Collection<String> myCollection = ... ;
      Spliterator<String> spliterator = Spliterators.spliterator(myCollection, 0); // 假设无特定特性,会自动添加 SIZED
      Stream<String> stream = StreamSupport.stream(spliterator, false);
      
      实际上,Collection.spliterator() 的默认实现通常就是返回一个 IteratorSpliterator(或类似的基于迭代器的 Spliterator)。
  2. 为 Iterator 提供有限的并行能力:

    • Iterator 本身是严格顺序的,不直接支持并行处理。
    • IteratorSpliterator 通过其 trySplit() 方法实现了一种“尽力而为”的并行化策略。它尝试从迭代器中批量读取元素到一个数组中,然后为这个数组创建一个 ArraySpliterator。这个 ArraySpliterator 是可以被并行处理的。
    • 虽然这种并行性不如那些原生支持高效分割的数据源(如数组或某些并发集合的 Spliterator),但它仍然可以在某些情况下提供性能改进,特别是当处理每个元素的操作比较耗时的时候。
  3. 统一数据源抽象:

    • Spliterator 是 Stream API 的核心数据源抽象。通过将 Iterator 转换为 Spliterator,所有数据源都可以通过统一的 Spliterator 接口进行操作,简化了 Stream API 内部的实现。
  4. 逐步迁移:

    • 当项目从旧的迭代方式迁移到 Stream API 时,IteratorSpliterator 使得可以逐步进行,而不需要立即重写所有数据访问逻辑。可以先将现有的 Iterator 包装起来,然后在后续重构中考虑是否可以提供更高效的、原生的 Spliterator 实现。

与 AbstractSpliterator 的关系:

  • AbstractSpliterator 是一个抽象基类,用于帮助用户创建自定义的 Spliterator,它要求子类实现 tryAdvance
  • IteratorSpliterator 是一个具体的 Spliterator 实现,它继承自 AbstractSpliterator。它直接实现了 Spliterator 接口,并使用内部持有的 Iterator 来实现 tryAdvance 和 trySplit 的元素获取逻辑。
  • 两者都实现了基于批处理的 trySplit 逻辑,但 AbstractSpliterator 是为了让用户提供元素,而 IteratorSpliterator 是为了包装一个已有的元素提供者 (Iterator)。

总而言之,IteratorSpliterator 是 Java 集合框架和 Stream API 之间一个非常重要的粘合剂,它使得基于 Iterator 的传统数据源能够无缝地集成到现代的、基于 Spliterator 和 Stream 的编程模型中,并提供了一定程度的并行处理能力。

trySplit的分割容量

IteratorSpliterator 和 AbstractSpliterator 的 trySplit 方法中批处理(batch)大小的选择是设计成尝试逐步增大的。

n = batch + BATCH_UNIT;

然后 batch = n

为什么采用这种递增的批处理大小策略?

这种算术级数增加批处理大小的策略是一种启发式方法,旨在平衡以下几个方面:

  • 并行开销与粒度
    • 如果每次分割的批次太小,会导致产生大量的并行任务,而创建和管理这些小任务的开销可能会超过并行带来的好处。
    • 如果批次太大,可能会导致任务粒度过粗,无法充分利用多核处理器的并行能力,或者在任务分配不均时导致某些核心空闲。
  • 适应性
    • 初始批次较小:对于元素总数较少的数据源,可以避免一次分割就取走大部分数据
    • 逐渐增大批次:对于元素总数较多的数据源,随着分割的进行,尝试获取更大的数据块,以期在后续的并行处理中每个任务能有足够的工作量,同时减少分割操作的总次数。
  • 减少分割次数
    • 通过逐步增大大块,可以期望以 O(sqrt(总元素数)) 级别的分割次数来处理整个数据源。这是一种在分割开销和并行任务数量之间寻求平衡的常见策略。
  • 对元素处理成本的普适性
    • 文档注释中提到,这种算术级数增长的分割大小“不会特别偏袒或惩罚轻量级与重量级元素操作的场景”。这意味着无论处理每个元素的成本高低,这种策略都能提供一个相对合理的开销与并行度的平衡。

总结一下:

batch 成员变量记录了上一次从当前 Spliterator 分割出去的元素数量。每次调用 trySplit 时,会尝试在上一次分割数量的基础上增加 BATCH_UNIT 来确定新的目标分割数量 n。然后,这个 n 会受到剩余元素总量和最大批次大小的限制。实际分割后,batch 成员变量会更新为这次实际分割的数量。这个机制导致了在对同一个 Spliterator 进行连续的 trySplit 操作时,尝试分割的批次大小会呈算术级数增长,直到达到上限或者元素耗尽。

http://www.dtcms.com/a/365229.html

相关文章:

  • 哪些AI生成PPT的软件或网站支持多平台使用?都支持哪些平台?
  • AI生成PPT工具排名:2025年高效办公新选择
  • 关于MySQL数据库连接超时问题及解决办法
  • AR智慧运维系统介绍
  • vue项目打包后dist部署到Nginx
  • 性能测试-jmeter9-直连数据库
  • 深度学习篇---模型组成部分
  • 财务文档处理优化:基于本地运行的PDF合并解决方案
  • 【51单片机】【protues仿真】基于51单片机压力测量仪系统
  • wpf触发器
  • Dify 从入门到精通(第 73/100 篇):Dify 的高级 RAG 优化(高级篇)
  • 调制端Phase Shift Discriminator(PSD)算法原理
  • 数据结构从青铜到王者第二十话---Map和Set(3)
  • windows安装PostgreSQL 和TimescaleDB
  • 数据结构:顺序栈与链栈的原理、实现及应用
  • 集成 Node.js 模块:文件系统与网络操作
  • 深入理解 Java 集合框架:底层原理与实战应用
  • 数据结构——二叉树+堆
  • .gitignore 文件为什么无效。
  • 开学季,老师如何用阅兵仪式激励学生?
  • PNP具身解读——RSS2025论文加州伯克利RLDG: 通过强化学习实现机器人通才策略提炼。
  • 在DDPM(扩散模型)中,反向过程为什么不能和前向一样一步解决,另外实际公式推导时反向过程每一步都能得到一个预测值,为什么还要一步一步的推导?
  • GEM5学习(4): 运行全系统模式的ARM系统
  • Docker 运行 PolarDB-for-PostgreSQL 的命令,并已包含数据持久化配置
  • 梅赛德斯-AMG PETRONAS F1车队携手SAP Cloud ERP:以数字化驱动精确与高效
  • HTML全屏功能实现汇总
  • 缠论笔线段画线,文华财经期货指标公式,好用的缠论指标源码
  • 从全栈开发到微服务架构:一位Java工程师的实战经验分享
  • 突破闭集限制:3D-MOOD 实现开集单目 3D 检测新 SOTA
  • Cesium 实战 - 自定义纹理材质 - 箭头流动线(图片纹理)