重生之我在CSDN学java 第二天(java基础)
文章目录
Java开发环境-idea的安装及使用
下载
到官网下载
选择版本下载
安装
点击安装包一键安装就可以了。之后是可以试用30天。各位自己网上找一下,每个月自动更新注册码,都可以重新免费使用30天
插件和配置
推荐观看
一、注释
- 单行注释://
- 多行注释:/* */
- 文档注释::/** **/
二、变量
1. 基本类型 类型:4大类8种
数据类型 | 内存占用 |
---|---|
整型 | |
byte | 1 |
short | 2 |
int(默认) | 4 |
long | 8 |
浮点型 | 8 |
float | 4 |
double | 8 |
字符型 | |
char | 2 |
布尔型 | |
boolean | 1 |
2. 类型转换
什么是自动类型转换,为什么要进行自动类型转换?
类型范围小的变量,可以直接复制给类型范围大的变量,自动类型转换的其他格式
自动类型转换的其他格式 byte、short、char——>int——>long——>float——>double
强制类型转换
float a = 20.34f;
int b = (int)a;
自动类型转换
int c = 10;
float d = c;
3. 装箱与拆箱
基本数据类型(如int, double等)可以转换为对应的包装类(如Integer, Double等),这个过程称为装箱(Boxing)。同样,包装类也可以转换回基本数据类型,这个过程称为拆箱(Unboxing)。Java 5引入了自动装箱和自动拆箱的概 念,使得这个过程可以自动进行。
基本数据类型到包装类的装箱
Java提供了基本数据类型的包装类:
int 对应 Integer
double 对应 Double
boolean 对应 Boolean
char 对应 Character
byte 对应 Byte
short 对应 Short
long 对应 Long
float 对应 Float
4. Java内存分析
Java内存分析是理解Java程序如何使用内存以及如何优化内存使用的重要方面。Java虚拟机(JVM)负责管理内存,包括堆内存、栈内存、方法区、直接内存等。下面是一些关键点,用于理解Java内存分析:
4.1. 堆内存(Heap)
对象分配:Java对象主要在堆内存中分配。当创建对象时,JVM会从堆内存中分配空间。
垃圾回收:JVM的垃圾回收器(Garbage Collector, GC)负责回收不再使用的对象所占用的内存。
内存泄漏:如果程序中存在对象不再使用但没有被垃圾回收器回收的情况,可能会导致内存泄漏。
4.2. 栈内存(Stack)
局部变量:方法中的局部变量(包括基本数据类型和对象引用)存储在栈内存中。
方法调用:每当一个方法被调用时,一个新的栈帧(Stack Frame)会被创建并压入栈中,包含方法参数、局部变量 等信息。方法执行完毕后,栈帧会被弹出栈。
4.3. 方法区(Method Area)
类信息:存储类的结构信息,如字段、方法代码、常量池等。
运行时常量池:存储编译期生成的各种字面量和符号引用。
4.4. 直接内存(Direct Memory)
NIO:Java的NIO(New Input/Output)类使用直接内存,允许Java程序直接访问操作系统底层的内存。
性能:直接内存访问比堆内存访问更快,但使用不当可能导致内存泄漏。
4.5. 内存分析工具
为了进行Java内存分析,可以使用以下工具:
JConsole:JVM自带的图形化监控工具,可以监控内存使用情况。
VisualVM:一个功能强大的多合一分析工具,可以监控内存使用、线程、CPU使用等。
MAT(Memory Analyzer Tool):Eclipse基金会提供的一个内存分析工具,可以分析内存泄漏和内存占用。
JProfiler:商业的性能分析工具,提供详细的内存和CPU分析功能。
YourKit:另一个商业性能分析工具,提供内存和CPU分析功能。
4.6. 内存分析最佳实践
合理使用对象:避免创建不必要的对象,减少内存占用。
避免内存泄漏:确保对象不再使用时能够被垃圾回收器回收。
优化垃圾回收:根据应用程序的特性选择合适的垃圾回收器和参数。
使用内存分析工具:定期使用内存分析工具检查内存使用情况,及时发现和解决内存问题。
5. 字符串操作
字符串操作是编程中非常常见且重要的部分。Java提供了丰富的字符串操作方法,这些方法主要集中在String类和StringBuilder类中。下面是一些常用的字符串操作方法的总结:
5.1. String类
String类是不可变的,这意味着一旦创建了String对象,其内容就不能被改变。所有修改字符串的方法都会返回一个新的String对象。
创建字符串
String str = "Hello, World!";
字符串比较
String str1 = "Hello";
String str2 = "World";
String str3 = "Hello";
str1.equals(str2); // 返回 false
str1.equals(str3); // 返回 true
获取字符串长度
tring str = "Hello";
int length = str.length(); // 返回 5
字符串查找
String str = "Hello, World!";
int index = str.indexOf("World"); // 返回 7
字符串提取
String str = "Hello, World!";
String subStr = str.substring(7); // 返回 "World!"
字符串转换
String str = "123";
int number = Integer.parseInt(str); // 将字符串转换为整数
5.2. String类StringBuilder类
StringBuilder类是可变的,它提供了用于修改字符串的方法,如添加、删除和插入字符。StringBuilder通常用于需要频繁修改字符串的场景。
创建StringBuilder对象
StringBuilder sb = new StringBuilder("Hello");
sb.append(", World!"); // 添加字符串
sb.insert(5, "Java "); // 在指定位置插入字符串
sb.delete(5, 10); // 删除指定范围的字符
转换为字符串
StringBuilder sb = new StringBuilder("Hello");
String str = sb.toString(); // 将StringBuilder转换为String
三、运算符
基本的算术运算符、+符号做连接符、自增自减运算符、赋值运算符、关系运算符、逻辑运算符、三元运算符、运算符的优先级
1. 本的算术运算符、+符号做连接符
符号 | 作用 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 与"%"相同,注意,在java中两个整数相除,结果还是整数 |
% | 取余 获取的是两个数据做除法的余数 |
"+"符号可以做连接符
"+"符号与字符串运算的时候是用作连接符的,其结果依然是一个字符串
“abc”+10——> “abc10”
2. 增自减运算符
符号 | 作用 |
---|---|
自增:++ | 放在某个变量前面或者后面,对变量自身的值加1 |
自减:– | 放在某个变量前面或者后面,对变量自身的值减1 |
注意:
++、–只能操作变量,不能操作字面量
自增自减运算符的注意事项
++、–如果不是单独使用(如在表达式中,或者同时有其他操作),放在变量前后会存在明显区别
放在变量前面,先拿变量的值进行运算
int a = 10;
int rs = ++a;(先加后用)
在变量的后面,先拿变量的值进行运算,再对变量的值进行+1、-1
int b =10;
int rs = b++;(先用后加)
3.赋值运算符
“=”,从右边往左看
4. 扩展赋值运算符
符号 | 用法 | 作用 | 底层代码形式 |
---|---|---|---|
+= | a+=b | 加后赋值 | a=(a的类型)(a+b); |
-= | a-=b | 减后赋值 | a=(a的类型)(a-b); |
*= | a*=b | 乘后赋值 | a=(a的类型)(a*b); |
/= | a/=b | 除后赋值 | a=(a的类型)(a/b); |
%= | a%=b | 取余后赋值 | a=(a的类型)(a%b); |
注意:扩展运算符隐含了强制类型转换
5. 逻辑运算符
把多个条件放在一起运算,最终返回布尔类型的值,true、false
符号 | 称呼 | 例子 | 运算逻辑 |
---|---|---|---|
& 逻辑与 | 2>1 & 3>2 | 多个条件必须都是true, | 结果才是true |
逻辑或 | 2>1 3<5 | 多个条件中只要有一个是true, | 结果就是true |
! 逻辑非 | !(2>1) | 你真我假,你假我真 | |
^ 逻辑异或 | 2>1^3>1 | 前后条件的结果相同,就直接返回false, | 前后条件的结果不同,才返回false |
符号 | 称呼 | 例子 | 运算逻辑 |
---|---|---|---|
&& 短路与 | 2>10 && 3>2 | 判断结果与"&"一样,过程不同, | 左边为false,右边则不执行 |
"||"逻辑或 | 2>1 || 3<5 | 判断结果"|"与一样,过程不同, |
注意:在java中,“&”,“|”,无论左边是false还是true,右边都要执行
6. 三元运算符、运算符的优先级
格式:条件表达式 ? 值1 : 值2
执行流程:首先计算关系表达式的值,如果值为true,返回值1,如果为false,返回值2
7. 运算符的优先级
在表达式中,哪个运算符先执行后执行是要看优先级的
例如:" *、/ “的优先级高于” +、- "
优先级 | 运算符 |
---|---|
1 | () |
2 | !、–、++ |
3 | *、/、% |
4 | +、- |
5 | <<、>>、>>> |
6 | <、<=、>、>=、instanceof(强制类型转换) |
7 | ==、!= |
8 | & |
9 | ^ |
10 | | |
11 | && |
12 | || |
13 | ? : |
14 | =、+=、-=、*=、/=、%=、&= |
四.条件判断和循环
1. 条件判断 if-else
if-else语句用于基于条件执行不同的代码块。if语句用于检查条件是否为真,如果为真,则执行if块内的代码。else语句是可选的,它提供了一个替代的代码块,当if条件为假时执行。
以下是if-else语句的基本语法:
if (condition) {
// 条件为真时执行的代码
} else {
// 条件为假时执行的代码
}
示例1:if-else
int a = 10;
if (a > 10) {
System.out.println("i 大于 10 时执行");
} else {
System.out.println("i 小于 10 时执行");
示例2:多条件判断 if-else-if
if (a >10){
System.out.println("i 大于 10 时执行");
}else if (a == 10){
System.out.println("i 等于 10 时执行");
}else{
System.out.println("i 小于 10 时执行");
}
2. switch语句
switch语句用于基于不同的情况执行不同的代码块。它提供了一种比多个if-else语句更清晰、更简洁的方式来处理多个条件分支。switch语句通常用于处理基于变量值的条件分支。
以下是switch语句的基本语法:
switch (expression) {
case value1:
// 当expression等于value1时执行的代码
break;
case value2:
// 当expression等于value2时执行的代码
break;
// 可以有多个case
default:
// 当expression不匹配任何case时执行的代码
}
expression是一个返回值的表达式,其结果必须是byte、short、int、char、String(Java 7及以上版本)、枚举类型 或可以隐式转换为int的类型。
case后面跟着的是与expression结果相匹配的值。
break语句用于终止switch语句,防止代码继续执行到下一个case。
default是可选的,当没有任何case匹配时执行。
下面是一个使用switch语句的简单Java程序示例:
int num = 3; // 假设今天要玩一局王者荣耀选择英雄
switch (num) {
case 1:
System.out.println("鲁班七号");
break;
case 2:
System.out.println("兰陵王");
break;
case 3:
System.out.println("妲己");
break;
case 4:
System.out.println("亚瑟");
break;
default:
System.out.println("随机选择英雄");
}
3. Scanner和Random
Scanner类用于从标准输入(如键盘)读取数据,而Random类用于生成随机数
Scanner类的使用
Scanner类位于java.util包中,用于从不同的数据源读取基本类型和字符串。
导入Scanner类
首先,需要导入Scanner类:
import java.util.Scanner;
创建Scanner对象
创建Scanner对象时,可以指定数据源。通常,我们使用System.in作为数据源,它代表标准输入(键盘):
Scanner scanner = new Scanner(System.in);
读取数据
使用Scanner对象的nextLine(), nextInt(), nextDouble(), next()等方法来读取不同类型的数据:
System.out.println("请输入你的名字:");
String name = scanner.nextLine(); // 读取一行字符串
System.out.println("请输入你的年龄:");
int age = scanner.nextInt(); // 读取一个整数
System.out.println("请输入你的分数:");
double score = scanner.nextDouble(); // 读取一个浮点数
scanner.close(); // 关闭scanner对象,释放资源
Random类的使用
Random类位于java.util包中,用于生成随机数。
创建Random对象
Random random = new Random();
生成随机数
使用Random对象的nextInt(), nextDouble(), nextLong()等方法来生成随机数:
int randomInt = random.nextInt(); // 生成一个随机整数
double randomDouble = random.nextDouble(); // 生成一个[0.0, 1.0)之间的随机浮点数
long randomLong = random.nextLong(); // 生成一个随机长整数
示例代码
下面是一个简单的Java程序,演示了如何结合使用Scanner和Random:
在这个程序中,我们首先提示用户输入一个数字(1-4)来选择英雄,或者输入0来随机选择英雄。用户输入后,程序 会根据输入的数字来决定是直接选择英雄还是通过Random类随机选择。
如果用户输入的是1到4之间的数字,程序会直接使用switch语句来选择对应的英雄。如果用户输入的是0,程序会使用 Random类生成一个1到4之间的随机数,然后再次使用switch语句来选择英雄。
最后,无论用户是通过输入选择还是随机选择,程序都会输出用户选择的英雄名称,并关闭Scanner对象以释放资 源。
请注意,这个程序假设用户输入的是有效的数字。在实际应用中,你可能需要添加额外的输入验证来确保用户输入的是预期的数字范围。
Scanner scanner = new Scanner(System.in);
Random random = new Random();
// 提示用户输入数字选择英雄
System.out.println("请输入一个数字(1-4)选择英雄,或者输入0随机选择英雄:");
int choice = scanner.nextInt();
// 根据用户的选择来选择英雄
String hero;
switch (choice) {
case 1:
hero = "鲁班七号";
break;
case 2:
hero = "兰陵王";
break;
case 3:
hero = "妲己";
break;
case 4:
hero = "亚瑟";
break;
case 0:
// 随机选择英雄
int randomHeroIndex = random.nextInt(4) + 1;
switch (randomHeroIndex) {
case 1:
hero = "鲁班七号";
break;
case 2:
hero = "兰陵王";
break;
case 3:
hero = "妲己";
break;
case 4:
hero = "亚瑟";
break;
default:
hero = "未知英雄";
break;
}
break;
default:
hero = "输入的数字不在1-4之间";
break;
}
// 输出选择的英雄
System.out.println("你选择的英雄是:" + hero);
// 关闭scanner对象
scanner.close();
4. while循环
while循环是最基本的循环结构,它会在执行循环体之前检查条件。如果条件为true,则执行循环体;如果条件为false,则退出循环。
while (condition) {
// 循环体
}
示例代码:
int i = 0;
while (i < 5) {
System.out.println(i);
i++;
}
5. do-while循环
do-while循环与while循环类似,但它的循环体至少会执行一次,因为条件检查是在循环体执行之后进行的。
do {
// 循环体
} while (condition);
示例代码:
int i = 0;
do {
System.out.println(i);
i++;
} while (i < 5);
6. for循环
for循环是一种更简洁的循环结构,它将初始化、条件检查和迭代步骤集中在一个表达式中。for循环适用于已知循环次数的情况。
for (initialization; condition; update) {
循环体
}
示例代码:
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
注意事项
在使用循环时,确保循环条件最终会变为false,否则可能会导致无限循环。
在循环体内,应避免无限递增或递减变量,这可能会导致变量溢出。
在for循环中,初始化、条件检查和迭代步骤可以省略,但分号(;)必须保留。
循环是编程中非常重要的概念,它们在处理重复任务、数据处理、算法实现等方面发挥着关键作用。合理使用循环可以提高代码的效率和可读性。
7.跳出 break
终止当次循环,进入下一次循环
如果输出的数为 4 则终止循环
for (int i = 0; i < 10; i++) {
if(i == 4){
break;
}
System.out.println(i);
}
8.继续 continue
终止当次循环,进入下一次循环
跳过 4
for (int i = 0; i < 10; i++) {
if(i == 4){
continue;
}
System.out.println(i);
}
9. 返回 return
终止所有循环,直接返回
如果输出的数为4终止所有循环
for (int i = 0; i < 10; i++) {
if(i == 4){
return;
}
System.out.println(i);
}
五、类与对象
1. 类(Class)
类是创建对象的蓝图或模板。它定义了对象的属性(成员变量)和行为(方法)。类可以包含构造方法、字段(变量)、方法和嵌套类等。
2. 对象(Object)
对象是类的实例。每个对象都有自己的属性值和方法。创建对象的过程称为实例化。
3. 类和对象的关系
类是对象的蓝图,定义了对象的结构和行为。
对象是类的实例,是根据类定义创建的具体实体。
一个类可以创建多个对象,每个对象都有自己的属性值。
4. 面向对象编程(OOP)的特性
封装(Encapsulation):将数据(属性)和行为(方法)绑定在一起,对外隐藏实现细节。
继承(Inheritance):允许创建一个类的子类,继承父类的属性和方法。
多态(Polymorphism):允许不同类的对象对同一消息做出响应。
抽象(Abstraction):隐藏复杂性,只向用户暴露必要的信息。
5. Static关键字:
和类一起加载,可以直接通过 (类名.静态方法) 调用方法。
代码块
执行顺序:静态代码块 > 构造方法 > 匿名代码块。静态代码块只执行一次,即使有多个同一个类的实例,仍只运行一次。
6. 抽象类
抽象方法,只有方法的名字,没有方法的实现。
不能new抽象类,只能靠子类来实现。
抽象方法必须在抽象类中,抽象类中可以有普通方法。
抽象类的所有方法,都需要由它的子类来实现。 除非子类为抽象类,则由子子类实现。
7. 接口:
Interface 定义的关键字,接口都需要有实现类。
接口中所有方法都是抽象的:public abstract,接口只可做方法的声明。
类实现接口需要重写接口中的所有方法。
利用接口可以实现多继承。
8. 方法的重写和重载:
方法的重写
需要有继承关系,子类重写父类的方法,与属性无关。
方法名相同,参数列表必须相同,方法体不同。
修饰符范围可以扩大但不能缩小:public>protected>default>private。
抛出的异常范围可以缩小但不能扩大。
重写的原因:父类的功能,子类可能不需要或不满足。
方法的重载
重载就是在同一个类中,有相同的函数名称,但形参不同的函数。
方法名必须相同,参数列表必须不同(参数个数不同,或类型不同,或参数排列顺序不同等)
方法的返回类型可以相同也可以不同。
实例
王者荣耀有很多英雄,比如兰陵王,团战可以输,鲁班必须死,亚瑟,妲己
所有这些英雄,都有一些共同的状态
比如,他们都有名字,hp,护甲,移动速度等等
这样我们就可以设计一种东西,叫做类,代表英雄这样一种事物
类: 英雄(Hero)
状态: 名字, 血量,护甲,移动速度
一个英雄有姓名,血量,护甲等等状态
这些状态就叫做一个类的属性
一个英雄可以通过打金币,购买装备,等方法去提升属性
这些动作就可以作为一种方法
*/
public class Hero {
//属性
private String name; //姓名
private float hurt; //伤害
private float hp; //血量
private float armor; //护甲
private int moveSpeed; //移动速度
private int death; //死亡数量
private int kill; //死亡数量
//方法 (改变属性的值)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getHp() {
return hp;
}
public void setHp(float hp) {
this.hp = hp;
}
public float getArmor() {
return armor;
}
public void setArmor(float armor) {
this.armor = armor;
}
public int getMoveSpeed() {
return moveSpeed;
}
public void setMoveSpeed(int moveSpeed) {
this.moveSpeed = moveSpeed;
}
public int getDeath() {
return death;
}
public void setDeath(int death) {
this.death = death;
}
public float getHurt() {
return hurt;
}
public void setHurt(float hurt) {
this.hurt = hurt;
}
public int getKill() {
return kill;
}
public void setKill(int kill) {
this.kill = kill;
}
public void setChaoShen(int num) {
if (num == 7){
System.out.println("你已经超神!");
}else{
System.out.println("你击杀了一名英雄!");
}
}
public void setChaoGui(int num) {
if (num == 7){
System.out.println("你已经超鬼!");
}else{
System.out.println("你已经被击杀!");
}
}
public Hero() {
}
public Hero(String name, float hurt, float hp, float armor, int moveSpeed, int death, int kill) {
this.name = name;
this.hurt = hurt;
this.hp = hp;
this.armor = armor;
this.moveSpeed = moveSpeed;
this.death = death;
this.kill = kill;
}
@Override
public String toString() {
return "Hero{" +
"名称:'" + name + '\'' +
", 伤害=" + hurt +
", 血量=" + hp +
", 护甲=" + armor +
", 移动速度=" + moveSpeed +
",死亡数=" + death +
",击杀数=" + kill +
'}';
}
}
实例化类
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("鲁班七号");
hero.setHp(3000f);
hero.setHurt(1f);
hero.setArmor(1f);
hero.setMoveSpeed(10);
System.out.println("基本信息"+hero);
}
六. 枚举
枚举(Enum)是一种特殊的数据类型,它允许开发者定义一组命名的常量。枚举类型是java.lang.Enum的子类,它提供了一种方式来定义一组固定的常量值,这些值是不可变的。
定义枚举
要定义一个枚举类型,可以使用enum关键字。枚举类型可以包含一组常量值,每个常量值都是枚举类型的一个实例。
public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
使用枚举
定义了枚举类型后,就可以像使用其他类一样使用它。枚举类型可以有方法和字段,但每个枚举常量都是唯一的实例。
public class EnumExample {
public static void main(String[] args) {
Day today = Day.MONDAY;
System.out.println(today); // 输出 MONDAY
if (today == Day.SATURDAY || today == Day.SUNDAY) {
System.out.println("It's the weekend!");
} else {
System.out.println("It's a weekday.");
}
}
}
七. 数组
1 . 什么是数组
数组是一种数据结构,用于存储固定大小的同类型元素。数组可以存储基本数据类型(如int、double、char等)或对象(如String、自定义对象等)。数组一旦创建,其大小就固定不变。
2. 声明数组
声明数组的语法如下:
type[] arrayName; | type arrayName[];
其中type是数组中元素的类型,arrayName是数组的名称。
3. 创建数组
创建数组时,需要指定数组的大小:
arrayName = new type[size]; | type[] arrayName = new type[size];
4. 初始化数组
数组创建后,可以通过索引访问数组中的元素,并进行初始化:
arrayName[index] = value;
其中index是数组的索引,value是要存储的值。数组索引从0开始。
下面是一个简单的Java程序,演示了如何声明、创建、初始化和使用数组:
// 声明并创建一个整型数组
int[] numbers = new int[5];
// 初始化数组
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
// 遍历数组并打印元素
for (int i = 0; i < numbers.length; i++) {
System.out.println("Element at index " + i + ": " + numbers[i]);
}
5. 多维数组
Java也支持多维数组,即数组的数组。例如,二维数组可以看作是表格,其中每个元素由两个索引标识。
int[][] twoDimArray = new int[3][4]; // 创建一个3行4列的二维数组
数组的常见操作
访问数组元素:通过索引访问数组中的元素。
遍历数组:使用for循环或for-each循环遍历数组中的所有元素。
数组长度:使用arrayName.length获取数组的长度。
数组拷贝:使用System.arraycopy()方法或Arrays.copyOf()方法拷贝数组。
数组排序:使用Arrays.sort()方法对数组进行排序。
数组是Java编程中非常基础且重要的概念,熟练掌握数组的使用对于编写高效的Java程序至关重要。
6. 多维数组数组排序
数组排序是常见的操作之一。Java标准库提供了Arrays类,其中包含sort方法,可以用来对数组进行排序。Arrays类 位于java.util包中,因此在使用之前需要导入
对基本数据类型数组排序
int[] numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
Arrays.sort(numbers); // 对整型数组进行排序
System.out.println(Arrays.toString(numbers)); // 打印排序后的数组
注意事项
Arrays.sort()方法使用了双轴快速排序算法(Dual-Pivot Quicksort)进行排序,对于基本数据类型和实现了 Comparable接口的对象数组,它提供了最优的性能。
对于自定义的Comparator,排序的稳定性取决于具体的实现。在Java 8及以后的版本中,Arrays.sort()方法对实现了Comparable接口的数组和使用Comparator的数组都保证了排序的稳定性。
通过使用Arrays.sort()方法,可以方便地对数组进行排序,无论是基本数据类型还是对象数组。
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复进行的,直到没有再需要交换的元素为止,这意味着数列已经排序完成。
1. 冒泡排序
冒泡排序的基本思想是:通过对待排序序列从前向后,依次比较相邻元素的值,若发现逆序则交换,使较大元素逐渐 从前移到后部(即“下沉”),就像水底的气泡一样逐渐向上冒。
冒泡排序算法步骤:
1.比较相邻的元素。如果第一个比第二个大,就交换它们两个。
2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3. 针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
Java实现冒泡排序:
public class BubbleSortExample{
public static void main(String[] args) {
int[] array = {64, 34, 25, 12, 22, 11, 90};
bubbleSort(array);
System.out.println("Sorted array:");
printArray(array);
}
// 冒泡排序的方法
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换 arr[j+1] 和 arr[j]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 打印数组的方法
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
冒泡排序在最坏情况和平均情况下的时间复杂度都是O(n^2),其中n是数组的长度。为了提高效率,可以添加一个标 志位来记录在某次遍历中是否发生了交换。如果没有发生交换,说明数组已经有序,可以提前结束排序。
优化后的冒泡排序:
public static void optimizedBubbleSort(int[] arr) {
int n = arr.length;
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换 arr[j+1] 和 arr[j]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果在这一轮中没有发生交换,说明数组已经有序,可以提前结束排序
if (!swapped) {
break;
}
}
}
2. 选择排序
选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理是每次从待排序的数据元素中选出最小(或最 大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排 序序列的末尾。以此类推,直到全部待排序的数据元素排完。
选择排序的主要优点与数据移动有关。如果某个元素已经位于正确的位置,它不会被移动。选择排序每次交换两个元素,这使得它在交换次数上比冒泡排序少,但选择排序的比较次数和冒泡排序相同。
选择排序算法步骤:
1. 从待排序的数据元素中,选出最小(或最大)的一个元素,存放在序列的起始位置。
2. 从剩余未排序元素中继续这个过程,直到所有元素均排序完毕。
Java实现选择排序:
public class SelectionSortExample {
public static void main(String[] args) {
int[] array = {64, 25, 12, 22, 11};
selectionSort(array);
System.out.println("Sorted array:");
printArray(array);
}
// 选择排序的方法
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
// 找到最小元素的索引
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 将找到的最小元素与第i个位置的元素交换
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
// 打印数组的方法
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
选择排序的性能分析:
时间复杂度:选择排序的时间复杂度为O(n^2),其中n是数组的长度。这是因为选择排序需要进行n-1次选择操作,每 次选择操作需要进行n-i次比较(i为当前选择操作的轮数)。
空间复杂度:选择排序的空间复杂度为O(1),因为它只需要常数级别的额外空间。
稳定性:选择排序是一种不稳定的排序算法,因为相等的元素可能会因为交换而改变它们的相对位置。
选择排序虽然简单,但效率不高,特别是对于大数据集来说,它的性能较差。在实际应用中,通常会使用更高效的排序算法,如快速排序、归并排序等。然而,选择排序在某些特定情况下(如数据量较小或对稳定性要求不高时)仍然可 以作为一种选择。
3. 插入排序
插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已 排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常使用in-place排序(即只需用到O(1)的额外空 间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步
插入排序算法步骤:
1. 从第一个元素开始,该元素可以认为已经被排序。
2. 取出下一个元素,在已经排序的元素序列中从后向前扫描。
3. 如果该元素(已排序)大于新元素,将该元素移到下一位置。
4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
5. 将新元素插入到该位置后。
6. 重复步骤2~5。
Java实现插入排序:
public class InsertionSortExample {
public static void main(String[] args) {
int[] array = {9, 5, 1, 4, 3};
insertionSort(array);
System.out.println("Sorted array:");
printArray(array);
}
// 插入排序的方法
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; ++i) {
int key = arr[i];
int j = i - 1;
// 将arr[i]插入到已排序的序列arr[0...i-1]中
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
// 打印数组的方法
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
插入排序的性能分析:
时间复杂度:插入排序的时间复杂度为O(n^2),其中n是数组的长度。这是因为插入排序需要进行n-1次插入操作,每次插入操作平均需要移动大约n/2个元素。
空间复杂度:插入排序的空间复杂度为O(1),因为它是一种原地排序算法,不需要额外的存储空间。
稳定性:插入排序是一种稳定的排序算法,即相等的元素在排序后保持原有的相对顺序。
插入排序在数据量较小或者数据已经基本有序的情况下表现良好,效率较高。在实际应用中,插入排序可以作为其他更复杂排序算法的补充,例如在快速排序的分区操作后,可以使用插入排序对小规模的子数组进行排序,以提高整体效率。
八、反射
反射(Reflection)是一种强大的机制,它允许程序在运行时访问、检测和修改类、接口、字段、方法和构造函数等对 象。反射提供了一种动态访问
1. 序反射的主要用途
1.动态创建对象:在不知道具体类名的情况下,可以创建对象。
2.动态调用方法:在运行时调用任意对象的方法。
3.动态访问字段:在运行时访问或修改对象的字段。
4.动态构造方法:在运行时调用任意构造函数创建对象。
5.获取类信息:获取类的结构信息,如类名、父类、实现的接口、方法、字段等。
2. 反射API
Java的反射API主要包含在java.lang.reflect包中,其中几个重要的类和接口包括:
Class:代表正在运行的Java应用程序中的类和接口。
Field:代表类的成员变量(字段)。
Method:代表类的方法。
Constructor:代表类的构造函数。
Modifier:提供静态方法和常量,用于解析Java语言修饰符。
2. 注解
注解(Annotation)是一种用于为代码提供元数据(metadata)的机制。注解不会直接影响代码的执行,但可以被编 译器、其他工具或运行时环境所读取和
注解的定义
注解是通过@interface关键字定义的,它是一个接口,可以包含方法,这些方法定义了注解的元 素。注解的元素可以有默认值。
public @interface MyAnnotation {
String value() default "default value";
int number() default 0;
}
注解的使用
注解可以应用于类、方法、字段、参数等,使用时在目标元素前加上@符号和注解的名称。
@MyAnnotation(value = "some value", number = 10)
public class MyClass {
@MyAnnotation
private String myField;
@MyAnnotation
public void myMethod() {
// 方法体
}
}
九、线程
线程(Thread)是程序执行的最小单元,它允许程序并发地执行多个任务。Java提供了java.lang.Thread类和java.lang.Runna
创建线程的两种方式
1. 通过继承Thread类并重写其run()方法来定义线程要执行的任务。
public class MyThread extends Thread {
@Override
public void run() {
// 线程要执行的任务
System.out.println("Thread is running.");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
2. 实现Runnable接口
通过实现Runnable接口并实现其run()方法来定义线程要执行的任务。
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程要执行的任务
System.out.println("Thread is running.");
}
}
public class ThreadExample {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 启动线程
}
}
3. 线程的生命周期
线程的生命周期包括以下状态:
新建(New):线程对象被创建,但尚未调用start()方法。
就绪(Runnable):线程对象调用了start()方法,等待CPU调度。
运行(Running):线程获得CPU时间片,正在执行任务。
阻塞(Blocked):线程因为某些原因(如等待I/O操作完成)暂时停止执行。
等待(Waiting):线程进入无限期等待状态,直到其他线程调用特定方法唤醒它。
超时等待(Timed Waiting):线程进入有限期等待状态,等待其他线程执行特定操作或经过指定时间。
终止(Terminated):线程执行完毕或因异常终止。
4.线程同步
在多线程环境中,多个线程可能会同时访问和修改共享资源,这可能导致数据不一致或竞态条件。
Java提供了synchronized关键字来实现线程同步
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
在上面的例子中,increment()方法被synchronized关键字修饰,确保同一时间只有一个线程可以执行该方法,从而保证了count变量的线程安全
5. 线程通信
Java提供了wait(), notify(), 和 notifyAll()方法来实现线程间的通信。
wait():使当前线程等待,直到其他线程调用此对象的notify()或notifyAll()方法。
notify():唤醒在此对象监视器上等待的单个线程。
notifyAll():唤醒在此对象监视器上等待的所有线程。
6. 线程池
线程池是一种管理线程生命周期的机制,它允许重用线程而不是为每个任务创建新线程。Java提供了ExecutorService接口和ThreadPoolExe
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5); // 创建一个固定
for (int i = 0; i < 10; i++) {
executor.submit(new MyRunnable()); // 提交任务到线程池
}
executor.shutdown(); // 关闭线程池
}
}
在上面的例子中,Executors.newFixedThreadPool(5)创建了一个固定大小为5的线程池,可以同时执行5个任务。提 交任务到线程池后,线
线程是Java并发编程的基础,合理使用线程可以提高程序的性能和响应性。然而,多线程编程也引入了复杂性,如线程安全问题和死锁等,需要开发者仔细设计和测试。
Socket用于实现网络通信中的客户端,ServerSocket类用于实现网络通信中的服务器端。通过Socket和ServerSocket,可以建立TCP,使用Scoket实现简单聊天
服务器端代码(Server.java)
import java.io.*;
import java.net.*;
import java.util.*;
// 服务器端类
public class Server {
// 服务器监听的端口号
private static final int PORT = 12345;
// 存储所有客户端的输出流
private static List<PrintWriter> clients = new ArrayList<>();
public static void main(String[] args) throws IOException {
// 打印服务器启动信息
System.out.println("服务器正在运行...");
// 创建服务器套接字,监听指定端口
ServerSocket serverSocket = new ServerSocket(PORT);
try {
// 循环等待客户端连接
while (true) {
// 接受客户端连接请求
Socket socket = serverSocket.accept();
// 创建一个新的线程来处理客户端连接
new Handler(socket).start();
}
} finally {
// 关闭服务器套接字
serverSocket.close();
}
}
// 处理客户端连接的内部类
private static class Handler extends Thread {
// 客户端套接字
private Socket socket;
// 客户端的输出流
private PrintWriter out;
public Handler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
// 获取输入流
InputStream input = socket.getInputStream();
// 创建缓冲读取器
BufferedReader reader = new BufferedReader(new InputStreamRea
// 创建输出流
out = new PrintWriter(socket.getOutputStream(), true);
// 将输出流添加到客户端列表中
synchronized (clients) {
clients.add(out);
}
// 循环读取客户端发送的消息
String message;
while ((message = reader.readLine()) != null) {
// 将消息转发给所有客户端
for (PrintWriter writer : clients) {
writer.println(message);
}
}
} catch (IOException e) {
// 打印异常信息
System.out.println(e.getMessage());
} finally {
// 如果输出流不为空,则从客户端列表中移除
if (out != null) {
synchronized (clients) {
clients.remove(out);
}
}
// 关闭客户端套接字
try {
socket.close();
} catch (IOException e) {
// 打印异常信息
System.out.println(e.getMessage());
}
}
}
}
}
客户端代码(Client.java)
import java.io.*;
import java.net.*;
// 客户端类
public class Client {
// 服务器地址
private static final String SERVER_ADDRESS = "localhost";
// 服务器端口
private static final int SERVER_PORT = 12345;
public static void main(String[] args) throws IOException {
// 打印连接信息
System.out.println("正在连接服务器...");
// 创建客户端套接字,连接服务器
Socket socket = new Socket(SERVER_ADDRESS, SERVER_PORT);
// 创建输入流读取器
BufferedReader in = new BufferedReader(new InputStreamReader(socket.g
// 创建输出流
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 创建控制台输入读取器
BufferedReader stdIn = new BufferedReader(new InputStreamReader(Syste
// 创建一个新线程来读取服务器发送的消息
new Thread(() -> {
try {
// 循环读取服务器发送的消息
String serverResponse;
while ((serverResponse = in.readLine()) != null) {
// 打印服务器发送的消息
System.out.println("服务器: " + serverResponse);
}
} catch (IOException e) {
// 打印异常信息
System.out.println("从控制台读取时发生错误。");
}
}).start();
// 循环读取用户输入并发送给服务器
String userInput;
while ((userInput = stdIn.readLine()) != null) {
// 发送用户输入的消息给服务器
out.println(userInput);
}
}
}
十、集合
集合(Collections)框架提供了一套接口和类,用于存储和操作对象的集合。集合框架是Java编程中非常重要的部分,它允许开发者以统一的方式处理对象集合,而无需关心集合的具体实现细节。Java集合框架主要包括以下几类:
1. 集合接口
Collection:集合框架的根接口,定义了添加、删除、遍历等基本操作。
Set:继承自Collection接口,不允许包含重复元素。
List:继承自Collection接口,允许包含重复元素,有序集合。
Queue:继承自Collection接口,用于处理先进先出(FIFO)的数据结构。
Deque:继承自Queue接口,支持两端操作的队列。
2. 集合类
ArrayList:基于动态数组实现的List接口的实现类,随机访问速度快。
LinkedList:基于双向链表实现的List接口的实现类,插入和删除操作速度快。
HashSet:基于哈希表实现的Set接口的实现类,不允许重复元素,无序。
LinkedHashSet:继承自HashSet,保持插入顺序。
TreeSet:基于红黑树实现的Set接口的实现类,不允许重复元素,有序。
HashMap:基于哈希表实现的Map接口的实现类,不允许重复键,无序。
LinkedHashMap:继承自HashMap,保持插入顺序。
TreeMap:基于红黑树实现的Map接口的实现类,不允许重复键,有序。
PriorityQueue:基于优先级堆实现的Queue接口的实现类,用于实现优先队列。
ArrayDeque:基于数组实现的Deque接口的实现类,用于实现双端队列。
3.工具类
Collections:提供了一系列静态方法,用于操作集合,如排序、搜索、同步等。
Arrays:提供了一系列静态方法,用于操作数组,如排序、搜索、填充等。
使用集合的注意事项
选择合适的集合类型:根据具体需求选择合适的集合类型,例如,如果需要快速查找元素,可以使用HashMap;如果需要保持插入顺序,可以使用LinkedHashMap。
线程安全:对于多线程环境,需要考虑集合的线程安全性。可以使用Collections工具类提供的同步方法,或者使用线 程安全的集合类,如ConcurrentHashMap。
性能考虑:集合的性能取决于其内部数据结构。例如,ArrayList在随机访问时性能较好,而LinkedList在插入和删除操作时性能较好。
十一、异常处理
异常两大类:Exception、Error
1.若多个catch捕获异常,异常等级由小到大。 Finally可以不要,一定会执行,用于处理善后工作。
2.自定义异常类:类继承Exception。Throw new MyException():抛出自定义异常。