Java复习笔记-基础
Java复习笔记
- 一、什么是JDK、JRE、JVM
- 二、Keyword-关键字
- 三、variable-变量
- 浮点数类型-float和double
- 字符类型-char
- 基本数据类型变量间运算规则
- 基本数据类型与 String 的运算
- ++和++
- 四、逻辑运算符
- 五、流程控制语句
- 关于if else 和 switch
- for循环
- while循环
- do while循环
- 六、Array-数组
- 数组的声明和初始化
- 数组在JVM中内存划分:
- 数组的排序算法
- 冒泡排序
Java体系平台
- Java SE(Java Standard Edition)标准版
- 支持面向桌面级应用(如 Windows 下的应用程序)的 Java 平台,即定位个人计算机的应用开发。
- 包括用户界面接口 AWT 及 Swing,网络功能与国际化、图像处理能力以及输入输出支持等。
- 此版本以前称为 J2SE
- Java EE(Java Enterprise Edition)企业版
- 为开发企业环境下的应用程序提供的一套解决方案,即定位在服务器端的Web 应用开发。
- JavaEE 是 JavaSE 的扩展,增加了用于服务器开发的类库。如:Servlet 能够延伸服务器的功能,通过请求-响应的模式来处理客户端的请求;JSP 是一种可以将 Java 程序代码内嵌在网页内的技术。
- 版本以前称为 J2EE
- Java ME(Java Micro Edition)小型版
- 支持 Java 程序运行在移动终端(手机、机顶盒)上的平台,即定位在消费性电子产品的应用开发
- JavaME 是 JavaSE 的内伸,精简了 JavaSE 的核心类库,同时也提供自己的扩展类。增加了适合微小装置的库 javax.microedition.io.*等。
- 此版本以前称为 J2ME
PS : Android 开发不等同于 Java ME 的开发
一、什么是JDK、JRE、JVM
JDK (Java Development Kit):是 Java 程序开发工具包,包含 JRE 和开发人员使用的工具。
JRE (Java Runtime Environment) :是 Java 程序的运行时环境,包含 JVM 和运行时所需要的核心类库。
JDK = JRE + 开发工具集(例如 Javac 编译工具等)
JRE = JVM + Java SE 标准类库
JVM(Java Virtual Machine ,Java 虚拟机):是一个虚拟的计算机,是 Java 程序的运行环境。JVM 具有指令集并使用不同的存储区域,负责执行指令,管理数据、内存、寄存器。
我们编写的 Java 代码,都运行在 JVM 之上。正是因为有了 JVM,才使得 Java程序具备了跨平台性。
使用JVM前后对比
JVM的自动内存管理(内存分配、内存回收)
Java 程序在运行过程中,涉及到运算的数据的分配、存储等都由 JVM 来完成
- Java 消除了程序员回收无用内存空间的职责。提供了一种系统级线程跟踪存储空间的分配情况,在内存空间达到相应阈值时,检查并释放可被释放的存储器空间。
- GC 的自动回收,提高了内存空间的利用效率,也提高了编程人员的效率,很大程度上减少了因为没有释放空间而导致的内存泄漏。
既然JVM能够自动管理内存,是否还存在内存泄漏问题?Yes
JVM 通过 GC 自动回收不可达对象(即没有任何引用指向的对象)的内存,但 GC 无法判断“逻辑上无用但技术上可达”的对象。这就是内存泄漏的根源。
- 长生命周期的对象持有短生命周期对象的引用,例如静态集合存储了一个不在需要的对象,该对象无法回收。
- 未关闭的资源(隐式内存泄漏),例如数据库连接、文件流、。
- 不合理的键对象设计(如 HashMap),例如使用可变对象(如 StringBuilder)作为 HashMap 的键,StringBuilder内容修改后无法通过 get() 访问原值,但键仍被 Map 引用。
只要对象被根引用(如静态变量、线程栈变量)间接引用,GC 就会认为它“存活”。
二、Keyword-关键字
定义:被 Java 语言赋予了特殊含义,用做专门用途的字符串(或单词),例如有 class、public 、 static 、 void 等,这些单词已经被 Java 定义好了。
特点:关键字都是小写字母
true,false,null 不在其中,它们看起来像关键字,其实是字面量,表示特殊的布尔值和空值。
三、variable-变量
变量的概念:
- 内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化
- 变量的构成包含三个要素:数据类型、变量名、存储的值
- Java 中变量声明的格式:数据类型 变量名 = 变量值
变量是程序中不可或缺的组成单位,最基本的存储单元。
使用变量注意:
- Java 中每个变量必须先声明,后使用。
- 使用变量名来访问这块区域的数据。
- 变量的作用域:其定义所在的一对{ }内。
- 变量只有在其作用域内才有效。出了作用域,变量不可以再被调用。
- 同一个作用域内,不能定义重名的变量。
变量的作用:用于在内存中保存数据。
Java 中变量的数据类型分为两大类:
基本数据类型:包括 整数类型、浮点数类型、字符类型、布尔类型。
引用数据类型:包括数组、 类、接口、枚举、注解、记录
基本数据类型的表示范围:
- 定义 long 类型的变量,赋值时需要以"l"或"L"作为后缀。
- Java 程序中变量通常声明为 int 型,除非不足以表示较大的数,才使用 long。
- Java 的整型常量默认为 int 型。
浮点数类型-float和double
float:单精度,尾数可以精确到 7 位有效数字。很多情况下,精度很难满足需求。
double:双精度,精度是 float 的两倍。通常采用此类型。
定义 float 类型的变量,赋值时需要以"f"或"F"作为后缀。
Java 的浮点型常量默认为 double 型。
并不是所有的小数都能可以精确的用二进制浮点数表示。二进制浮点数不能精确的表示 0.1、0.01、0.001 这样 10 的负次幂。浮点类型 float、double 的数据不适合在不容许舍入误差的金融计算领域。如果需要精确数字计算或保留指定位数的精度,需要使用 BigDecimal 类。
//测试 1:(解释见章末企业真题:为什么 0.1 + 0.2 不等于 0.3)
System.out.println(0.1 + 0.2);//0.30000000000000004
//测试 2:
float ff1 = 123123123f;
float ff2 = ff1 + 1;
System.out.println(ff1); //1.2312312E8
System.out.println(ff2); //1.2312312E8
System.out.println(ff1 == ff2); //true
测试1:
计算机使用 二进制(base-2) 存储浮点数,而人类通常使用 十进制(base-10)。十进制小数(如 0.1 和 0.2)在二进制中是 无限循环小数,无法精确存储。
测试2:
float 的精度不足以区分 123123123 和 123123124,导致 ff1 + 1 的计算结果仍然是 ff1。
计算机存储单位:
字节(Byte):是计算机用于计量存储容量的基本单位,一个字节等于 8 bit。
位(bit):是数据存储的最小单位。二进制数系统中,每个 0 或 1 就是一个位,叫做 bit(比特),其中 8 bit 就称为一个字节(Byte)。
转换关系:
8 bit = 1 Byte
1024 Byte = 1 KB
1024 KB = 1 MB
1024 MB = 1 GB
1024 GB = 1 TB
字符类型-char
Java 中的所有字符都使用 Unicode 编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符。
转义字符 | 说明 | Unicode 表示方式 |
---|---|---|
\n | 换行符 | \u000a |
\t | 制表符 | \u0009 |
" | 双引号 | \u0022 |
’ | 单引号 | \u0027 |
\ | 反斜线 | \u005c |
\b | 退格符 | \u0008 |
\r | 回车符 | \u000 |
char 类型是可以进行运算的。因为它都对应有 Unicode 码,可以看做是一个数值。
拓展:Java 虚拟机中没有任何供 boolean 值专用的字节码指令,Java 语言表达所操作的 boolean 值,在编译之后都使用 java 虚拟机中的 int 数据类型来代替:true 用 1 表示,false 用 0 表示。——《java 虚拟机规范 8 版》
基本数据类型变量间运算规则
在 Java 程序中,不同的基本数据类型(只有 7 种,不包含 boolean 类型)变量的值经常需要进行相互转换。
转换的方式有两种:自动类型提升和强制类型转换。
自动类型提升
规则:将取值范围小(或容量小)的类型自动提升为取值范围大(或容量大)的类型 。
基本数据类型的转换规则如图所示:
当把存储范围小的值(常量值、变量的值、表达式计算的结果值)赋值给了存储范围大的变量时:
int i = 'A';//char 自动升级为 int,其实就是把字符的编码值赋值给 i 变量了
double d = 10;//int 自动升级为 double
long num = 1234567; //右边的整数常量值如果在 int 范围呢,编译和运行都可以通过,这里涉及到数据类型转换
//byte bigB = 130;//错误,右边的整数常量值超过 byte 范围
long bigNum = 12345678912L;//右边的整数常量值如果超过 int 范围,必须加 L,显式表示 long 类型。否则编译不通过
当存储范围小的数据类型与存储范围大的数据类型变量一起混合运算时,会按照其中最大的类型运算。
int i = 1;
byte b = 1;
double d = 1.0;
double sum = i + b + d;//混合运算,升级为 double
当 byte,short,char 数据类型的变量进行算术运算时,按照 int 类型处理。
byte b1 = 1;
byte b2 = 2;
byte b3 = b1 + b2;//编译报错,b1 + b2 自动升级为 int
char c1 = '0';
char c2 = 'A';
int i = c1 + c2;//至少需要使用 int 类型来接收
System.out.println(c1 + c2);//113
强制类型转换
规则:将取值范围大(或容量大)的类型强制转换成取值范围小(或容量小)的类型。
自动类型提升是 Java 自动执行的,而强制类型转换是自动类型提升的逆运算,需要我们自己手动执行。
当把存储范围大的值(常量值、变量的值、表达式计算的结果值)强制转换为存储范围小的变量时,可能会损失精度或溢出。
int i = (int)3.14;//损失精度
double d = 1.2;
int num = (int)d;//损失精度
int i = 200;
byte b = (byte)i;//溢出
当某个值想要提升数据类型时,也可以使用强制类型转换。这种情况的强制类型转换是没有风险的,通常省略。
int i = 1;
int j = 2;
double bigger = (double)(i/j);
声明 long 类型变量时,可以出现省略后缀的情况。float 则不同。
long l1 = 123L;
long l2 = 123;//如何理解呢? 此时可以看做是 int 类型的 123 自动类型提升为 long 类型
//long l3 = 123123123123; //报错,因为 123123123123 超出了 int 的范围。
long l4 = 123123123123L;
//float f1 = 12.3; //报错,因为 12.3 看做是 double,不能自动转换为 float
类型
float f2 = 12.3F;
float f3 = (float)12.3;
例题Demo:
1)short s = 5;s = s-2; //判断:no ,s - 2 运算时,short 会被 自动提升为 int(因为 2 是 int) ,没有进行强制转换报错。
2) byte b = 3;b = b + 4; //判断:no , b + 4 转换为int,无法复制给byteb = (byte)(b+4); //判断:yes
3)char c = ‘a’; //'a' 的 ASCII 码是 97int i = 5;float d = .314F;double result = c+i+d; //判断:yes ,char也是可以运算的,结果自动类型转换为double
4) byte b = 5;short s = 3;short t = s + b; //判断:no,运算结果是 int,不能直接赋值给 short。
基本数据类型与 String 的运算
String 不是基本数据类型,属于引用数据类型,使用一对""来表示一个字符串,内部可以包含 0 个、1 个或多个字符。
- 任意八种基本数据类型的数据与 String 类型只能进行连接“+”运算,且结果一定也是 String 类型
System.out.println("" + 1 + 2);//12
int num = 10;
boolean b1 = true;
String s1 = "abc";
String s2 = s1 + num + b1;
System.out.println(s2);//abc10true
- String 类型不能通过强制类型()转换,转为其他的类型
String str = "123";
int num = (int)str;//错误的
int num = Integer.parseInt(str);//正确的,借助包装类的方法才能转
++和++
和其他变量放在一起使用或者和输出语句放在一起使用,前++和后++就产生了不同。
• 变量前++ :变量先自增 1,然后再运算。
• 变量后++ :变量先运算,然后再自增 1。
public class ArithmeticTest4 {
public static void main(String[] args) {
// 其他变量放在一起使用
int x = 3;
//int y = ++x; // y 的值是 4,x 的值是 4,
int y = x++; // y 的值是 3,x 的值是 4
System.out.println(x);
System.out.println(y);
System.out.println("==========");// 和输出语句一起
int z = 5;
//System.out.println(++z);// 输出结果是 6,z 的值也是 6
System.out.println(z++);// 输出结果是 5,z 的值是 6
System.out.println(z);}
}
四、逻辑运算符
运算符说明:
- & 和 &&:表示"且"关系,当符号左右两边布尔值都是 true 时,结果才能为 true。否则,为 false。
- | 和 || :表示"或"关系,当符号两边布尔值有一边为 true 时,结果为true。当两边都为 false 时,结果为 false
- ! :表示"非"关系,当变量布尔值为 true 时,结果为 false。当变量布尔值为 false 时,结果为 true。
- ^ :当符号左右两边布尔值不同时,结果为 true。当两边布尔值相同时,结果为 false。
区分“&”和“&&”:
- 相同点:如果符号左边是 true,则二者都执行符号右边的操作
- 不同点:
- & : 如果符号左边是 false,则继续执行符号右边的操作
- && :如果符号左边是 false,则不再继续执行符号右边的操作
建议:开发中,推荐使用 &&
区分“|”和“||”:
- 相同点:如果符号左边是 false,则二者都执行符号右边的操作
- 不同点:
- | : 如果符号左边是 true,则继续执行符号右边的操作
- || :如果符号左边是 true,则不再继续执行符号右边的操作
建议:开发中,推荐使用 ||
编码与解码:
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。
字符编码(Character Encoding):就是一套自然语言的字符与二进制数之间的对应规则。
字符集:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
五、流程控制语句
关于if else 和 switch
凡是使用 switch-case 的结构都可以转换为 if-else 结构。反之,不成立。
-
开发经验:如果既可以使用 switch-case,又可以使用 if-else,建议使用 switchcase。因为效率稍高。
- if-else 语句优势
- if 语句的条件是一个布尔类型值,if 条件表达式为 true 则进入分支,可以用于范围的判断,也可以用于等值的判断,使用范围更广。
- switch 语句的条件是一个常量值(byte,short,int,char,枚举,String),只能判断某个变量或表达式的结果是否等于某个常量值,使用场景较狭窄。
- switch 语句优势
- 当条件是判断某个变量或表达式是否等于某个固定的常量值时,使用 if 和 switch 都可以,习惯上使用 switch 更多。因为效率稍高。当条件是区间范围的判断时,只能使用 if 语句。
- 使用 switch 可以利用穿透性,同时执行多个分支,而 if…else 没有穿透性。
- if-else 语句优势
在Java中,switch-case结构通常比等价的if-else链更高效,主要原因如下:
-
跳表实现机制
switch-case:现代JVM通常使用跳表(jump table)或哈希表实现,时间复杂度接近O(1)if-else:需要按顺序逐个检查条件,时间复杂度为O(n)
-
编译器优化
对于密集的case值,编译器会生成tableswitch指令,直接通过索引跳转对于稀疏的case值,使用lookupswitch指令,性能仍优于if-else
-
分支预测优势
switch-case的分支模式更规律,CPU的分支预测器能更有效地预测 -
字节码差异
if-else会生成一系列条件跳转指令switch-case生成专门的switch指令,处理更高效
注意事项
- 当case数量很少时(如少于5个),性能差异可能不明显
- Java 7+支持String的switch,但会先转换为hashcode比较
- 对于枚举类型,switch-case同样高效
for循环
语法格式:
for (①初始化部分; ②循环条件部分; ④迭代部分){③循环体部分;
}
执行过程:①-②-③-④-②-③-④-②-③-④-.....-②
int num = 1;
for(System.out.println("a");num < 3; System.out.println("c"),num++){System.out.println("b");
}
输出结果:
a
b
c
b
c
for循环中,初始化部分只执行一次。
while循环
语法格式:
①初始化部分
while(②循环条件部分){③循环体部分;④迭代部分;
}
执行过程:①-②-③-④-②-③-④-②-③-④-...-②
while(循环条件)中循环条件必须是 boolean 类型。
• 注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
• for 循环和 while 循环可以相互转换。二者没有性能上的差别。实际开发中,根据具体结构的情况,选择哪个格式更合适、美观。
• for 循环与 while 循环的区别:初始化条件部分的作用域不同。
do while循环
语法格式:
①初始化部分;
do{
③循环体部分
④迭代部分
}while(②循环条件部分);
执行过程:①-③-④-②-③-④-②-③-④-...-②
• 结尾 while(循环条件)中循环条件必须是 boolean 类型
• do{}while();最后有一个分号
• do-while 结构的循环体语句是至少会执行一次,这个和 for 和 while 是不一样的
• 循环的三个结构 for、while、do-while 三者是可以相互转换的。
三种循环结构都具有四个要素:
- 循环变量的初始化条件
- 循环条件
- 循环体语句块
- 循环变量的修改的迭代表达式
从循环次数角度分析
- do-while 循环至少执行一次循环体语句。
- for 和 while 循环先判断循环条件语句是否成立,然后决定是否执行循环体。
如何选择
- 遍历有明显的循环次数(范围)的需求,选择 for 循环
- 遍历没有明显的循环次数(范围)的需求,选择 while 循环
- 如果循环体语句块至少执行一次,可以考虑使用 do-while 循环
- 本质上:三种循环之间完全可以互相转换,都能实现循环的功能
六、Array-数组
数组(Array):是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。
数组中的概念:
- 数组名
- 下标(或索引)
- 元素
- 数组的长度
数组的特点:
- 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
- 创建数组对象会在内存中开辟一整块连续的空间。占据的空间的大小,取决于数组的长度和数组中元素的类型。
- 数组中的元素在内存中是依次紧密排列的,有序的。
- 数组,一旦初始化完成,其长度就是确定的。数组的长度一旦确定,就不能修改。
- 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
- 数组名中引用的是这块连续空间的首地址。
基本数据类型元素的数组:每个元素位置存储基本数据类型的值
引用数据类型元素的数组:每个元素位置存储对象(本质是存储对象的首地址)
一维数组:存储一组数据。
二维数组:存储多组数据,相当于二维表,一行代表一组数据,只是这里的二维表每一行长度不要求一样。
二维数组相当于一维数组中存储了多个一维数组的引用。
数组的声明和初始化
// TODO 一维数组的声明String[] stringArray;//int[] intArray;// TODO 数组的静态初始化int[] intArray = new int[]{1,2,3};stringArray = new String[]{"aimyon","zutomayo","milet"};// TODO 数组的动态初始化Double[] doubleArray = new Double[4];doubleArray[0] = 3.14;
推荐使用的数组声明方式: 数组类型[] 数组名;
静态初始化
如果数组变量的初始化和数组元素的赋值操作同时进行,那就称为静态初始化。
静态初始化,本质是用静态数据(编译时已知)为数组初始化。此时数组的长度由静态数据的个数决定。
数据类型[] 数组名 = new 数据类型[]{元素 1,元素 2,元素 3,...};
or
数据类型[] 数组名;
数组名 = new 数据类型[]{元素 1,元素 2,元素 3,...};
new:关键字,创建数组使用的关键字。因为数组本身是引用数据类型,所以要用 new 创建数组实体。
动态初始化
数组变量的初始化和数组元素的赋值操作分开进行,即为动态初始化。
动态初始化中,只确定了元素的个数(即数组的长度),而元素值此时只是默认值,还并未真正赋自己期望的值。真正期望的数据需要后续单独一个一个赋值。
数组存储的元素的数据类型[] 数组名字 = new 数组存储的元素的数据类型[长度];
or
数组存储的数据类型[] 数组名字;
数组名字 = new 数组存储的数据类型[长度];
[长度]:数组的长度,表示数组容器中可以最多存储多少个元素。 数组有定长特性,长度一旦指定,不可更改。
数组元素默认值:
数组是引用类型,当我们使用动态初始化方式创建数组时,元素值只是默认值。
对于基本数据类型而言,默认初始化值各有不同。对于引用数据类型而言,默认初始化值为 null(注意与 0 不同!)
String是引用类型,为null
数组在JVM中内存划分:
为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
区域名称 | 作用 | 资源释放 |
---|---|---|
虚拟机栈 | 线程私有(每个线程有自己的栈),用于存储正在执行的每个 Java 方法的局部变量表等。局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 | 方法结束自动出栈 |
堆内存 | 所有线程共享,存储对象(包括数组对象),new 来创建的,都存储在堆内存。(引用对象) | 由 GC 回收 |
方法区(永久代/元空间) | 所有线程共享,存储已被虚拟机加载的类信息、常量、(静态变量)、即时编译器编译后的代码等数据。 | 类卸载时释放 |
本地方法栈 | 线程私有,当程序中调用了 native 的本地方法时,本地方法执行期间的内存区域 | 随方法结束释放 |
程序计数器 | 线程私有(类似JVM栈),程序计数器是 CPU 中的寄存器,它包含每一个线程下一条要执行的指令的地址 | 不回收,没有内存泄漏隐患 |
简单理解:
元空间:运行java程序时,JVM加载.class字节码文件到元空间中存储。(类的存储位置,随类的卸载释放资源)
堆内存:完全二叉树,存储代码中的引用对象。
本地方法栈:线程私有栈,存储非java编写的方法状态
虚拟机栈:线程私有栈,存储用java编写的方法状态。
为什么要用两个栈存储方法状态?
安全隔离:Java 字节码和本地代码的执行环境不同,分开栈可以避免互相干扰。
性能优化:本地方法可能直接操作硬件或系统资源,需要独立的调用约定(如寄存器使用)。
ps: 开发者广泛称虚拟机栈为方法栈。
为什么数组的下标从0开始?
因为第一个元素距离数组首地址间隔 0 个单元格。
int[] x, y[];
//x 是一维数组,y 是二维数组
数组的排序算法
衡量排序算法的优劣:
时间复杂度:分析关键字的比较次数和记录的移动次数
- 常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)<O(nn)
空间复杂度:分析排序算法中需要多少辅助内存
- 一个算法的空间复杂度 S(n)定义为该算法所耗费的存储空间,它也是问题规模 n 的函数。
稳定性:若两个记录 A 和 B 的关键字值相等,但排序后 A、B 的先后次序保持不变,则称这种排序算法是稳定的。
排序算法分类:内部排序和外部排序
- 内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
- 外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。
十大内部排序算法:
冒泡排序
排序思想:
比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较为止。
public static void main(String[] args) {int[] intArray = new int[]{1,11,10,22,5,6,7,8};for (int i = 0; i < intArray.length - 1; i++) {boolean flag = true;//假设数组已经是有序的for (int j = 0; j < intArray.length - 1 - i; j++) {//希望的是 intArray[j] < intArray[j+1]if (intArray[j] > intArray[j + 1]) {//交换 intArray[j]与 intArray[j+1]int temp = intArray[j];intArray[j] = intArray[j + 1];intArray[j + 1] = temp;flag = false;}}if (flag) {break;}}}
每轮结束后,数组末尾的 i 个元素已是最大值且有序,无需再比较,且每次内循环的便利都会对数组进行遍历,如果存在一次遍历没有发生交换,即证明已经有序。