Java Learning 7: Application learning and source code analysis of String, StringBuffer, StringBuilder

1. Learning and application of String class:

1.1. Objects of the String class are final

First of all, we must know that in Java, most strings are instantiated and manipulated using the String class. In Java, a string is an object, and a string variable is a reference to execute this object. But this reference is a constant reference (C++ concept), which means: the content of the referenced object cannot be changed by changing the constant reference. In Java, in order to prevent you from accidentally changing the constant reference and causing the content of the String object to be changed, you are not allowed to use subscripts and square brackets to access the content of the String object. For example, the following program provides a method to access through subscripts:

public static void main(String[] args){
    
    
		String str = new String("Hello World");
		System.out.println(str);
		
		for(int i = 0;i < str.length();++i)
			System.out.print(str.charAt(i) + " ");
			// 不支持写法:System.out.print(str[i] + " ");
	}

Think about it, everyone, since the built-in method can access it, can it be modified?
It cannot be modified directly! Everyone should pay attention here:

principle:

In Java, we can change the String object through some built-in methods, but these changes do not really modify the content pointed to by the object, but create a new object, copy the content of the old object, and then add it Change, let the reference go to point again! Then objects that are not pointed to by any reference will be automatically recycled by the garbage collection mechanism. This is safe, but it is also a waste of resources! After all, garbage collection takes time.

Learn some useful built-in methods:

Construction method:

Insert picture description here

Here are the last three to focus on, look at the program examples:
	public static void main(String[] args){
    
    
		char[] str1 = {
    
    'H', 'e', 'l', 'l', 'o'};
		System.out.println(str1);
		System.out.println(str1.length);
		
		String str2 = new String("Hello");
		String str3 = new String(str1);
		String str4 = new String(str3);
		
		System.out.println("长度的变化是   一开始是:" + str1.length + "后来是:" + str3.length());
		
		System.out.println(str2);
		System.out.println(str3);
		System.out.println(str4);
	}		

The output of the program is:
Insert picture description here

What does this show? ? ?

Note 1: Do not add'\0' from the character array to the end of the string
Note 2: String can be instantiated from a character array or String can be instantiated
Other commonly used methods:

Insert picture description here
Insert picture description here

The most important (personally think) are:
length(): returns the length of the string (without'\0')
charAt(index): returns the (index + 1) character, which is equivalent to subscript access (but read-only)
substring (s, t): returns the first address of the substring in the interval [s, t) (equivalent to a reference)
compareTo(s1, s2): returns the difference (s1-s2), the difference is:
if the first If the character is equal to the first character of the parameter, the second character is compared with the second character of the parameter, and so on, until one of the compared characters or the compared characters is compared, then it is the length Difference This is an effective way to compare the size of strings!

After all comparisons are complete, then compare the length of the characters.
replace(OldChar, NewChar): Replace all OldChar in the string with NewChar
valueOf(Obj): Replace the object instance Obj entirely with a string (the original change)

Let's experiment with examples:
	public static void main(String[] args){
    
    
		
		// 实验方法:charAt(index)、length():
		String str1 = new String("abcde");
		for(int i = 0;i < str1.length();++i)
			System.out.print(str1.charAt(i) + " ");
		System.out.println();
		
		// 实验方法:substring(s, t):
		String str2 = new String(str1.substring(0, 3));
		System.out.print("str1 = " + str1 + "  str2 = " + str2 + ", str1 - str2 = " + str1.compareTo(str2));
	
		// 实验方法:A.compareTo(B):
		System.out.println();
		String str3 = new String("abcd");
		String str4 = new String("abec");
		System.out.println("str4 - str3 = " + str4.compareTo(str3));
		
		String str5 = new String("abecg");
		System.out.println("str5 - str4 = " + str5.compareTo(str4));
		
		// 实验方法:A.replace(Old, New)、valueOf(Obj);
		String str6 = "aabbcdeea";
		str6 = str6.replace('a', 'x');
		System.out.println("str6 = " + str6);
		
		double d = 3.1415926;
		String str7 = new String();
		str7 = String.valueOf(d);// valueOf()是静态方法
		System.out.println("str7 = " + str7);
			
	}	

Look at the experimental results:
Insert picture description here

Let's see what we learned?

First: valueOf(Obj) is a static method! The static method is at the class level, not the object level, so we need to use the method to call the static method: class name. method name to call the
second: for the kind of method that returns a reference to a new object, we must use A reference name to receive the return value, otherwise this method may become invalid! ! ! (Remember!)

1.2. Selected reading of the source code of the String class:

1.2.1, compareTo(); source code:

 public int compareTo(String anotherString) {
    
    
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
    
    
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
    
    
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }
Source code analysis:

This code is nothing advanced, we mainly learn some of our commonly used methods through source code. From this code, we can see that if two strings are compared, if there are characters in their common length that are not equal, then the difference between the two characters will be returned. This difference is the ASCii code of the two characters Difference. If the strings in the common length part are exactly the same, the difference in length is returned. This method can indeed compare strings effectively, but the efficiency is not too high. One point is that the efficiency of the source code of the Java String is not too high. The character matching uses the BF algorithm, so it will not be interpreted.

1.2.2, equals (Obj); source code:

 public boolean equals(Object anObject) {
    
    
        if (this == anObject) {
    
    
            return true;
        }
        if (anObject instanceof String) {
    
    
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
    
    
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
    
    
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
This source code tells us two things:

The first point: When it
comes to the parameters of the object, you must first judge it and do exception handling!
The second point:
Because of the instantiation of String objects, if it is that kind of deep copy, two objects will point to the same address, and the references of these two objects are the same, so there is no need to judge, directly return true, this The operation is really worth learning.

1.2.3, the source code of hash'code();:

The third source code: rewrite the hashcode() of the String object;
public int hashCode() {
    
    
        int h = hash;
        if (h == 0 && value.length > 0) {
    
    
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
    
    
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
        // 要我我会改进:
        // return h & 0x7fffffff;
    }

The code for this code is the BKDRHash algorithm, which is a very common string hash algorithm. Generally, hash seeds are used, seed = 31, 17, ... and other prime numbers, and then a polynomial is calculated as the value of the hash, but generally The hash value processed in this way is very large, and it needs to be modulo, and the modulo brings about hash conflicts, so a lot of code is usually added to deal with hash conflicts. To be honest, I was disappointed to see this source code, because I thought that String's hashcode could actually come up with high bite-bit data and a proper way to handle conflicts... In fact, it is not as good as written in my algorithm book. ...

Two, StringBuffer class & StringBuilder class

As we said before, objects of the String class are final. In other words, if we want to change the value of the String object, we actually open up a new space, get new content, and then let the object reference point to the new content, so will waste space and time, the efficiency is very low, so we have the string needs to be modified when use StringBuffer and StringBuilder to improve efficiency, but maybe the difference is still quite large, we have to learn about.

2.1. The difference between them and String objects:

The main difference is that the objects of StringBuffer and StringBuilder are not final. They can be modified directly on the string without opening up new space.
In comparison, StringBuilder is more efficient than StringBuffer! But StringBuilder cannot do synchronous access (multithreaded), so in the case of thread safety (synchronous access) is required, a relatively inefficient StringBuffer must be used! However, since StringBuilder is the most efficient , it is recommended to use StringBuilder under normal circumstances (no need for synchronous access).
We use a picture to describe their inheritance relationship:
Insert picture description here

2.2. Instantiation of StringBuffer and StringBuilder:

The instantiation of StringBuffer and StringBuilder must use constructor to instantiate. It cannot be constructed like String, which is similar to copy construction. That is not possible (in fact, it is also unsafe, it is a deep copy...)
instance Method:

public static void main(String[] args){
    
    
		
		StringBuffer str1 = new StringBuffer("I am StringBuffer");
		StringBuilder str2 = new StringBuilder("I am StringBuilder");
		
		System.out.println(str1);
		System.out.println(str2);
	}	

result:
Insert picture description here

But there are exceptions to everything, this null is to construct an empty string without the help of a constructor:

public static void main(String[] args){
    
    
		StringBuilder str1 = null;
		if(null == str1)
			System.out.println("Yes, str1 is null");
		StringBuffer str2 = null;
		if(null == str2)
			System.out.println("Yes, str2 is null");
	}	

Result:
Insert picture description here
But this initialization is completely different from StringBuffer str = new StringBuffer();, because the latter initialized empty string actually allocates 16 buffers, there is space and can be accessed.

The commonly used StringBuffer constructor:

Insert picture description here
From this constructor, we can know why StringBuffer and StringBuilder can directly operate on the string without having to reopen a new string object to reference. Because these strings have their own buffer, this buffer is adapted to the operation of changing the length of the string.
Insert picture description here
Can the capacity be automatically increased? We will see the source code later!

2.3. Common methods of StringBuffer:

Insert picture description here

A few notes on these methods:
the first:

println(); will not accept the parameters of StringBuffer and StringBuilder. If you need to access it, you need to use toString(); to convert to String type, but I found through experiments that this is not necessary...

	public static void main(String[] args){
    
    
		StringBuffer str = new StringBuffer();
		str.append("Hello").append(" Java!");
		System.out.println(str);
	}	

Insert picture description here
It's totally unaffected. Could it be that the teacher made a mistake? ? ? Let's take a look at the source code and know the connotation of the reason:
Insert picture description here
we will know when we see the source code, as long as it is output, the function parameter of println(); can be any type of object , but in the actual output print, you can only The object becomes a string type before it can be output ! !

second:

Generally speaking, we want to reduce the operation of the string concatenation character'+', we can interpret this process: For
example: String str = "Hello" + "World";
This process is actually: first instantiate a StringBuffer object Hello, and then call "Hello".append( "World"); This method, to splice the string together, then call the toString(); method, and finally return a constructor to str, which is equivalent to instantiating str into the entire string. This operation is very troublesome Yes, if there are too many'+' operations, it is very bad! The efficiency will be very low!

fourth:

If the required capacity is relatively large (greater than 16), it is best to add the required capacity when instantiating, otherwise using the default capacity (16) will cause relatively large overhead (the overhead is in expansion). It comes from the idea of ​​Java programming. Let’s take a look at how this expansion algorithm is implemented later (guess it may be the same as the vector of c++, do bitwise operations, 2^k to expand)

Three, StringBuffer&StringBuilder source code:

The source code of the subclass is not easy to read, it is inheritance and polymorphism. If you don't talk about it, just look at the source code of the parent class.

3.1. Expansion code:

 private void ensureCapacityInternal(int minimumCapacity) {
    
    
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
    
    
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

We can see that if we need a very large capacity but do not implement the defined capacity ourselves (that is, use the default capacity of 16), we may often call this method. We can know that the copyof method must be an O The method (n) is called too many times, which will lead to very low efficiency! So if you use a large capacity, you must specify the capacity when you instantiate it.

Other code: it feels the same, nothing special

But I think what I really need to learn is the robustness of this code. The execution efficiency of this source code is indeed not high, but the robustness is in place! In addition, this thread safety issue, wait until I learn about multi-threading, thread pool and other related knowledge, let's study it! Although I don't seem to find the relevant code in the source code, I will add it later!

Guess you like

Origin blog.csdn.net/qq_44274276/article/details/104955546