创build一个可变的java.lang.String
Java String
是不可改变的常识。 不可变的string是Java自成立以来的重要补充。 不变性允许快速访问和大量的优化,与C风格的string相比,显着减less错误,并有助于实施安全模型。
有可能创build一个可变的不使用黑客,即
-
java.lang.refect
-
sun.misc.Unsafe
- 引导类加载器中的类
- JNI(或JNA,因为它需要JNI)
但是,在普通的Java中是否可以这样做,以便随时可以修改string? 问题是如何 ?
使用Charset构造函数创build一个java.lang.String
,可以注入自己的Charset,它带有您自己的CharsetDecoder
。 CharsetDecoder
获取对decodeLoop方法中的CharBuffer
对象的引用。 CharBuffer包装原始String对象的char []。 由于CharsetDecoder有一个引用,所以你可以使用CharBuffer来改变底层的char [],因此你有一个可变的String。
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得到了很好的回答。 我想说所谓的谜题是相当容易的,只要看看可用的string的一个人应该能够找出如何部分,一个
演练
感兴趣的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; }
通过传递字符集而不是字符集名称来避免查找chartsetName-> charset,c-tor提供了将byte[]
转换为string的方法。 它也允许传递一个任意的Charset对象来创buildString。 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开发人员复制数组很像任何其他string构造(例如public String(char value[])
)。 但是,有一个例外 – 如果没有安装SecurityManager,则不会复制char []。
//修剪给定的字符数组到给定的长度 // private static char [] safeTrim(char [] ca,int len,Charset cs){ 如果(len == ca.length &&(System.getSecurityManager()== null || cs.getClass()。getClassLoader0()== null)) 返回ca; 其他 返回Arrays.copyOf(ca,len); }
所以如果没有SecurityManager,那么可以通过String来引用可修改的CharBuffer / char []。
一切看起来很好 – 除了byte[]
也被复制(上面的粗体)。 这是Java开发者懒惰和大量错误的地方。
副本是必要的,以防止stream氓字符集(上面的例子)能够改变源字节[]。 但是,想象一下包含less数String的大约512KB byte[]
缓冲区的情况。 试图创build一个小的,less数图表 – new String(buf, position, position+32,charset)
导致大量的512KB字节[]复制。 如果缓冲区大小为1KB左右,影响将永远不会被真正注意到。 然而,使用大缓冲区的情况下,性能的影响是非常巨大的。 简单的修复就是复制相关的部分。
…或者java.nio
的devise者想通过引入只读缓冲区来思考。 简单地调用ByteBuffer.asReadOnlyBuffer()
就足够了(如果Charset.getClassLoader()!= null)*有时候,即使是使用java.lang
也可能完全错误。
* Class.getClassLoader()为引导类返回null,即与JVM本身一起提供的类。
我会说StringBuilder(或multithreading使用StringBuffer)。 是的,最后你得到一个不可变的string。 但是,这是要走的路。
例如,在循环中追加string的最好方法是使用StringBuilder。 当你使用“fu”+variables+“ba”时,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(); } } }
简单的方法来交换java
和javac
引导类path
1)转到jdk安装并复制到单独的文件夹rt.jar
和src.zip
2)从源zip中解压String.java并将其内部char数组的私有字段值更改为public
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ public final char value[];
3)在javac的帮助下编译修改过的String.java:
javac String.java
4)将编译的String.class和其他编译的类移动到该目录中的rt.jar
5)创build使用String专用字段的testing类
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)创build空dir target
并编译testing类
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
许可证。