字符流到字节/字节数组流

时间:2018-10-12 11:21:35

标签: arrays java-8 char byte java-stream

以下代码采用String s,转换为char数组,从中过滤数字,然后将其转换为string,然后转换为byte数组。

char charArray[] = s.toCharArray();
StringBuffer sb = new StringBuffer(charArray.length);
for(int i=0; i<=charArray.length-1; i++) {
    if (Character.isDigit(charArray[i]))
        sb.append(charArray[i]);
}
byte[] bytes = sb.toString().getBytes(Charset.forName("UTF-8")); 

我正在尝试将上述代码更改为流方法。以下工作正常。

s.chars()
.sequential()
.mapToObj(ch -> (char) ch)
.filter(Character::isDigit)
.collect(StringBuilder::new,
        StringBuilder::append, StringBuilder::append)
.toString()
.getBytes(Charset.forName("UTF-8"));

我认为可能会有更好的方法。

我们可以直接将Stream<Character>转换为byte[]并跳过两者之间的转换吗?

2 个答案:

答案 0 :(得分:4)

首先,两个变体都存在无法正确处理BMP之外的字符的问题。

要支持这些字符,可以使用codePoints()来代替chars()。您可以在目标appendCodePoint上使用StringBuilder,以在整个操作过程中始终使用代码点。为此,您必须删除不必要的.mapToObj(ch -> (char) ch)步骤,该步骤的删除还消除了创建Stream<Character>的开销。

然后,通过直接使用StringStringBuilder进行编码,可以避免在两种情况下都转换为Charset。对于流变体:

StringBuilder sb = s.codePoints()
    .filter(Character::isDigit)
    .collect(StringBuilder::new,
             StringBuilder::appendCodePoint, StringBuilder::append);

ByteBuffer bb = StandardCharsets.UTF_8.encode(CharBuffer.wrap(sb));
byte[] utf8Bytes = new byte[bb.remaining()];
bb.get(utf8Bytes);

直接用代码点流执行转换并不容易。 Charset API中不仅没有这种支持,而且没有简单的方法将Stream收集到byte[]数组中。

一种可能性是

byte[] utf8Bytes = s.codePoints()
    .filter(Character::isDigit)
    .flatMap(c -> c<128? IntStream.of(c):
        c<0x800? IntStream.of((c>>>6)|0xC0, c&0x3f|0x80):
        c<0x10000? IntStream.of((c>>>12)|0xE0, (c>>>6)&0x3f|0x80, c&0x3f|0x80):
        IntStream.of((c>>>18)|0xF0, (c>>>12)&0x3f|0x80, (c>>>6)&0x3f|0x80, c&0x3f|0x80))
    .collect(
        () -> new Object() { byte[] array = new byte[8]; int size;
            byte[] result(){ return array.length==size? array: Arrays.copyOf(array,size); }
        },
        (b,i) -> {
            if(b.array.length == b.size) b.array=Arrays.copyOf(b.array, b.size*2);
            b.array[b.size++] = (byte)i;
        },
        (a,b) -> {
            if(a.array.length<a.size+b.size) a.array=Arrays.copyOf(a.array,a.size+b.size);
            System.arraycopy(b.array, 0, a.array, a.size, b.size);
            a.size+=b.size;
        }).result();

flatMap步骤将代码点流转换为UTF-8单元流。 (与UTF-8 description on Wikipedia比较)collect步骤将int的值收集到byte[]数组中。

可以通过创建一个专用收集器来消除flatMap步骤,该收集器将代码点流直接收集到byte[]数组中

byte[] utf8Bytes = s.codePoints()
    .filter(Character::isDigit)
    .collect(
        () -> new Object() { byte[] array = new byte[8]; int size;
            byte[] result(){ return array.length==size? array: Arrays.copyOf(array,size); }
            void put(int c) {
                if(array.length == size) array=Arrays.copyOf(array, size*2);
                array[size++] = (byte)c;
            }
        },
        (b,c) -> {
            if(c < 128) b.put(c);
            else {
                if(c<0x800) b.put((c>>>6)|0xC0);
                else {
                    if(c<0x10000) b.put((c>>>12)|0xE0);
                    else {
                        b.put((c>>>18)|0xF0);
                        b.put((c>>>12)&0x3f|0x80);
                    }
                    b.put((c>>>6)&0x3f|0x80);
                }
                b.put(c&0x3f|0x80);
            }
       },
       (a,b) -> {
            if(a.array.length<a.size+b.size) a.array=Arrays.copyOf(a.array,a.size+b.size);
            System.arraycopy(b.array, 0, a.array, a.size, b.size);
            a.size+=b.size;
       }).result();

但不会增加可读性。

您可以使用String之类的方法来测试解决方案

String s = "some test text 1234 ✔ 3 ";

并将结果打印为

System.out.println(Arrays.toString(utf8Bytes));
System.out.println(new String(utf8Bytes, StandardCharsets.UTF_8));

应该产生

[49, 50, 51, 52, -17, -68, -109, -16, -99, -97, -99]
12343

很明显,第一个变量是最简单的,即使它没有直接创建byte[]数组,它也具有合理的性能。此外,它是唯一可以适应获取其他结果字符集的变体。

但即使是

byte[] utf8Bytes = s.codePoints()
    .filter(Character::isDigit)
    .collect(StringBuilder::new,
             StringBuilder::appendCodePoint, StringBuilder::append)
    .toString().getBytes(StandardCharsets.UTF_8);
不管toString()操作是否带有复制操作,

都还不错。

答案 1 :(得分:0)

这不是很简单吗?

byte [] bytes = s.replaceAll("[^\\d]", "").getBytes(Charset.forName("UTF-8"));