The article directories

  • Java Basic Syntax (9) — String class
  • I. Definition method
  • Second, the memory
  • 3. Strings are equal
  • String constant pool
  • (1) Direct assignment
  • (2) Adopt the construction method
  • (3) Use of intern
  • Understand that strings are immutable
  • Characters, bytes, and strings
  • (1) Characters and strings
  • (2) bytes and strings
  • (3) Summary
  • Common operations on strings
  • (1) String comparison
  • (2) String search
  • (3) String replacement
  • (4) String splitting
  • (5) String interception
  • (6) Other operation methods
  • (7) String manipulation exercises
    • 1. Invert the string
    • 2. Flip the string
  • StringBuffer and StringBuilder
  • (1) Append method
  • (2) Pay attention
  • (3) Difference
  • Finished!

Java Basic Syntax (9) — String class

\

This content introduces the outline

\



\

Strings are the type that we’re not going to use very much in the future. It is very simple and convenient to use, so we must use it skillfully.

\

Is there a string type in C? The answer is “no”!!

\

Char *p = “hello”;

\

Is p of type a string? No, P is a pointer!

\

In Java, there is a String — String

\

I. Definition method

\

There are many ways to create a String. Common ways to construct a String are as follows:

Method one: direct assignment method

String str1 = "hello";
Copy the code

New String()

String str2 = new String("hello");
Copy the code

Create char array ch, new String (ch)

char chs[] = {'h'.'e'.'l'.'l'.'l'.'o'};
String str3 = new String(chs);
Copy the code

\

Second, the memory

\

Before we do that, we’ll introduce the concept of a string constant pool

Sting Constant Pool Properties of the string constant pool

\

1. As of JDK.7, the string constant pool was moved to the heap

2. The data in the pool is not duplicated

\

Let’s go through a series of exercises to familiarize ourselves with string constant pooling and the memory storage of string type data.

  public static void main(String[] args) {
       String str1 = "hello";
       String str2 = new String("hello");
        System.out.println(str1 == str2);

        String str3 = "hello";
        System.out.println(str1 == str3);
    }
Copy the code

\

Let’s look at code like this. STR stands for reference \ address. What are the two prints?

\

Let’s look at the results

\

\

This result shows that str1 and STR2 store the address is different, str1 and STR3 store the address is the same.

\

Ok, so why is that? Let’s look at the memory of these string variables.

If “hello” is stored in the constant pool, it will take up memory. If this space is 111, then STR1 will hold 111.

Str2 new a String, then it must open memory on the heap, assuming the memory address is 888. In this String, there is a value[] that holds the String passed in from orginal. Val == “hello”, because there is already “hello” in the string constant pool, so val refers directly to “hello” in the constant pool. But str2 still points to the space 888 is in the heap.



So str1 is not equal to str2.

\

And then str3 is also equal to “hello”, and he’s going to put hello in the constant pool. If str3 stores the “hello” address in the constant pool, it will refer to the original hello address in the constant pool.

\

So str1 is equal to str3

\

Let’s do another set of exercises

    public static void main(String[] args) {

        String str1 = "hello";
        String str2 = "hel"+"lo";
        System.out.println(str1==str2);

        String str3 = new String("hel") +"lo";
        System.out.println(str1==str3);

    }
Copy the code

Please judge the results of the two prints…

The results are as follows:



Now let’s look at the memory storage of the STR variable in this set of code

Str1 points to “Hello” in the string constant pool

Str2 is a combination of “hel” and “lo”. The constant is already defined at compile time, so at compile time, it is already treated as “hello”, so it also refers to “hello” in the constant pool.

\

So str1 is equal to str2

\

Str3 first creates a new String(“hel”) object, creating a space in the heap where “hel” is stored in the constant pool, and then creating a space in the constant pool where “LO” is stored. The “+” between the two parts of the heap combines the String object with the constant pool “lo” to create a new space in the heap. In this memory, val == “hello” and STR3 points to the merged object with the address 999.

So str1 is not str3.

\

Let’s do another set of exercises

 public static void func(String str,char[] array){
        str = "abcdef";
        array[0] = 'g';
    }

    public static void main(String[] args) {
        String str1 = "hello";
        char[] val = {'a'};
        System.out.println(str1);
        System.out.println(Arrays.toString(val));
        func(str1,val);
        System.out.println("= = = = = = = = = = = = = = = = =");
        System.out.println(str1);
        System.out.println(Arrays.toString(val));
    }
Copy the code

\

Let’s see, what do we print if we take String STR as an argument, change the contents of STR, and pass val to change the elements of the array?

\



\

We see that the contents of String STR are unchanged, but the elements of array val are.

Let’s look at it from a memory point of view.

Str1 points to “hello” in the string constant field at address 888

Val, as an array reference, points to the array space cleared in the heap at address 777

As a function parameter, STR takes the value of the str1 argument, which is 888. In this case, STR points to “hello” in the constant field, but inside the method, STR = “abcde” has a memory of “abcde” in the string constant field, at address 000. Finally, STR is stored at address 000.

Array takes the value of the val argument, which is 777. Array points to the space created in the heap. Array is used to change the contents of the array element and, ultimately, the contents of the val argument.

\

3. Strings are equal

\

If we now have two int variables, we can use == to determine their equality.

str1 = "world";
System.out.println(str2);
// Execution result
//Hello
int x = 10 ;
int y = 10 ;
System.out.println(x == y); 
// Execution result
//true
Copy the code

What if we now use == on String objects?

\

Code 1

String str1 = "Hello";
String str2 = "Hello"; 
System.out.println(str1 == str2); 
// Execution result
//true 
Copy the code

It seems that there is no problem, and then try to change the code, found that the situation is not very good.

\

Code 2

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
// Execution result
//false
Copy the code

\

In the previous exercises, we used str1 == str2 to compare the references/addresses of two strings. If we compare the contents of strings, we need to use equals.

\

 public static void main(String[] args) {
        String str1 = "hello";
        String str2 = new String("hello");
        
        System.out.println(str1==str2);          // Compare references
        System.out.println(str1.equals(str2));   // Compare the contents of str1 and str2 strings
        
        String str3 = "hello";
        System.out.println(str1.equals(str3));   // Compare the contents of str1 and str3 strings
    }
Copy the code

The final print

\

\

The printed result matches the content comparison of the string.

Common comparison methods:

Let’s look at another situation,

  public static void main(String[] args) {
        String str1 = null;
        String str2 = "hello";
        System.out.println(str1.equals(str2));
    }
Copy the code

When you run the program, the following happens:

\

Null pointer exception because of null. Any method will have an exception.

So make sure str1 is not null.

So if we change it, \

public static void main(String[] args) {
        String str1 = null;
        String str2 = "hello";
        System.out.println(str2.equals(str1));
    }
Copy the code

\

So we know equals () can be null in parentheses, but it must not be null before the dot.

public static void main(String[] args) {
        String str1 = "hello";

        System.out.println(str1.equals("hello"));  1 / / way
        System.out.println("hello".equals(str1));  2 / / way
    }
Copy the code

\

When writing code in this case, we should always choose method 2 to ensure that equals is not null in case of an exception.

\

String constant pool

\

In the example above, the two instantiations of the String class are direct assignment and new to a new String.

\

(1) Direct assignment

\

System.out.println("Hello".equals(str));  // Execute result false
String str1 = "hello" ;
String str2 = "hello" ; 
String str3 = "hello" ; 
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true
Copy the code

\

The String class is designed using a shared design pattern

There is actually an object pool (string constant pool) that is automatically maintained under the JVM

If a String object is instantiated using the direct assignment mode, the instantiated object (String content) will be automatically saved to the object pool.

The next time you continue to declare String objects using the directly assigned schema, the object pool will reference the specified contents directly

If not, create a new string object and save it in the object pool for future use

\

Understanding “Pool”

“Pooling” is a common and important way to improve efficiency in programming. We will encounter various “memory pools”, “thread pools”, “database connection pools” in future study. However, the concept of pools is not unique to computers, but also comes from life. For example, in real life, there is a kind of goddess called “green tea”, who may flirt with other diaosi while dating a handsome man with rich or handsome men. This diaosi is called a “back-up”. So why a back-up? Because once broke up with gao Fu shuai, you can immediately find a spare tire to take dish, so high efficiency. If the goddess is hooking up with multiple losers at the same time, then the back-up is called the back-up pool.

\

(2) Adopt the construction method

\

It is standard practice for class objects to be instantiated using constructors. Analyze the following procedures:

\

String str = new String("hello");
Copy the code

\

There are two drawbacks to this approach:

\

1. Using the String constructor opens up two heap Spaces, and one of them becomes garbage space (the String constant “hello” is also an anonymous object that is used once and is no longer used, and is garbage space, which is automatically reclaimed by the JVM).

2. String sharing problem. The same string may be stored more than once, wasting space.

\

(3) Use of intern

String str1 = "hello";
String str2 = new String("hello").intren();
Copy the code

\

Defining strings by constructor from above, we’re wasting memory space, and there’s a method called intern() that pools them manually.

\

So what does that mean?

\

This first checks to see if the string passed to the constructor exists in the string constant pool, and if so, passes the reference from the constant pool to the current reference type variable.

To sum up, we generally use the direct assignment method to create strings.

Let’s look at another set of code that looks like this, and let’s draw its memory structure

In the first step of the code, new two strings “1”, create two objects in the heap that point to “1” in the constant pool, concatenate them together,s3.interb(),s3 is pooled manually, “11” is not in the pool, so we pass the reference 555 of “11” in the heap into the constant pool. S4 points to “11” in the pool, which already has a reference to “11”, so s4 points to s3’s pooled reference.

So the result is true.

So, we solved a problem

In the constant pool, you can place literal constants of strings as well as references. S3.intern (), the object to which s3 refers does not exist in the string constant pool, and the pool is pooled with s3 references from the heap.

\

Understand that strings are immutable

\

A string is an immutable object. Its contents are immutable. What does that mean?

  public static void main(String[] args) {
        String str = "hello" ;
        str = str + " world" ;
        str += "!!!" ;
        System.out.println(str);
    }
Copy the code

\

For this type of code, at first glance we think that we are successfully concatenating STR with another string each time, but this is not possible. STR originally pointed to “hello”, but after concatenating STR with “world”, a new object “helll world” is generated, which concatenates “!!!” again. Then a new object “Hello World!!” is created. In memory, multiple objects are generated.

The last thing we need is “Hello world!!” , but it opens up 5 memory Spaces.

Concatenation in a loop opens up more memory!!

So this code is extremely undesirable!!

How to concatenate StringBuff and StringBuilder?

\

Characters, bytes, and strings

\

(1) Characters and strings

\

A String contains an array of characters that can be converted to and from char[]

1. Character array to string

    public static void main(String[] args) {
        char[] val = {'h'.'e'.'l'.'l'.'o'};
        String str = new String(val);
        System.out.println(val);
    }
Copy the code

The result of our STR is “hello”, and it can also give two more arguments.

\

2. Convert part of the character array to a string



Offset – the offset

Count — Convert several

 public static void main(String[] args) {
        char[] val = {'h'.'e'.'l'.'l'.'o'};
        String str = new String(val,1.2);
        System.out.println(str);
    }
Copy the code

At this point, we offset val by 1 and convert the two array elements to strings

The printed result should be EL

The running results are as follows:

\

3. Convert indexes in the string to characters

 public static void main(String[] args) {
        String str = "hello";
        char ch = str.charAt(1);
        System.out.println(ch);
    }
Copy the code

The index starts at 0, we type 1, so it’s converted to e in the string

The running results are as follows:

\

4. Convert strings to character arrays

 public static void main(String[] args) {
        String str = "hello";
        char[] val = str.toCharArray();
        System.out.println(Arrays.toString(val));
    }
Copy the code

We receive STR converted characters in a character array.

The running results are as follows:

\



\

Ok, now that we have these character and string methods, let’s continue with a few exercises.

\

Practice a \

Given a string, determine whether the string consists entirely of numbers.

Turn a string into an array of characters and check if each character is a number between “0” and “9”.

  public static boolean func1(String str){
        for (int i = 0; i <str.length() ; i++) {
            if(str.charAt(i)>'9' || str.charAt(i)<'0') {return false; }}return true;
    }
Copy the code

\

(2) bytes and strings

\

Bytes are often used for data transmission and encoding conversion. String can also be easily converted to byte[]

\

Common methods:

1. Byte arrays are converted to strings

  public static void main(String[] args) {
        byte[] bytes = {97.98.99.100};
        String str = new String(bytes);
        System.out.println(str);
    }
Copy the code

Running results:



The contents of the string are the characters in the byte array and Ascii table.

\

2. Part of the byte array is converted to a string

  public static void main(String[] args) {
        byte[] bytes = {97.98.99.100};
        String str = new String(bytes,2.1);
        System.out.println(str);
    }
Copy the code

Running results:

\

3. Convert strings to byte arrays

   public static void main(String[] args) {
        String str = "abcd";
        byte[] bytes = str.getBytes();
        System.out.println(Arrays.toString(bytes));
    }
Copy the code

Running results:

\

(3) Summary

\

So when to use byte[] and when to use char[]?

\

Byte [] is a String processed byte by byte, which is suitable for network transmission and data storage scenarios. It’s better for working with binary data.

Char [] String is handled character by character and is more suitable for manipulating text data, especially if it contains Chinese characters.

\

Common operations on strings

\

(1) String comparison

\

We’ve used the equals() method provided by the String class above, which is itself a case-sensitive equality check. In addition to this method, the String class also provides the following comparison operations.

1. Case sensitive comparison

   public static void main(String[] args) {
        String str1 = "abcd";
        String str2 = "Abcd";
        System.out.println(str1.equals(str2));
    }
Copy the code

Running results:



Note that the equals method we use is case sensitive.

\

2. Case insensitive comparisons

    public static void main(String[] args) {
        String str1 = "abcd";
        String str2 = "Abcd";
        System.out.println(str1.equalsIgnoreCase(str2));
    }
Copy the code

Running results:

This kind of case-insensitive comparison is quite common, for example, when it comes to captchas.

\

3. Compare the size relationship between two strings

public static void main(String[] args) {
        String str1 = "abcd";
        String str2 = "Abcd";
        System.out.println(str1.compareTo(str2));
    }
Copy the code

Runtime results





Now that we know how to compare strings, let’s do an exercise

\

Compares whether strings are equal

\

参 考 答 案 :

\

Str1 appends the word1 string array to str1, and the word2 string array to str2. Equals equals returns true and false.

Note: parameters and other issues should be considered comprehensively

\

(2) String search

\

The existence of a specified content can be determined from a complete string, and the lookup method is defined as follows:

Checks whether a string contains substrings

\

We can take a look at the source code of the CONTAINS method first



Use of the CONTAINS method

 public static void main(String[] args) {
       String str = "bcdabc";
       boolean flg = str.contains("abc");
        System.out.println(flg);
    }
Copy the code

The results

So you can tell that there is a substring of “ABC” in the string “badabc”.

\

Find the subscript of the substring

\

Let’s take a look at the index method of a parameter source



\

The use of the index method with one argument

\

    public static void main(String[] args) {
        String str = "ababcabcdabcde";
        int index1 = str.indexOf("abc");
        int index2 = str.indexOf("hello");
        System.out.println(index1);
        System.out.println("= = = = = = = = = = = =");
        System.out.println(index2);
    }
Copy the code

Running results:

The use of the index method with two parameters

\

Below we see an index method, which indicates that by default index is searched from index 0. If given a subscript, the string is searched from the specified subscript position.

\

Use:

 public static void main(String[] args) {
        String str = "abcabcdabcdef";
        int index1 = str.indexOf("abc");
        int index2 = str.indexOf("abc".6);
        System.out.println(index1);
        System.out.println("= = = = = = = = = = = = = = = = =");
        System.out.println(index2);
    }
Copy the code

Running results:

Find the position of the substring from back to front

\

LastIndexOf looks up the position of the substring from back to front

Use of the lastIndexOf method

 public static void main(String[] args) {
        String str = "abcabcdabcdef";
        int index = str.lastIndexOf("abc");
        System.out.println(index);
    }
Copy the code

Running results:



LastIndexOf also has a two-parameter method that searches backwards from the specified index.

Starts with an argument string



There is also a two-argument method that determines whether the specified string starts at the specified position

\

Determines whether the specified string is terminated

\

(3) String replacement

(1) Replace all specified contents

The use of the replaceAll

 public static void main(String[] args) {
        String str = "abcabcacbabc";
        System.out.println(str);
        System.out.println("= = = = = = = = = = = = = = = = =");
        String ret = str.replaceAll("ab"."AB");
        System.out.println(ret);
    }
Copy the code

Running results:

\

Successfully replace all “ab” with “ab”.

\

(2) Replace the first content to be replaced.

The use of the replaceFirst

 public static void main(String[] args) {
        String str = "abcabcacbabc";
        System.out.println(str);
        System.out.println("= = = = = = = = = = = = = = = = =");
        String ret = str.replaceFirst("ab"."AB");
        System.out.println(ret);
    }
Copy the code

Running results:



\

Note:

Since strings are immutable objects, substitution does not modify the current string, but produces a new string.

\

(4) String splitting

\

A complete string can be divided into substrings with specified delimiters.

1. Split all strings

\

The type we receive is an array of strings, and when we pass a parameter, we pass a symbol that we want to split.

\

The use of the split

  public static void main(String[] args) {
        String str = "rain7 is cool";
        String[] strs = str.split("");
        for(String s:strs) { System.out.println(s); }}Copy the code

When we use the split method, we split our STR string with a space as a separator

\

Let’s look at the effect of splitting

2. Split method with two arguments

\

Again, take the string above

   public static void main(String[] args) {
        String str = "rain7 is cool";
        String[] strs = str.split("".2);
        for(String s:strs) { System.out.println(s); }}Copy the code

Running results:

In addition to taking the string as an argument, we set limit to 2, so the split array length is 2, so the result looks like this.

\

Difficult points:

Splitting is a particularly common operation. Be sure to focus on it. In addition, some special characters may not be correctly segmented as separators, and need to be escaped

\

Example 1

Splitting IP Addresses

Let’s say we want to split the IP address, 192.168.1.1, with “. Segmentation.

When we run it, we find that the print is empty. Why is that?

Some symbols are special and must be escaped

"\." can mean a real ". At the same time"\" also needs to be escaped, so another slash is added. "\ \." In this case, the string can only be separated by ". ".Copy the code
public static void main(String[] args) {
        String str = "192.168.1.1";
        String[] strs = str.split("\ \.");
        for(String s:strs) { System.out.println(s); }}Copy the code

\

The results

1.character"|"."*"."+"You have to put an escape character in front of it"\ \".
2.And if it is"\", then you have to write"\ \".3. If there are more than one delimiter in a string, use"|"As a hyphen.Copy the code

\

The use of a hyphen “|”

 public static void main(String[] args) {
        String str = "[email protected]";
        String[] ret = str.split("@ | \ \.");
        for(String s:ret) { System.out.println(s); }}Copy the code

Running results:

\

Let’s do an exercise:

Code solution:

 public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String ret ="";
            String str = scanner.nextLine();
            String[] strs = str.split("");
            for(String s:strs) { ret += s; } System.out.println(ret); }}Copy the code

Running results:

Note:

2. It is not recommended to concatenate strings in the for loop. You can see how to concatenate strings in the StringBuilder StringBuffer section.

(5) String interception

To extract portions of a complete string. Available methods are as follows:

1. Intercepts the specified subscript to the end of the string

Use of methods

 public static void main(String[] args) {
        String str = "ilikeBeijing";
        String ret = str.substring(4);
        System.out.println(ret);
    }
Copy the code

Running results:

2. The subString method with two arguments intercepts the content of the string in the specified subscript range

Use of methods

 public static void main(String[] args) {
        String str = "HelloWorld";
        String ret = str.substring(4.9);
        System.out.println(ret);
    }
Copy the code

The results



Note:

1. Specify the range of subscripts to be left closed and right open

2. The truncated string is a new object

(6) Other operation methods

There are many other ways to manipulate strings, but we’ll cover them briefly here.

(7) String manipulation exercises

\

1. Invert the string

\

Topic request

Invert the whole string

\

Code solution:

   public static String reverse(String s){
        if(s==null) {return null;
        }

        int begun = 0;
        int end = s.length()-1;
        char[] chars = s.toCharArray();
        while(begun<end){
            char tmp = chars[begun] ;
            chars [begun] = chars [end];
            chars[end] = tmp;
            begun++;
            end--;
        }
        return new String(chars);
    }


    public static void main(String[] args) {
        String str = "Hello World!";
        String ret = reverse(str);
        System.out.println(ret);
    }
Copy the code

\

Running results:

\

Successfully invert the string

\

2. Flip the string

\

First of all, we’re going to implement a method that we pass in a string and an integer size. Flip the left half of size to the right half. As shown in the figure:

\

\

Train of thought:

1. First, invert the left half of size separately.

2. Invert the right half of size separately.

3. Invert the entire string.



\

Code display:

 public static String reverse(String s,int begun,int end){
        if(s==null) {return null;
        }

        char[] chars = s.toCharArray();
        while(begun<end){
            char tmp = chars[begun] ;
            chars [begun] = chars [end];
            chars[end] = tmp;
            begun++;
            end--;
        }
        return new String(chars);
    }

    public static String reversSentence(String str,int k){
        str = reverse(str,0,k-1);
        str = reverse(str,k,str.length()-1);
        str = reverse(str,0,str.length()-1);

        return str;
    }

    public static void main(String[] args) {
       Scanner scanner = new Scanner(System.in);
       String str = scanner.next();
       int n = scanner.nextInt();
       String ret = reversSentence(str,n);
        System.out.println(ret);
    }
Copy the code

Running results:

StringBuffer and StringBuilder

\

StringBuffer and StringBuilder are new string types.

Strings are generally easy to operate on, but because of the immutable nature of strings, classes StringBuffer and StringBuilder are provided to make it easier to modify strings.

StringBuffer and StringBuilder are mostly the same in function, and we’ll focus on StringBuffer here.

\

(1) Append method

\

String concatenation uses “+” in String, but this operation needs to be changed to the append() method in the StringBuffer class.

\

The main difference between a String and a StringBuffer is that the contents of a String cannot be modified, whereas the contents of a StringBuffer can be modified. StingBuffer is considered for frequent string modifications.

\

 public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append("a");
        sb.append("b");
        sb.append("c");
        System.out.println(sb);
    }
Copy the code

Running results:



Let’s take a look at the source code for the Append method of StringBuffer



Finally, we return this, concatenating the string itself. StringBuffer also has its own override toString method, which can be printed directly.

Let’s look at the following code:

  public static void main(String[] args) {
       String str1 = "abc";
       String str2 = "def";
       String str3 = str1+str2;
       System.out.println(str3);

    }
Copy the code

Let’s compile the above code:



During compilation, we see the stringBuilder.append method appear;

\

Let’s write this in StringBuilder:

   public static void main(String[] args) {
        String str1 ="abc";
        String str2 = "def";
        StringBuilder sb = new StringBuilder();
        sb.append(str1);
        sb.append(str2);
        String str3 = sb.toString();
        System.out.println(str3);
    }
Copy the code

Description:

The “+” concatenation of strings is optimized to be a StringBuilder using the append method

\

(2) Pay attention

\

Note: The String and StringBuffer classes cannot be converted directly. If you want to convert to each other, you can use the following principles:

String to StringBuffer: using the constructor of StringBuffer or the append() method StringBuffer toString: calling toString().

In addition to the append() method, StringBuffer has some methods that the String class does not:

\

String inversion:

public synchronized StringBuffer reverse(a);
Copy the code

(3) Difference

\

The difference between String and StringBuilder and StringBuffer

When a String is concatenated, the underlying layer is optimized to StringBuilder

Concatenation of strings produces temporary objects, but the latter two only return a reference to the current object each time.

The contents of String cannot be modified. The contents of StringBuffer and StringBuilder can be modified.

\

The difference between StringBuilder and StringBuffer

Let’s look at the append methods of these two classes

\

So the main difference between StringBuffer and StringBuilder is thread safety.

\

1.StringBuffer and StringBuilder are similar in many ways

2.StringBuffer is a thread-safe operation that is synchronized. StringBuilder does not use synchronization, which is a thread-unsafe operation

\

String manipulation is a very common operation in our future work. It is very simple and convenient to use, so we must use it skillfully.

\

Well, today’s knowledge to share here, I hope you can practice, master, thank you for your appreciation and attention! \

Thank you!


\

Java basic syntax (10) – understanding exceptions have been updated, hope you pay more attention to oh!


\

Finished!