@mhaller 很好地回答了这个问题。我会说所谓的谜题非常简单,只需查看 String one 的可用 c-tors 就应该能够找出 how 部分,一个
演练
感兴趣的 C-tor 在下面,如果您要闯入/破解/寻找安全漏洞,请始终寻找非最终任意类。这里的案例是java.nio.charset.Charset
//String
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
char[] v = StringCoding.decode(charset, bytes, offset, length);
this.offset = 0;
this.count = v.length;
this.value = v;
}
c-tor 通过传递 Charset 而不是图表集名称来避免查找 chartsetName->charset,提供了据称快速的方法来将
byte[] 转换为 String。
它还允许传递任意 Charset 对象来创建字符串。 Charset 主路由将
java.nio.ByteBuffer 的内容转换为
CharBuffer。 CharBuffer 可以包含对 char[] 的引用,并且可以通过
array() 获得,而且 CharBuffer 也是完全可修改的。
//StringCoding
static char[] decode(Charset cs, byte[] ba, int off, int len) {
StringDecoder sd = new StringDecoder(cs, cs.name());
byte[] b = Arrays.copyOf(ba, ba.length);
return sd.decode(b, off, len);
}
//StringDecoder
char[] decode(byte[] ba, int off, int len) {
int en = scale(len, cd.maxCharsPerByte());
char[] ca = new char[en];
if (len == 0)
return ca;
cd.reset();
ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
CharBuffer cb = CharBuffer.wrap(ca);
try {
CoderResult cr = cd.decode(bb, cb, true);
if (!cr.isUnderflow())
cr.throwException();
cr = cd.flush(cb);
if (!cr.isUnderflow())
cr.throwException();
} catch (CharacterCodingException x) {
// Substitution is always enabled,
// so this shouldn't happen
throw new Error(x);
}
return safeTrim(ca, cb.position(), cs);
}
为了防止更改char[],Java 开发人员会像任何其他字符串构造一样复制数组(例如public String(char value[]))。但是有一个例外 - 如果没有安装 SecurityManager,则不会复制 char[]。
//Trim the given char array to the given length
//
private static char[] safeTrim(char[] ca, int len, Charset cs) {
if (len == ca.length
&& (System.getSecurityManager() == null
|| cs.getClass().getClassLoader0() == null))
return ca;
else
return Arrays.copyOf(ca, len);
}
因此,如果没有 SecurityManager,那么绝对有可能拥有一个被字符串引用的可修改 CharBuffer/char[]。
现在一切看起来都很好 - 除了byte[] 也被复制(上面的粗体字)。这是
Java 开发人员在哪里变得懒惰并大错特错。
副本对于防止流氓字符集(上面的示例)能够更改源字节[]是必要的。但是,想象一下大约 512KB byte[] 缓冲区包含少量字符串的情况。试图创建一个小而少的图表 - new String(buf, position, position+32,charset) 导致大量 512KB 字节 [] 副本。如果缓冲区为 1KB 左右,则永远不会真正注意到影响。但是,对于大缓冲区,性能影响确实很大。简单的解决方法是复制相关部分。
...或者java.nio 的设计者考虑过引入只读缓冲区。只需调用 ByteBuffer.asReadOnlyBuffer() 就足够了(如果 Charset.getClassLoader()!=null)*
有时,即使是在 java.lang 工作的人也会完全搞错。
*Class.getClassLoader() 为引导类返回 null,即 JVM 本身附带的类。