StringBuffer、StringBuilder以及String的一点发现

String和StringBuffer还有StringBuilder之前的区别和特性这里不详细展开,可以自行google。本文主要是讲一些在看源码时的一些发现。

线程安全原理

源码分析一下为什么StringBuffer是线程安全的,而StringBuilder不是。原以为是有缓存或者可重入锁机制,后来发现是在方法层面用了synchronized修饰符。例如经常用的下列方法:

public synchronized StringBuffer append(StringBuffer sb) {
        toStringCache = null;
        super.append(sb);
        return this;
    }

性能优化

但是StringBuffer在toString()方法中有个优化点,就是StringBuffer中有个toStringCache的缓存数组,它使得当调用过一次toString()后,如果没有修改过StringBuffer中即将要输出的字符值,再次toString()时不需要重新从属性value中根据当前字符数据长度将数据拷贝出来,直接用toStringCache生成String对象。

   public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

所以多次调用toString()会比StringBuilder具有更好的性能。因为下列方法只调用一次。

 public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

意外发现

在看String类的源码时,发现一个和序列化相关的成员变量:

/**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

通过注释可以知道,对于String类型的序列化在流处理中是个特例。那为什么要创建一个长度为零的数据呢?
首先看一下其他类的serialPersistentFields属性是如何赋值的,下图是ConcurrentHashMap中的serialPersistentFields属性:

/** For serialization compatibility. */
    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("segments", Segment[].class),
        new ObjectStreamField("segmentMask", Integer.TYPE),
        new ObjectStreamField("segmentShift", Integer.TYPE)
    };

serialPersistentFields是什么

简单说就是一个类想申明它在序列化时一定要序列化哪些成员变量的列表。下面我们拿ObjectOutputStream举例,在调用其writeObject(Object obj)方法时,首先会进入一个判断,判断是否使用覆写逻辑,如果子类有自己特殊的序列化逻辑的话,可以将成员变量enableOverride置为true,然后便可覆写protected void writeObjectOverride(Object obj)方法实现特殊逻辑。我们这里主要看writeObject0(obj, false)

public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }

中间省略了一些逻辑,直接看ObjectStreamClass这个类,下面这个方法是获取需要序列化属性的逻辑,getDeclaredSerialFields()是获取序列化对象声明的一定要序列化出来的属性列表的方法,如果没有特殊声明则调用getDefaultSerialFields()获取默认的序列化属性。

private static ObjectStreamField[] getSerialFields(Class<?> cl)
        throws InvalidClassException
    {
        ObjectStreamField[] fields;
        if (Serializable.class.isAssignableFrom(cl) &&
            !Externalizable.class.isAssignableFrom(cl) &&
            !Proxy.isProxyClass(cl) &&
            !cl.isInterface())
        {
            if ((fields = getDeclaredSerialFields(cl)) == null) {
                fields = getDefaultSerialFields(cl);
            }
            Arrays.sort(fields);
        } else {
            fields = NO_FIELDS;
        }
        return fields;
    }

最终某个类需要被序列化的成员变量的列表被放在某个ObjectStreamClass实例的成员变量fields中:

 /** serializable fields */
    private ObjectStreamField[] fields;

getDeclaredSerialFields()详情如下,其中"serialPersistentFields"便是我们之前在String类中看到的成员变量:

private static ObjectStreamField[] getDeclaredSerialFields(Class<?> cl)
        throws InvalidClassException
    {
        ObjectStreamField[] serialPersistentFields = null;
        try {
            Field f = cl.getDeclaredField("serialPersistentFields");
            int mask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
            if ((f.getModifiers() & mask) == mask) {
                f.setAccessible(true);
                serialPersistentFields = (ObjectStreamField[]) f.get(null);
            }
        } catch (Exception ex) {
        }
        if (serialPersistentFields == null) {
            return null;
        } else if (serialPersistentFields.length == 0) {
            return NO_FIELDS;
        }
    ...
    ...
    }

可以看到,如果是String类,他会返回一个NO_FIELDS变量,其实质也是new ObjectStreamField[0]。因为不是null所以返回之后上面介绍的getSerialFields(Class<?> cl)方法也不会走getDefaultSerialFields()逻辑,那么问题来了,String当中的字符串数据是如何序列化出来的呢?我们接着分析writeObject0(obj, false)方法逻辑:

  /**
     * Underlying writeObject/writeUnshared implementation.
     */
    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
      ...
      ...
      ...
           desc = ObjectStreamClass.lookup(cl, true);
      ...
      ...
      ...
            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else 
      ...
      ...
      ...

从上图可以看到,String类型的逻辑是独立的,并且不需要ObjectStreamClass帮助,所以可以解释为什么String的成员变量serialPersistentFields可以赋值一个空的数组。

试验数据

测试方法

public static void main(String[] args) throws IOException {
        String aa = "123s";
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xxx"))) {
            oos.writeObject(aa);
        } catch (Exception e) {

        }
        System.out.println(aa);
    }

断点

ObjectOutputStream.java:1134
ObjectOutputStream.java:1172
ObjectStreamClass.java:1730

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343