1 Java 概述

程序:一系列有序指令的集合

1.1 Java 历史

  • 目前 Java 版权属于甲骨文公司。

  • 长期支持的版本只有 Java8 与 Java11。这两个版本也是最多使用的版本。

  • Java SE:标准版

    Java EE:企业版(重要)

    Java ME:小型版(少)

1.2 Java 重要特点

  1. Java 语言是面向对象的(oop)

    简单来说,面向对象是一种程序设计技术。其重点放在数据(对象)和对象的接口上。

    ——何为面向对象?详见 [[ 6 面向对象编程 ]](https://i-melody.github.io/2021/11/29/Java/入门阶段/6 面向对象编程(基础)/)

  2. Java 语言是健壮的。其强类型机制、异常处理、垃圾自动收集是健壮性的保证。

    Java 强调早期问题检测、后期动态检测,及消除易出错的情况。其编译器能检测很多其他语言仅在运行时才会发现的问题。

    ——异常见 [[ 11 异常 ]](https://i-melody.github.io/2021/12/18/Java/入门阶段/11 异常/)

  3. Java 语言是跨平台性的:一个编译好的 .class 文件可以在多个不同系统下直接运行。

    Java 中没有 “依赖具体实现” 的地方。其基本数据类型大小、有关运算的行为等都有明确说明。其绝大多数库都能很好地支持平台独立性,而不用担心操作系统。

  4. 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
2
javac [选项] 源文件名.java			//[] 中是可选项
DOC
  • 通过编译器将 Java 源文件编译成 JVM 可识别的字节码文件。字节码文件是二进制格式的,其格式是统一的。在源文件目录下使用 Javac 编译工具对 Java 文件进行编译。
  • 如果没有错误将没有提示,当前目录会对应其中每一个类生成对应名称的 .class 文件,即字节码文件,也是可执行的 Java 程序。

1.5.2 运行

1
2
java [选项] 程序名 [参数列表]			//[] 中是可选项
DOC
  • 有了可执行的 Java 程序(字节码文件)
  • 通过运行工具 Java.exe 对字节码文件进行执行,本质是将 .class 文件装载到 JVM 运行。
注意,修改后的 .Java 源文件需要重新编译

1.6 Java 开发注意事项和细节说明

  1. 源文件以 .java 为扩展名,源文件的基本组成部分是类(class)

  2. Java 应用程序的执行入口是 main() 方法。其有固定的书写格式:

    public static void main(string[]args){…}

  3. Java 语言严格区分大小写

  4. Java 方法由一条条语句构成,每个语句都以 ; 结束

  5. 大括号 { } 是成对出现的,缺一不可。习惯先写 {} 再写代码

  6. 一个源文件中最多只有一个 public 类,其余类不限。

  7. 如果文件中包含 public 类,则文件名必须按该类命名。

  8. 也可以把 main 方法写在非 public 类中,然后运行指定非 public 类,这样入口方法是非 public 类的主方法。

  9. 在控制台按 tab 可以实现代码补齐。按方向键 ↑ 或 ↓ 可以调用历史代码。

1.7 代码规范

  1. 类、方法的注释要以 Javadoc 的方式来写

  2. 非 Javadoc 的注释,往往是给维护者看的,着重告诉读者为什么这样写,如何修改,注意什么问题等。

  3. 不要用 a b,这种名称命名变量,尽量写得清楚 int age = 10;

    另外,Java 源代码使用的是 Unicode 码,因此汉语也能作为标识符。但不推荐使用汉语做标识符。

  4. 使用 tab 键操作,使代码右移。使用 shift+tab 键,使代码左移。

  5. 运算符两边各加入空格。注意排版规范。

  6. 源文件使用 UTF-8 编码。

  7. 代码行宽度不要超过 80 个字符。超过时通过换行保持简洁。

  8. 代码编写次行风格行尾风格

    次行风格:换行输入{ },使其总在行头

    行尾风格:在一行的末尾输入 {,换行输入 }

  9. 一段代码完成一个小功能,尽量不要混合。这样更加灵活。

1.8 Java 转义字符

  1. \t:一个制表位,实现对齐功能

  2. \n:换行符

  3. \\:一个 \

  4. \":一个 "

  5. \':一个 '

  6. \r:一个回车(不是换行)

  7. \.:一个小圆点 .

  8. \b:退格键

  9. \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
      /**
      *@see com.test.Example#act()
      *@see <a herf="../../../../../../">Melody's Box</a>
      *@see "一段文本"
      */JAVA
      1. 只提供类、方法或变量的名字。那个场合,使用 # 来分隔类名和方法名
      2. @see 后有一个 < 字符的场合,需要指定超链接。可以指向任何 URL
      3. @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
2
C:\Users\Melody>jshell
CMD

在 cmd 中输入 jshell 以启动 JShell

1
2
3
4
|  欢迎使用 JShell -- 版本 11.0.12
| 要大致了解该版本, 请键入: /help intro

jshell>CMD

JShell 会显示一个问候语,之后出现提示符

1
2
3
4
5
jshell> int n1 = 10;
n1 ==> 10

jshell> n1++;
$2 ==> 10CMD

输入一条语句,JShell 会自动打印每条输入语句的值

其中的 $2 表示该值可以用于将来的计算。上面的 n1 是自己定义的标识符

1
2
3
4
5
6
7
8
9
10
jshell> Math.
E IEEEremainder( PI abs( acos( addExact(
asin( atan( atan2( cbrt( ceil( class
copySign( cos( cosh( decrementExact( exp( expm1(
floor( floorDiv( floorMod( fma( getExponent( hypot(
incrementExact( log( log10( log1p( max( min(
multiplyExact( multiplyFull( multiplyHigh( negateExact( nextAfter( nextDown(
nextUp( pow( random() rint( round( scalb(
signum( sin( sinh( sqrt( subtractExact( tan(
tanh( toDegrees( toIntExact( toRadians( ulp(CMD

输入不完整的语句后,按 tab 键实现方法补全。上面的场合,提示了所有方法的列表

1
2
jshell> Math.pow(
CMD

自动补全后的代码。可以手动填入剩余部分。

可以按 ↑ 键自动填充运行过的代码,以实现重复运行

附录

如何快速学习技术和知识点

1 查看需求

  1. 工作需要跳槽
  2. 对方要求
  3. 技术控

2 能否使用传统技术解决

  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 命令

查看帮助:helphelp 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
2
3
4
5
6
int a;
a = 100;

int b = 100;

int c = 5, d;JAVA

2.1 变量使用注意事项

  1. 变量表示内存中的一个存储区域。不同变量,不同类型,占用的空间大小不同。如 int 有 4 byte,而 double 有 8 byte。
  2. 该区域有自己的名称 变量名 和类型 数据类型
  3. 变量必须先声明,后使用。
  4. 变量在同一作用域内不能重名。
  5. 该区域的数据 · 值可以在同一类型范围内变化。
  6. 变量的三个基本要素:类型 + 名称 + 值

2.2 程序中 + 的使用

  1. 当左右两边都是数值型,做加法运算

  2. 当左右两边任意一方为字符串,做拼接运算

  3. 运算顺序是从左到右的

    1
    2
    System.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 字节

- 引用数据类型(复合数据类型)

2.4 整数类型

用于存放整数值

  • byte 占用 1 字节,范围 -128 ~ 127
  • short 占用 2 字节,范围 -215 ~ 215 - 1
  • int 占用 4 字节,范围 -231 ~ 231 - 1
  • long 占用 8 字节,范围 -263 ~ 263 - 1

使用细节:

  1. Java 各整数类型有固定的范围和字符长度,不受具体 OS(操作系统)影响,以保证 Java 程序的可移植性。
  2. Java 默认整型常量为 int ,要声明 long 型常量必须后加 lL
  3. 从 Java 7 开始,加上前缀 0b0B 就可以写二进制数。
  4. 从 Java 7 开始,可以为数字字面添加下划线。这不会影响数字的值,只是为了方便阅读。
1
2
3
4
5
int n = 0b0010;
n = 0b001;
n = 100_0_000000;
n = 0B0000_0010_1100;
float f = 1.0F;JAVA

如果基本的整数、浮点类型不能满足范围、精度的需求,可以使用 “大数”

—— 大数,见 [[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.403E38
  • double 双精度(15 位有效数字),占用 8 字节,范围约 -1.798E308 ~ 1.798E308

浮点数在机器中存放形式为:浮点数 = 符号位 + 指数位 + 尾数位

因此,尾数部分可能丢失,造成精度损失。换言之,小数都是近似值

2.5.1 使用细节

  1. 与整数类型相似,有固定的范围和字符长度,不受具体 OS(操作系统)影响。

  2. Java 默认浮点常量为 double ,要声明 float 型常量必须后加 ”f“ 或 ”F“

  3. 浮点型常量有两种表示形式

    十进制数形式:5.13315.4F.414

    科学计数法:5.12e2 即[5.12 × 102]、5.12E-2 即[5.12 / 102]

  4. 通常情况下,应该使用 double 类型,以其更为精确。

  5. 浮点数使用陷阱:当我们对运算结果是小数的进行相对判断时,要小心。(因为小数都是近似值

    正确方法是:以两个数差值的绝对值,在某个精度范围内判断

    1
    2
    3
    if (Math.abs(num1 - num2) < 0.00001) {
    System.out.println("插值范围内认为相等");
    }JAVA
  6. 特殊的浮点类型常量

    • 正无穷大:Float.POSITIVE_INFINITYDouble.POSITIVE_INFINITY

      (浮点数运算中)一个正数除以 0,会得到该值

    • 负无穷大:Float.NEGATIVE_INFINITYDouble.NEGATIVE_INFINITY

      (浮点数运算中)一个负数除以 0,会得到该值

    • 0 / 0:Float.NaNDouble.NaN

      (浮点数运算中)0 除以 0,会得到该值

    • 最大、最小值:Float.MAX_VALUEDouble.MIN_VALUE

  7. 不能用运算符来比较特殊值,而要用特别的方法

    1
    2
    3
    4
    5
    double 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
  8. 由于不同处理器寄存浮点数的策略可能不同,浮点数运算的结果也可能不同。

    —— 见 [[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 使用细节

  1. 字符常量用单引号括起 `‘字’

  2. char 的本质是一个整数,输出时,输出的是 unicode 码对应的字符。[unicode 码查询](https://i-melody.github.io/2021/11/22/Java/入门阶段/2 变量/tool.chinaz.com/Tools/Unicode.aspx) 。

    要输出那个整数,用 int

    1
    2
    char c1 = 'a';
    System.out.println((int)c1);JAVA
  3. 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
  4. 字符允许使用转义符(见 [1.8 Java 转义字符]

    1
    2
    char c = '\u0041';
    JAVA

    转义序列 \u 能出现在引号外。所有这些转义序列会在解析代码前得到处理

    • 以下字符串是空串:

      1
      2
      String s = "\u0022+\u0022";
      JAVA

      因为 \u0022 表示引号。该代码等同于以下代码

      1
      2
      String s = "" + "";
      JAVA
    • 以下注释会报错:

      1
      2
      // \u000A is a newline
      JAVA

      因为 \u000A 是换行符。在解析前会得到处理。在程序看来,上述注释等于以下写法

      1
      2
      // 
      is a newlineJAVA
    • 以下注释也会报错:

      1
      2
      // look inside c:\users
      JAVA

      因为程序认为,\users 不是一个合法的转义字符

    • 在某些场合下这种写法似乎也能实现:

      1
      2
      int\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
    2
    String str = "🎶Melody🎶";
    char c = str.charAt(1); // <———— 这个场合,c 是 🎶 符号的第二个代码单元而非 'M'JAVA

    因此,一般不建议在程序中使用 char 类型

2.7 布尔类型

boolean 只允许取值 turefalse ,没有 null。适用于逻辑运算,通常用于程序流程控制

1
if` `while` `do-while` `for

使用细节:

  1. 不可以用 0 或 非0 的整数替代 falseture 。这点和 C语言 不同。

  2. 不能让布尔类型转换为其他类型。如需转换,请使用如下方法:

    1
    2
    boolean 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. 如若把大精度(容量)数据赋值给小精度(容量)类型,就会报错(小数由于精度原因,大赋小会丢失精度,必不可用。但整数大赋小时:1.赋予具体数值时,判断范围。2.变量赋值时,判断类型。反之进行自动类型转换。

  3. byte short char 三者不会相互自动转换,但可以计算。计算时首先转化为 int

    byte a = 1;

    byte b = 1;

    a + b 结果是 int 类型

  4. boolean 类型不参与自动转换

  5. 自动提升原则:表达式结果的类型自动提升为操作数中最大的类型。

2.8.2 强制类型转换

强制类型转换:自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时加上强制转换符 ( ) ,但可能造成精度降低或溢出,要格外注意。

2.8.2.1 使用细节

  1. 当进行数据从大到小转换时,用强制转换。

  2. 强制转换只能对最近的操作数有效,往往会使用 ( ) 提升优先级。

    1
    2
    int a = (int)(3 * 2.5 + 1.1 * 6);
    JAVA
  3. char 可以保留 int 的常量值,但不能保存其变量值。此时需要强制类型转换。

    1
    2
    3
    int a = 10;
    char b = 10;
    char c = (char)a;JAVA
  4. 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
    3
    int n1 = 100;
    String s = n1 + "";
    System.out.println(n1 + "" + n1 + "" + n1 + "");JAVA
  • String 转基本数据类型:通过基本数据类型的包装类调用 parseXX 方法。

    1
    2
    String s = "100";
    int n1 = Interger parseInt(s);JAVA

    特别的,把 String 转换为 char

    1
    2
    char c = s.charAt(0);		// 得到 s 字符串中的第一个字符。
    JAVA

2.8.3.1 使用细节

  1. String 转成基本数据类型时,要保证其能转换为有效数据。即不能把 "Hello" 转换成 int
  2. 如果格式不正确,会抛出[异常](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
    5
    int 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 使用细节

  1. 关系运算符结果都是 boolean 型,要么是 ture ,要么是 false
  2. 关系运算符的表达式,称为关系表达式
  3. 比较运算符 == 不要误写为 =
  4. 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
    8
    int 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 ,则 || 不会判断第二个。

  • ! :取反。ature,则结果为 false。反之为 ture

  • ^:逻辑异或。a b 不同时,结果为 ture,否则为 false

3.4 赋值运算符

将某个运算后的值,赋给指定变量

  • 基本赋值运算符:=

  • 符合赋值运算符:+= ; -= ; *= ; /= ; %=

    a += b 等价于 a = a + b。其余同理。

3.4.1 使用细节

  1. 运算顺序从右往左。

    1
    2
    int num = a + b + c;	// 先运行(a + b + c),再结算int num =
    JAVA
  2. 运算符左边只能是变量,右边可以是变量、表达式、常量值。

  3. 复合赋值运算符会进行类型转换。

    1
    2
    byte 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. 表达式1 和 表达式2 要为可以赋给接受变量的类型(或可以自动转换,或进行强制转换)

  2. 三元运算符可以转成 if--else

  3. 三元运算符是一个整体。

    1
    2
    Object 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 从左向右
==!= 从左向右
& 从左向右
^ 从左向右
` `
&& 从左向右
`
?:(三元运算符) 从右向左
=+=-=*=/=&=%=^=、` =<<=>>=>>>=`
  1. 运算符有不同优先级。优先级高的总是优先于低的。
  2. 只有单目运算符、赋值运算符是从右向左运算的。

3.7 标识符

Java 对各种变量、方法和类等命名时使用的字符序列称为标识符

凡是自己可以起名字的地方都叫标识符 double height = 0.0;

3.7.1 命名规则

  1. 标识符由 26 个大、小写英文字母,0 - 9 阿拉伯数字,_ 或 $ 符号组成。

  2. 数字不能开头。 错误示范:int 3a = 1;

  3. 不能使用关键字和保留字(可以包含)。[具体的关键字和保留字请自行查询](https://i-melody.github.io/2021/11/23/Java/入门阶段/3 运算符/www.baidu.com)

  4. 严格区分大小写,长度无限制。

  5. 不能包含空格。

  6. 与多数编程语言不同。Java 可以用任何 Unicode 字符(特殊字符除外)作为标识符。但不推荐这么做

    来 String 一只猫:

    1
    2
    String ᓚᘏᗢ = "✪ ω ✪";				// 喵?
    JAVA

3.7.2 命名规范

  1. 包名:多单词组成时所有字母都小写:aaa.bbb.ccc
  2. 类名、接口名:多单词组成时,采用大驼峰法,所有单词的首字母大写:XxxYyyZzz
  3. 变量名、方法名:多单词组成时,采小驼峰法(驼峰法),第一个单词首字母小写,第二个开始每个单词首字母大写:xxxYyyZzz
  4. 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
  5. 更详细的规则查看 Java 编码规范

3.8 输入与输出

为满足读取用户输入、输出的需求,Java 提供了几个基本类

3.8.1 Scanner 类

在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。

Input.java ,需要一个 扫描器(对象),就是 Scanner

Scanner 属于 java.util 包。其包含许多方法

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Scanner;		//[1] 导入

public class Code3_8_1{
public static void main(String[] args){
Scanner aScannerName = new Scanner(System.in); //[2] 创建 Scanner 对象
System.out.println("\n输入点什么吧!");
String pointSomething = aScannerName.next(); //[3] 接收用户输入
System.out.println("\n接下来,输入一个数字吧!");
double aNumber = aScannerName.nextDouble(); //[3] 接收用户输入
System.out.println("\n你输入的\"点什么\"是:\""
+ pointSomething + "\"\n你输入的\"一个数字\"是:\""
+ aNumber + "\"");
}
}JAVA
  1. 导入该类所在的包
  2. 创建该类对象(声明变量)
  3. 调用里面的功能,接收用户输入

构造方法:

常用方法:

  • String next():读取下一个输入对象

    Scanner 对象用空白(空格、水平制表符或回车换行符)作为输入的分隔元素

  • Double nextDouble():读取下一个 double

    Int 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
    2
    System.out.printf("%s,你来啦,给你 %d 拳", "柏枝凪斗", 1);	// <———— 给了柏枝凪斗一拳
    JAVA

    以 % 开头的 格式说明符 都用相应参数替换。格式说明符尾部的转换符表示要格式化的数值类型

    转换符 类型 转换符 类型
    d 十进制整数 s 字符串
    x 十六进制整数 c 字符
    o 八进制整数 b 布尔值
    f 定点浮点数 h 散列码
    e 指数浮点数 tx 或 Tx 日期时间(T强制大写)
    g 通用浮点数 % 百分号
    a 十六进制浮点数 n 行分隔符

    也能指定控制格式化输出外观的各种标志

    1
    2
    System.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):将该格式的小数部分允许的最大位数设置为 n

    setMinimunFactionDigits(n):将该格式的小数部分允许的最小位数设置为 n

  • setMaximunIntegerDigits(n):将该格式的整数部分允许的最大位数设置为 n

    setMinimunIntegerDigits(n):将该格式的整数部分允许的最小位数设置为 n

3.8.4 DecimalFormat 类

与 NumberFormat 类不同,DecimalFormat 类可以直接实例化对象。

实例化:

  • new DecimalFormat(pattern)

    其中 pattern 是一个 String,代表格式化处理模式。如

    1
    2
    DecimalFormat df = new DecimalFormat("0.###");		//格式对象,保留 3 位小数
    JAVA

常用方法:

  • format(num):获取 num 的格式化语句
  • applyPattern(pattern):变更要使用的格式

3.9 进制

  • 二进制:数字有 0 - 1,满 2 进 1,以 0b0B 开头
  • 十进制
  • 八进制:0 - 7,满 8 进 1,以 0 开头表示
  • 十六进制:0 - 9 及 A - F,满 16 进 1,以0x0X 开头。此处的 A - F 不分大小写。

3.9.1 进制的转换

  • 其他进制 转 十进制:略
  • 十进制 转 其他进制:将该数不断除以对象进制数,直到商为0为止,将每步得到的余数倒过来。
  • 二进制 与 八进制 或 十六进制 互相转换:二进制 从低位开始,每 3 位一组,转成对应的 八进制 即可。反之同理。十六进制亦同理(每 4 位一组)。

3.9.2 原码、反码、补码

对于有符号数而言:

  1. 二进制的最高位是符号位:0表示正数,1表示负数。
  2. 正数的原码、反码、补码都一样。
  3. 负数的反码 = 原码符号位不变,其他位取反(0 变 1,1 变 0)
  4. 负数的补码 = 反码 + 1。也就是说,负数的反码 = 补码 - 1。
  5. 0 的反码、补码都是 0。
  6. Java 没有无符号数。换言之,Java 的数都是有符号的。
  7. 计算机运算时,都是以补码的方式来运算的。
  8. 当我们看运算结果时,要看其原码。

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

使用细节:

  1. 位运算符只对整型、字符类型有效

  2. 移位运算中,左侧操作 int 的场合,右侧操作数以 32 取模;long 的场合,右侧操作数以 64 取模。

    1
    2
    int 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
2
graph LR
A(A语句)-->B(B语句)-->C(C语句)-->D(D语句)-->E(...)

语句:Java 中最小的执行单位。语句分为 单语句 和 复合语句。

  • 单语句:通常意义的一条语句。语句间以分号 ; 分隔。

  • 复合语句:一对大括号括起来的语句组。也称为 “块”

    1
    2
    3
    4
    5
    {
    语句1;
    语句2;
    ...
    }JAVA

    块中可以有多条语句。块后没有分号 ;

4.2 分支控制 if-else

让程序有选择的执行。主要分为:单分支控制、双分支控制

4.2.1 单分支控制

1
2
if (条件表达式) 语句;
JAVA

特别地,把代码块(复合语句)作为语句的场合也能这样写:

1
2
3
if (条件表达式) {
执行代码块;
}JAVA

当条件表达式为 ture,就会执行 {执行代码块;};如果为 false 则不执行。特别地:如果 {执行代码块;} 中只有一条代码,也可以不写 { }(但还是建议写上)

4.2.2 双分支控制

1
2
3
4
5
if (条件表达式) {
执行代码块;
} else {
执行代码块2;
}JAVA

当条件表达式为 ture,就会执行 {执行代码块1;};如果为 false 则执行 {执行代码块2;}

4.2.3 多分支控制

1
2
3
4
5
6
7
8
9
10
11
if (条件表达式) {
执行代码块;
} else if (条件表达式2) {
执行代码块2;
} else if (条件表达式3) {
执行代码块3;
}
...
else {
执行代码块n;
}JAVA

特别地:多分支可以没有 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
2
3
4
5
6
7
8
9
10
11
12
switch(表达式){
case 常量1:
语句块1;
break; //break 语句可选
case 常量2:
语句块2;
break; //break 语句可选
...
default: //default 语句可选
default语句块;
break; //break 语句可选
}JAVA
  1. switch 关键字,表示 switch 分支。
  2. 表达式 对应一个值。该值必须是 int 或 char(char 可以转化为 int)。是 byte 或 short 的场合,要提升为 int。不允许 long、double 或 float
  3. case 常量1; 表示:当 表达式 的值等于 常量1 ,则执行 语句块1
  4. break; 表示退出 switch 分支。
  5. 表达式 的值匹配 常量1 ,则执行 语句块1,如果不匹配,则继续匹配 常量2 ,以此类推。
  6. 如果全都不匹配,则执行 default
  7. 如果不写 break; ,则会发生穿透,即不进行判断而继续执行下一语句块。

4.3.1 使用细节

  1. 表达式; 数据类型,应和 case 后的 常量 类型一致,或者是可以自动转换成可以比较的类型。如:输入的是 char常量int

  2. switch 中 表达式 的返回值必须是:byte short int char ``enum String

    ——enum 是什么?详见 [[10.1 枚举 ]](https://i-melody.github.io/2021/12/17/Java/入门阶段/10 枚举和注解/#10-1-枚举)

  3. case 语句中的值必须是 常量 或 常量表达式,不能是 变量。

  4. default 是可选的。没有就不执行。

  5. break; 用来跳出 switch 分支。如果不写,会持续执行语句,直到分支结束或遇到下一个 break;

4.3.2 与 if-else 分支结构的取舍

4.4 for 循环控制

让代码可以循环执行。

1
2
3
for(循环变量初始化;循环条件;循环变量迭代){
循环操作(代码块);
}JAVA
  • for 关键字,表示循环控制
  • 四个要素:1. 循环变量初始化 2. 循环的条件 3. 循环的操作 4. 循环变量迭代

所有循环开始前仅一次进行初始化。直到循环条件变为 false 前,执行循环操作。每轮循环结束后,进行循环变量迭代。

  • 循环操作可以有多条语句
  • 如果循环操作只有一条语句,可以省略 " ",但建议不省略

4.4.1 使用细节

  1. 循环条件是返回一个 boolean 值(turefalse)的公式。

    循环条件可以为空。这个场合,默认为真(true)

  2. for(;循环条件;){ } 其中的初始化和变量迭代可以写在别处,但 ; 不能省略。如果不写在别处,那个 循环变量初始化 中声明的变量只能在该 for 循环中使用。

  3. 控制台用 ctrl + c 强制结束一个流程

  4. 循环初始值可以有多条初始化语句,但要求类型一样,并用 , 隔开。

    变量迭代也可以有多条代码,用 , 隔开。

4.4.2 for each(泛型 for 循环)

泛型 for 循环(增强 for 循环)能用来依次处理数组(或其他元素集合)中的每个元素,而不必考虑下标值

1
2
3
for(int i : nums){						//其中 nums 是一个一维 int 数组
System.out.println(i);
}JAVA

上述写法(增强 for 写法)类似于以下写法

1
2
3
for(int i = 0; i < nums.length; i++){
System.out.println(nums[i]);
}JAVA

泛型 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
2
3
4
while(循环条件){
循环体(代码块);
循环变量迭代;
}JAVA

while 也有四要素,只是位置和 for 不同

4.5.1 使用细节

  1. 循环条件是返回一个 boolean 值(turefalse)的公式。

    while 循环中,循环条件不能为空。

  2. while 循环是先判断再执行语句。

4.6 do..while 循环控制

1
2
3
4
do{
循环体;
循环变量迭代;
}while(循环条件);JAVA
  1. do while 是关键字
  2. 也有四要素,位置不同
  3. 先执行,再判断。也就是说,一定会至少执行一次
  4. 最后有一个 ;
  5. whiledo..while 区别:“要账”

4.6.1 使用细节

  1. 循环条件是返回一个 boolean 值(turefalse)的公式。
  2. 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
2
3
4
5
6
7
8
a:{
b: {
c: {
...
berak b;
}
}
}JAVA
  1. a: b: c: 是标签,名字由程序员指定
  2. break 后指定哪个标签就退出到哪里
  3. 实际开发中,尽量不要使用标签

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
    3
    int[] ints = new int[5];				// 创建了数组 name,存放5个int
    int ints2[] = new int[1]; // 这种写法也行
    ints[2] = 15; // 访问数组第3个数JAVA
  • 构造方式2:动态初始化

    1
    2
    3
    char[] chars;							// 先声明数组 name,此时数组是 null
    chars = new char[2]; // 分配内存空间,可以存放数据了
    chars[1] = '\t';JAVA
  • 构造方式3:静态初始化

    1
    2
    boolean[] bools = {true, false, true, false};
    String[] strs = new String[]{"阿伟,你又在打电动噢", "烦啦"};JAVA

    确切知道数组每个元素的场合可以用这个方法。

数组的使用方法:

  • 访问数组元素:数组名[元素下标]

    其中,元素下标从 0 开始编号。如:访问 strs 数组的第一个元素 strs[0]

  • 数组长度:数组名.length

    是一个 int 值。不能通过试图改变该值来改变数组容量

5.1.1 数组赋值机制

  1. 基本数据类型赋值,赋值方式是值拷贝。这个值就是具体的数据,且互不影响

  2. 数组在默认情况下是引用传递,赋的值是地址,赋值方式为引用传达。

    1
    2
    3
    int[] array1 = {0, 0, 0};
    int[] array2 = array1;
    array2[0] = 100;JAVA

    上述情况下,array1[0] 也会变成 100。因为数组在 JVM 的 栈 里是一个地址,指向 堆 里的一个空间。这两个数组在上述情况下指向同一空间。

    1
    2
    3
    4
    5
    int[] 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.Scanner;
public class Code5_1_3{

public static void main(String[] args){

Scanner inP = new Scanner(System.in);
int[] arr1 = {1, 2, 3}; // 这是原数组
int add = 0; // 这个变量记录输入的新元素的值
int count = arr1.length; // 这个变量是新数组的长度
char yN = 'a'; // 记录询问继续与否时用户的输入字符

do{
/* 创建多一位的新数组,把新元素赋给新数组的最后一位 */
System.out.println("请输入添加数字:");
add = inP.nextInt();
int[] tem = new int[arr1.length + 1];
tem[count] = add;

/* 把旧数组的值全部赋给新数组 */
for(int i = 0; i < arr1.length; i++){
tem[i] = arr1[i];
}

/* 把新数组保存下来,输出现在的数组 */
arr1 = tem;
count++;
System.out.println("\n\n当前数组为:");

for(int i = 0; i < arr1.length; i++){
System.out.print(arr1[i] + " ");
}

/* 询问是否继续添加,输入n跳出,否则循环 */
System.out.println("\n\n是否继续添加?(Y/N)");
yN = inP.next().charAt(0);

}while(yN != 'N' && yN != 'n');

}
}JAVA

5.2 二维数组

1
2
3
4
5
int[][] ints;					// 声明一个二维数组
int[] ints2[]; // 也能这样声明
int ints3[][]; // 这样也行
int[] x,y[]; // 声明了两个数组,一个是 int[] x 一个是 int[][] y
// 把 int[] 视作一个类型,就能很好地理解这个写法JAVA

二维数组实际是由多个一维数组组成的,它的各个元素的长度可以相同,也可以不同。

数组是一个对象,所以二维数组的元素存放的是一维数组的地址。

二维数组构造方法:

  • 构造方法1:动态初始化

    1
    2
    int[][] many_ints = new int[3][4]	// 创建 有3个 包含4个元素的一维数组 的二维数组
    JAVA
  • 构造方法2:动态初始化

    1
    2
    double[][] many_doubles;			// 先声明变量
    many_doubles = new double[3][4]; // 再开辟空间JAVA
  • 构造方法3:动态初始化-列数不确定

    1
    2
    3
    4
    char[][] many_chars = new char[3][];	// 创建一个三行列数不确定的二维数组
    for (int i = 0; i < 3; i++) {
    many_chars[i] = new char[i + 1]; // 此时,每个数组空间依次增大
    }JAVA
  • 构造方法4:静态初始化

    1
    2
    int[][] many_many = {{1, 3}, {4, 10, 2}, {95}};
    JAVA

二维数组使用方法:

  • ints.length:该二维数组的长度,这里是 3
  • ints[0]:该二维数组的第一个子数组
  • ints[0].length:该二维数组的第一个子数组的长度,这里是 4
  • ints[1][0]:该二维数组第二个子数组的第一个元素的值,这里是 21

这是一维数组:int[] a、这是二维数组:int[][] b。好了,现在来写一个堆排序吧

5.3 查找算法

在 Java 中,常用的查找有 4 种:

  • 顺序查找(遍历)
  • 二分查找
  • 插值查找
  • 斐波那契查找

5.3.1 线性查找

逐一比对,直到发现目标值

1
2
3
4
5
6
public static int seqSearch(int[] array, int target) {
for (int i = 0; i < array.length; i++) {
if (array[i] == target) return i;
}
return -1;
}JAVA

5.3.2 二分查找

二分查找要求数组必须是有序数组。

每次取出那个中位数。目标值小于中位数的场合,则在较小一侧的范围内继续二分查找。否则在那个较大一侧查找。

递归方式二分查找:

1
2
3
4
5
6
7
8
9
10
11
12
public static int binarySearch(int[] array, int target) {
return binarySearch(array, target, 0, array.length);
}

public static int binarySearch(int[] array, int target, int l, int r) {
if (l >= r) return -l - 1;
int p = (l + r) / 2;
if (target == array[p]) return p;
else if (target > array[p]) {
return binarySearch(array, target, p + 1, r);
} else return binarySearch(array, target, l, p - 1);
}JAVA

非递归方式二分查找:

1
2
3
4
5
6
7
8
9
10
11
public static int binarySearch2(int[] array, int target) {
int l = 0;
int r = array.length;
while (r > l) {
int p = (r + l) / 2;
if (target == array[p]) return p;
else if (target > array[p]) l = p + 1;
else r = p - 1;
}
return -l - 1;
}JAVA

5.3.3 插值查找

插值查找类似于二分查找,但其不是取出中位数,而是从自适应的位置取出一个元素

那个自适应的取出位置 ���=���+(ℎ��ℎ−���)×������−���[���]���[ℎ��ℎ]−���[���]p**os=low+(highlowa**rr[high]−a**rr[low]targe**ta**rr[low]

上面的 ���low 是查找范围的下界,ℎ��ℎhigh 是查找范围的上界,������targe**t 是目标值,���[�]a**rr[i] 是数组第 �i 个元素的值

可见,如若那个目标值更靠近某一端,这个自适应的取出位置也会更接近那一端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static int insertSearch(int[] array, int target) {
if (target > array[array.length - 1]) return -array.length;
else if (target < array[0]) return 0;
int l = 0;
int r = array.length;
int p = 0;
while (r >= l) {
p = l + (target - array[l]) * (r - 1 - l) / (array[r - 1] - array[l]);
if (target == array[p]) return p;
else if (target > array[p]) l = p + 1;
else r = p - 1;
}
return -p;
}JAVA

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
2
3
4
5
6
7
8
9
10
比前一个元素小

有未确定的值

待排序序列
遍历
元素
序列
交换
有序序列

如此,各元素不断接近自己的位置。值较大的元素逐渐向后移动,就像水下的气泡一样逐渐上浮。

特别地:如果在某次排序中没有发生过任何交换,则此时是已完成排序,可提前结束排序过程。

上面是 b 站大佬 说数人 制作的动画,演示了冒泡排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void bubble_sort(int[] array) {
/* 共执行 n - 1 轮排序 */
for(int count = 1; count < array.length; count++){
boolean non_exchange = true; // 该轮排序进行了交换时变为 false
/* 每轮遍历 n - count 次 */
for(int n = 0; n < array.length - count; n++){
/* 当比较时,如果逆序,则交换 */
if(array[n] > array[n + 1]){
non_exchange = false;
int temp = array[n + 1];
array[n + 1] = array[n];
array[n] = temp;
}
}

/* 如果没有交换过,则提前结束排序过程 */
if (non_exchange) break;
}
}JAVA

如果有 �n 个元素,就进行 �−1n−1 轮比较,第 �k 轮排序比较 �−�nk 个元素,并进行最多 �−�nk 次交换。时间复杂度为 �(�2)O(n2)

对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行冒泡排序。
那个统计时间是:6416 ms

5.4.2 选择排序

**选择排序:**从 待排序序列 中,按指定规则选出某一元素,再依照规定交换位置后达到排序目的。

1
2
3
4
5
6
7
8
为空
不为空
待排序序列
遍历查找
最小元素
与首位交换
剩余序列
有序序列

从 待排序序列 中找出 最小值,与下标 0 位置元素 交换。之后在剩余元素中找出 最小值,与下标 1 元素 交换。以此类推,直到完成。

上面是 b 站大佬 说数人 制作的动画,演示了选择排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void choice_sort(int[] array) {
for (int p = 0; p < array.length - 1; p++) {
int min_index = p; // loc 指向那个当前找到的最小值的下标
/* 找出当前剩余元素中的最小值 */
for (int n = p + 1; n < array.length; n++) {
if (array[n] < array[min_index]) min_index = n;
}
/* 将找到的最小值与对应位置元素交换 */
if (min_index != p) {
int temp = array[min_index];
array[min_index] = array[p];
array[p] = temp;
}
}
}JAVA

如果有 �n 个元素,就进行 �−1n−1 轮选择,第 �k 轮选择要比较 �−�nk 个元素,并进行 11 次交换。时间复杂度为 �(�2)O(n2)

对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行选择排序。
那个统计时间是:1616 ms

5.4.3 插入排序

**插入排序:**在 待排序序列 中的元素,以插入的方式找寻该元素的适当位置,以达到排序目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
首元素

其他元素



无序表不为空
待排序序列
有序表
有序列表
无序表
取出元素
插入

将 待排序序列 的元素视为一个 有序表 和一个 无序表。起初, 有序表 中只有一个元素, 无序表 中有 n - 1 个元素。

排序过程中每次从 无序表 中取出第一个元素,把它依次与 有序表 元素进行比较,确定其在 有序表 的位置,将其插入 有序表 ,成为新的 有序表 。

上面是 b 站大佬 说数人 制作的动画,演示了插入排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void insert_sort(int[] array) {
/* 数组的前一部分视为有序表,后一部分视为无序表 */
for (int p = 1; p < array.length; p++) {
int temp = array[p]; // 那个 无序表首元素 的值
int insertPos = p; // 那个 无序表首元素 在有序表的位置

/* 逆向遍历那个有序表,以确定该无序表首元素在有序表的位置。
到达位置前,将有序表的插入位置后的元素后移 */
for (int n = p - 1; n >= 0; n--) {
if (array[n] < temp) break;
array[n + 1] = array[n];
insertPos--;
}
/* 将该元素插入指定位置 */
array[insertPos] = temp;
}
}JAVA

如果有 �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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
长度的一半

步长是 1














待排序序列
步长
分组
序列
有序序列
组1
插入排序
步长减半
组2
插入排序
组3
插入排序
...
插入排序

希儿排序也是一种插入排序,是简单插入排序的高效改进版。也称为:缩小增量排序。

希儿排序是把 待排序序列 按下标的一定 步长 进行 分组,对每组使用 插入排序法。随着 步长 逐渐减少,每组包含的 元素个数 也越来越多。

当 步长 减至 1 时,整个 待排序序列 恰被分为 1 组,算法便中止。

上面是 b 站大佬 说数人 制作的动画,演示了希儿排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void seele_sort(int[] array) {
int step = array.length / 2; // 步长。该值等于分组数量

/* 初始将数组为 2 数一组,那么每组的步长就是 array.length / 2
步长是那个分组中,相邻两个数的下标间隔。步长的值也等于组数
直到那个分组大小等于原序列,也就是步长 == 1 时,进行最后一次排序 */
while (step >= 1) {

/* 一共有 step 组,对每一组都进行一次完整的插入排序 */
for (int group = 0; group < step; group++) {

/* 每一组中,下一个元素的下标是:当前下标 + step
每组的起始下标是 group,根据插入排序法,将每组视为有序表和无序表
那个无序表从每组第二个元素开始计数,也就是下标 group + step */
for (int toSort = group + step; toSort < array.length; toSort += step) {
int insertPos = toSort;
int temp;
/* 已经有序时直接 continue */
if ((temp = array[toSort]) >= array[toSort - step]) continue;
/* 寻找插入位置。在找到位置前,移动那些有序表元素 */
for (int exPos = toSort - step; exPos >= group; exPos -= step) {
if (array[exPos] < temp) break;
array[exPos + step] = array[exPos];
insertPos -= step;
}
/* 执行插入 */
array[insertPos] = temp;
}
}
/* 每轮循环后,分组容量变为 2 倍,那么步长就会变为一半 */
step /= 2;
}
}JAVA

如果有 �n 个元素,就进行 log⁡2�log2n 轮排序。每轮排序最多进行 �n 次比较。因此,时间复杂度为 �(�log⁡�)O(nlogn)

对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行希儿排序。
那个统计时间是:11 ms

5.4.5 快速排序

**快速排序:**是对冒泡排序的一种改进。通过一趟排序,将 待排序序列 分割 成独立的两部分,其中一部分的所有数据都比另一部分小,再按此方法对这两部分数据 分别进行快速排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
取出一个元素

取出一个元素

取出一个元素



取出一个元素



取出一个元素

取出一个元素



取出一个元素










待排序序列
比较
较小元素序列
比较
较小元素序列
...
元素
元素
较大元素序列
..
元素
元素
较大元素序列
比较
较小元素序列
...
元素
元素
较大元素序列
...
元素
元素
有序序列

整个排序过程可以 递归进行,最终使整个数据变成有序序列。

上面是 b 站大佬 说数人 制作的动画,演示了快速排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void quick_sort(int[] array) {
quick_sort(array, 0, array.length);
}

private static void quick_sort(int[] array, int start, int end) {
if (start == end) return;
int key = array[end - 1]; // 将最后一个值取出作为比较值
int l = start; // 左指针
int r = end; // 右指针
/* 遍历指定区间,将那些较小值放到左侧,较大值放到右侧 */
for (int i = start; i < r; ) {
if (array[i] < key) {
array[l++] = array[i];
} else if (array[i] > key) {
int temp = array[--r];
array[r] = array[i];
array[i] = temp;
continue;
}
i++;
}
/* key 值可能重复,那些左右指针间的空位置都是 key 值 */
for (int i = l; i < r; i++) {
array[i] = key;
}
quick_sort(array, start, l); // 对那个较小侧区间再进行快速排序
quick_sort(array, r, end); // 对那个较大测区间再进行快速排序
}JAVA

如果有 �n 个元素,则最多进行 log⁡2�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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
待排序序列
分解
元素1
排序
组1
合并
组1
合并
有序序列
元素2
元素3
排序
组2
元素4
元素5
排序
组3
合并
组2
元素6
元素7
排序
组4
元素8

对 2 个元素进行比较、排序,是很容易的。对 2 个有序序列,将其合并为新的有序序列,也是容易的。

因此,我们把 待排序序列 分成许多 组,每组包含 2 个元素,并对每组进行排序。再逐渐 合并 那些组,最终就能得到一个完整的有序序列。

上面是 b 站大佬 说数人 制作的动画,演示了归并排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public static void merger_sort(int[] array) {
int tl = (int) Math.ceil(array.length / 2.0);
int[][] temp = new int[tl][];
for (int i = 0; ; i += 2) {
if (i == array.length) {
if (i == array.length + 1) temp[i / 2] = new int[]{array[i - 1]};
break;
} else {
if (array[i] > array[i + 1]) {
temp[i / 2] = new int[]{array[i + 1], array[i]};
} else {
temp[i / 2] = new int[]{array[i], array[i + 1]};
}
}
}
while (temp.length != 1) {
temp = combine(temp);
}
System.arraycopy(temp[0], 0, array, 0, array.length);
}

private static int[][] combine(int[][] temp) {
int tl = (int) Math.ceil(temp.length / 2.0);
int[][] tt = new int[tl][];
for (int i = 0; i < tt.length; i++) {
int tp = 2 * i;
if (tp + 1 >= temp.length) tt[i] = temp[tp];
else tt[i] = combine(temp[tp], temp[tp + 1]);
}
return tt;
}

private static int[] combine(int[] a, int[] b) {
int[] ret = new int[a.length + b.length];
int ap = 0;
int bp = 0;
int rp = 0;
while (true) {
if (ap >= a.length) {
while (bp < b.length) {
ret[rp++] = b[bp++];
}
break;
} else if (bp >= b.length) {
while (ap < a.length) {
ret[rp++] = a[ap++];
}
break;
} else if (a[ap] > b[bp]) {
ret[rp++] = b[bp++];
} else {
ret[rp++] = a[ap++];
}
}
return ret;
}JAVA

如果有 �n 个元素,则发生 log⁡2�log2n 轮合并。每轮排序最多进行 �n 次比较与 �n 次插入。因此,时间复杂度为 �(�log⁡�)O(nlogn)

对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行归并排序。
那个统计时间是:15 ms

5.4.7 基数排序

**基数排序:**属于分配式排序(又称 “桶排序”。顾名思义,通过键值各个位的值,将 待排序序列 的元素分配至某些 桶 中,达到排序的作用)。基数排序是经典的空间换时间的算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
是 0

是 1

是 2

是 3

是 4

是 5

是 6

是 7

是 8

是 9


小于 10k
大于等于 10k
待排序序列
第 k 轮
元素的第 k 位
桶0
新序列
桶1
桶2
桶3
桶4
桶5
桶6
桶7
桶8
桶9
序列的最大值
有序序列

基数排序法是桶排序的扩展。将整数 按照位数切割成不同数字,然后 按照每个位数分别比较。

该方法只能用于比较正数。你非要比较负数?咱也不说不行,你自己看着办吧

上面是 b 站大佬 说数人 制作的动画,演示了基数排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void radix_sort(int[] array) {
int[][] bucket = new int[10][array.length];
int sub = 1;
int border = 9;
int[] p = new int[10];
while (true) {
boolean noMore = true;
for (int i :array) {
int pos = (i / sub) % 10;
bucket[pos][p[pos]++] = i;
if (i > border) noMore = false;
}
int n = 0;
for (int i = 0; i < 10; i++) {
int j = 0;
while (p[i] > 0) {
array[n++] = bucket[i][j++];
--p[i];
}
}
sub *= 10;
border = 10 * sub - 1;
if (noMore) break;
}
}JAVA

若桶数为 �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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
整理
...








遍历结束

继续遍历




子节点更大
节点

逆序遍历非叶节点
当前序列
与子节点交换
切换到子节点
整理
待排序序列
剩余元素

唯一元素?
有序序列
首尾交换
  1. 将 待排序序列 构成一个 大顶堆(数组形式)

    从最后一个非叶节点开始,从左至右,从下至上进行调整。

    调整时如果乱序,则将子节点中较大方的值与该节点交换即可。交换后,那些子节点乱序的场合也要依次调整。

    调整完成后,就得到了一个 大顶堆。

  2. 此时,堆顶元素即整个序列的最大值。将其 与队列末尾元素交换。

  3. 对剩余元素进行调整,使其恢复成 大顶堆。

  4. 重复上述步骤,就得到了有序序列。

什么是堆 生成大顶堆 维护大顶堆 堆排序

上面是 b 站大佬 说数人 制作的动画,演示了堆排序的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static void heap_sort(int[] array) {
int len = array.length;
for (int j = array.length / 2 + 1; j >= 0; j--) {
heap_help(array, j, len);
}
heap_swap(array, --len);
while (len > 1) {
int tar = len;
while (tar > 0) {
tar = (tar - 1) / 2;
heap_help(array, tar, len);
}
heap_swap(array, --len);
}
}

private static void heap_help(int[] array, int tar, int length) {
int temp = array[tar];
for (int i = tar * 2 + 1; i < length; i = i * 2 + 1) {
if (i < length - 1 && array[i] < array[i + 1]) i++;
if (array[i] > temp) {
array[tar] = array[i];
tar = i;
} else {
break;
}
}
array[tar] = temp;
}

private static void heap_swap(int[] array, int index) {
int temp = array[0];
array[0] = array[index];
array[index] = temp;
}JAVA

构建堆时,若有 �n 个元素,即进行 �n 次整理。每次整理最多进行 log⁡2�log2n 次比较 。之后,每次取出堆顶元素后,需进行一次整理。故时间复杂度是 �(�log⁡�)O(nlogn)

对随机数组 int[] array(array.length = 80000;array[i] ∊ [0, 107))进行堆排序。
那个统计时间是:16 ms