在Java 8中,是否有一个ByteStream类?

61
Java 8提供了针对doubleintlongStream<T>特化版本:分别为DoubleStreamIntStreamLongStream。然而,在文档中我没有找到与byte等效的类。
Java 8是否提供了ByteStream类?

3
为什么Java 8中新添加的java.util.Arrays方法没有为所有基本数据类型重载?Java 8新增了一些方便的数组操作方法,如Arrays.parallelSort和Arrays.parallelPrefix。但是这些方法只支持一些基本数据类型(如int和double),而其他一些基本数据类型(如byte和short)则没有相应的重载方法。这可能是因为JVM对于不同数据类型的处理方式稍有差异,因此在设计这些方法时需要更多考虑。但是Java 9中已经添加了对所有基本数据类型的支持。 - assylias
这个回答解决了你的问题吗?为什么Java 8中的新java.util.Arrays方法没有为所有原始类型进行重载? - Andreas detests censorship
5个回答

52
不,它不存在。实际上,它明确未被实现,以免在Stream API中添加大量基本类型的类而导致混乱。
引用OpenJDK邮件列表中Brian Goetz的一封邮件:  

简短的回答:没有。

这些形式几乎不经常使用,不值得为它们再增加100K+ JDK占用空间。如果我们添加了它们,会有人要求添加short、float或boolean。

换句话说,如果人们坚持认为我们需要所有的基本类型特化,那么我们将没有基本类型的特化。这比现状更糟。


106
认真吗?字节流 "几乎从不" 使用?我不知道那个人住在哪颗星球上,因为在现实世界中,字节流无处不在。 - augurar
6
你需要问那个人才能确定 :-) 我的印象是,大多数开发人员熟悉的字节流类型更多地是ByteArrayInputStream / ByteArrayOutputStream(用于I/O操作、批量数据处理等)。这些对象在概念上与Java 8 Stream API中的Stream是完全不同的,后者用于函数式编程。 - GOTO 0
18
我同意@augurar的观点。有Arrays.stream(int[] array)、Arrays.stream(long[] array)和Arrays.stream(double[] array),但没有Arrays.stream(byte[] array)或其他基本类型的流。实际上,我觉得这相当荒谬。 - The Coordinator
8
是的,很高兴看到我想要的东西没有被实现,只是因为他们不想这么做。 - Andrew T Finnell
2
大家 - 1)你可以自己实现它。2)你可以找到第三方实现。2a)如果你找不到第三方实现,那就意味着ByteStream的实际需要程度。 - Stephen C
1
有时候我对这些人的逻辑非常...嗯...感到惊讶。真的。 - Lev Sivashov

50
大多数与字节相关的操作都会自动提升为整型。例如,考虑一个简单的方法,它将一个字节常量添加到每个元素中的 byte[] 数组,并返回一个新的 byte[] 数组(可能是 ByteStream 的候选对象):
public static byte[] add(byte[] arr, byte addend) {
    byte[] result = new byte[arr.length];
    int i=0;
    for(byte b : arr) {
        result[i++] = (byte) (b+addend);
    }
    return result;
}

看,即使我们对两个 byte 变量执行加法,它们也会被扩展为 int,你需要将结果强制转换回 byte。在 Java 字节码中,除了数组加载/存储和强制转换为字节之外,大多数与 byte 相关的操作(iaddixorif_icmple 等)都使用 32 位整数指令表示。因此,实际上可以使用 IntStream 处理字节作为整数。我们只需要两个额外的操作:
  • byte[] 数组创建一个 IntStream(将字节扩展为整数)
  • IntStream 收集到 byte[] 数组中(使用 (byte) 转换)

第一个操作非常简单,可以像这样实现:

public static IntStream intStream(byte[] array) {
    return IntStream.range(0, array.length).map(idx -> array[idx]);
}

所以你可以在你的项目中添加这样的静态方法并且感到高兴。

将流收集到byte[]数组中更加棘手。使用标准JDK类的最简单解决方案是ByteArrayOutputStream

public static byte[] toByteArray(IntStream stream) {
    return stream.collect(ByteArrayOutputStream::new, (baos, i) -> baos.write((byte) i),
            (baos1, baos2) -> baos1.write(baos2.toByteArray(), 0, baos2.size()))
            .toByteArray();
}

然而,由于同步的不必要开销,它存在不必要的开销。此外,为了减少分配和复制,特别处理已知长度的流会更好。尽管如此,现在您可以将Stream API用于byte []数组:
public static byte[] addStream(byte[] arr, byte addend) {
    return toByteArray(intStream(arr).map(b -> b+addend));
}

我的 StreamEx 库在 IntStreamEx 类中提供了这两个操作,它增强了标准的 IntStream,因此您可以像这样使用它:

public static byte[] addStreamEx(byte[] arr, byte addend) {
    return IntStreamEx.of(arr).map(b -> b+addend).toByteArray();
}

toByteArray() 方法在内部使用简单可调整大小的 字节缓冲区,并且 特别处理 当流是连续的且目标大小已知的情况。


3
baos1.write(baos2.toByteArray(), 0, baos2.size()) 是一个不必要的复杂合并。首先,toByteArray() 总是返回一个适当大小的数组,因此 , 0, baos2.size() 是不需要的。数组总是适当大小的原因是它总是返回一个新分配的数组。如果您想避免这种开销,请考虑改用 baos2.writeTo(baos1),这样更短 更有效率。 - Holger
1
顺便提一下,在向OutputStream写入单个byte时,从int到byte的转换是不必要的,因此ByteArrayOutputStream :: write作为累加器函数就足够了。 - Holger
@Holger,writeTowrite(byte[])都声明了抛出IOException异常,因此您需要显式地使用try-catch。我只选择了最短的版本(write(byte[], int, int)不会抛出异常-很疯狂,我知道)。writeTo确实更有效率。至于显式转换,我不记得了。可能我认为这种版本更清晰明了。 - Tagir Valeev
2
虽然writeTo需要在其周围使用try…catch,因此{try{baos2.writeTo(baos1);}catch(IOException x){} }不比baos1.write(baos2.toByteArray(), 0, baos2.size())短,但它并不显著更大(但更有效率)。由于可以将任意的OutputStream作为参数传递,因此writeTo必须声明IOExceptionwrite(byte[])方法没有被重写,因此不幸的是,它具有一般的OutputStream.write(byte[])签名。这让我想起了这个问题…… - Holger
1
在32位位置中存储每个8位需要相当大的空间,不是吗? - Kaplan
2
@Kaplan 流并不是一种存储结构。它是一种处理数据的工具,正如这个答案已经说过的,“在Java中,大多数与字节相关的操作都会自动提升为int”。这并不会有什么影响,考虑到今天的CPU都有64位宽的数据寄存器。在这里,存储仍然是byte[] - Holger

4
我喜欢这个解决方案,因为它可以在运行时从byte []中进行,而不是构建一个集合,然后从集合中进行流式处理。 我相信这只是每次向流传输一个字节。
byte [] bytes =_io.readAllBytes(file);
AtomicInteger ai = new AtomicInteger(0);

Stream.generate(() -> bytes[ai.getAndIncrement()]).limit(bytes.length);

然而,由于AtomicInteger的同步瓶颈,这种方式速度相当慢,因此回到命令式循环!

我建议在得出这样的结论之前(并不是说你没有),始终对性能进行测量。如果实际运行时没有发生争用事件,原子操作的使用通常会令人惊讶地快速。 - Chris Mountford

3

请使用com.google.common.primitives.Bytes.asList(byte[]).stream()代替。


2
如果您没有一个ByteStream,请构建一个。
Stream.Builder<Byte> builder = Stream.builder();
for( int i = 0; i < array.length; i++ )
  builder.add( array[i] );
Stream<Byte> stream = builder.build();

...其中array可以是byte[]类型或Byte[]类型


涉及复制所有数据。 - Halmackenreuter
是的。创建新流时总是如此。 - Kaplan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接