Java
1 Java 概述
程序:一系列有序指令的集合
1.1 Java 历史
-
目前 Java 版权属于甲骨文公司。
-
长期支持的版本只有 Java8 与 Java11。这两个版本也是最多使用的版本。
-
Java SE:标准版
Java EE:企业版(重要)
Java ME:小型版(少)
1.2 Java 重要特点
-
Java 语言是面向对象的(oop)
简单来说,面向对象是一种程序设计技术。其重点放在数据(对象)和对象的接口上。
——何为面向对象?详见 [[ 6 面向对象编程 ]](https://i-melody.github.io/2021/11/29/Java/入门阶段/6 面向对象编程(基础)/)
-
Java 语言是健壮的。其强类型机制、异常处理、垃圾自动收集是健壮性的保证。
Java 强调早期问题检测、后期动态检测,及消除易出错的情况。其编译器能检测很多其他语言仅在运行时才会发现的问题。
——异常见 [[ 11 异常 ]](https://i-melody.github.io/2021/12/18/Java/入门阶段/11 异常/)
-
Java 语言是跨平台性的:一个编译好的
.class
文件可以在多个不同系统下直接运行。Java 中没有 “依赖具体实现” 的地方。其基本数据类型大小、有关运算的行为等都有明确说明。其绝大多数库都能很好地支持平台独立性,而不用担心操作系统。
-
Java 语言是解释型的:解释型语言编译后需要解释器才能运行。相对的,编译型语言可以被直接执行。
Java 解释器能在任何移植了解释器的机器上直接执行 Java 字节码。
1.3 Java 的开发工具
- javac:Java 编译器。将 Java 程序编译成字节码
- java:Java 解释器。执行已经转换为字节码的文件
- jdb:Java 调试器。调试 Java 程序
- javap:反编译。将类文件还原回方法和变量
- javadoc:文档生成器。创建 HTML 文件
1.4 Java 运行基础
JVM:Java 虚拟机
- JVM 是–跨平台性的基础。被包含在 JDK 中。
- 不同平台有各自对应的不同 JVM
- JVM 屏蔽了底层平台的区别。能做到 ”一次编译,到处运行”
JDK 全称:Java Development Kit(Java 开发工具包)
- JDK = JRE + Java 的开发工具(Java,Javac,Javadoc 等等)
- 给开发人员使用的,包含 JRE
JRE:Java Runtime Enviroment(Java 运行环境)
- JRE = JVM + Java SE 标准类库(Java 的核心类库)
- 运行一个 Java 程序的基本条件
1.5 Java 执行流程分析
.Java
文件(源文件) — javac(编译)—.class
文件(字节码文件) — java(运行)— 结果
1.5.1 编译
1 | javac [选项] 源文件名.java //[] 中是可选项 |
- 通过编译器将 Java 源文件编译成 JVM 可识别的字节码文件。字节码文件是二进制格式的,其格式是统一的。在源文件目录下使用 Javac 编译工具对 Java 文件进行编译。
- 如果没有错误将没有提示,当前目录会对应其中每一个类生成对应名称的
.class
文件,即字节码文件,也是可执行的 Java 程序。
1.5.2 运行
1 | java [选项] 程序名 [参数列表] //[] 中是可选项 |
- 有了可执行的 Java 程序(字节码文件)
- 通过运行工具
Java.exe
对字节码文件进行执行,本质是将.class
文件装载到 JVM 运行。
注意,修改后的 .Java 源文件需要重新编译
1.6 Java 开发注意事项和细节说明
-
源文件以
.java
为扩展名,源文件的基本组成部分是类(class) -
Java 应用程序的执行入口是
main()
方法。其有固定的书写格式:public static void main(string[]args){…}
-
Java 语言严格区分大小写。
-
Java 方法由一条条语句构成,每个语句都以
;
结束。 -
大括号
{
}
是成对出现的,缺一不可。习惯先写{}
再写代码 -
一个源文件中最多只有一个 public 类,其余类不限。
-
如果文件中包含 public 类,则文件名必须按该类命名。
-
也可以把 main 方法写在非 public 类中,然后运行指定非 public 类,这样入口方法是非 public 类的主方法。
-
在控制台按 tab 可以实现代码补齐。按方向键 ↑ 或 ↓ 可以调用历史代码。
1.7 代码规范
-
类、方法的注释要以 Javadoc 的方式来写
-
非 Javadoc 的注释,往往是给维护者看的,着重告诉读者为什么这样写,如何修改,注意什么问题等。
-
不要用
a
b
,这种名称命名变量,尽量写得清楚int age = 10;
另外,Java 源代码使用的是 Unicode 码,因此汉语也能作为标识符。但不推荐使用汉语做标识符。
-
使用 tab 键操作,使代码右移。使用 shift+tab 键,使代码左移。
-
运算符两边各加入空格。注意排版规范。
-
源文件使用 UTF-8 编码。
-
代码行宽度不要超过 80 个字符。超过时通过换行保持简洁。
-
代码编写次行风格及行尾风格
次行风格:换行输入
{
}
,使其总在行头行尾风格:在一行的末尾输入
{
,换行输入}
-
一段代码完成一个小功能,尽量不要混合。这样更加灵活。
1.8 Java 转义字符
-
\t
:一个制表位,实现对齐功能 -
\n
:换行符 -
\\
:一个\
-
\"
:一个"
-
\'
:一个'
-
\r
:一个回车(不是换行) -
\.
:一个小圆点.
-
\b
:退格键 -
\u????
:一个具体的 Unicode 字符。其中 ??? 是 4 位 16 进制数\???
:一个具体的 Unicode 字符。其中 ??? 是 3 位 8 进制数
1.9 注释
注释:用于注解说明程序的文字。其提高了代码的可读性,是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现。
被注释的文字不会被 JVM 解释执行。
1.9.1 注释类型
-
单行注释:
1
2//这是一条单行注释
JAVA选中文字按
ctrl
+/
将选中文字变为单行注释 -
多行注释:
1
2
3
4/* 这
是一段
多行注释
*/JAVA多行注释中不允许多行注释嵌套。
-
文档注释:
1
2
3
4/**
*@auther Melody
*@version 3.2.0
*/JAVA以下写法也同样合法:
1
2
3/**
就是说咱可以每行开头不加星号的
*/JAVA
1.9.2 文档注释
文档注释的注释内容可被 JDK 中的 Javadoc 工具解析,生成一套以 HTML 形式体现的说明文档。
抽取注释:
javadoc -d 生成目录 -author -.. 文档名.java
文档注释包含 标记 和紧随其后的 自由格式文本:
-
标记:
以
@
开始,如@since
下面列出了一些 通用注释:
-
@since 始于
:创建一个 始于 条目。其后文本可以是引入该特性的版本的任何描述 -
@author 作者
:产生一个 作者 条目。可以使用多个@author
标记 -
@version 版本
:产生一个 版本 条目。这里的文本可以是对当前版本的任意描述 -
@link 超链接
:产生一个 超链接,链接到 javadoc 相关部分或外部文档@see 引用
:在 see also 部分增加一个超链接。可以添加多个@see
标记,但必须放在一起。这里的 引用 有以下选择:
1
2
3
4
5/**
* com.test.Example#act()
* <a herf="../../../../../../">Melody's Box</a>
* "一段文本"
*/JAVA- 只提供类、方法或变量的名字。那个场合,使用 # 来分隔类名和方法名
@see
后有一个 < 字符的场合,需要指定超链接。可以指向任何 URL@see
后有一个 “ 字符的场合,文本会显示在 see alse 部分
-
-
自由格式文本:
第一句应该是一个概要性的句子。javadoc 会自动抽取这些语句生成概要页
自由格式文本中,可以使用 HTML 修饰符
注释的插入
javadoc 工具抽取文档注释时,会从以下位置抽取:
-
模块
-
包(包注释)
要想产生包注释,必须在每个包目录中添加一个单独的文件
有 2 种方法:
- 提供一个名为 package-info.java 的文件。其中 只能 包含文档注释,以及后面的一个 package 语句。不能包含更多的代码或注释。
- 提供一个名为 package.html 的 HTML 文件。此时会抽取
<body>...</body>
间的所有文本
-
公共类和接口(类注释)
类注释必须放在 import 语句后,类定义之前
-
公共的和受保护的字段
只需要对公共字段(通常是静态常量)建立文档
-
公共的和受保护的构造器和方法
方法注释必须放在所描述的方法之前。除了通用标记外,还能使用如下标记:
-
@param 变量描述
:该标记能给当前方法的 参数 部分添加一个条目。该描述可以占据多行。一个方法的所有
@param
标记必须放在一起 -
@return 返回值描述
:该标记能给当前方法的 返回值 部分添加一个条目。该描述可以占据多行。 -
@throws 异常描述
:该标记能给当前方法的可能抛出的 异常 添加一个条目。
-
1.10 JShell
Java 9 中引入了一种使用 Java 的方法,即 JShell。
JShell 程序提供了一个 “读取 - 计算 - 打印循环”。键入一个 Java 表达式,JShell 会评估你的输入,打印结果,并等待下一个输入。
在命令提示符中输入
jshell
以启动 JShell
1 | C:\Users\Melody>jshell |
在 cmd 中输入 jshell 以启动 JShell
1 | | 欢迎使用 JShell -- 版本 11.0.12 |
JShell 会显示一个问候语,之后出现提示符
1 | jshell> int n1 = 10; |
输入一条语句,JShell 会自动打印每条输入语句的值
其中的 $2 表示该值可以用于将来的计算。上面的 n1 是自己定义的标识符
1 | jshell> Math. |
输入不完整的语句后,按 tab 键实现方法补全。上面的场合,提示了所有方法的列表
1 | jshell> Math.pow( |
自动补全后的代码。可以手动填入剩余部分。
可以按 ↑ 键自动填充运行过的代码,以实现重复运行
附录
如何快速学习技术和知识点
1 查看需求
- 工作需要跳槽
- 对方要求
- 技术控
2 能否使用传统技术解决
- 能解决,但不完美
- 解决不了
3 引出学习的新技术和知识点
4 学习新技术或知识点的基本原理和基本语法(不要考虑细节)
5 快速入门(完成一个基本程序,crud)(能跑起来给老板看)
6 开始研究技术的注意事项,使用细节,使用规范,如何优化(无穷无尽)
DOS(只要了解)
DOS:Disk Operating System(磁盘操作系统)
DOS 的基本原理
在 cmd(控制台)输入指令 — DOS系统 接受指令 — 解析指令 — 执行指令
-
相对路径和绝对路径(举例从 JDK8\bin 访问到 JDK8\jre\bin\LICENSE)
相对路径:从当前目录开始定位,形成的路径
..\jre\bin\LICENSE
返回上一级:
..\
绝对路径:从顶级目录开始定位,形成的路径
d:\Program\JDK8\jre\bin
常用的 DOS 命令
查看帮助:
help
或help cd
等查看目录内容:
dir
查看当前目录或
dir d:\Program\JDK8\bin
查看指定目录切换到其他盘:
cd /D d:
从 C盘 切换至 D盘切换到当前盘的其他目录:
cd d:\Program\JDK8\jre\bin
返回上级目录:
cd ..
切换至根目录:
cd \
查看子集目录:
tree
当前目录或
tree d:/Program
指定目录清屏:
cls
退出:
exit
创建/删除目录:
md 目录名
rd 目录名
拷贝/删除文件:
copy 文件名 目录
del 文件名
移动文件:
mov
2 变量
变量:变量是程序的基本组成单位
变量的三个基本要素:类型 + 名称 + 值
示例:
int a = 1
类型 int 名称 a 值 1
如何声明变量:
1 | int a; |
2.1 变量使用注意事项
- 变量表示内存中的一个存储区域。不同变量,不同类型,占用的空间大小不同。如
int
有 4 byte,而double
有 8 byte。 - 该区域有自己的名称
变量名
和类型数据类型
- 变量必须先声明,后使用。
- 变量在同一作用域内不能重名。
- 该区域的数据 · 值可以在同一类型范围内变化。
- 变量的三个基本要素:类型 + 名称 + 值
2.2 程序中 +
的使用
-
当左右两边都是数值型,做加法运算
-
当左右两边任意一方为字符串,做拼接运算
-
运算顺序是从左到右的
1
2System.out.println(1 + 1 + "a" + 1 + 1); // 输出 2a11
JAVA
2.3 Java 数据类型
- 基本数据类型(本章)
- 数值型
- 整数类型:
- byte:占用 1 字节
- short:占用 2 字节
- int:占用 4 字节
- long:占用 8 字节
- 浮点(小数)类型:
- float:占用 4 字节
- double:占用 8 字节
- 整数类型:
- 字符型
- char:存放单个字符,占用 2 字节
- 布尔型
- boolean:存放 true(真),false(假)。占用 1 字节
- 引用数据类型(复合数据类型)
-
类:class
——详见 [[ 6 类与对象 ]](https://i-melody.github.io/2021/11/29/Java/入门阶段/6 面向对象编程(基础)/#6-1-类与对象(OOP))
-
接口:interface
——详见 [[ 9.7 接口 ]](https://i-melody.github.io/2021/12/14/Java/入门阶段/9 面向对象编程(高级)/#9-7-接口)
-
数组:
[]
——详见 [[ 5 数组、排序和查找 ]](https://i-melody.github.io/2021/11/27/Java/入门阶段/5 数组、排序和查找/#5-数组、排序和查找)
2.4 整数类型
用于存放整数值
byte
占用 1 字节,范围 -128 ~ 127short
占用 2 字节,范围 -215 ~ 215 - 1int
占用 4 字节,范围 -231 ~ 231 - 1long
占用 8 字节,范围 -263 ~ 263 - 1
使用细节:
- Java 各整数类型有固定的范围和字符长度,不受具体 OS(操作系统)影响,以保证 Java 程序的可移植性。
- Java 默认整型常量为
int
,要声明long
型常量必须后加l
或L
。 - 从 Java 7 开始,加上前缀
0b
或0B
就可以写二进制数。 - 从 Java 7 开始,可以为数字字面添加下划线。这不会影响数字的值,只是为了方便阅读。
1 | int n = 0b0010; |
如果基本的整数、浮点类型不能满足范围、精度的需求,可以使用 “大数”
—— 大数,见 [[12.8 BigInteger 和 BigDecimal 类]](https://i-melody.github.io/2021/12/19/Java/入门阶段/12 常用类/#12-8-BigInteger-和-BigDecimal-类)
2.5 浮点类型
可以表示一个小数
float
单精度(6 ~ 7 位有效数字),占用 4 字节,范围约 -3.403E38 ~ 3.403E38double
双精度(15 位有效数字),占用 8 字节,范围约 -1.798E308 ~ 1.798E308
浮点数在机器中存放形式为:浮点数 = 符号位 + 指数位 + 尾数位
因此,尾数部分可能丢失,造成精度损失。换言之,小数都是近似值
2.5.1 使用细节
-
与整数类型相似,有固定的范围和字符长度,不受具体 OS(操作系统)影响。
-
Java 默认浮点常量为
double
,要声明float
型常量必须后加 ”f“ 或 ”F“ -
浮点型常量有两种表示形式
十进制数形式:
5.13
、315.4F
、.414
科学计数法:
5.12e2
即[5.12 × 102]、5.12E-2
即[5.12 / 102] -
通常情况下,应该使用
double
类型,以其更为精确。 -
浮点数使用陷阱:当我们对运算结果是小数的进行相对判断时,要小心。(因为小数都是近似值)
正确方法是:以两个数差值的绝对值,在某个精度范围内判断
1
2
3if (Math.abs(num1 - num2) < 0.00001) {
System.out.println("插值范围内认为相等");
}JAVA -
特殊的浮点类型常量
-
正无穷大:
Float.POSITIVE_INFINITY
、Double.POSITIVE_INFINITY
(浮点数运算中)一个正数除以 0,会得到该值
-
负无穷大:
Float.NEGATIVE_INFINITY
、Double.NEGATIVE_INFINITY
(浮点数运算中)一个负数除以 0,会得到该值
-
0 / 0:
Float.NaN
、Double.NaN
(浮点数运算中)0 除以 0,会得到该值
-
最大、最小值:
Float.MAX_VALUE
、Double.MIN_VALUE
-
-
不能用运算符来比较特殊值,而要用特别的方法
1
2
3
4
5double num = 0.0 / 0;
System.out.println(num == Double.NaN); // <——— 始终为 false。不能如此比较
System.out.println(Double.isNaN(num)); // <——— 判断是否是 NaN
num = 1.0 / 0;
System.out.println(Double.isInfinite(num)); // <——— 是否是无穷大JAVA -
由于不同处理器寄存浮点数的策略可能不同,浮点数运算的结果也可能不同。
—— 见 [[12.1.4 strictfp 关键字]](https://i-melody.github.io/2021/12/19/Java/入门阶段/12 常用类/#12-1-4-strictfp-关键字)
2.6 字符类型
可以表示单个字符。(可以存放一个数字,因为其字符是数字编号的。输出时会输出数字对应的字符。”编码的概念“)
1 char c1 = 'a';` `char c2 = '\t';` `char c3 = '字';` `char c4 = 99;
2.6.1 使用细节
-
字符常量用单引号括起 `‘字’
-
char
的本质是一个整数,输出时,输出的是 unicode 码对应的字符。[unicode 码查询](https://i-melody.github.io/2021/11/22/Java/入门阶段/2 变量/tool.chinaz.com/Tools/Unicode.aspx) 。要输出那个整数,用
int
1
2char c1 = 'a';
System.out.println((int)c1);JAVA -
char
是可以进行运算的,其相当于一个整数。注意与 [[2.2 示例]](https://i-melody.github.io/2021/11/22/Java/入门阶段/2 变量/#2-2-程序中-的使用) 的区别1
2
3
4// 注:(int)'a' = 97
char c1 = 'a' + 1; // 相当于 char c1 = 'b'
System.out.println('a' + 1); // 这个代码输出 98
System.out.println("a" + 1); // 这个代码输出 a1JAVA -
字符允许使用转义符(见 [1.8 Java 转义字符])
1
2char c = '\u0041';
JAVA转义序列 \u 能出现在引号外。所有这些转义序列会在解析代码前得到处理
-
以下字符串是空串:
1
2String s = "\u0022+\u0022";
JAVA因为 \u0022 表示引号。该代码等同于以下代码
1
2String s = "" + "";
JAVA -
以下注释会报错:
1
2// \u000A is a newline
JAVA因为 \u000A 是换行符。在解析前会得到处理。在程序看来,上述注释等于以下写法
1
2//
is a newlineJAVA -
以下注释也会报错:
1
2// look inside c:\users
JAVA因为程序认为,\users 不是一个合法的转义字符
-
在某些场合下这种写法似乎也能实现:
1
2int\u005B\u005D a; // int[] a; 一个数组
JAVA
-
2.6.2 字符本质与编码表
-
字符类型的本质,是把字符对应的码值编程二进制,存储。显示时将二进制代码转化为码值,找到对应的字符。
-
字符与码值的对应关系是字符编码表规定的。
ASCII 编码表,占用 1 byte,共有 128 个字符。
Unicode 编码表,占用 2 byte,字母汉字都占用 2 byte,这样可能浪费空间。0 - 127 的字符与 ASCII 相同,所以兼容 ASCII。
UTF-8 编码表,根据不同符号大小可变(1 - 6 byte),字母占用 1 byte,汉字占用 3 byte。是 Unicode 的改进,是互联网上使用最广的 Unicode 实现方式。
GBK 编码表,可以表示汉字,字母占用 1 byte,汉字占用 2 byte。
GB2312 编码表,可以表示汉字(GB2312 < GBK)
BIG5 编码表,可以存放繁体中文(香港,台湾)
-
UTF-16 编码采用不同长度的编码表示所有 Unicode 码点。包含从 U+0000 到 U+FFFF 的经典 Unicode 代码(16位,1 个代码单元),以及 U+10000 到 U+10FFFF 的辅助字符(32位,2 个代码单元)
-
在 Java 中,char 类型描述的是 UTF-16 编码中的 1 个代码单元。
字符串中的一个辅助字符(如 🎶)可能占用 2 个代码单元。这个场合,使用 char 可能会导致错误
1
2String str = "🎶Melody🎶";
char c = str.charAt(1); // <———— 这个场合,c 是 🎶 符号的第二个代码单元而非 'M'JAVA因此,一般不建议在程序中使用 char 类型
2.7 布尔类型
boolean
只允许取值ture
或false
,没有null
。适用于逻辑运算,通常用于程序流程控制
1 if` `while` `do-while` `for
使用细节:
-
不可以用 0 或 非0 的整数替代
false
或ture
。这点和 C语言 不同。 -
不能让布尔类型转换为其他类型。如需转换,请使用如下方法:
1
2boolean b = true;
int n = b ? 0 : 1;JAVA——见 [[3.5 三元运算符]](https://i-melody.github.io/2021/11/23/Java/入门阶段/3 运算符/#3-5-三元运算符)
2.8 基本数据类型转换
2.8.1 自动类型转换
自动类型转换:Java 在进行赋值或运算时,精度(容量)小的类型自动转换为精度(容量)大的类型。
1
2 char` > `int `> `long` > `float` > `double
byte` > `short` > `int `> `long` > `float` > `double例子:
int a = 'c'
或者double b = 80
2.8.1.1 使用细节
-
有多种类型数据混合运算时,系统会将所有数据转换成容量最大的那种,再进行运算。
-
如若把大精度(容量)数据赋值给小精度(容量)类型,就会报错(小数由于精度原因,大赋小会丢失精度,必不可用。但整数大赋小时:1.赋予具体数值时,判断范围。2.变量赋值时,判断类型。反之进行自动类型转换。
-
byte
short
char
三者不会相互自动转换,但可以计算。计算时首先转化为int
。byte a = 1;
byte b = 1;
a + b
结果是int
类型 -
boolean
类型不参与自动转换 -
自动提升原则:表达式结果的类型自动提升为操作数中最大的类型。
2.8.2 强制类型转换
强制类型转换:自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时加上强制转换符
( )
,但可能造成精度降低或溢出,要格外注意。
2.8.2.1 使用细节
-
当进行数据从大到小转换时,用强制转换。
-
强制转换只能对最近的操作数有效,往往会使用
( )
提升优先级。1
2int a = (int)(3 * 2.5 + 1.1 * 6);
JAVA -
char
可以保留int
的常量值,但不能保存其变量值。此时需要强制类型转换。1
2
3int a = 10;
char b = 10;
char c = (char)a;JAVA -
byte
short
char
在进行运算时,当作int
处理。
2.8.3 基本数据类型和 String
的转换
-
基本类型转
String
:基本数据类型加上" "
。即利用了 [[2.2.2]](https://i-melody.github.io/2021/11/22/Java/入门阶段/2 变量/#2-2-程序中-的使用) 中的方法。1
2
3int n1 = 100;
String s = n1 + "";
System.out.println(n1 + "" + n1 + "" + n1 + "");JAVA -
String
转基本数据类型:通过基本数据类型的包装类调用parseXX
方法。1
2String s = "100";
int n1 = Interger parseInt(s);JAVA特别的,把
String
转换为char
1
2char c = s.charAt(0); // 得到 s 字符串中的第一个字符。
JAVA
2.8.3.1 使用细节
- 将
String
转成基本数据类型时,要保证其能转换为有效数据。即不能把"Hello"
转换成int
。 - 如果格式不正确,会抛出[异常](https://i-melody.github.io/2021/12/18/Java/入门阶段/11 异常/),程序会中止。
3.1 算数运算符
算术运算符是对数值类型的变量进行运算的运算符,在 Java 程序中使用得非常多。其运算结果是一个数值量。
-
+
;-
;*
;/
:加(正)、减(负)、乘、除5 / 2 = 2;
因为是int
。同理5.0 / 2 = 2.5
-
%
:取模(求余数),结果和被取模数同号。其实a % b == a - (int)a / b * b;
11 % 9 = 2;
-11 % 9 = -2;
11 % -9 = 2;
-11 % -9 = -2;
-
++
:自增。++i
先自增后赋值;i++
先赋值后自增1
2
3
4
5int i = 10;
int j = ++i; //等价于 i = i + 1; j = i; 此时 i = 10; j = 10`
int k = i++; //等价于 k = i; i = i + 1; 此时 i = 11; k = 10`
i = i++; //系统会先后执行 int temp = i; i = i + 1; i = temp
i = ++i; //系统会先后执行 i = i + 1; int temp = i; i = tempJAVA -
--
:自减。和++
同理。 -
+
:字符串相加
3.2 关系运算符(比较运算符)
关系运算符结果都为
boolean
型,要么是ture
要么是false
。其关系表达式经常用在if
结构的条件中或循环结构的条件中。
==
:相等于。8 == 7
结果false
!=
:不等于<
>
:小于、大于<=
>=
:小于等于、大于等于instanceof
:检查是否是类的对象。"a" istanceof String
结果ture
3.2.1 使用细节
- 关系运算符结果都是
boolean
型,要么是ture
,要么是false
- 关系运算符的表达式,称为关系表达式
- 比较运算符
==
不要误写为=
- Java 允许将
==
、!=
两种运算用于任何数据类型
3.3 逻辑运算符
用于连接多个条件(多个关系表达式),最终的结果也是一个
boolean
值。
-
&&
:短路与。a
b
同时为ture
,则结果为ture
,否则为false
。 -
&
:逻辑与。a
b
同时为ture
,则结果为ture
,否则为false
。&&
与&
的区别,在于a && b
的场合,a = false
时,则b
不判断。而&
会完成判断。开发中多用&&
,因为其效率更高。1
2
3
4
5
6
7
8int a = 1;
int b = 1;
if (a++ > 1 && ++b < 1) System.out.println("Nothing happened");
/*
此时 a 经历了先判断后自增,返回 false 并且 a = 2
但此时 b = 1; 因为 a = false; 所以 ++b 不执行。
*/JAVA -
||
:短路或。a
b
任一为ture
,则结果为ture
,否则为false
。 -
|
:逻辑或。a
b
任一为ture
,则结果为ture
,否则为false
。两者的区别和
&&
与&
相似,若第一个为ture
,则||
不会判断第二个。 -
!
:取反。a
为ture
,则结果为false
。反之为ture
。 -
^
:逻辑异或。a
b
不同时,结果为ture
,否则为false
。
3.4 赋值运算符
将某个运算后的值,赋给指定变量
-
基本赋值运算符:
=
-
符合赋值运算符:
+=
;-=
;*=
;/=
;%=
等a += b
等价于a = a + b
。其余同理。
3.4.1 使用细节
-
运算顺序从右往左。
1
2int num = a + b + c; // 先运行(a + b + c),再结算int num =
JAVA -
运算符左边只能是变量,右边可以是变量、表达式、常量值。
-
复合赋值运算符会进行类型转换。
1
2byte b = 2; b += 3; // 此时 b += 3 等价于 b = (byte)(b + 3)
b++; // 同理JAVA
3.5 三元运算符
基本语法:
条件表达式 ? 表达式1 : 表达式2;
运算规则:如果条件表达式为
ture
,运算的结果是表达式1;反之为表达式2。
1
2
3 int a = 10;
int b = 11;
int result = (a == b ? a++ : b++); // 此时 a = 10 result = 11 b = 12JAVA
3.5.1 使用细节
-
表达式1 和 表达式2 要为可以赋给接受变量的类型(或可以自动转换,或进行强制转换)
-
三元运算符可以转成
if--else
-
三元运算符是一个整体。
1
2Object obj = true ? new Integer(1) : new Double(2.2);
System.out.print(obj)JAVA上例中,系统将会输出
1.0
因为 三元运算符 是一个整体,所以根据 [2.8.1.1.1] 发生了类型转换。
3.6 运算符优先级
运算符(优先级从高到低) | 结核性 |
---|---|
[] 、() 方法调用 |
从左向右 |
! 、~ 、++ 、-- 、+ (一元运算)、- (一元运算)、强制类型转换、new |
从右向左 |
* 、/ 、% |
从左向右 |
+ 、- |
从左向右 |
<< 、>> 、>>> |
从左向右 |
< 、> 、<= 、>= 、instanceof |
从左向右 |
== 、!= |
从左向右 |
& |
从左向右 |
^ |
从左向右 |
` | ` |
&& |
从左向右 |
` | |
?: (三元运算符) |
从右向左 |
= 、+= 、-= 、*= 、/= 、&= 、%= 、^= 、` |
=、 <<=、 >>=、 >>>=` |
- 运算符有不同优先级。优先级高的总是优先于低的。
- 只有单目运算符、赋值运算符是从右向左运算的。
3.7 标识符
Java 对各种变量、方法和类等命名时使用的字符序列称为标识符
凡是自己可以起名字的地方都叫标识符
double height = 0.0;
3.7.1 命名规则
-
标识符由 26 个大、小写英文字母,0 - 9 阿拉伯数字,_ 或 $ 符号组成。
-
数字不能开头。 错误示范:
int 3a = 1;
-
不能使用关键字和保留字(可以包含)。[具体的关键字和保留字请自行查询](https://i-melody.github.io/2021/11/23/Java/入门阶段/3 运算符/www.baidu.com)
-
严格区分大小写,长度无限制。
-
不能包含空格。
-
与多数编程语言不同。Java 可以用任何 Unicode 字符(特殊字符除外)作为标识符。但不推荐这么做
来 String 一只猫:
1
2String ᓚᘏᗢ = "✪ ω ✪"; // 喵?
JAVA
3.7.2 命名规范
- 包名:多单词组成时所有字母都小写:aaa.bbb.ccc
- 类名、接口名:多单词组成时,采用大驼峰法,所有单词的首字母大写:XxxYyyZzz
- 变量名、方法名:多单词组成时,采小驼峰法(驼峰法),第一个单词首字母小写,第二个开始每个单词首字母大写:xxxYyyZzz
- 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
- 更详细的规则查看 Java 编码规范
3.8 输入与输出
为满足读取用户输入、输出的需求,Java 提供了几个基本类
3.8.1 Scanner 类
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
Input.java
,需要一个 扫描器(对象),就是 ScannerScanner 属于 java.util 包。其包含许多方法
示例:
1 | import java.util.Scanner; //[1] 导入 |
- 导入该类所在的包
- 创建该类对象(声明变量)
- 调用里面的功能,接收用户输入
构造方法:
-
new Scanner(System.in)
:该方法接收一个参数(输入源)。其中System.in
代表标准输入流。默认情况指键盘。—— 见 [[17.2.3.4 标准输入 / 输出流]](https://i-melody.github.io/2022/01/06/Java/入门阶段/17 IO流/#17-2-3-4-标准输入-x2F-输出流)
常用方法:
-
String next()
:读取下一个输入对象Scanner 对象用空白(空格、水平制表符或回车换行符)作为输入的分隔元素
-
Double nextDouble()
:读取下一个 doubleInt nextInt()
:读取下一个 int -
String nextLine()
:读入当前行的所有输入,直到行尾 -
boolean hasNext()
:输入中是否还有其他单词boolean hasNextInt()
、boolean hasNextDouble()
3.8.2 格式化输出
System.out 标准输出流。调用该流方法以输出内容至控制台窗口
-
println(String s)
:输出字符,结束后换行print(String s)
:输出字符,结束后不换行println(int n)
、println(char c)
、println(Object obj)
…… -
printf(String format, Object ... args)
:格式化输出1
2System.out.printf("%s,你来啦,给你 %d 拳", "柏枝凪斗", 1); // <———— 给了柏枝凪斗一拳
JAVA以 % 开头的 格式说明符 都用相应参数替换。格式说明符尾部的转换符表示要格式化的数值类型
转换符 类型 转换符 类型 d 十进制整数 s 字符串 x 十六进制整数 c 字符 o 八进制整数 b 布尔值 f 定点浮点数 h 散列码 e 指数浮点数 tx 或 Tx 日期时间(T强制大写) g 通用浮点数 % 百分号 a 十六进制浮点数 n 行分隔符 也能指定控制格式化输出外观的各种标志
1
2System.out.printf("% (4.12f", Math.PI); // <————— 保留 12 位,正数添加空格,负数添加括号
JAVA标志 目的 + 打印正数和负数的符号 (空格) 在正数前添加空格 0 数字前面补 0 - 左对齐 ( 负号被括号环绕 , 添加分组分隔符 #(对于 f 格式) 包含小数点 #(对于 x 或 0 格式) 添加前缀 0x 或 0 $ 指定要格式化的参数索引:%1$d 以十进制打印第 1 个参数 < 格式化前面说明的数值:%d%<x 十进制打印后,再以十六进制打印一遍
3.8.3 NumberFormat 类
Java 提供的格式化输出功能,能使打印或显示是信息更美观
NumberFormat 类属于 Java 标准类库,在 java.text 包中
NumberFormat 类不能直接创建对象。利用类中的静态方法获取一个对象实例
获取实例:
NumberFormat.getInstance()
:返回当前默认语言环境的默认数值格式NumberFormat.getCurrnecyInstance()
:返回当前默认语言环境的通用格式NumberFormat.getNumberInstance()
:返回当前默认语言环境的通用数值格式NumberFormat.getPercentInstance()
:返回当前默认语言环境的百分比格式
常用方法:
-
format(num)
:获取 num 的格式化语句 -
setMaximunFactionDigits(n)
:将该格式的小数部分允许的最大位数设置为 nsetMinimunFactionDigits(n)
:将该格式的小数部分允许的最小位数设置为 n -
setMaximunIntegerDigits(n)
:将该格式的整数部分允许的最大位数设置为 nsetMinimunIntegerDigits(n)
:将该格式的整数部分允许的最小位数设置为 n
3.8.4 DecimalFormat 类
与 NumberFormat 类不同,DecimalFormat 类可以直接实例化对象。
实例化:
-
new DecimalFormat(pattern)
其中 pattern 是一个 String,代表格式化处理模式。如
1
2DecimalFormat df = new DecimalFormat("0.###"); //格式对象,保留 3 位小数
JAVA
常用方法:
format(num)
:获取 num 的格式化语句applyPattern(pattern)
:变更要使用的格式
3.9 进制
- 二进制:数字有 0 - 1,满 2 进 1,以
0b
或0B
开头 - 十进制
- 八进制:0 - 7,满 8 进 1,以
0
开头表示 - 十六进制:0 - 9 及 A - F,满 16 进 1,以
0x
或0X
开头。此处的 A - F 不分大小写。
3.9.1 进制的转换
- 其他进制 转 十进制:略
- 十进制 转 其他进制:将该数不断除以对象进制数,直到商为0为止,将每步得到的余数倒过来。
- 二进制 与 八进制 或 十六进制 互相转换:二进制 从低位开始,每 3 位一组,转成对应的 八进制 即可。反之同理。十六进制亦同理(每 4 位一组)。
3.9.2 原码、反码、补码
对于有符号数而言:
- 二进制的最高位是符号位:0表示正数,1表示负数。
- 正数的原码、反码、补码都一样。
- 负数的反码 = 原码符号位不变,其他位取反(0 变 1,1 变 0)
- 负数的补码 = 反码 + 1。也就是说,负数的反码 = 补码 - 1。
- 0 的反码、补码都是 0。
- Java 没有无符号数。换言之,Java 的数都是有符号的。
- 计算机运算时,都是以补码的方式来运算的。
- 当我们看运算结果时,要看其原码。
3.10 位运算符
-
&
按位与:两位都为 1,结果为 1,否则为 0 -
|
按位或:两位有一个为 1,结果为 1,否则为 0 -
^
按位异或:两位不同,结果为 1,否则为 0 -
~
按位取反:把 0 变 1,1 变 0以下是几个示例:
-
2 & 3
:2 的原码:00000000 00000000 00000000 00000010
2 的补码:00000000 00000000 00000000 00000010
3 的原码:00000000 00000000 00000000 00000011
3 的补码:00000000 00000000 00000000 00000011
补码运算结果:00000000 00000000 00000000 00000010
结果转成原码:00000000 00000000 00000000 00000010
运算结果:2
-
~-2
:得到 -2 的原码:10000000 00000000 00000000 00000010
得到 -2 的反码:11111111 11111111 11111111 11111101
得到 -2 的补码:11111111 11111111 11111111 11111110
补码运算结果:00000000 00000000 00000000 00000001
结果转成原码:00000000 00000000 00000000 00000001
运算结果:1
-
~2
:2 的原码:00000000 00000000 00000000 00000010
2 的补码:00000000 00000000 00000000 00000010
补码运算:11111111 11111111 11111111 11111101
结果转成反码:11111111 11111111 11111111 11111100
反码转成原码:10000000 00000000 00000000 00000011
运算结果:-3
-
-
>>
算术右移:低位溢出,符号位不变,用符号位补溢出的高位 -
<<
算数左移:符号位不变,低位补 0 -
>>>
逻辑右移(无符号右移):低位溢出,高位补 0下面是示例:
-
将数字 1 算术右移 2 位:
int a = 1 >> 2
1 的补码:00000000 00000000 00000000 00000001
补码结果:00000000 00000000 00000000 00000000
结果:00000000 00000000 00000000 00000000
-
将数字 1 算术左移 2 位:
int b = 1 << 2
1 的补码:00000000 00000000 00000000 00000001
补码结果:00000000 00000000 00000000 00000100
结果:00000000 00000000 00000000 00000100
也就是说,
1 << 2
本质是1 * 2 * 2 = 4
而
1 >> 2
本质是1 / 2 / 2 = 0
-
使用细节:
-
位运算符只对整型、字符类型有效
-
移位运算中,左侧操作 int 的场合,右侧操作数以 32 取模;long 的场合,右侧操作数以 64 取模。
1
2int n = 20;
n >>= 32; //这个场合,n 不改变。这样能保证左侧数字不被全部移走JAVA
附录
Java API 文档
API:Application Programming Iterface(应用程序编程接口),是 Java 提供的基本编程接口(Java 提供的一些类和方法)。
Java 语言提供了大量基础类,为了告诉开发者如何使用这些类,及类里包含的方法,就有了API文档。[具体有哪些请查找](https://i-melody.github.io/2021/11/23/Java/入门阶段/3 运算符/www.matools.com);特别地,Java 8 请查找
使用方法
- 包 —— 类 / 接口 —— 方法
- 直接检索
4.1 顺序控制
程序从上到下逐行执行,中间没有任何判断和跳转(默认的控制顺序)
比如:Java 定义变量时采用合法的前向引用。
1 | graph LR |
语句:Java 中最小的执行单位。语句分为 单语句 和 复合语句。
-
单语句:通常意义的一条语句。语句间以分号
;
分隔。 -
复合语句:一对大括号括起来的语句组。也称为 “块”
1
2
3
4
5{
语句1;
语句2;
...
}JAVA块中可以有多条语句。块后没有分号
;
4.2 分支控制 if-else
让程序有选择的执行。主要分为:单分支控制、双分支控制
4.2.1 单分支控制
1 | if (条件表达式) 语句; |
特别地,把代码块(复合语句)作为语句的场合也能这样写:
1 | if (条件表达式) { |
当条件表达式为 ture
,就会执行 {执行代码块;}
;如果为 false
则不执行。特别地:如果 {执行代码块;}
中只有一条代码,也可以不写 { }
(但还是建议写上)
4.2.2 双分支控制
1 | if (条件表达式) { |
当条件表达式为 ture
,就会执行 {执行代码块1;}
;如果为 false
则执行 {执行代码块2;}
。
4.2.3 多分支控制
1 | if (条件表达式) { |
特别地:多分支可以没有 else
。此时如果条件都不成立,则无执行入口
4.2.4 嵌套分支
在一个分支结构中又完整嵌套了另一个完整的分支结构。里面的分支称为内层分支,外面的分支称为外层分支。
Java 规定,else 子句属于逻辑上距其最近,且没有匹配 else 的 if 语句:
1
2
3
4 int n = 0;
if (n > 0) n++;
if (n > 1) n++;
else n--; //属于上面这个 if 语句JAVA这个场合,这个 else 语句属于上面的
if (n > 1)
这个语句
要想改变那个匹配关系,要使用 { }
改变语句结构:
1
2
3
4
5 int n = 0;
if (n > 0) {
n++;
if (n > 1) n++;
} else n--;JAVA
规范:嵌套尽量不超过 3 层(可读性不好)
4.3 switch
分支结构
1 | switch(表达式){ |
switch
关键字,表示 switch 分支。表达式
对应一个值。该值必须是 int 或 char(char 可以转化为 int)。是 byte 或 short 的场合,要提升为 int。不允许 long、double 或 floatcase 常量1;
表示:当表达式
的值等于常量1
,则执行语句块1
。break;
表示退出 switch 分支。- 当
表达式
的值匹配常量1
,则执行语句块1
,如果不匹配,则继续匹配常量2
,以此类推。 - 如果全都不匹配,则执行
default
。 - 如果不写
break;
,则会发生穿透,即不进行判断而继续执行下一语句块。
4.3.1 使用细节
-
表达式;
数据类型,应和case
后的常量
类型一致,或者是可以自动转换成可以比较的类型。如:输入的是char
而常量
是int
-
switch 中
表达式
的返回值必须是:byte
short
int
char ``enum
String
——
enum
是什么?详见 [[10.1 枚举 ]](https://i-melody.github.io/2021/12/17/Java/入门阶段/10 枚举和注解/#10-1-枚举) -
case
语句中的值必须是 常量 或 常量表达式,不能是 变量。 -
default
是可选的。没有就不执行。 -
break;
用来跳出 switch 分支。如果不写,会持续执行语句,直到分支结束或遇到下一个break;
4.3.2 与 if-else
分支结构的取舍
- 如果判断的具体数值不多,且符合 [[4.3.1.1]](https://i-melody.github.io/2021/11/24/Java/入门阶段/4 程序控制结构/#4-3-1-使用细节) ,推荐使用 switch。
- 对区间判断的场合,对结果为 boolean 类型的判断,使用
if-else
。
4.4 for
循环控制
让代码可以循环执行。
1 | for(循环变量初始化;循环条件;循环变量迭代){ |
for
关键字,表示循环控制- 四个要素:1. 循环变量初始化 2. 循环的条件 3. 循环的操作 4. 循环变量迭代
所有循环开始前仅一次进行初始化。直到循环条件变为 false 前,执行循环操作。每轮循环结束后,进行循环变量迭代。
- 循环操作可以有多条语句
- 如果循环操作只有一条语句,可以省略
" "
,但建议不省略
4.4.1 使用细节
-
循环条件是返回一个 boolean 值(
ture
或false
)的公式。循环条件可以为空。这个场合,默认为真(true)
-
for(;循环条件;){ }
其中的初始化和变量迭代可以写在别处,但;
不能省略。如果不写在别处,那个循环变量初始化
中声明的变量只能在该 for 循环中使用。 -
控制台用 ctrl + c 强制结束一个流程
-
循环初始值可以有多条初始化语句,但要求类型一样,并用
,
隔开。变量迭代也可以有多条代码,用
,
隔开。
4.4.2 for each(泛型 for 循环)
泛型 for 循环(增强 for 循环)能用来依次处理数组(或其他元素集合)中的每个元素,而不必考虑下标值
1 | for(int i : nums){ //其中 nums 是一个一维 int 数组 |
上述写法(增强 for
写法)类似于以下写法
1 | for(int i = 0; i < nums.length; i++){ |
泛型 for 循环适用于数组或一个实现了 Iterable 接口的对象。泛型 for 循环的本质是一个 Iterator(迭代器)
—— 迭代器,见 [[13.2.2.1 用 Iterator 遍历元素]](https://i-melody.github.io/2021/12/22/Java/入门阶段/13 集合/#13-2-2-2-用-enhanced-for-遍历元素)
4.5 while
循环控制
1 | while(循环条件){ |
while
也有四要素,只是位置和 for
不同
4.5.1 使用细节
-
循环条件是返回一个 boolean 值(
ture
或false
)的公式。while 循环中,循环条件不能为空。
-
while
循环是先判断再执行语句。
4.6 do..while
循环控制
1 | do{ |
do
while
是关键字- 也有四要素,位置不同
- 先执行,再判断。也就是说,一定会至少执行一次
- 最后有一个
;
while
与do..while
区别:“要账”
4.6.1 使用细节
- 循环条件是返回一个 boolean 值(
ture
或false
)的公式。 do..while
循环是先执行再判断的语句。因此至少执行一次。
4.7 多重循环控制
将一个循环放在另一个循环体内,就形成了嵌套循环。建议一般使用两层,最多不超过三层。
嵌套循环 是把 内层循环 当成 外层循环 的 循环体。只有内层 false
时才可能结束当层循环。
若内层执行 n 次,外层 m 次,则合计会循环 n*m 次
以下是一个示例(乘法口诀)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 >public class Code4_7{
public static void main(String[] args){
int a = 1; //声明第一个乘数
int b = 1; //声明第二个乘数
do{
//直到 a > b 为止,a 不断增长,并让两数相乘,输出公式
do{
System.out.print(a + " * " + b + " = " + a * b + "\t");
a++;
} while (a <= b);
//把 a 重置为 1,让 b 增长,然后循环
a = 1;
System.out.println();
b++;
} while (b <= 9);
}
}JAVA
4.8 跳转控制语句
跳转控制语句用于分支或循环中,以便程序员更好控制程序执行方向
4.8.1 标签
1 | a:{ |
a:
b:
c:
是标签,名字由程序员指定break
后指定哪个标签就退出到哪里- 实际开发中,尽量不要使用标签
4.8.2 break
用于中止一个语句块的执行
语法:break;
break 可以被用在三种场合中
-
switch 语句中,以跳出判断(结束穿透)
-
for、while、do…while 循环语句中,以跳出循环
-
语句块中,以跳过本块中所有剩余语句
break
语句出现在多层嵌套的语句块中时,可以通过 标签 指明要终止的时哪一层语句块。
4.8.3 continue
在循环中出现。用于结束本次循环,继续下一次循环
语法:continue;
进行下次循环前,仍会判断循环条件是否满足
在多层嵌套循环中,可以通过标签指出跳出哪次循环(同 break
)
4.8.4 return
用于方法。表示跳出所在的方法
语法:return;
方法有返回值的场合,将返回值写在 return 后:return 值;
——见 [[6.2 成员方法]](https://i-melody.github.io/2021/11/29/Java/入门阶段/6 面向对象编程(基础)/#6-2-成员方法)
如果写在 主方法 则跳出程序
5 数组、排序和查找
数组:可以存放多个同一类型的数据。数组也是一种数据,是引用类型。
即:数组就是一组数据。
5.1 一维数组
数组可以是多个相同类型数据的组合,实现对这些数据的统一管理。
数组中的元素可以是任何数据类型。包括基本类型和引用类型。
数组的下标从 0 开始。且必须在指定范围内使用,否则报错。
数组属于 引用类型,数组型数据是 对象(Object)
数组创建后,如果没有赋值,有默认值:int(0),short(0),byte(0),long(0L),float(0.0F),double(0.0),char(\u0000),boolean(false),String(null),Object(null)
数组的构造方法:
使用数组的步骤:1.声明数组并开辟空间 2.给数组各个元素赋值 3.使用数组
-
构造方式1:动态初始化
1
2
3int[] ints = new int[5]; // 创建了数组 name,存放5个int
int ints2[] = new int[1]; // 这种写法也行
ints[2] = 15; // 访问数组第3个数JAVA -
构造方式2:动态初始化
1
2
3char[] chars; // 先声明数组 name,此时数组是 null
chars = new char[2]; // 分配内存空间,可以存放数据了
chars[1] = '\t';JAVA -
构造方式3:静态初始化
1
2boolean[] bools = {true, false, true, false};
String[] strs = new String[]{"阿伟,你又在打电动噢", "烦啦"};JAVA确切知道数组每个元素的场合可以用这个方法。
数组的使用方法:
-
访问数组元素:
数组名[元素下标]
其中,元素下标从 0 开始编号。如:访问 strs 数组的第一个元素
strs[0]
-
数组长度:
数组名.length
是一个 int 值。不能通过试图改变该值来改变数组容量
5.1.1 数组赋值机制
-
基本数据类型赋值,赋值方式是值拷贝。这个值就是具体的数据,且互不影响
-
数组在默认情况下是引用传递,赋的值是地址,赋值方式为引用传达。
1
2
3int[] array1 = {0, 0, 0};
int[] array2 = array1;
array2[0] = 100;JAVA上述情况下,
array1[0]
也会变成100
。因为数组在 JVM 的 栈 里是一个地址,指向 堆 里的一个空间。这两个数组在上述情况下指向同一空间。1
2
3
4
5int[] array1 = {0, 0, 0};
int[] array2 = new int[array1.length];
for (int i = 0;i < array1.length;i++) {
array2[i] = array1[i];
}JAVA上述方式拷贝后,两数组相互独立。
5.1.2 数组的扩容
当数组达到上限时,创建一个容量更大的新数组。将旧数组的元素依次放入,之后替换旧数组。
以下是一个扩容方法:
1 | import java.util.Scanner; |
5.2 二维数组
1 | int[][] ints; // 声明一个二维数组 |
二维数组实际是由多个一维数组组成的,它的各个元素的长度可以相同,也可以不同。
数组是一个对象,所以二维数组的元素存放的是一维数组的地址。
二维数组构造方法:
-
构造方法1:动态初始化
1
2int[][] many_ints = new int[3][4] // 创建 有3个 包含4个元素的一维数组 的二维数组
JAVA -
构造方法2:动态初始化
1
2double[][] many_doubles; // 先声明变量
many_doubles = new double[3][4]; // 再开辟空间JAVA -
构造方法3:动态初始化-列数不确定
1
2
3
4char[][] many_chars = new char[3][]; // 创建一个三行列数不确定的二维数组
for (int i = 0; i < 3; i++) {
many_chars[i] = new char[i + 1]; // 此时,每个数组空间依次增大
}JAVA -
构造方法4:静态初始化
1
2int[][] many_many = {{1, 3}, {4, 10, 2}, {95}};
JAVA
二维数组使用方法:
ints.length
:该二维数组的长度,这里是 3ints[0]
:该二维数组的第一个子数组ints[0].length
:该二维数组的第一个子数组的长度,这里是 4ints[1][0]
:该二维数组第二个子数组的第一个元素的值,这里是 21
这是一维数组:int[] a、这是二维数组:int[][] b。好了,现在来写一个堆排序吧
5.3 查找算法
在 Java 中,常用的查找有 4 种:
- 顺序查找(遍历)
- 二分查找
- 插值查找
- 斐波那契查找
5.3.1 线性查找
逐一比对,直到发现目标值
1 | public static int seqSearch(int[] array, int target) { |
5.3.2 二分查找
二分查找要求数组必须是有序数组。
每次取出那个中位数。目标值小于中位数的场合,则在较小一侧的范围内继续二分查找。否则在那个较大一侧查找。
递归方式二分查找:
1 | public static int binarySearch(int[] array, int target) { |
非递归方式二分查找:
1 | public static int binarySearch2(int[] array, int target) { |
5.3.3 插值查找
插值查找类似于二分查找,但其不是取出中位数,而是从自适应的位置取出一个元素
那个自适应的取出位置 ���=���+(ℎ��ℎ−���)×������−���[���]���[ℎ��ℎ]−���[���]p**os=low+(high−low)×a**rr[high]−a**rr[low]targe**t−a**rr[low]
上面的 ���low 是查找范围的下界,ℎ��ℎhigh 是查找范围的上界,������targe**t 是目标值,���[�]a**rr[i] 是数组第 �i 个元素的值
可见,如若那个目标值更靠近某一端,这个自适应的取出位置也会更接近那一端
1 | public static int insertSearch(int[] array, int target) { |
5.3.4 斐波那契查找
斐波那契查找原理与前两种类似,仅仅改变了中间节点的位置。
其中间节点不是中位或插值,而是位于黄金分割点附近。
……是黄金分割,真好呀!
5.4 排序算法
排序也叫排序算法。是将一组数据,依指定的顺序进行排列的过程
排序分为两类:
-
内部排序:将所有要处理的数据加载到内部存储器中进行排序
内部排序主要有以下几种:
- 插入排序:直接插入排序、希儿排序
- 选择排序:简单选择排序、堆排序
- 交换排序:冒泡排序、快速排序
- 归并排序
- 基数排序
-
外部排序:数据量庞大,无法全部加载到内存中,需要借助外部存储进行排序
如:合并排序法、直接合并排序法
排序算法的时间复杂度
排序法 | 平均时间 | 最差情形 | 稳定性 | 额外空间 | 说明 |
---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n2) | 稳定 | O(1) | n 小时较好 |
交换排序 | O(n2) | O(n2) | 不稳定 | O(1) | n 小时较好 |
选择排序 | O(n2) | O(n2) | 不稳定 | O(1) | n 小时较好 |
插入排序 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数排序 | O(n × k) | O(n × k) | 稳定 | O(n) | k 是 “桶” 的个数 |
Shell 排序 | O(n㏒2n) | O(n㏒22n) | 不稳定 | O(1) | n 大时较好 |
快速排序 | O(n㏒2n) | O(n2) | 不稳定 | O(n㏒n) | n 大时较好 |
归并排序 | O(n㏒2n) | O(n㏒2n) | 稳定 | O(1) | n 小时较好 |
堆排序 | O(n㏒2n) | O(n㏒2n) | 不稳定 | O(1) | n 大时较好 |
**稳定性:**排序后,那些原本相等元素的相对顺序不改变
—— 时间复杂度,见 [26.1.1 时间复杂度]
5.4.1 冒泡排序
**冒泡排序:**通过对 待排序序列 从后向前遍历,依次比较相邻元素的值。若发现 逆序 则交换。
1 | 比前一个元素小 |
如此,各元素不断接近自己的位置。值较大的元素逐渐向后移动,就像水下的气泡一样逐渐上浮。
特别地:如果在某次排序中没有发生过任何交换,则此时是已完成排序,可提前结束排序过程。
上面是 b 站大佬 说数人 制作的动画,演示了冒泡排序的工作原理
1 | public static void bubble_sort(int[] array) { |
如果有 �n 个元素,就进行 �−1n−1 轮比较,第 �k 轮排序比较 �−�n−k 个元素,并进行最多 �−�n−k 次交换。时间复杂度为 �(�2)O(n2)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行冒泡排序。
那个统计时间是:6416 ms
5.4.2 选择排序
**选择排序:**从 待排序序列 中,按指定规则选出某一元素,再依照规定交换位置后达到排序目的。
1 | 为空 |
从 待排序序列 中找出 最小值,与下标 0 位置元素 交换。之后在剩余元素中找出 最小值,与下标 1 元素 交换。以此类推,直到完成。
上面是 b 站大佬 说数人 制作的动画,演示了选择排序的工作原理
1 | public static void choice_sort(int[] array) { |
如果有 �n 个元素,就进行 �−1n−1 轮选择,第 �k 轮选择要比较 �−�n−k 个元素,并进行 11 次交换。时间复杂度为 �(�2)O(n2)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行选择排序。
那个统计时间是:1616 ms
5.4.3 插入排序
**插入排序:**在 待排序序列 中的元素,以插入的方式找寻该元素的适当位置,以达到排序目的。
1 | 首元素 |
将 待排序序列 的元素视为一个 有序表 和一个 无序表。起初, 有序表 中只有一个元素, 无序表 中有 n - 1 个元素。
排序过程中每次从 无序表 中取出第一个元素,把它依次与 有序表 元素进行比较,确定其在 有序表 的位置,将其插入 有序表 ,成为新的 有序表 。
上面是 b 站大佬 说数人 制作的动画,演示了插入排序的工作原理
1 | public static void insert_sort(int[] array) { |
如果有 �n 个元素,就进行 �−1n−1 轮插入,第 �k 轮插入需要进行 �k 次比较、移动和交换,平均每轮需要 �22n 次操作。时间复杂度为 �(�2)O(n2)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行插入排序。
那个统计时间是:423 ms
5.4.4 希儿排序
**希儿排序:**是希儿通过在量子之海中领悟事物的演变模式,于 1959 年提出的一种排序算法。希儿真的很聪明!
1 | 长度的一半 |
希儿排序也是一种插入排序,是简单插入排序的高效改进版。也称为:缩小增量排序。
希儿排序是把 待排序序列 按下标的一定 步长 进行 分组,对每组使用 插入排序法。随着 步长 逐渐减少,每组包含的 元素个数 也越来越多。
当 步长 减至 1 时,整个 待排序序列 恰被分为 1 组,算法便中止。
上面是 b 站大佬 说数人 制作的动画,演示了希儿排序的工作原理
1 | public static void seele_sort(int[] array) { |
如果有 �n 个元素,就进行 log2�log2n 轮排序。每轮排序最多进行 �n 次比较。因此,时间复杂度为 �(�log�)O(nlogn)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行希儿排序。
那个统计时间是:11 ms
5.4.5 快速排序
**快速排序:**是对冒泡排序的一种改进。通过一趟排序,将 待排序序列 分割 成独立的两部分,其中一部分的所有数据都比另一部分小,再按此方法对这两部分数据 分别进行快速排序。
1 | 取出一个元素 |
整个排序过程可以 递归进行,最终使整个数据变成有序序列。
上面是 b 站大佬 说数人 制作的动画,演示了快速排序的工作原理
1 | public static void quick_sort(int[] array) { |
如果有 �n 个元素,则最多进行 log2�log2n(每轮都选到中位数时)至 �n(每轮都选到极值时)轮分组。任意一轮分组中最多有 �n 次比较、�n 次交换。因此,时间复杂度为 �(�log�)O(nlogn),最差情况为 �(�2)O(n2)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行快速排序。
那个统计时间是:16 ms
5.4.6 归并排序
**归并排序:**利用归并的思想实现的排序方法。该算法采用经典的分治策略(将问题分解成几个小问题,然后分而治之)。
1 | 待排序序列 |
对 2 个元素进行比较、排序,是很容易的。对 2 个有序序列,将其合并为新的有序序列,也是容易的。
因此,我们把 待排序序列 分成许多 组,每组包含 2 个元素,并对每组进行排序。再逐渐 合并 那些组,最终就能得到一个完整的有序序列。
上面是 b 站大佬 说数人 制作的动画,演示了归并排序的工作原理
1 | public static void merger_sort(int[] array) { |
如果有 �n 个元素,则发生 log2�log2n 轮合并。每轮排序最多进行 �n 次比较与 �n 次插入。因此,时间复杂度为 �(�log�)O(nlogn)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行归并排序。
那个统计时间是:15 ms
5.4.7 基数排序
**基数排序:**属于分配式排序(又称 “桶排序”。顾名思义,通过键值各个位的值,将 待排序序列 的元素分配至某些 桶 中,达到排序的作用)。基数排序是经典的空间换时间的算法
1 | 是 0 |
基数排序法是桶排序的扩展。将整数 按照位数切割成不同数字,然后 按照每个位数分别比较。
该方法只能用于比较正数。你非要比较负数?咱也不说不行,你自己看着办吧
上面是 b 站大佬 说数人 制作的动画,演示了基数排序的工作原理
1 | public static void radix_sort(int[] array) { |
若桶数为 �R,最大值为 �B,则需要进行 log��logR**B 轮排序。每轮排序进行 �n 次计算、插入。因此,时间复杂度是 �(�log��)O(nlogR**B)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行基数排序。
那个统计时间是:15 ms
5.4.8 堆排序
**堆排序:**利用 堆 数据结构而设计的一种排序算法。堆排序是一种选择排序,也是一种不稳定排序。
堆 是具有以下性质的二叉树:
-
大顶堆:每个节点的值都大于等于其子节点的值
即,对于任意节点 ���[�]a**rr[i] 都有$ arr[i] \ge arr[2i + 1]$ 并且 ���[�]≥���[2�+2]a**rr[i]≥a**rr[2i+2]
-
小顶堆:每个节点的值都小于等于其子节点的值
即,对于任意节点 ���[�]a**rr[i] 都有 ���[�]≤���[2�+1]a**rr[i]≤a**rr[2i+1] 并且 ���[�]≤���[2�+2]a**rr[i]≤a**rr[2i+2]
——见 [[14.1.1 顺序存储二叉树]](https://i-melody.github.io/2022/06/02/Java/入门阶段/14 树/#14-1-1-顺序存储二叉树)
1 | 整理 |
-
将 待排序序列 构成一个 大顶堆(数组形式)
从最后一个非叶节点开始,从左至右,从下至上进行调整。
调整时如果乱序,则将子节点中较大方的值与该节点交换即可。交换后,那些子节点乱序的场合也要依次调整。
调整完成后,就得到了一个 大顶堆。
-
此时,堆顶元素即整个序列的最大值。将其 与队列末尾元素交换。
-
对剩余元素进行调整,使其恢复成 大顶堆。
-
重复上述步骤,就得到了有序序列。
什么是堆 | 生成大顶堆 | 维护大顶堆 | 堆排序 |
---|---|---|---|
上面是 b 站大佬 说数人 制作的动画,演示了堆排序的工作原理
1 | public static void heap_sort(int[] array) { |
构建堆时,若有 �n 个元素,即进行 �n 次整理。每次整理最多进行 log2�log2n 次比较 。之后,每次取出堆顶元素后,需进行一次整理。故时间复杂度是 �(�log�)O(nlogn)
对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行堆排序。
那个统计时间是:16 ms