这是常识,Java的String
s为不可变的。 不可改变的字符串是很好的补充到Java自成立以来。 不变性允许快速访问和大量的优化,显著不易出错与C风格的字符串,并有助于加强安全模型。
它可以创建不使用黑客手段可变之一,即
-
java.lang.reflect
-
sun.misc.Unsafe
- 在引导类加载器类
- JNI(或JNA因为它需要JNI)
但有可能在短短普通的Java,所以该字符串可以在任何时候进行修改? 现在的问题是如何 ?
这是常识,Java的String
s为不可变的。 不可改变的字符串是很好的补充到Java自成立以来。 不变性允许快速访问和大量的优化,显著不易出错与C风格的字符串,并有助于加强安全模型。
它可以创建不使用黑客手段可变之一,即
java.lang.reflect
sun.misc.Unsafe
但有可能在短短普通的Java,所以该字符串可以在任何时候进行修改? 现在的问题是如何 ?
创建java.lang.String
与字符集的构造,可以注入自己的字符集,这使你自己的CharsetDecoder
。 的CharsetDecoder
到达一个基准CharBuffer
在decodeLoop方法对象。 所述的CharBuffer包装了原始字符串对象的字符[]。 由于CharsetDecoder具有对它的引用,可以改变使用的CharBuffer底层炭[],因此你有一个可变的字符串。
public class MutableStringTest {
// http://stackoverflow.com/questions/11146255/how-to-create-mutable-java-lang-string#11146288
@Test
public void testMutableString() throws Exception {
final String s = createModifiableString();
System.out.println(s);
modify(s);
System.out.println(s);
}
private final AtomicReference<CharBuffer> cbRef = new AtomicReference<CharBuffer>();
private String createModifiableString() {
Charset charset = new Charset("foo", null) {
@Override
public boolean contains(Charset cs) {
return false;
}
@Override
public CharsetDecoder newDecoder() {
CharsetDecoder cd = new CharsetDecoder(this, 1.0f, 1.0f) {
@Override
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
cbRef.set(out);
while(in.remaining()>0) {
out.append((char)in.get());
}
return CoderResult.UNDERFLOW;
}
};
return cd;
}
@Override
public CharsetEncoder newEncoder() {
return null;
}
};
return new String("abc".getBytes(), charset);
}
private void modify(String s) {
CharBuffer charBuffer = cbRef.get();
charBuffer.position(0);
charBuffer.put("xyz");
}
}
运行代码打印
abc
zzz
我不知道如何正确地实施decodeLoop(),但我现在不关心:)
这个问题由@mhaller收到了很好的答案。 我想说的所谓的拼图是很容易和通过看字符串的可用C-职责范围应该能够找出如何部,
演练
感兴趣的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提供所谓快速的方式转换成byte[]
通过将字符集不chartset名称,以避免查找chartsetName->字符集为String。 它还允许通过一个任意字符集对象创建的字符串。 字符集主路由的内容转换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[])
但是有一个例外 - 如果没有安装安全管理器,炭[]是不可复制的。
//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); }
所以,如果没有安全管理器这是绝对可能有一个修改的CharBuffer /炭[]这是由一个String引用。
一切正常现在-除了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本身到来。
我要说的StringBuilder(或StringBuffer的多线程使用)。 是的,在结束时,你得到一个不可改变的字符串。 但是,这是要走的路。
例如追加字符串在一个循环的最佳方式是使用StringBuilder。 当您使用“福” +变量+“把” Java本身使用StringBuilder的。
http://docs.oracle.com/javase/6/docs/api/java/lang/StringBuilder.html
追加(泡壳).append(5).appen( “dfgdfg”)的toString();
// How to achieve String Mutability
import java.lang.reflect.Field;
public class MutableString {
public static void main(String[] args) {
String s = "Hello";
mutate(s);
System.out.println(s);
}
public static void mutate(String s) {
try {
String t = "Hello world";
Field val = String.class.getDeclaredField("value");
Field count = String.class.getDeclaredField("count");
val.setAccessible(true);
count.setAccessible(true);
count.setInt (s, t.length ());
val.set (s, val.get(t));
}
catch (Exception e) { e.printStackTrace(); }
}
}
不要重新发明轮子。 Apache的百科全书也是这样做的。
MutableObject<String> mutableString = new MutableObject<>();
Simplier方式交换的自举类路径java
和javac
1)进入jdk安装并复制到单独的文件夹rt.jar
和src.zip
2)从源解包String.java拉链和内字符数组的私有字段值更改为公众
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
public final char value[];
3)编译修改String.java用javac的帮助:
javac String.java
4)移动编译String.class等编译的类到这个目录中的rt.jar
5)创建使用字符串私有字段测试类
package exp;
class MutableStringExp {
public static void main(String[] args) {
String letter = "A";
System.out.println(letter);
letter.value[0] = 'X';
System.out.println(letter);
}
}
6)创建空的目录target
并编译测试类
javac -Xbootclasspath:rt.jar -d target MutableStringExp.java
7)运行它
java -Xbootclasspath:rt.jar -cp "target" exp.MutableStringExp
输出是:
A
X
PS这只会与修改工作rt.jar
,并使用此选项覆盖rt.jar
是违反了jre
许可证。