Во-первых, оба ваших варианта имеют проблему с неправильной обработкой символов вне 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()
копированиеработа.