本文参考自廖雪峰老师的官方教程:https://www.liaoxuefeng.com/wiki/1252599548343744
Java介于编译型语言和解释型语言之间。编译型语言如C、C++,代码是直接编译成机器码执行,但是不同的平台(x86、ARM等)CPU的指令集不同,因此,需要编译出每一种平台的对应机器码。解释型语言如Python、Ruby没有这个问题,可以由解释器直接加载源码然后运行,代价是运行效率太低。而Java是将代码编译成一种“字节码”,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机(JVM),不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果。
Java的三个版本:
┌───────────────────────────┐ Java EE是企业版,在Java SE的基础上加上了大量的API
│Java EE │ 和库,以便方便开发Web应用、数据库、消息服务等
│ ┌────────────────────┐ │
│ │Java SE │ │ Java SE就是标准版,包含标准的JVM和标准库
│ │ ┌─────────────┐ │ │
│ │ │ Java ME │ │ │ Java ME是一个针对嵌入式设备的“瘦身版”,但是不流行
│ │ └─────────────┘ │ │
│ └────────────────────┘ │
└───────────────────────────┘
这里是Java SE(Standard Edition)的学习笔记,Java SE是整个Java平台的核心,包含以下知识:
区别 jdk、jre、jvm:
┌─ ┌──────────────────────────────────┐ JDK: Java Development Kit
│ │ Compiler, debugger, etc. │ Java开发工具包,包括JRE和其他如
│ └──────────────────────────────────┘ 编译工具javac.exe,打包工具jar.exe等
JDK ┌─ ┌──────────────────────────────────┐
│ │ │ │ JRE: Java Runtime Environment
│ JRE │ JVM + Runtime Library │ Java 运行环境包括虚拟机JVM和核心类库
│ │ │ │ 如果只需要运行java程序,有JRE即可
└─ └─ └──────────────────────────────────┘
┌───────┐┌───────┐┌───────┐┌───────┐ JVM: Java Virtual Machine
│Windows││ Linux ││ macOS ││others │ Java的跨平台特性就得益于JVM
└───────┘└───────┘└───────┘└───────┘
简单而言,使用JDK的开发工具完成的java程序,交给JRE去运行。
从Oracle的官网下载 JDK安装。
安装完成后设置环境变量:
设置一个JAVA_HOME
的环境变量,它指向JDK的安装目录。
通常Eclipse/IntelliJ Idea/Tomcat等软件就是通过搜索JAVA_HOME变量来找到并使用安装好的jdk。
C:\Program Files\Java\jdk-14
然后,把JAVA_HOME
的bin
目录附加到系统环境变量PATH
上。
%JAVA_HOME%\bin;
把JAVA_HOME
的bin
目录添加到PATH
中是为了在任意文件夹下都可以运行java
。打开命令提示符窗口,输入命令java -version
,即可看到版本信息。
bin
目录下的java
可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码。
之后设置CLASSPATH
环境变量:
JDK在默认情况下会到当前工作目录下(变量值用“.
”表示)以及JDK的lib目录下寻找所需的class文件,因此如果Java程序放在这两个目录中,即使不设置CLASSPATH变量执行环境也可以找得到。但是如果Java程序放在其他目录下,运行时则需要设置CLASSPATH变量。
.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar
但是廖老师在讲classpath时提到不用设置,参考https://www.liaoxuefeng.com/wiki/1252599548343744/1260466914339296。他说设置classpath是因为JVM需要知道,如果要加载一个Hello的类,应该去哪搜索对应的Hello.class
文件。
他推荐在启动JVM时设置classpath
,就是给java
命令传入-classpath
或-cp
参数:
java -cp .;C:\work\project1\bin Hello
不传的话默认就是当前目录。
我们在自己编写的class
中,会引用Java核心库的class
,这些又该去哪里找?
就是上面教程说的lib
目录下的tools.jar
,rt.jar
。但廖老师说,根本不需要告诉JVM如何去Java核心库查找class
,JVM怎么可能笨到连自己的核心库在哪都不知道?所以尽量不要设置classpath
!
创建一个Hello.java文件,输入如下代码:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!"); //打印hello world!
}
}
java是完全面向对象的语言,一个程序的基本单位就是class
,所以最外层是一个类,类名必须和文件名一致,一般类名必须以英文字母开头,后接字母,数字和下划线的组合,**建议以大写字母开头,不同单词之间大写字母分隔。**如:NoteBook,不建议Note_Book。
pubilc
表示该class
是公开的,不写的话这个类就无法从命令行执行。
main方法是Java程序的固定入口方法,其必须是静态方法,方法名必须为main
,括号内的参数必须是String数组。方法名的命名建议首字母小写,不同单词之间以大写字母分隔。如:playVR,不建议play_VR。
在这个方法里面我们写了一条语句,语句才是真正的执行代码。其向控制台打印一个hello,world!
运行java程序:
┌──────────────────┐
│ Hello.java │<─── source code
└──────────────────┘
│ compile 控制台输入javac Hello.java
▼
┌──────────────────┐
│ Hello.class │<─── byte code
└──────────────────┘
│ execute 控制台输入java Hello
▼
┌──────────────────┐
│ Run on JVM │
└──────────────────┘
Java 11新增了一个功能,它可以直接运行一个单文件源码。
$ java Hello.java
Hello, world!
在Java中,变量分为两种:基本类型的变量和引用类型的变量。
变量必须先定义后使用:
int x = 1;
定义时不写初始值,默认赋值0
。
基本数据类型:
整数类型:byte,short,int,long
浮点数类型(小数):float,double
参考:https://blog.csdn.net/a327369238/article/details/52354811
float: -3.4x 1 0 38 10^{38} 1038~3.4x 1 0 38 10^{38} 1038 即− 2 128 2^{128} 2128~ 2 128 2^{128} 2128,占用4个字节
double: 最大可表示1.79x 1 0 308 10^{308} 10308,占用8个字节
字符类型:char
占用2个字节,因为除了可表示标准的ASCII外,还可以表示一个Unicode字符
布尔类型:boolean
理论上存储布尔类型只需要1 bit,但是通常JVM内部会把boolean
表示为4字节整数。
引用类型:
除了上述基本类型的变量,剩下的都是引用类型。例如,引用类型最常用的就是String
字符串:
String s = "hello";
引用类型的变量类似于C语言的指针,它内部存储一个“地址”,指向某个对象在内存的位置。
常量:
定义变量的时候,如果加上final
修饰符,这个变量就变成了常量:常量名通常全部大写。
final double PI = 3.14;
常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。
变量作用域:
在语句块{}
中定义的变量,它有一个作用域,就是从定义处开始,到语句块结束。
var关键字:
var sb = new StringBuilder();
等价于:
StringBuilder sb = new StringBuilder();
使用var
定义变量,仅仅是少写了变量类型而已。
在Java的计算表达式中,
数值计算运算优先级从高到低依次是:
()
!
~
++
--
*
/
%
+
-
<<
>>
>>>
移位运算(只对于整数)&
位运算|
+=
-=
*=
/=
关系运算符的优先级从高到低依次是:
!
>
,>=
,<
,<=
==
,!=
&&
||
三元运算b ? x : y
会首先计算b
,如果b
为true
,则只计算x
,否则,只计算y
。此外,x
和y
的类型必须相同,因为返回值不是boolean
,而是x
和y
之一。
类型转换:
两个整数相除只能得到结果的整数部分。
int x = 1 / 10 //结果是0
在运算过程中,如果参与运算的数类型不一致,那么计算结果为较大类型。
short s = 1234;
int i = 123456;
float x = s + i; // s自动转型为int //结果自动转为float
强制类型转换:
int i = 12345;
short s = (short) i; // 12345
int i2 = 12345678;
short s2 = (short) i2; // 24910 结果是错的
int n3 = (int) (12.7); // 12 浮点数的小数部分会被丢掉
int n4 = (int) 1.2e20; // 2147483647 溢出时,返回整型的最大值
浮点数误差:
浮点数常常无法精确表示。例如:浮点数0.1
在计算机中就无法精确表示,因为十进制的0.1
换算成二进制是一个无限循环小数,很显然,无论使用float
还是double
,都只能存储一个0.1
的近似值。但是,0.5
这个浮点数又可以精确地表示。
double x = 1.0 / 10; //0.1
double y = 1 - 9.0 / 10; //0.09999999999999998
由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数.
溢出:
整数运算在除数为0
时会报错,而浮点数运算在除数为0
时,不会报错,但会返回几个特殊值:
NaN
表示Not a NumberInfinity
表示无穷大-Infinity
表示负无穷大短路运算
布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
Java在内存中总是使用Unicode表示字符,所以,一个字符占用两个字节。
在java中,用双引号"..."
表示字符串。一个字符串可以存储0个到任意个字符。
常见的转义字符包括:
\"
表示字符"
\'
表示字符'
\\
表示字符\
\n
表示换行符\r
表示回车符\t
表示Tab\u####
表示一个Unicode编码的字符Java的编译器对字符串做了特殊照顾,可以使用+
连接任意字符串和其他数据类型。其他类型会先转换成字符串再拼接。
从Java 13开始,字符串可以用"""..."""
表示多行字符串(Text Blocks)。
字符串不可变特性:
字符串赋值变的不是字符串,而是变量的指向。
看一下这样一段代码:
String s = "hello";
String t = s;
s = "world";
System.out.println(t); // t是"hello"还是"world"
执行String s = "hello";
时,JVM虚拟机先创建字符串"hello"
(其实就是char[]数组),然后,把字符串变量s
指向它:
s
│
▼
┌───┬───────────┬───┐
│ │ "hello" │ │
└───┴───────────┴───┘
执行String t = s;
时,t也指向"hello"
字符串。
之后执行s = "world";
时,JVM虚拟机先创建字符串"world"
,然后,把字符串变量s
指向它:
s ──────────────┐
│
▼
┌───┬───────────┬───┬───────────┬───┐
│ │ "hello" │ │ "world" │ │
└───┴───────────┴───┴───────────┴───┘
原来的字符串"hello"
还在,只是我们无法通过变量s
访问它而已。因此,字符串的不可变是指字符串内容不可变。
所以最后s
指向"world"
,t
指向"hello"
。
空值null
引用类型的变量可以指向一个空值null
,它表示不存在,即该变量不指向任何对象。
String s1 = null; // s1是null
String s2; // 没有赋初值值,s2也是null
String s3 = s1; // s3也是null
String s4 = ""; // s4指向空字符串,不是null
创建:
int[] ns = new int[5]; //指定数组大小创建
int[] ns = new int[] {
68, 79, 91, 85, 62 }; //指定内容创建数组
int[] ns = {
68, 79, 91, 85, 62 }; //简写
Java的数组有几个特点:
0
,浮点型是0.0
,布尔型是false
;要访问数组中的某一个元素,需要使用索引。数组索引从0
开始。
可以用数组变量.length
获取数组大小。
数组是引用类型:
执行ns = new int[] { 68, 79, 91, 85, 62 };
时,它指向一个5个元素的数组:
ns
│
▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │68 │79 │91 │85 │62 │ │
└───┴───┴───┴───┴───┴───┴───┘
之后执行ns = new int[] { 1, 2, 3 };
时,它指向一个新的3个元素的数组:
ns ──────────────────────┐
│
▼
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ │68 │79 │91 │85 │62 │ │ 1 │ 2 │ 3 │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
和字符串一样,原有的5个元素的数组并没有改变,只是无法通过变量ns
引用到它们而已。
直接打印数组变量,得到的是数组在JVM中的引用地址:
int[] ns = {
1, 1, 2, 3, 5, 8 };
System.out.println(ns); // 类似 [I@7852e922
字符串数组
定义一个字符串数组:
String[] names = {
"ABC", "XYZ", "zoo"
};
对于String[]
类型的数组变量names
,它实际上包含3个元素,但每个元素都指向某个字符串对象:
┌─────────────────────────┐
names │ ┌─────────────────────┼───────────┐
│ │ │ │ │
▼ │ │ ▼ ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │░░░│░░░│░░░│ │ "ABC" │ │ "XYZ" │ │ "zoo" │ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┘
│ ▲
└─────────────────┘
对names[1]
进行赋值,例如names[1] = "cat";
,效果如下:
┌─────────────────────────────────────────────────┐
names │ ┌─────────────────────────────────┐ │
│ │ │ │ │
▼ │ │ ▼ ▼
┌───┬───┬─┴─┬─┴─┬───┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│ │░░░│░░░│░░░│ │ "ABC" │ │ "XYZ" │ │ "zoo" │ │ "cat" │ │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
│ ▲
└─────────────────┘
输入与输出:
一般使用System.out.println()
来向屏幕输出一些内容。
println
是print line的缩写,表示输出并换行,如果输出后不想换行,可以用print()
。
如果要把数据显示成我们期望的格式,就需要使用格式化输出的功能。格式化输出使用System.out.printf()
,通过使用占位符%?
,printf()
可以把后面的参数格式化成指定格式:
double d = 3.1415926;
System.out.printf("%.2f\n", d); // 显示两位小数3.14
System.out.printf("%.4f\n", d); // 显示4位小数3.1416
Java的格式化功能提供了多种占位符:
占位符 | 说明 |
---|---|
%d | 格式化输出整数 |
%x | 格式化输出十六进制整数 |
%f | 格式化输出浮点数 |
%e | 格式化输出科学计数法表示的浮点数 |
%s | 格式化字符串 |
Java提供Scanner对象来方便输入,读取对应的类型可以使用:scanner.nextLine()
/ nextInt()
/ nextDouble()
/ …
下面是一个例子:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}
if判断:
if (条件) {
// 条件满足时执行
}else {
//条件不满足时执行
}
注意:在Java中,判断值类型的变量是否相等,可以使用==
运算符。但是,要判断引用类型的变量内容是否相等,必须使用equals()
方法。
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
if (s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
注意:执行语句s1.equals(s2)
时,如果变量s1
为null
,会报NullPointerException
,要避免这个错误我们可以用短路运算符:
String s1 = null;
if (s1 != null && s1.equals("hello")) {
System.out.println("hello");
}
switch判断:
switch (option) {
case 1:
System.out.println("Selected 1");
break;
case 2:
System.out.println("Selected 2");
break;
case 3:
System.out.println("Selected 3");
break;
default:
System.out.println("Not selected");
break;
}
switch的匹配项里面可以匹配字符串。
Java 12开始,switch
语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要break
语句:
switch (fruit) {
case "apple" -> System.out.println("Selected apple");
case "pear" -> System.out.println("Selected pear");
case "mango" -> {
System.out.println("Selected mango");
System.out.println("Good choice!");
}
default -> System.out.println("No fruit selected");
}
新的switch语法还可以直接返回值,如果还需要复杂的语句处理后返回,可以用yield
语句:
int opt = switch (fruit) {
case "apple" -> 1;
case "pear", "mango" -> 2;
default -> {
int code = fruit.hashCode();
yield code; // switch语句返回值
}
}; //注意分号结束
while循环:
while (条件表达式) {
条件满足时执行循环语句
}
while
循环是先判断循环条件,再循环,因此,有可能一次循环都不做。
do while
循环则是先执行循环,再判断条件,条件满足时继续循环,条件不满足时退出。
do {
执行循环语句
} while (条件表达式);
for循环:
for (初始条件; 循环检测条件; 循环后更新计数器) {
// 执行语句
}
for each循环:
for循环常用于遍历数组:
int[] ns = {
1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
System.out.println(ns[i]);
}
for each
循环可以更简单地遍历数组:
int[] ns = {
1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
除了数组外,for each
循环能够遍历所有“可迭代”的数据类型,包括List
、Map
等。
break和continue:
break
会跳出当前循环,也就是整个循环都不会执行了。而continue
则是提前结束本次循环,直接继续执行下次循环。
对于数组的循环打印,除了可以用循环,Java标准库还提供了Arrays.toString()
方法:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = {
1, 1, 2, 3, 5, 8 };
System.out.println(Arrays.toString(ns));
}
}
冒泡排序算法:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] ns = {
28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
// 排序前:
System.out.println(Arrays.toString(ns));
for (int i = 0; i < ns.length - 1; i++) {
for (int j = 0; j < ns.length - i - 1; j++) {
if (ns[j] > ns[j+1]) {
// 交换ns[j]和ns[j+1]:
int tmp = ns[j];
ns[j] = ns[j+1];
ns[j+1] = tmp;
}
}
}
//Arrays.sort(ns); //Java的标准库已经内置的排序功能
// 排序后:
System.out.println(Arrays.toString(ns));
}
}
注意:对数组排序实际上修改了数组本身。
在内存中,一个整型数组表示如下:
┌───┬───┬───┬───┐
ns───>│ 9 │ 3 │ 6 │ 5 │
└───┴───┴───┴───┘
当我们调用Arrays.sort(ns);
后,这个整型数组在内存中变为:
┌───┬───┬───┬───┐
ns───>│ 3 │ 5 │ 6 │ 9 │
└───┴───┴───┴───┘
定义:
int[][] ns = {
{
1, 2, 3, 4 },
{
5, 6, 7, 8 },
{
9, 10, 11, 12 }
};
调用System.out.println(ns.length);
,输出为3。其内存结构如下:
┌───┬───┬───┬───┐
┌───┐ ┌──>│ 1 │ 2 │ 3 │ 4 │
ns ─────>│░░░│──┘ └───┴───┴───┴───┘
├───┤ ┌───┬───┬───┬───┐
│░░░│─────>│ 5 │ 6 │ 7 │ 8 │
├───┤ └───┴───┴───┴───┘
│░░░│──┐ ┌───┬───┬───┬───┐
└───┘ └──>│ 9 │10 │11 │12 │
└───┴───┴───┴───┘
所以,访问二维数组的某个元素需要使用array[row][col]
。
打印二维数组可以用两层for循环,也可以使用Java标准库的Arrays.deepToString(ns)
。
main
方法可以接受一个命令行参数,它是一个String[]
数组。这个命令行参数由JVM接收用户输入并传给main
方法。
比如在命令行运行程序Main,给他传入一个参数-version:
$ java Main -version
以下程序就可以把-version返回给我们:
public class Main {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
这里的内容写成了xmind思维导图。有时间再整理。
在Java中,String
是一个引用类型,它本身也是一个class
。
String s = "Hello!";
实际上字符串在String
内部是通过一个char[]
数组表示的:
String s = new String(new char[] {
'H', 'e', 'l', 'l', 'o', '!'});
Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的private final char[]
字段,以及没有任何修改char[]
的方法实现的。
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
java字符串是引用变量,所以不能用==
进行比较。但是上面的程序==
输出为true。原因是
Java编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池,自然s1
和s2
的引用就是相同的。
字符串操作:
//大写
s.toUpperCase()
//小写
s.toLowerCase()
//比较
s.equals("hello")
//比较忽略大小写
s.equalsIgnoreCase("Hello")
// 是否包含子串:
"Hello".contains("ll"); // true
//搜索子串
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true
//提取子串,索引号是从0开始的
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"
//trim()方法可以移除字符串首尾空白字符。空白字符包括空格,\t,\r,\n
" \tHello\r\n ".trim(); // "Hello"
//isEmpty()和isBlank()判断字符串是否为空和空白字符串
"".isEmpty(); // true,因为字符串长度为0
" ".isEmpty(); // false,因为字符串长度不为0
" \n".isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符
//替换字符串
String s = "hello";
s.replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w'
s.replace("ll", "~~"); // "he~~o",所有子串"ll"被替换为"~~"
//正则表达式替换字符串
String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
//split()方法分割字符串
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
//拼接字符串使用静态方法join()
String[] arr = {
"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"
格式化字符串
//formatted()方法和format()静态方法,传入其他参数,替换占位符,然后生成新的字符串
String s = "Hi %s, your score is %d!";
System.out.println(s.formatted("Alice", 80));
System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
有几个占位符,后面就传入几个参数。参数类型要和占位符一致。
%s
:显示字符串;%d
:显示整数;%x
:显示十六进制整数;%f
:显示浮点数。可以带格式,如%.2f
表示显示两位小数。类型转换
//静态方法valueOf()将其他类型转换成字符串
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
要把字符串转换为其他类型,就需要根据情况。
int n1 = Integer.parseInt("123"); // 123
boolean b1 = Boolean.parseBoolean("true"); // true
String
和char[]
类型互相转换:
char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String
new String(char[])
创建新的String
实例时,它并不会直接引用传入的char[]
数组,而是会复制一份,所以,修改外部的char[]
数组不会影响String
实例内部的char[]
数组。
字符编码:
在Java中,char
类型实际上就是两个字节的Unicode
编码。String
也总是以Unicode编码表示。
如果我们要手动把字符串转换成其他编码,可以这样做:
byte[] b = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b = "Hello".getBytes("GBK"); // 按GBK编码转换
注意:转换编码后,就不再是char
类型,而是byte
类型表示的数组。
如果要把已知编码的byte[]
转换为String
,可以这样做:
byte[] b = ...
String s1 = new String(b, "GBK"); // 按GBK转换
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8转换
String s = "";
for (int i = 0; i < 1000; i++) {
s = s + "," + i;
}
上述程序在拼接字符串的时候,Java每次都会创建新的字符串,扔掉旧的字符串。这样,绝大部分字符串都是临时对象,不但浪费内存,还会影响GC效率。
为了能高效拼接字符串,Java标准库提供了StringBuilder
,可以预分配缓存区:
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(',')
.append(i);
}
sb.insert(0,"index");
String s = sb.toString();
查看StringBuilder
的源码,可以发现,进行链式操作的关键是,定义的append()
方法会返回this
,这样,就可以不断调用自身的其他方法。