美文网首页
StringBuffer、StringBuilder以及Stri

StringBuffer、StringBuilder以及Stri

作者: pizpiz | 来源:发表于2020-05-12 20:34 被阅读0次

    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

    相关文章

      网友评论

          本文标题:StringBuffer、StringBuilder以及Stri

          本文链接:https://www.haomeiwen.com/subject/awmbnhtx.html