以下代码采用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[]
并跳过两者之间的转换吗?
答案 0 :(得分:4)
首先,两个变体都存在无法正确处理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中不仅没有这种支持,而且没有简单的方法将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"));