Java基础学习笔记(一) o(* ̄︶ ̄*)o

134 阅读26分钟

Java基础学习笔记

一、Java 三大版本

JavaSE:标准版(桌面程序、控制台开发...)
JavaEE:企业级开发(web端、服务器开发...)
JavaME:嵌入式开发(手机、家电...)

二、Java 语言特点

1. 计算机语言类型有哪些?

根据 执行方式 的不同,计算机语言大致可以分为 编译型语言、解释型语言
1.编译型语言:编译型语言执行的时候,CPU可直接读取可执行代码(机器语言),速度快;但是要根据不同CPU安装对应编译器,还需要根据不同操作系统选用应不同启动代码,不便利
例如:C、C++、Delphi等;其中选取C语言为代表来说明,C编程的基本策略是,用程序把源代码文件转换为可执行文件。
2.解释型语言:解释型语言执行的时候,需要解释器翻译一行,CPU执行一行,速度相对较慢;仅需要根据不同操作系统安装对应解释器,十分便利
例如:Python、JavaScript、Perl、Shell等;其中选取Python为代表来说明,Python编程的基本策略是,用程序把源代码文件一行一行地转换为可执行代码。

根据 编程方式 的不同,计算机语言大致可以分为 面向过程语言、面向对象语言、函数式语言
1.面向过程语言:以 “过程”(函数)为核心,按步骤解决问题,强调代码的顺序执行。
例如:C、Fortran等;
2.面向对象语言:以 “对象” 为核心,通过封装、继承、多态组织代码,适合复杂系统开发。
例如:C#、Java、Ruby等;
3.函数式语言:以 “函数” 为基本单元,强调纯函数(无副作用)和不可变数据,适合并行计算。
例如:Scala、Haskell、Lisp等;

2. Java属于哪种类型的计算机语言?

Java属于半解释半编译型、面向对象型 的语言。
通过Java程序运行流程可分析出半解释半编译的特性;通过Java程序的编程方式可知面向对象的特性。

3. Java程序运行流程

1.源代码(.java)通过Java编译器编译成字节码文件(.class)

// 通过javac命令将java源文件(.java)编译为字节码文件(.class)
javac HelloWorld.java

2.字节码文件(.class)通过Java虚拟机(JVM)执行;
首先Java虚拟机(JVM)通过类加载器加载字节码文件(.class),其次通过字节码校验器进行校验,保证数据安全性,然后将字节码文件(.class)通过解释器即时编译器(JIT)二者双管齐下(半解释半编译)生成机器码,其中解释器负责解释执行即时编译器(JIT)负责将热代码编译为本地机器码并缓存,用以提升效率,最后将机器码输出给操作系统(OS)。

// 通过java命令解释并执行字节码文件(.class)
java HelloWorld

Tips:即时编译技术(JIT)在Java1.2版本中首次被引入,标志着Java从纯解释执行向半解释半编译执行模式的转型。Java虚拟机(JVM)通过即时编译技术(JIT)将热点代码编译为本地机器码,提升了执行效率,使Java拥有接近编译型语言的性能。

Java程序运行流程图例:

java程序运行流程.png

4. Java语言优缺点

优点:

1.跨平台性:Java代码(.java)编译为字节码(.class),由Java虚拟机(JVM)解释执行,屏蔽了不同操作系统的底层差异。在不同平台安装其对应的Java虚拟机(JVM),即可实现“一次编写,到处运行”。一次开发可在Windows、Linux、macOS等多个平台运行,适合分布式系统和云服务。
2.面向对象设计:封装、继承、多态,支持复杂软件架构设计(如 MVC 模式)。代码可复用性高、可维护性强,适合大型团队协作开发。
3.强大的生态系统:提供丰富的API(如集合框架、多线程、网络编程)。第三方框架有Spring(企业开发)、Hibernate(ORM)、JUnit(测试)等,加速开发周期。
4.自动内存管理(GC 垃圾回收机制):JVM自动回收不再使用的对象内存,减少内存泄漏风险。相比C/C++的手动内存管理,开发效率更高,降低调试成本。
5.高性能:频繁执行的热点代码由JVM中的即时编译技术(JIT)即时编译为机器码,接近原生性能。
6.安全性:"字节码校验"JVM在加载字节码时进行安全检查,防止恶意代码。"沙箱机制"限制程序访问系统资源,适合Web应用和移动开发。

缺点:

1.语法复杂:需要编写大量样板代码(如getter/setter、异常处理)。
2.程序迭代速度较慢:大型项目编译耗时较长,相比脚本语言(如 Python)迭代速度慢。

三、JDK、JRE、JVM

20250610145617.png JDK(Java Development Kit):Java开发环境,整个Java的核心,包含了Java开发工具和JRE。

JRE(Java Runtime Environment):Java运行环境,包含虚拟机Java基础类库和Java虚拟机(JVM)。

JVM(Java Virtual Machine):Java虚拟机,源代码(.java)编译成字节码文件(.class)后,字节码文件(.class)在JVM上运行。

四、Java关键字

1. 基本概念

Java 关键字(Keywords)是编程语言中被赋予特殊含义的单词,开发者不能将它们用作变量名、类名或方法名等标识符。

2. 关键字有哪些?

abstractassertbooleanbreakbytecasecatchchar、class、const、continuedefaultdodoubleelseenum、extends、finalfinallyfloatfor、goto、if、implements、importinstanceofint、interface、longnativenewpackageprivateprotectedpublicreturnshortstaticstrictfpsuperswitchsynchronizedthisthrowthrowstransienttryvoidvolatilewhile

Tips:具体含义跟随后续学习深入了解。

五、Java标识符

1. 基本概念

在 Java 中,标识符(Identifier)是用于给类、变量、方法、接口、包等命名的字符序列。它是程序员自定义的名称,用于唯一标识一个编程元素。

2. 命名规则

  1. 只能由 字母(A-Z/a-z)、数字(0-9)、下划线(_)、美元符号($)组成,不能包含空格、标点符号(如#%&等)或其他特殊字符。
    示例myVar_name$valueuser123 是合法的;my-var(特殊字符)、123abc(以数字开头)、class(关键字)是非法的。

  2. 不能以数字开头,标识符必须以 字母(A-Z/a-z)、下划线(_)或美元符号($)开头。
    示例a1 合法,1a 非法。

  3. 不能使用 Java 关键字(如classpublicstatic等)或保留字(如gotoconst)作为标识符。
    示例int(关键字)、goto(保留字)均是非法的。

  4. 区分大小写,Java是大小写敏感的语言,因此 MyClass 和 myclass 是两个不同的标识符。
    示例:变量 age 和 Age 被视为不同变量。

3. 命名规范

  1. 驼峰命名法(Camel Case)

    • 类名、接口名:每个单词首字母大写(大驼峰,Upper Camel Case)。
      示例StudentInfoUserService
    • 变量名、方法名:首单词首字母小写,后续单词首字母大写(小驼峰,Lower Camel Case)。
      示例userNamegetAge()
  2. 包名:全小写,通常用公司域名倒置(避免命名冲突)。
    示例com.xu.liu

  3. 常量名:全大写,单词间用下划线分隔。
    示例MAX_SIZEDEFAULT_VALUE

  4. 避免使用单字符命名(除临时变量或循环变量),推荐用有意义的名称(如countc更易读)。

  5. 不建议使用$_,虽然技术上合法,但$常用于自动生成的代码(如内部类),_在 Java 9 后被保留为关键字(用作无效标识符),因此应尽量避免在自定义标识符中使用。

六、Java数据类型

1.变量

基本概念:在Java中,变量是存储数据的容器,用于存储和操作数据,由数据类型、变量名、值三部分组成。

String name = '刘旭涛';

Tips:

  1. 声明变量时必须指定类型,且变量只能存储该类型的值
  2. 变量的作用域决定变量的可见性,应避免命名冲突。

2. 数据类型

Java数据类型分为两大类:基本数据类型、引用数据类型

基本数据类型(四类八种):
  整数型:byteshortintlong
  浮点型:floatdouble
  字符型:char
  布尔型:boolean

引用数据类型
  除 基本数据类型 以外的,都称之为 引用数据类型

图示数据类型.png

3. 数据类型转换

Java数据类型转换分为两种:自动类型转换(隐式转换)、强制类型转换(显式转换)

自动类型转换(隐式转换):当小范围数据类型赋值给大范围数据类型时,Java 会自动转换。

强制类型转换(显式转换):当大范围数据类型赋值给小范围数据类型时,需要手动显式转换,可能导致数据精度丢失

Tips
  1.范围从小到大:byte → short → int → long → float → double
  2.字符型参与时:char → int → long → float → double
  3.布尔型:boolean 不可与其他类型转换

注意事项
  1.数据溢出:强制转换时可能导致数据溢出(如 int 转 byte)。
  2.精度丢失:浮点数转整数时会截断小数部分。
  3.类型兼容性:boolean 不可与其他类型转换。
  4.字符串转换异常:若字符串格式不合法(如 "abc" 转 int),会抛出 NumberFormatException

示例

------------------------------自动类型转换(隐式转换)------------------------------
// 整数类型转换
int numInt = 100;
long numLong = numInt; // int → long(自动转换)

// 整数到浮点数转换
float numFloat = numInt; // int → float(自动转换)

// 字符到整数转换
char ch = 'A';
int ascii = ch; // char → int(自动转换),结果为 65

// 不同类型运算时的自动转换
int a = 10;
double b = 3.5;
double result = a + b; // int → double(自动转换)

------------------------------强制类型转换(显式转换)------------------------------
// 浮点数到整数的强制转换(精度丢失)
double numDouble = 3.14;
int numInt = (int) numDouble; // 结果为 3(小数部分被截断)

// 大范围整数到小范围整数的强制转换(数据溢出)
long bigNum = 2147483648L; // 超过 int 最大值
int smallNum = (int) bigNum; // 结果为 -2147483648(数据溢出)

// 字符与整数的强制转换
int ascii = 97;
char ch = (char) ascii; // 结果为 'a'

七、Java运算符

1. 算数运算符

在Java中,算术运算符有:加+、减-、乘*、除/、取余%、自增++、自减--

运算符描述示例结果
+加法5 + 27
-减法5 - 23
\*乘法5 * 210
/除法5 / 22(整数相除取整)
%取余5 % 21
++自增(前缀 / 后缀)int a=5; ++a;
int b=2; b++; 
a 变为 6
b 变为 3
--自减(前缀 / 后缀)int a=5; --a;
int b=2; --b; 
a 变为 4
b 变为 1

Tips(关于浮点型数据的除和取余)
1.浮点型数据进行除/、取余%运算时,除数可以为0,不会抛出异常,但会产生特殊值。
  - 正数除以或取余 0:结果为Infinity(正无穷大)
  - 负数除以或取余 0:结果为-Infinity(负无穷大)
  - 零除以或取余 0:结果为NaN(Not a Number,非数字)
2.浮点型数据进行除/、取余%运算时,结果值可以是小数。
  - 除以:浮点型数据进行除/运算时不取整
  - 取余:取余%运算本身就可能余小数
3.浮点型数据进行除/、取余%运算时,结果值的符号规则为:
  - 除以:同号为正,异号为负
  - 取余:与被除数符号一致
4.浮点型数据永远存在精度问题,在Java中推荐使用BigDecimal处理高精度场景。


Tips(关于自增自减)
1.自增自减运算时,分为前缀后缀两种使用方式:
  - 前缀:先将变量的值 加/减 1,然后再使用 增加/减少 后的值进行运算
  - 后缀:先使用变量原来的值进行运算,运算结束后再将变量的值 加/减 1

int a = 5;
int b = ++a; // 先将a加1(a变为6),再将6赋值给b
System.out.println("a = " + a); // 输出:a = 6
System.out.println("b = " + b); // 输出:b = 6

int c = 5;
int d = c++; // 先将5赋值给d,然后c再加1(c变为6)
System.out.println("c = " + c); // 输出:c = 6
System.out.println("d = " + d); // 输出:d = 5

2.在各种运算符的组合使用中,自增自减运算符优先级最高!

int a = 5;
int result = ++a + 3; // 先将a加1(a=6),再计算6+3=9
System.out.println("result = " + result); // 输出:9
System.out.println("a = " + a); // 输出:6

int b = 5;
int result = b++ + 3; // 先计算5+3=8,再将b加1(b=6)
System.out.println("result = " + result); // 输出:8
System.out.println("b = " + b); // 输出:6

3.自增自减赋值给原变量时的问题讨论与分析,先说结论:
  - 前缀自增自减原变量值改变(会加减1)
  - 后缀自增自减原变量值不变(不会加减1)

int x = 8;
x = ++x;// 先对原变量x进行加减运算(x变为9),再将x的值(9)复制出来,最后原变量x又指向了复制出来的 9
System.out.println(x);// 输出 9

int y = 6;
y = y++;// 先将y的值(6)复制出来,再对原变量y进行加减运算(x变为7),最后原变量y又指向了复制出来的 6
System.out.println(y);// 输出 6

4.总结自增自减参与运算时的运算流程!!!
  - 前缀:先将原变量进行加减运算,此时原变量的值已经改变,再将运算后原变量的值复制出来,最后复制出来值参与运算(赋值=也是运算行为,属于赋值运算符)
  - 后缀:先将原变量的值复制出来,再将原变量进行加减运算,此时原变量也已经改变,但是最后参与运算的是之前复制出来的值(赋值=也是运算行为,属于赋值运算符)

// 前缀运算流程分析,示例代码如下:
int x = 8;
x = x + 1;// 先将原变量进行加减运算,此时原变量的值已经改变
int temp = x;// 再将运算后原变量的值复制出来,最后复制出来值参与运算

// 后缀运算流程分析,示例代码如下:
int x = 8;
int temp = x;// 先将原变量的值复制出来
x = x + 1;// 再将原变量进行加减运算,此时原变量也已经改变,但是最后参与运算的是之前复制出来的值

5.建议在代码中清晰地使用自增自减,以提高代码可读性,避免在复杂表达式中混用前缀和后缀形式。

2. 赋值运算符

在Java中,赋值运算符有:等于=、加等于+=、减等于-=、乘等于*=、除等于/=、取余等于%=

运算符描述示例结果
=等于int a = 5; a = 2;a 变为 2
+=加等于int a = 5; a += 2;a 变为 7
-=减等于int a = 5; a -= 2;a 变为 3
*=乘等于int a = 5; a *= 2;a 变为 10
/=除等于int a = 5; a /= 2;a 变为 2
%=取余等于int a = 5; a %= 2;a 变为 1

Tips
1.赋值运算符会自动进行类型转换

byte b1 = 5;
b1 += 2;// 这里2是默认int类型,运算时会自动处理类型转换,不会出现编译错误,等价于 b1 = (byte) (b1 + 2);

2.赋值运算符会先计算右侧表达式的值,再进行赋值操作

int x = 5;
x *= 2 + 3; // 先计算2+3得5,再计算x*5,结果为25

3. 关系运算符

在Java中,关系运算符有:等等于==、不等于!=、大于>、小于<、大于等于>=、小于等于<=

运算符描述示例结果
==等等于5 == 2false
!=不等于5 != 2true
>大于5 > 2true
<小于5 < 2false
>=大于等于5 >= 2true
<=小于等于5 <= 2false

Tips:
1.关系表达式的运算结果必然是布尔类型,也就是truefalse
2.关系运算符经常会在条件语句和循环语句中被使用

// if-else语句
int number = 10;
if (number > 0) {
    System.out.println("正数");
} else {
    System.out.println("非正数");
}

// for循环
for (int i = 0; i <= 5; i++) {
    System.out.print(i + " "); // 输出:0 1 2 3 4 5
}

4. 逻辑运算符

在Java中,逻辑运算符有:与&、或|、短路与&&、短路或||、非!、异或^

  • &:只有当两边表达式都为true时,结果才是true,且两边的表达式都会执行。

  • 短路与&&:只有当两边表达式都为true时,结果才是true,左边为true时,右边不执行。

  • |:只要两边表达式中有一个为true,结果就为true,且两边的表达式都会执行。

  • 短路与||:只有当两边表达式都为true时,结果才是true,左边为true时,右边不执行。

  • !:对单个表达式的布尔值进行取反。

  • 异或^:判断两边表达式是否不同,当两边表达式的布尔值不同时,结果为true;相同时,结果则为false。

// 与& 示例
int x = 5;
System.out.println(false & (x++ > 10)); // 输出false
System.out.println(x); // 输出6,说明x++执行了

// 或| 示例
int x = 5;
System.out.println(true | (x++ > 10)); // 输出true
System.out.println(x); // 输出6,意味着x++执行了

// 短路与&& 示例
int x = 5;
System.out.println(false && (x++ > 10)); // 输出false
System.out.println(x); // 输出5,说明x++未执行

// 短路或|| 示例
int x = 5;
System.out.println(true || (x++ > 10)); // 输出true
System.out.println(x); // 输出5,表明x++未执行

// 非! 示例
boolean a = true;
System.out.println(!a); // 输出false

// 异或^ 示例
boolean a = true;
boolean b = false;
System.out.println(a ^ b); // 输出true

boolean c = true;
boolean d = true;
System.out.println(c ^ d); // 输出false

boolean e = false;
boolean f = false;
System.out.println(e ^ f); // 输出false

5. 位运算符

6. 三元运算符

在Java中,三元运算符的写法是:布尔表达式 ? 表达式1 : 表达式2

int score = 85;// 成绩分数为85
String result = (flag >= 60) ? "及格" : "不及格";// 如果大于等于60分为及格,否则就不及格

Tips
1.表达式1和表达式2的返回数据类型必须相同
2.三元表达式的执行顺序为:先执行布尔表达式,再执行对应的表达式,另外一个表达式不执行

int value1 = 5;
int value2 = 2;
int result = value1++ > 1 ? value1 : value2++;

System.out.println(value1);// 6
System.out.println(value2);// 2
System.out.println(result);// 6

八、流程结构

1. 顺序结构

Java 的基本流程结构就是顺序结构,可以理解为 "代码从上至下一行一行执行"

2. 选择结构

在Java中,选择结构的语句有:if-elseswitch

// if语句基本写法("bol"必须为boolean数据类型,且当"bol"为true时,大括号内的代码才执行;否则直接跳过本段代码)
if(bol) {
  // do something or do nothing...
}
// switch语句的基本写法(依据某个变量的值,从多个分支里挑选一个来执行)
switch(value) {
    case value1:
        // do something or do nothing...
        break;
    case value2:
        // do something or do nothing...
        break;
    default:
        // do something or do nothing...
}

Tips

  1. if-else语句可以有单选、双选、多选、嵌套等多种用法。
单选(基本写法,小括号内参数必须为boolean数据类型,且当"bol"true时,大括号内代码才执行,
否则直接跳过本段代码)
if(bol) {
  // do something or do nothing...
}

双选(小括号内参数必须为boolean数据类型,且当"bol"true时,大括号内代码才执行,
否则必须执行"else"中的代码)
if(bol) {
  // do something or do nothing...
} else {
  // do something else or do nothing...
}

多选(小括号内参数必须为boolean数据类型,且当"bol1"true时,大括号内代码才执行,
否则依次进行判断,一旦满足某一条件,立刻执行该段代码且后续不再进行判断)
if(bol) {
  // do something or do nothing...
} else if(bol2) {
  // do something else or do nothing...
} else if(bol3) {
  // do something else or do nothing...
} else {
  // do something else or do nothing...
}

嵌套(小括号内参数必须为boolean数据类型,且当"bol1"true时,大括号内代码才执行,
然后当"bol2"true时,继续执行...以此类推)
if(bol1) {
  // do something or do nothing...
  if(bol2) {
    // do something else or do nothing...
  }
}
  1. switch语句的注意事项
default 为默认选项,当所有的 case 都不匹配时,执行 default 中的语句。
    
"注意":每次"case"结尾都要加"break",否则会出现 "case穿透",
程序就会无视后续case的条件,直接继续执行下一个case分支的代码,直到碰到终止语句或者switch结束

3. 循环结构

在Java中,循环结构的语句有:foriwhiledo-while增强for

// fori循环的基本写法(多用于明确循环次数时使用)
for (int i = 0; i < 5; i++) {
    System.out.println("循环次数:" + i);
}

// while循环先对条件进行判断,条件为true时才会执行循环体,直到条件不满足才会停止
int i = 0;
while (i < 5) {
    System.out.println("循环次数:" + i);
    i++;
}

// do-while循环会先执行一次循环体,然后再判断条件,条件为true时才会执行循环体,直到条件不满足才会停止
int i = 0;
do {
    System.out.println("循环次数:" + i);
    i++;
} while (i < 5) {
    // do something...
    i++;
}

// 增强for循环主要用于遍历数组或者集合
int[] numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
    System.out.println("本次循环的值为:" + num);
}

Tips:

  1. while 循环一定要注意死循环问题,当条件永远为true时,循环将无限次执行,导致内存溢出

4. break,continue

break:
	杀死当前循环体,直接从当前循环体中跳出,意为"打断""结束"continue:
	跳过本次循环序列,是指从本次循环中跳出,意为"跳过""继续""注意:break和continue只会对当前循环体产生影响,并不会对外层循环产生影响!"

九、数组

1. 数组的基本概念

在Java中,数组的概念是 用于存储一组相同数据类型元素的容器,元素的顺序存在连续性。
数组本身属于引用数据类型,其存储的元素可以是基本数据类型,也可以是引用数据类型。

数组的索引是从0开始的,其表示的是数组元素距离首地址的偏移量

当数组存储的元素也为数组时,我们称之为多维数组

img

2. 数组的特点

在Java中,数组具有 固定长度、相同类型、连续存储、索引访问等特点。

固定长度:数组一旦创建,其长度就是固定且不可改变的
相同类型:数组中存储的元素,其数据类型必须是相同的
连续存储:数组中存储的元素,其存放顺序是有序的(按照声明时的前后顺序排列),这使得数组的访问效率较高
索引访问:通过索引(从0开始)可以直接访问数组中的任意元素,时间复杂度为O(1)

3. 数组的创建与操作

// ----------------------------------- 声明数组 -----------------------------------
// 声明int类型数组
int[] arr1;
int arr2[]; // 不推荐这种写法

// 声明引用类型数组
String[] strArr;

// ----------------------------------- 初始化数组 ---------------------------------
// 静态初始化:直接指定元素值
int[] nums = {1, 2, 3, 4, 5};

// 动态初始化:仅指定长度,元素为默认值
int[] scores = new int[5]; // 元素默认值为0

// 先声明后初始化
String[] names;
names = new String[]{"Alice", "Bob", "Charlie"};

// ----------------------------------- 数组的操作 ---------------------------------
// 1.通过索引访问元素
int[] arr = {10, 20, 30};
System.out.println(arr[0]); // 输出10(第一个元素)
arr[1] = 25; // 修改第二个元素
// 2.获取数组的长度
int length = arr.length; // 使用length属性,注意没有括号
// 3.遍历数组
// 3.1 fori循环
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// 3.2 增强for循环
for (int num : arr) {
    System.out.println(num);
}

4. 数组的内存示意图:

栈中存储变量,变量记录着数组实体的地址值
堆中存储数据实体,数据实体带有其唯一的地址值

栈中的变量,通过地址值连接找到对应堆中的数组实体数据。 20250811103246.png

5. 数组的常见算法

5.1 数值型数组的特征值计算(最大值、最小值、求和、平均值)
/**
 * ----------------------------------- 查找数组中的最大值 -----------------------------------
 */
public static double findMax(double[] array) {
    if (array == null || array.length == 0) {
        throw new IllegalArgumentException("数组不能为空或长度为0");
    }
    
    double max = array[0];
    for (int i = 1; i < array.length; i++) {
        if (array[i] > max) {
            max = array[i];
        }
    }
    return max;
}
/**
 * ----------------------------------- 查找数组中的最小值 -----------------------------------
 */
public static double findMin(double[] array) {
    if (array == null || array.length == 0) {
        throw new IllegalArgumentException("数组不能为空或长度为0");
    }
    
    double min = array[0];
    for (int i = 1; i < array.length; i++) {
        if (array[i] < min) {
            min = array[i];
        }
    }
    return min;
}
/**
 * ----------------------------------- 计算数组元素的总和 -----------------------------------
 */
public static double calculateSum(double[] array) {
    if (array == null || array.length == 0) {
        return 0;
    }
    
    double sum = 0;
    for (double num : array) {
        sum += num;
    }
    return sum;
}

/**
 * ----------------------------------- 计算数组元素的平均值 -----------------------------------
 */
public static double calculateAverage(double[] array) {
    if (array == null || array.length == 0) {
        throw new IllegalArgumentException("数组不能为空或长度为0");
    }
    
    return calculateSum(array) / array.length;
}
    
    
5.2 数组元素的赋值(杨辉三角、回型数)
/**
 * 生成杨辉三角
 * 特点:
 * 1. 第i行有i个元素
 * 2. 每行的第一个和最后一个元素都是1
 * 3. 中间元素等于上一行相邻两个元素之和
 */
public static int[][] generateYanghuiTriangle(int rows) {
    if (rows <= 0) {
        return new int[0][];
    }
    
    int[][] triangle = new int[rows][];
    for (int i = 0; i < rows; i++) {
        // 第i行有i+1个元素
        triangle[i] = new int[i + 1];
        // 第一个元素为1
        triangle[i][0] = 1;
        // 最后一个元素为1
        triangle[i][i] = 1;
        
        // 填充中间元素
        for (int j = 1; j < i; j++) {
            triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
        }
    }
    return triangle;
}

/**
 * 生成回型数(螺旋矩阵)
 * 特点:数字从1开始按顺时针螺旋方式填充n×n矩阵
 */
public static int[][] generateSpiralMatrix(int n) {
    if (n <= 0) {
        return new int[0][0];
    }
    
    int[][] matrix = new int[n][n];
    int current = 1; // 要填充的当前数字
    int top = 0;     // 上边界
    int bottom = n - 1; // 下边界
    int left = 0;    // 左边界
    int right = n - 1; // 右边界
    
    while (current <= n * n) {
        // 从左到右填充上边界
        for (int col = left; col <= right; col++) {
            matrix[top][col] = current++;
        }
        top++; // 上边界下移
        
        // 从上到下填充右边界
        for (int row = top; row <= bottom; row++) {
            matrix[row][right] = current++;
        }
        right--; // 右边界左移
        
        // 从右到左填充下边界(如果还有行需要填充)
        if (top <= bottom) {
            for (int col = right; col >= left; col--) {
                matrix[bottom][col] = current++;
            }
            bottom--; // 下边界上移
        }
        
        // 从下到上填充左边界(如果还有列需要填充)
        if (left <= right) {
            for (int row = bottom; row >= top; row--) {
                matrix[row][left] = current++;
            }
            left++; // 左边界右移
        }
    }
    return matrix;
}
5.3 数组的赋值、复制、反转、扩容、缩容
   ----------------------------------- 赋值数组 -----------------------------------
    int[] oldArr = {1, 2, 3, 4, 5};
    int[] newArr;
    newArr = oldArr;
    
/**
 * ----------------------------------- 复制数组 -----------------------------------
 */
public static int[] copyArray(int[] array) {
    if (array == null) {
        return null;
    }
    int[] newArray = new int[array.length];
    
    for (int i = 0; i < array.length; i++) {
        newArray[i] = array[i];
    }
    return newArray;
}

/**
 * 反转数组
 */
public static int[] reverseArray(int[] array) {
    if (array == null || array.length <= 1) {
        return array;
    }
    int[] reversed = new int[array.length];
    for (int i = 0; i < array.length; i++) {
        reversed[i] = array[array.length - 1 - i];
    }
    return reversed;
}

/**
 * ----------------------------------- 数组扩容 -----------------------------------
 * @param array 原数组
 * @param newLength 新长度,必须大于原长度
 */
public static int[] expandArray(int[] array, int newLength) {
    if (array == null) {
        return null;
    }
    if (newLength <= array.length) {
        throw new IllegalArgumentException("新长度必须大于原长度");
    }
    
    int[] newArray = new int[newLength];
    for (int i = 0; i < array.length; i++) {
        newArray[i] = array[i];
    }
    return newArray;
}

/**
 * ----------------------------------- 数组缩容 -----------------------------------
 * @param array 原数组
 * @param newLength 新长度,必须小于原长度
 */
public static int[] shrinkArray(int[] array, int newLength) {
    if (array == null) {
        return null;
    }
    if (newLength >= array.length || newLength <= 0) {
        throw new IllegalArgumentException("新长度必须大于0且小于原长度");
    }
    
    int[] newArray = new int[newLength];
    for (int i = 0; i < newLength; i++) {
        newArray[i] = array[i];
    }
    return newArray;
}
5.4 数组元素的查找(线性查找、二分法查找(有序数组))
/*
 * ----------------------------------- 线性查找 -----------------------------------
 */
public static int linearSearch(int[] array, int target) {
    if (array == null || array.length == 0) {
        return -1;
    }
    // 遍历数组,逐个比较
    for (int i = 0; i < array.length; i++) {
        if (array[i] == target) {
            return i; // 找到目标,返回索引
        }
    }
    return -1; // 未找到
}
/*
 * ----------------------------------- 二分法查找 -----------------------------------
 * 二分法查找依赖于《有序数组》(通常为升序),通过不断缩小查找范围实现高效查找
 * 1. 取数组中间元素与目标值比较;
 * 2. 若中间元素等于目标值,返回索引;
 * 3. 若中间元素大于目标值,缩小范围到左半部分;
 * 4. 若中间元素小于目标值,缩小范围到右半部分;
 * 5. 重复上述步骤,直到找到目标或范围为空(为空则代表未找到)
 */
// 非递归实现二分查找
public static int binarySearch(int[] array, int target) {
    if (array == null || array.length == 0) {
        return -1;
    }
    int left = 0; // 左边界索引
    int right = array.length - 1; // 右边界索引

    while (left <= right) {
        int mid = left + (right - left) / 2; // 计算中间索引(避免溢出)
        if (array[mid] == target) {
            return mid; // 找到目标
        } else if (array[mid] > target) {
            right = mid - 1; // 目标在左半部分
        } else {
            left = mid + 1; // 目标在右半部分
        }
    }
    return -1; // 未找到
}

// 递归实现二分查找
public static int binarySearchRecursive(int[] array, int target, int left, int right) {
    if (left > right) {
        return -1; // 范围无效,未找到
    }
    int mid = left + (right - left) / 2;
    if (array[mid] == target) {
        return mid;
    } else if (array[mid] > target) {
        return binarySearchRecursive(array, target, left, mid - 1); // 递归左半部分
    } else {
        return binarySearchRecursive(array, target, mid + 1, right); // 递归右半部分
    }
}

两种查找算法对比

特性线性查找二分法查找
适用数组无序或有序必须有序(升序)
时间复杂度O(n)O(log n)
空间复杂度O (1)(非递归)O (1)(非递归)/ O (log n)(递归,栈空间)
实现复杂度简单较复杂
适用场景小规模数据、无序数组大规模数据、有序数组
5.5 数组的排序(冒泡排序(最简单)、快速排序(最常用))
5.5.1 冒泡排序

核心思想:通过重复比较相邻元素并交换位置,使较大的元素逐渐 "浮" 到数组末尾

/*
 * 在乱序数组中,从"首位"开始,与下一位进行比较,
 * 将较大值前置或后置(也可以说"调换位置",取决于排序规则:升序较大值后置,降序较大值前置)
 * 如此反复若干次(数组长度-1)即可成功排序
 */
public static int[] bubbleSort(int[] arr) {
        //临时容器变量(空杯子)
        int temp = 0;
        //外层循环 决定了外部需要大循环几次(次数为 数组长度-1)
        //  例:[1,2,3,4,5]
        // 第一次:12,23,34,45
        // 第二次:12,23,34
        // 第三次:12,23
        // 第四次:12
        for (int i = 0; i < arr.length - 1; i++) {
            //内部循环 进行比较(将较大值向后移)
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        return arr;
    }

img

5.5.2 快速排序

核心思想:采用分治法,选择一个基准元素,将数组分成两部分,然后递归排序

/**
 * 快速排序实现
 * 原理:选择一个"基准"元素,将数组分为两部分,
 * 一部分所有元素都小于基准,另一部分所有元素都大于基准,
 * 然后递归地对这两部分进行排序
 */
public static void quickSort(int[] array, int low, int high) {
    if (array == null || array.length <= 1 || low >= high) {
        return;
    }
    
    // 选择基准元素(这里选择数组中间的元素)
    int pivot = array[low + (high - low) / 2];
    
    // 将数组分为两部分
    int i = low, j = high;
    while (i <= j) {
        // 从左向右找到第一个大于等于基准的元素
        while (array[i] < pivot) {
            i++;
        }
        // 从右向左找到第一个小于等于基准的元素
        while (array[j] > pivot) {
            j--;
        }
        // 如果找到的左侧元素位置小于等于右侧元素位置,则交换它们
        if (i <= j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
            i++;
            j--;
        }
    }
    
    // 递归排序左半部分
    if (low < j) {
        quickSort(array, low, j);
    }
    // 递归排序右半部分
    if (high > i) {
        quickSort(array, i, high);
    }
}

6. Arrays工具类

在 Java 中,java.util.Arrays 是一个工具类,提供了一系列静态方法,用于操作数组(如排序、查找、填充、比较等)。它可以简化数组操作的代码,提高开发效率。

/*
 * -------------------------------- 常见Arrays内置算法举例 --------------------------------
 */
 
// Java的Arrays工具类内置的二分法查找算法
int index = Arrays.binarySearch(sortedArr, target); // 返回查找到的索引,若返回负数则代表未找到

// Java的Arrays工具类内置的快速排序算法(改进的快速排序算法:双轴快速排序)
Arrays.sort(arr);