CONTENTS
1. Immutable String
String
Objects 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: Formatter
Formatter
Formatter
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: Integer
Double
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]
*/
}
}
Scanner
The 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.