Поток символа в поток байтового / байтового массива - PullRequest
0 голосов
/ 12 октября 2018

Следующий код принимает 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")); 

Iпытаюсь изменить приведенный выше код для потокового подхода.Следующее работает.

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[] и пропустить преобразование в Stringмежду?

Ответы [ 2 ]

0 голосов
/ 12 октября 2018

Во-первых, оба ваших варианта имеют проблему с неправильной обработкой символов вне BMP .

Для поддержки этих символов существует codePoints() в качестве альтернативы chars(),Вы можете использовать appendCodePoint для цели StringBuilder, чтобы последовательно использовать кодовые точки на протяжении всей операции.Для этого вы должны удалить ненужный шаг .mapToObj(ch -> (char) ch), удаление которого также устраняет накладные расходы на создание Stream<Character>.

Затем вы можете избежать преобразования в String в обоих случаях,кодирование StringBuilder с использованием 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 нет такой поддержки, нет прямого способа собрать поток в массив 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 в Википедии ) Шаг 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() копированиеработа.

0 голосов
/ 12 октября 2018

Не будет ли это проще с:

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