Detailed explanation of Java strings and input and output

1. Immutable String

StringObjects of class are immutable, and every method in this class that appears to modify the value of String actually creates and returns a completely new String object, which contains the modified content.

package string;

public class StringDemo {
    
    
    static String upcase(String s) {
    
    
        return s.toUpperCase();
    }

    public static void main(String[] args) {
    
    
        String s = "Hello";
        String t = upcase(s);
        System.out.println(s);  // Hello
        System.out.println(t);  // HELLO
    }
}

Whens is passed toupcase(), what is actually passed is a reference to the s object, the object pointed to by this reference only exists in a single physical location, and only the reference is copied when passed. Copy

Inupcase(), the parameters only survives in the method body of this method. Whenupcase() is completed, , the local references will disappear. upcase() Returns the result of execution: a reference to a new string.

2. Overloading the addition operator and StringBuilder

immutability may cause efficiency problems. A typical example is operator+, which is overloaded for String objects ( a> are the only overloaded operators in Java. Java does not allow programmers to overload other operators. , which is different from C++). Operator overloading means that the corresponding operation takes on additional meaning when used with a specific class. and String's ++=

Let's say we have the following code:

package string;

public class StringConcat {
    
    
    public static void main(String[] args) {
    
    
        String s = "Hello";
        String t = s + " world";
        System.out.println(t);  // Hello world
    }
}

We can use the JDK’s own javap tool to decompile the above code:

javap -c StringConcat

You can also view it through the View -> Show Bytecode option after compiling the code in IDEA, the content is as follows:

// class version 52.0 (52)
// access flags 0x21
public class string/StringConcat {
    
    

  // compiled from: StringConcat.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lstring/StringConcat; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    LDC "Hello"
    ASTORE 1
   L1
    LINENUMBER 6 L1
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC " world"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L2
    LINENUMBER 7 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 8 L3
    RETURN
   L4
    LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
    LOCALVARIABLE s Ljava/lang/String; L1 L4 1
    LOCALVARIABLE t Ljava/lang/String; L2 L4 2
    MAXSTACK = 2
    MAXLOCALS = 3
}

Statements like DUP and INVOKEVIRTUAL are equivalent to the JVM's assembly language. You don’t need to understand what it means here. What needs to be paid attention to is the introduction of the java.lang.StringBuilder class by the compiler. StringBuilder is not used in our code, but the compiler Decided to use it anyway since it's more efficient.

Here, the compiler creates a StringBuilder object to build the strings and calls append() once for each string, twice in total . Finally, it calls toString() to generate the result and saves it as t (implemented using the ASTORE 2 statement).

You may think that you can use it freely String, but the compiler will optimize the use of strings. When creating a toString() method, if the operation is simple, you can usually rely on the compiler to construct the result on its own in a sensible way. But if it involves loops and there are certain requirements for performance, then Need to use StringBuilder explicitly. Let’s look at an example:

package string;

import java.util.Random;
import java.util.stream.Collectors;

public class UsingStringBuilder {
    
    
    static String getString1() {
    
    
        Random rand = new Random(47);
        StringBuilder res = new StringBuilder("[");
        for (int i = 0; i < 5; i++) {
    
    
            res.append(rand.nextInt(10));
            res.append(", ");
        }
        res.delete(res.length() - 2, res.length());  // 删除最后一个逗号和空格
        res.append("]");
        return res.toString();
    }

    static String getString2() {
    
    
        String res = new Random(47)
            .ints(5, 0, 10)
            .mapToObj(Integer::toString)
            .collect(Collectors.joining(", "));
        return "[" + res + "]";
    }

    public static void main(String[] args) {
    
    
        System.out.println(getString1());  // [8, 5, 3, 1, 1]
        System.out.println(getString2());  // [8, 5, 3, 1, 1]
    }
}

IngetString1(), the final result is formed by splicing each part separately using the append() statement. If you want to take a shortcut, execute something like For operations such as append(rand.nextInt(10) + ", "), the compiler will intervene and start creating more StringBuilder objects, affecting performance.

getString2() uses Stream, and the generated code is more pleasing to the eye. In fact, Collectors.joining() is implemented internally using StringBuilder , so there is no performance penalty in using this approach.

StringBuilder was introduced in Java 5, before that, Java used StringBuffer, which is thread-safe and therefore more expensive, used StringBuilder Doing string operations is faster.

3. Unconscious recursion

Like other classes, Java's standard collections ultimately inherit from Object, so they also contain a toString() method. This method is overridden in a collection so that the resulting string it generates represents the container itself, as well as all objects held by that container. Taking ArrayList.toString() as an example, it will traverse the elements of ArrayList and call the toString() method for each element:

package string;

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Cat {
    
    
    private final int id;
    private final static Random rand = new Random(47);

    Cat() {
    
    
        id = rand.nextInt(10);
    }

    @Override
    public String toString() {
    
    
        return "Cat " + id;
    }
}

public class ArrayListDisplay {
    
    
    public static void main(String[] args) {
    
    
        List<Cat> cats = Stream.generate(Cat::new)
            .limit(5)
            .collect(Collectors.toList());

        System.out.println(cats);  // [Cat 8, Cat 5, Cat 3, Cat 1, Cat 1]
    }
}

If you want toString() to print the memory address of an object, do not use this:

@Override
public String toString() {
    
    
    return "Cat " + this;
}

This will get a long exception stack, because the compiler sees that String is followed by a non-String content, and will try to call toString() method is automatically converted, which will recursively call its own toString(). Therefore we need to directly call the method of Object: toString()

package string;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Cat {
    
    
    Cat() {
    
    }

    @Override
    public String toString() {
    
    
        return "Cat " + super.toString();
    }
}

public class ArrayListDisplay {
    
    
    public static void main(String[] args) {
    
    
        List<Cat> cats = Stream.generate(Cat::new)
            .limit(3)
            .collect(Collectors.toList());

        System.out.println(cats);  // [Cat string.Cat@5fd0d5ae, Cat string.Cat@2d98a335, Cat string.Cat@16b98e56]
    }
}

4. Format output

4.1 printf() and format()

Those who have a C language background must be familiar with printf(). printf() uses special placeholders to represent the location of data. The format() method introduced in Java 5 can be used on PrintStream or PrintWriter objects, so it can also be used directly on System.out.

format() and printf() are equivalent in that they require only a format string followed by parameters, each of which corresponds to a format specifier. The String class also has a static format() method which produces a format string, as we will see later.

package string;

public class PrintfAndFormat {
    
    
    public static void main(String[] args) {
    
    
        int x = 2;
        double y = 3.14;

        System.out.printf("[%d, %f]%n", x, y);  // %n同C语言中的\n
        System.out.format("[%d, %f]%n", x, y);

        /*
         * [2, 3.140000]
         * [2, 3.140000]
         */
    }
}

4.2 Formatter class

All formatting functions in Java are handled by the classes in the java.util package. You can Think of it as a converter, converting formatted strings and data into the desired result. When creating a object, you can pass information to the constructor to indicate where you want the results to be output: FormatterFormatterFormatter

package string;

import java.io.PrintStream;
import java.util.Formatter;

public class FormatterDemo {
    
    
    public static void main(String[] args) {
    
    
        PrintStream out = System.out;
        Formatter f = new Formatter(out);

        int x = 2;
        double y = 3.14;
        f.format("[%d, %f]%n", x, y);  // [2, 3.140000]
    }
}

4.3 Format specifiers

If you want to control spacing and alignment when inserting data, you need more detailed format specifiers. Generally, we will control the minimum length of the field, or the number of decimal places of the floating point number. After setting the length, the default is right-aligned. You can use - to set it to left-aligned, for example:

%.2f %6d %-15s

Let's look at a simple example that uses the generator pattern, where you create a starting object, then add content to it, and finally use the build() method to generate the final result :

package string;

import java.util.Formatter;

public class ReceiptBuilder {
    
    
    private double total = 0;
    private Formatter f = new Formatter(new StringBuilder());

    public ReceiptBuilder() {
    
    
        f.format("%-10s %8s %8s%n", "Fruit", "Quantity", "Price");
        f.format("%-10s %8s %8s%n", "-----", "--------", "-----");
    }

    public void add(String name, int quantity, double price) {
    
    
        f.format("%-10s %8d %8.2f%n", name, quantity, price);
        total += price;
    }

    public String build() {
    
    
        f.format("%-10s %8s %8s%n", "", "", "-----");
        f.format("%-10s %8s %8.2f%n", "Total", "", total);
        return f.toString();
    }

    public static void main(String[] args) {
    
    
        ReceiptBuilder receiptBuilder = new ReceiptBuilder();
        receiptBuilder.add("Apple", 3, 2.4);
        receiptBuilder.add("Banana", 1, 6);
        receiptBuilder.add("Orange", 2, 3.45);
        System.out.println(receiptBuilder.build());

        /*
         * Fruit      Quantity    Price
         * -----      --------    -----
         * Apple             3     2.40
         * Banana            1     6.00
         * Orange            2     3.45
         *                        -----
         * Total                  11.85
         */
    }
}

Once passesStringBuilder to the Formatter constructor, it has a place to buildString, which is fine Use constructor parameters to send it to standard output or even a file.

Note: If the formatted output parameter%b is used to output a non-boolean primitive type or Boolean object, then only The parameter type is not null, the formatting result is always true, even the value 0 is output as true, which is the same as the C language Different.

4.4 String.format()

Java 5 also borrows from the sprintf() used to create strings in the C language and provides the String.format() method. It is a static method with exactly the same parameters as the method of the Formatter class, but returns a :format()String

package string;

public class DatabaseException extends Exception {
    
    
    public DatabaseException(int transactionID, int queryID, String msg) {
    
    
        super(String.format("(t%d, q%d) %s", transactionID, queryID, msg));
    }

    public static void main(String[] args) {
    
    
        try {
    
    
            throw new DatabaseException(3, 7, "Write failed");
        } catch (Exception e) {
    
    
            System.out.println(e);  // string.DatabaseException: (t3, q7) Write failed
        }
    }
}

5. Scan input

So far, reading data from human-readable files or standard input is still relatively painful. The general solution is to read a line of text, perform word segmentation analysis on it, and then use < Various methods in a i=1>, and other classes are used to parse data: IntegerDouble

package string;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.stream.Stream;

public class SimpleRead {
    
    
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    public static void main(String[] args) {
    
    
        try {
    
    
            Integer[] inputs = Stream.of(in.readLine().split(" ")).mapToInt(Integer::parseInt).boxed().toArray(Integer[]::new);  // 转换成包装类型数组
            // int [] inputs = Stream.of(in.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();  // 转换成基本类型数组

            System.out.println(Arrays.asList(inputs));
        } catch (IOException e) {
    
    
            System.out.println(e);
        }

        /*
         * input: 3 1 4 7 6
         * output: [3, 1, 4, 7, 6]
         */
    }
}

BufferedReader has a readLine() method, which can read one line from the input object (here is standard inputInputStreamReader) at a time, The readLine() method converts each line read into a String object. We use the split() method to split the string into one with spaces. String[], and then use Stream to convert each string in the array to an integer and rebuild it into an integer array.

The Scanner class added in Java 5 greatly reduces the burden of scanning input:

package string;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class BetterRead {
    
    
    public static void main(String[] args) {
    
    
        Scanner sc = new Scanner(System.in);
        List<Integer> inputs = new ArrayList<>();

        while (sc.hasNextInt())
            inputs.add(sc.nextInt());

        System.out.println(inputs);

        /*
         * input: 1 2 33 5 end 1 2
         * output: [1, 2, 33, 5]
         */
    }
}

ScannerThe constructor for can accept any type of input object, including File objects, InputStream, String, or < /span>Readable interface.

In Scanner, input, tokenization and parsing operations are included in various types of next() methods. A plain next() returns the next String, all basic types (except Char) and BigDecimal and BigInteger have correspondingnext() methods. All next() methods are blocking, which means that they can only provide a complete set of available data when the input stream It will be returned only when it is participled. You can also judge whether the type of the next input participle is correct based on whether the corresponding hasNext() method returns true.

Scanner will assume that IOException indicates the end of input, so Scanner will hide IOException.

5.1 Scanner separator

By default, Scanner separates the input data by spaces, but it can also be specified in the form of a regular expression:

package string;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class ScannerDelimite {
    
    
    public static void main(String[] args) {
    
    
        Scanner sc = new Scanner("3, 12,  7 , 6, 9");
        sc.useDelimiter("\\s*,\\s*");

        List<Integer> inputs = new ArrayList<>();
        while (sc.hasNextInt())
            inputs.add(sc.nextInt());
        System.out.println(inputs);  // [3, 12, 7, 6, 9]
    }
}

This example uses commas (surrounded by any number of whitespace characters) as the delimiter for reading a given string, but the same technique can be used to read comma-delimited files:

package string;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

public class ScannerDelimite {
    
    
    public static void main(String[] args) throws IOException {
    
    
        Scanner sc = new Scanner(Files.lines(Paths.get("src/file/numbers.txt")).collect(Collectors.joining(",")));
        sc.useDelimiter("\\s*,\\s*");

        List<Integer> inputs = new ArrayList<>();
        while (sc.hasNextInt())
            inputs.add(sc.nextInt());
        System.out.println(inputs);  // [3, 12, 7, 6, 9, 5, 1]
    }
}

Among them numbers.txt Sentence content below:

3, 12,  7 , 6, 9
5, 1

5.2 Scan using regular expressions

In addition to scanning predefined basic types, you can also scan with your own defined regular expression patterns, which is very useful when scanning more complex data. The following example scans firewall logs for threat data:

package string;

import java.util.Scanner;
import java.util.regex.MatchResult;

public class ThreatAnalyzer {
    
    
    static String threatData =
        "12.124.52.163@03/11/2023\n" +
        "23.141.61.192@01/11/2023\n" +
        "Over";

    public static void main(String[] args) {
    
    
        Scanner sc = new Scanner(threatData);
        String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@" + "(\\d{2}/\\d{2}/\\d{4})";

        while (sc.hasNext(pattern)) {
    
    
            sc.next(pattern);
            MatchResult match = sc.match();
            String ip = match.group(1);
            String date = match.group(2);
            System.out.printf("Threat on %s from %s%n", date, ip);
        }

        /*
         * Threat on 03/11/2023 from 12.124.52.163
         * Threat on 01/11/2023 from 23.141.61.192
         */
    }
}

next() When used with a specific pattern, the pattern is matched against the next input token, and the result is provided by the match() method.

Guess you like

Origin blog.csdn.net/m0_51755720/article/details/134181841