Talk about String, StringBuilder, StringBuffer

Posted by YodaOfCamarilla on Fri, 10 May 2019 16:43:16 +0200

[TOC]

Strings are a sequence of characters, and Java provides three classes, String, StringBuilder, and StringBuffer, to encapsulate strings

String

The String class is an immutable class. After a String object is created, the character sequence in the object is immutable until the object is destroyed.

Why is it immutable

jdk1.8
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    //Replacing char arrays with byte arrays in jdk1.9 offers the benefits of compact strings: smaller memory footprint and faster operation.
    //Constructor
     public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
    //Constructor
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    //Return a new char[]
    public char[] toCharArray() {
        // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }
 }

From the code above, let's see how String guarantees immutability.

  • String class is decorated with final and cannot be inherited
  • All members inside a string are set to private variables and are not accessible externally
  • No exposed interface to modify value
  • value is modified by final, so the reference to the variable is invariant.
  • Char[]. As a reference type, you can still modify the instance object by reference, using copyOf internally for this String(char value[]) constructor instead of copying value[] directly to the internal variable`.
  • Instead of returning a reference to the value directly when getting the value, a new char is returned as arraycopy()[]
  • Functions in the String class also reveal an immutable flavor everywhere, such as replace()
public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                //Create a new char[], without changing the values in the original object
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                //Finally, return the newly created String object
                return new String(buf, true);
            }
        }
        return this;
    }

Of course, immutable is not absolute, or you can get a variable value reference through reflection and change the value object instance by modifying the array with value[]

        String a = "Hello World!";
        String b = new String("Hello World!");
        String c = "Hello World!";

       //Modify the value array referenced by a string through reflection
        Field field = a.getClass().getDeclaredField("value");
        field.setAccessible(true);
        char[] value = (char[]) field.get(a);
        System.out.println(value);//Hello World!
        value[5] = '&';
        System.out.println(value);//Hello&World!

        // Verify that b, c are changed
        System.out.println(b);//Hello&World! 
        System.out.println(c);//Hello&World!

How do I draw immutable benefits from writing here?Forget reflection, let's talk about the immutable benefits

The advantage of immutability

Thread security guaranteed

The same string instance can be shared by multiple threads.

Basic information security is guaranteed

For example, the IP address of a network communication, the class loader reads a class based on its fully qualified name, and so on, and immutability provides security.

String Cache (Constant Pool) Requirements

Statistics show that about half of strings used by common applications are duplicated, reducing memory consumption and object creation overhead to avoid creating duplicate strings.The JVM provides string caching, a pool of string constants.If the string is mutable, we can change the value of the same total memory space in the constant pool by reference, and other references to that space will also change.

hash mapping and caching are supported.

Because strings are immutable, hashcode s are cached when they are created and do not need to be recalculated.This makes strings suitable for use as keys in Maps, which can be processed faster than other key objects.This is why keys in HashMap tend to use strings.

Invariant Disadvantage

Because of its immutability, universal operations such as string splicing and clipping often have a significant impact on Application performance.

To solve this problem, java provides two solutions

  • String Constant Pool
  • StringBuilder, StringBuffer are mutable

String Constant Pool

Or an example just reflected

        String a = "Hello World!";
        String b = new String("Hello World!");
        String c = "Hello World!";
        //Determines whether a string variable points to the same block of memory
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(b == c);

        // Observing the true position of the value array of variables in a, b, c by reflection
        Field a_field = a.getClass().getDeclaredField("value");
        a_field.setAccessible(true);
        System.out.println(a_field.get(a));

        Field b_field = b.getClass().getDeclaredField("value");
        b_field.setAccessible(true);
        System.out.println(b_field.get(b));

        Field c_field = c.getClass().getDeclaredField("value");
        c_field.setAccessible(true);
        System.out.println(c_field.get(c));
        //Reflection reveals that the variable value in a String object points to the same block of memory

output

false
true
false
[C@6f94fa3e
[C@6f94fa3e
[C@6f94fa3e

Creation of string constants:

  1. Determines if a "Hello World!" constant exists in the constant pool, if any, returns its reference address in the pool directly
  2. If not, first create a char["Hello World!".length()] array object, then create a string object in the constant pool and initialize the member variable value of the string object with the array object, then return a reference to the string, such as assigning a value to a

Thus, it is self-evident that the objects a and c point to the same memory space in the constant pool.

The creation of b object is based on the above process.
"Hello World!" The reference returned when the constant is created passes through the String constructor.

    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

Inside the constructor, the referenced object member variable value is assigned to the internal member variable value, and then the newly created character creation object reference is assigned to b, which happens in the heap.

Feel the difference between these two lines of code

  String b = new String(a);
  String b = new String("Hello World!");

StringBuilder and StringBuffer

Both are variable

To make up for String's shortcomings, Java has provided StringBuffer and StringBuilder variable string classes.

Both inherit to AbstractStringBuilder, which uses the char[] value character array

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
}

You can see that neither the AbstractStringBuilder class nor its member variable value uses the final keyword.

Default length of value array

StringBuilder and StringBuffer's value array default initial length is 16

    public StringBuilder() {
        super(16);
    }
    public StringBuffer() {
        super(16);
    }

If the length of the string we stitch is approximately predictable, it is best to specify the appropriate capacity to avoid the cost of multiple extensions.

Expansion incurs multiple overhead: discarding the old array, creating a new array, and arrycopy.

Differences between the two

StringBuilder is thread-safe, StringBuffer is thread-safe.

Methods in the StringBuffer class use synchronized synchronized lock to secure threads.
The topic about locks is very large and will be explained in separate articles. A good blog is recommended here. If you are interested, you can read it.

synchronized implementation of JVM source analysis

Topics: Java jvm network