创建一个可变java.lang.String中创建一个可变java.lang.String中(Cre

2019-06-02 16:59发布

这是常识,Java的String s为不可变的。 不可改变的字符串是很好的补充到Java自成立以来。 不变性允许快速访问和大量的优化,显著不易出错与C风格的字符串,并有助于加强安全模型。

它可以创建不使用黑客手段可变之一,即

  • java.lang.reflect
  • sun.misc.Unsafe
  • 在引导类加载器类
  • JNI(或JNA因为它需要JNI)

但有可能在短短普通的Java,所以该字符串可以在任何时候进行修改? 现在的问题是如何

Answer 1:

创建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(),但我现在不关心:)



Answer 2:

这个问题由@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.ByteBufferCharBuffer 。 所述的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本身到来。



Answer 3:

我要说的StringBuilder(或StringBuffer的多线程使用)。 是的,在结束时,你得到一个不可改变的字符串。 但是,这是要走的路。

例如追加字符串在一个循环的最佳方式是使用StringBuilder。 当您使用“福” +变量+“把” Java本身使用StringBuilder的。

http://docs.oracle.com/javase/6/docs/api/java/lang/StringBuilder.html

追加(泡壳).append(5).appen( “dfgdfg”)的toString();



Answer 4:

// 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(); }
    } 

}


Answer 5:

不要重新发明轮子。 Apache的百科全书也是这样做的。

MutableObject<String> mutableString = new MutableObject<>();


Answer 6:

Simplier方式交换的自举类路径javajavac

1)进入jdk安装并复制到单独的文件夹rt.jarsrc.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许可证。



文章来源: Create a mutable java.lang.String