计算机网络:
您的计算机 朋友的计算机 ----> 互联网协议: IP Internet protocol
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
作用: 共享信息、程序和数据
分类
局域网(LAN,Local Area Network)
城域网(MAN,Metropolis Area Network)
广域网(WAN,Wide Area Network)
* 硬件
- CPU:中央处理器,负责计算机的核心运算,它是计算机的最核心部件,指挥官。 1 + 1 = 2
- 内存:临时存储区域,程序在运行的过程当中,一些数据的临时存储区域。
- 主板:链接各个部件
- 显卡
- 声卡
- 鼠标
- 键盘
- 硬盘【外存】:永久性保存,断电之后再启动,数据仍然存在。
怎么打开DOS命令窗口呢?
查看IP地址:
清屏:
cls
DOS窗口当中也可以设置字体和屏幕以及文字的颜色。
退出DOS命令窗口
exit
怎么从DOS命令窗口当中复制文本:
任意位置点击鼠标右键–>标记 --> 选择你要复制的文本 --> 点击鼠标右键 (此时已经到剪贴板当中了)
找一个位置粘贴即可。
强行终止DOS命令窗口中正在运行的程序:ctrl + c
打开DOS命令窗口默认所在的路径是:C:\Users\Administrator???
创建目录:mkdir abc【表示在当前所在目录下新建一个目录,起名abc】
关于目录切换命令:cd
cd 命令的语法格式:
cd 路径
路径分为:
绝对路径:
C:\Users\Administrator
D:\用户目录\收藏夹
F:\tools\CamtasiaStudio-v6.00
…
从硬盘的根路径作为出发点。
相对路径:
从当前所在的位置作为起点的路径。
自动补全:
cd e 【然后按tab键,当前所在的目录下所有以e开始的目录自动补全路径,
当这个自动补全的路径不是自己想要的路径,可以继续使用tab键】
回到上级目录:cd … 【…是一个路径,代表当前路径的上级路径】
cd …/…/…/
直接回到根路径:cd \
查看当前目录下所有的子文件和子目录:
dir
不要把相关重要的资料放到桌面上,因为桌面是属于C盘系统盘。
怎么切换盘符:【不需要使用cd命令】
c: 回车
d: 回车
e: 回车
f: 回车
打开注册表:
regedit
* win + r 打开运行窗口
* win + d 显示桌面
* win + e 打开资源管理器
* win + L 锁屏
* alt + tab 应用之间的切换
9、“通用的”文本编辑快捷键:
* ctrl + a 全选
* ctrl + c 复制
* ctrl + v 粘贴
* ctrl + s 保存
* ctrl + x 剪切
* ctrl + z 撤销
* ctrl + y 重做
* tab 缩进/多行缩进
* shift + tab 取消缩进
* HOME 回到行首
* END 回到行尾
* shift + home 选中一行
* shift + end 选中一行
* ctrl + shift + 向右或者向左的箭头 选中一个单词
* 鼠标双击:选中一个单词
* 鼠标三击:选中一行
* ctrl + end 回到文件末尾
* ctrl + home 回到文件头
我们创建的项目,在d:\ideawork目录的demo下
.idea
目录和demo.iml
和我们开发无关,是IDEA工具自己使用的
out
目录是存储编译后的.class文件
src
目录是存储我们编写的.java源文件
快捷键 | 功能 |
---|---|
Alt+Enter | 导入包,自动修正代码 |
Ctrl+Y | 删除光标所在行 |
Ctrl+D | 复制光标所在行的内容,插入光标位置下面 |
Ctrl+Alt+L | 格式化代码 |
Ctrl+/ | 单行注释 |
Ctrl+Shift+/ | 选中代码注释,多行注释,再按取消注释 |
Alt+Ins | 自动生成代码,toString,get,set等方法 |
Alt+Shift+上下箭头 | 移动当前代码行 |
Ctrl+Alt+T | 生成if while try异常判断语句 |
java内置注解
@Override
覆盖父类方法
@Deprecated(不赞成)
用于方法,表明方法已过期,不建议使用
@Suppvisewarning
忽略警告,例如当我们要使用已过时方法,有的编译器会出现警告,
@Suppvisewarning("deprecation")表示忽略此警告
@Suppvisewarning("all")表示忽略所有警告
-------------------------------------------------------------
元注解
用来注解其他注解
@Target
声明注解用在什么地方
ElementType.CONSTRUCTOR 用于构造器
ElementType.FIELD 用于成员属性
ElementType.LOCAL_VARIABLE 局部变量
ElementType.METHOD 方法
ElementType.PACKAGE 包
ElementType.PARAMETER 用于参数
ElementType.TYPE 类、接口或enum(enum可视为特殊的类)
ElementType.ANNOTATION_TYPE 注解
@Rentention
注解信息保留到什么时候
RententionPolicy.SOURCE 只在源代码中保留,编译阶段抛弃
RententionPolicy.CLASS 只保留到编译阶段(CLASS文件),VM中运行时抛弃
RententionPolicy.RUNTIME 运行时也保留,可通过反射机制获取注解信息
@Document
注解将被包含至javadoc中
备注: javadoc是Sun公司提供的一种技术,能从源代码中抽取类、方法、成员、注释等生成一个和源代码配套的API帮助文档
指令: javadoc xxx.java
@Inherited
子类可继承父类的此种注解(如果父类有的话)
@FunctionalInterface
函数式接口---> 1amda
@Reptable
注解可以被多次使用
//对于一个注解 参数是value参数名可以省略
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
//组成部分:参数
//参数类型:字面量类型(基本+string)(数组),枚举类型,Class
//使用参数定义用户基本信息
String username() default“无名氏";
int age() default 18;
string[]hobby();
Dayofweek day() default DayofWeek.MONDAY;
class?> clazz() default User.class; //?泛型通配符可以是任意的数据类型
}
* Java Development Kits
* Java开发工具箱【Java开发必备】
关键术语:
* JDK【Java开发工具箱】
* JRE【Java的运行时环境】
* JVM【Java虚拟机】
三者之间的关系:
JDK 中包含JRE,JRE中包含JVM。
java中的注释怎么写呢?
单行注释
//单行注释,只注释当前行
多行注释
/*
多行注释
多行注释
多行注释
多行注释
多行注释
…
*/
javadoc注释
/**
注意:这种注释是比较专业的注释,该注释信息会被javadoc.exe工具解析提取并生成帮助文档。
标识符是用来给变量、类、方法以及包进行命名的,如Welcome、main、System、age、name、gender等。标识符需要遵守一定的规则:
英文字母26个(区分大小写)
、0-9数字
、$(美元符号)
和_(下划线)
。表示类名的标识符(大驼峰)
每个单词的首字母大写,如Man, GoodMan
表示方法和变量的标识符(小驼峰)
第一个单词小写,从第二个单词开始首字母大写,如eat(), eatFood()
Java中的关键字保留字 | |||||
---|---|---|---|---|---|
abstract | assert | boolean | break | byte | case |
catch | char | class | const | continue | default |
do | double | else | extends | final | finally |
float | for | goto | if | implements | import |
instanceof | int | interface | long | native | new |
null | package | private | protected | public | return |
short | static | strictfp | super | switch | synchronized |
this | throw | throws | transient | try | void |
volatile | while |
数据类型分成两大类
整数
、浮点数
、字符
、布尔
。类
、数组
、接口
。基本数据类型分成: 4类8种
数据类型 | 关键字 | 内存占用 | 取值范围 |
---|---|---|---|
字节型 | byte | 1个字节 | -128 至 127 |
短整型 | short | 2个字节 | -32768 至 32767 |
整型 | int(默认) | 4个字节 | -231 至 231-1 |
长整型 | long | 8个字节 | -263 至 263-1 19位数字 |
单精度浮点数 | float | 4个字节 | 1.4013E-45 至 3.4028E+38 |
双精度浮点数 | double(默认) | 8个字节 | 4.9E-324 至 1.7977E+308 |
字符型 | char | 2个字节 | 0 至 216-1 |
布尔类型 | boolean | 1个字节 | true,false |
取值范围由小到大: byte < short < int < long < float < double
(面向对象时,再重点讲解成员变量和静态变量)
类型 | 声明位置 | 从属于 | 生命周期 |
---|---|---|---|
局部变量 | 方法或语句块内部 | 方法/语句块 | 从声明位置开始,直到方法或语句块执行完毕,局部变量消失 |
成员变量(实例变量) | 类内部,方法外部 | 对象 | 对象创建,成员变量也跟着创建。对象消失,成员变量也跟着消失; |
静态变量(类变量) | 类内部,static修饰 | 类 | 类被加载,静态变量就有效;类被卸载,静态变量消失。 |
// 数据类型 变量名1 = 值1 , 变量名2 = 值2 ... ;
int a = 10, b = 20, c = 30;
// 数据类型 变量名1, 变量名2 ...;
// 变量名1 = 值1;
// 变量名2 = 值2;
int d, e;
d = 40;
e = 50;
// 上面两种混合
int f = 10, g;
g = 20;
在同一个作用域(一对大括号)中, 声明的变量名不能重复
变量在使用前必须初始化(赋值).
定义long类型的变量时, 需要在值的后面加上L(建议大写)
long l = 2147483648L;
float f = 12.3F;
(规范是程序员的基本准则,不规范会直接损害你的个人形象):
类型转换分为两种: 自动类型转换, 强制类型转换
boolean类型不能发生类型转换
char类型可以和int进行类型的转换
byte, short和char, 参与运算的时候, 都会自动转换成int类型.
常量优化机制
不同数据类型参与运算:
取值范围大的和取值范围小的参与运算的话, 会先将小的转换成大的, 然后再参与运算
int a = 10;
double d = 12.3;
// 先把int类型的10, 转换成double类型, 然后再运算
double sum = a + d;
(byte, short, char) >> int >> long >> float >> double
强制转换 : 把一个表示数据范围大的数值或者变量赋值给另一个表示数据范围小的变量
格式:目标数据类型 变量名 **= (目标数据类型) **值或者变量 ;
强制类型转换,有可能会发生精度损失
运算符: +, -, * , >, < …
表达式: 使用运算符连接起来的一段代码
运算符 | 作用 | 说明 |
---|---|---|
+ | 加 | |
- | 减 | |
* | 乘 | |
/ | 除 | |
% | 取余(取模, 模于) | 获取的是两个数据除法运算得到的余数 |
**注意事项: **
/
: 如果使用整数和整数相除, 得到的结果还是整数.(不是四舍五入) char c1 = 'a'; // 97
char c2 = '0'; // 48
// 两个char类型运算, 会自动提升为int类型
int sum = c1 + c2;
System.out.println(sum); // 145
运算符的优先级唯独要记住一个, 赋值运算符的优先级最低!!!
运算符 | 运算规则 |
---|---|
= | 赋值, 把等号右边赋值给等号左边 |
+= | 加然后赋值, a += b 等效于 a = a + b |
-= | 同理 |
*= | 同理 |
/= | 同理 |
%= | 同理 |
赋值运算符的注意事项(面试题)
// byte short
short s = 10;
// s = s + 9; // 一 : s + 9的结果是 int类型, 这里就是将int类型赋值给short类型
// 二, 不会出错, 因为 += 的内部隐含了一个强制类型转换
s += 9; // 二: s = (short)(s + 9)
// 问, 一和二, 单独执行
// 哪个有问题, 哪个没问题, 为什么?
运算符 | 作用 | 描述 |
---|---|---|
++ | 自增 | a++, a的值加1, 等效于: a = a + 1 |
– | 自减 | a–, a的值减1, 等效于 a = a - 1 |
正确的理解方式 (b = a++/++a)
无论++在前还是在后都先执行++,
如果++在前, 最终产生的是++之后的值;
如果++在后, 最终产生的是++之前的值;
关系运算符/比较运算符: 就是两个数据之间的比较运算, 运算出来的结果都是布尔类型(true/false)
运算符 | 介绍 |
---|---|
> |
a>b , 判断a是否大于b |
< |
a |
>= |
a>=b, 判断a是否是大于等于b |
<= |
a<=b, 判断a是否是小于等于b |
== |
a==b, 判断a和b是否相等 |
!= |
a!=b, 判断a和b是否不想等 |
注意事项:
=
是赋值;==
是比较!
运算符 | 作用 | 描述 |
---|---|---|
& | 逻辑与(和, 并且), 遇false则false | a&b, a和b都是true结果为true; 只要有一个是false结果就为false. |
| | 逻辑或, 遇true则true | a|b, a和b都是false结果为false; 只要有一个是true结果就是true. |
^ | 逻辑异或, 相同则false, 不同则true | a^b, a和b的结果不同为true, 相同为false |
! | 逻辑非, 取反 | !a, !true的结果是false; !false的结果是true |
重点掌握: 与, 或, 非
短路的逻辑运算符
运算符 | 介绍 |
---|---|
&& 双与/短路与 |
如果&&的左边为false, 那么右边不执行 |
|| 双或/短路或 |
如果||的左边为true, 那么右边不执行 |
格式
数据类型 变量名 = 判断条件 ? 值1 : 值2;
判断条件: 结果为boolean类型的表达式
执行流程
案例
/*
两只老虎案例
*/
public class Demo05 {
public static void main(String[] args) {
// 定义两个变量, 分别代表两只老虎的体重
// 180 和200 是整数, 所以使用 int类型
// weight: 体重/重量
int weight1 = 180; // 第一只老虎的体重
int weight2 = 200; // 第二只老虎的体重
// 由于最终得到的结果是true/false, 所以需要使用boolean
// is: 是否 equal: 相等
boolean isEqual = (weight1 == weight2) ? true : false;
// 输出结果
System.out.println("两只老虎的体重是否相同: " + isEqual);
}
}
/*
三个和尚案例
*/
public class Demo07 {
public static void main(String[] args) {
// 定义三个变量, 分别代表3个人的身高
// 身高是整数, 使用int类型
int height1 = 150;
int height2 = 210;
int height3 = 165;
// 使用三元运算符, 获取前两个人的最大身高 , 使用临时变量进行存储
int tempHeight = height1 > height2 ? height1 : height2;
// 使用三元运算符, 比较临时身高和第三个人的身高
int maxHeight = tempHeight > height3 ? tempHeight : height3;
// 输出
System.out.println("三个和尚的最高身高为: " + maxHeight);
}
}
流程控制语句
选择结构语句
顺序结构
顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
1.if语句格式1
if: 如果
只能对一种情况进行判断
格式:
if (比较表达式) {
语句体;
}
2.if语句格式2
可以对两种情况进行判断
格式:
if (关系表达式) {
语句体1;
} else {
语句体2;
}
3.if语句格式3
if - else if : 可以判断多种情况
注意: 最后的else可以不写, 但是建议写上!~
格式:
if (关系表达式1) {
语句体1;
} else if (关系表达式2) {
语句体2;
}
…
else {
语句体n+1;
}
import java.util.Scanner;
public class IfDemo03 {
public static void main(String[] args) {
System.out.println("开始");
// 需求:键盘录入一个星期数(1,2,...7),输出对应的星期一,星期二,...星期日
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个星期数(1-7):");
int week = sc.nextInt();
if(week == 1) {
System.out.println("星期一");
} else if(week == 2) {
System.out.println("星期二");
} else if(week == 3) {
System.out.println("星期三");
} else if(week == 4) {
System.out.println("星期四");
} else if(week == 5) {
System.out.println("星期五");
} else if(week == 6) {
System.out.println("星期六");
} else {
System.out.println("星期日");
}
System.out.println("结束");
}
}
1. switch语句的格式
// 表达式 : 可以是一个常量, 也可以是变量(常用的方式)
// byte int short char 枚举enum 字符串(String)。
switch(表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
...
default:
语句体n+1;
break;
}
2.switch语句的注意事项
3.常见问题
案例:春夏秋冬
需求:一年有12个月,分属于春夏秋冬4个季节,键盘录入一个月份,请用程序实现判断该月份属于哪个季节,并输出。
分析:
优化后代码
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入月份:");
int month = sc.nextInt();
switch (month) {
case 12:
case 1:
case 2:
System.out.println("冬季");
break;
case 3:
case 4:
case 5:
System.out.println("春季");
break;
case 6:
case 7:
case 8:
System.out.println("夏季");
break;
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("您的输入有误");
break;
}
}
总结
for (初始化语句;条件判断语句;条件控制语句) {
循环体语句;
}
示例代码:
public class ForTest05 {
public static void main(String[] args) {
//定义变量count,用于保存“水仙花数”的数量,初始值为0
int count = 0;
//输出所有的水仙花数必然要使用到循环,遍历所有的三位数,三位数从100开始,到999结束
for(int i=100; i<1000; i++) {
//在计算之前获取三位数中每个位上的值
int ge = i%10;
int shi = i/10%10;
int bai = i/10/10%10;
//在判定水仙花数的过程中,满足条件不再输出,更改为修改count的值,使count+1
if(ge*ge*ge + shi*shi*shi + bai*bai*bai == i) {
count++;
}
}
//打印输出最终结果
System.out.println("水仙花共有:" + count + "个");
}
}
while循环完整格式:
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}
示例代码:
public class WhileTest {
public static void main(String[] args) {
//定义一个计数器,初始值为0
int count = 0;
//定义纸张厚度
double paper = 0.1;
//定义珠穆朗玛峰的高度
int zf = 8844430;
//因为要反复折叠,所以要使用循环,但是不知道折叠多少次,这种情况下更适合使用while循环
//折叠的过程中当纸张厚度大于珠峰就停止了,因此继续执行的要求是纸张厚度小于珠峰高度
while(paper <= zf) {
//循环的执行过程中每次纸张折叠,纸张的厚度要加倍
paper *= 2;
//在循环中执行累加,对应折叠了多少次
count++;
}
//打印计数器的值
System.out.println("需要折叠:" + count + "次");
}
}
完整格式:
初始化语句;
do {
循环体语句;
条件控制语句;
}while(条件判断语句);
示例代码:
public class DoWhileDemo {
public static void main(String[] args) {
//需求:在控制台输出5次"HelloWorld"
//for循环实现
for(int i=1; i<=5; i++) {
System.out.println("HelloWorld");
}
System.out.println("--------");
//do...while循环实现
int j = 1;
do {
System.out.println("HelloWorld");
j++;
}while(j<=5);
}
}
for和while使用哪一个?
如果明确循环次数, 使用for循环
如果不明确循环次数, 使用while循环
无限循环语句中, 如果循环体中没有出口(下一天的课程中学习的break), 循环的后面不能写任何代码
跳转控制语句(break)
跳转控制语句(continue)
注意: continue只能在循环中进行使用!
返回(方法) return
可以使用在(方法)程序中的任意一个地方。
public static void main(String[] args) {
System.out.println("main。。。。。。。。");
//模拟用户登录:用户名 密码
Scanner input = new Scanner(System.in);
System.out.println("请录入用户名:");
String username = input.nextLine();
System.out.println("请录入密码:");
String password = input.nextLine();
//在开发中使用卫语句处理: (先拿相反的结果)
if(!"admin".equals(username) || !"1234".equals(password)){
//失败
System.out.println("用户名或者密码不符,登录失败");
return;
}
//登录成功的逻辑
System.out.println("欢迎你:"+username);
}
循环嵌套概述:在循环中,继续定义循环
理解:
结论:
注意:
案例 : 在控制台打印九九乘法表
需求 : 在控制台打印九九乘法表
效果 :
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
代码实现:
public class Test3 {
public static void main(String[] args) {
for(int i = 1; i <= 9; i++){
for(int j = 1; j <= i; j++){
System.out.print(j + "*" + i + "=" + (i * j) + "\t");
}
System.out.println();
}
}
}
几乎以后不会遇到
定义标签的格式
标签名:
结束指定的标签:
break 标签名;
continue 标签名;
数组是一种 [固定长度] 的容器, 可用来存储同种数据类型的多个值.
注意: double类型的数组容器, 内部可以存储double小数, 也可以存储int类型整数.
int类型的数组容器, 内部可以存储 byte, short, charr 类型
数组名[索引];
使用方式:
存入: 数组名[索引] = 数据;
将等号右边的数据, 存入数组中指定索引的位置
取出:
System.out.println(数组名[索引]);
打印数组中指定索引位置的元素
数据类型 变量名 = 数组名[索引];
将数组中指定索引位置的元素, 赋值给等号左边定义的变量
区域名称 | 作用 |
---|---|
栈内存 | 方法运行时,进入的内存,局部变量都存放于这块内存当中 |
堆内存 | new出来的内容(数组, 对象)都会进入堆内存,并且会存在地址值 |
方法区 | 字节码文件 (.class文件) 加载时进入的内存 |
本地方法栈 | 调用操作系统相关资源 |
寄存器 | 给CPU使用 |
JDK7 : 方法区 ( 永久代 ) 在堆内存的
JDK8 : 方法区成为了一块独立的内存空间 ( 元空间 )
数组定义格式
数据类型[] 数组名;
示例: int[] arr;
数据类型 数组名[];
示例: double arr[];
静态初始化格式 :
完整格式:
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3...};
正确范例 : int[] arr = new int[]{11,22,33};
简化格式:
数据类型[] 数组名 = {元素1, 元素2, 元素3...};
范例 : int[] arr = {11,22,33};
动态初始化格式 :
数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr = new int[5];
* 等号左边:
- int:数组的数据类型
- []:代表这是一个数组
- arr:代表数组的名称
* 等号右边:
- new:为数组开辟内存空间
- int:数组的数据类型
- []:代表这是一个数组
- 5:代表数组的长度
注意 :
打印数组名 , 出现的是数组在内存中的 【内存地址】
//增强for循环(不能直接获得index)
// for((数组元素)数据类型 变量名称:数组名称){
// //变量名称(是数组里面的任意元素)
// }
//遍历数组
int count = 1;
for (int score : studentScore) {//studentScore每个元素依次赋值给score
System.out.println("第" + count + "元素:"+score);
count++;
}
1.动态录入学生个数 以及学生成绩,获得总成绩,成绩最值
public static void main(String[] args) {
//动态录入学生个数 以及学生成绩,获得总成绩,成绩最值
Scanner input = new Scanner(System.in);
System.out.println("请录入学生的个数:");
int studentCount = input.nextInt();
double totalScore = 0;
double[] studentScore = new double[studentCount];
for (int i = 0; i < studentCount; i++) {
System.out.println("请录入第" + (i + 1) + "个学生的成绩:");
double score = input.nextDouble();
//统一维护studentCount个score
studentScore[i] = score;
totalScore += score;
}
//相邻数组元素相互比较 (三元运算符)
double max = studentScore[0];
double min = studentScore[0];
// for (int i = 1; i < studentScore.length; i++) {
// }
for (double s : studentScore) {
max = (max > s) ? max : s;
min = (min < s) ? min : s;
}
System.out.println("总成绩:" + totalScore);
System.out.println("成绩最大值:" + max);
System.out.println("成绩最小值:" + min);
}
提供了很多功能,可以操作数组元素。 java.util.Arrays
Arrays.toString("数组名称");// 将数组元素拼接成字符串。
Arrays.equals(数组1,数组2); //比较2个数组元素是否一致 boolean
Arrays.copyOf(源数组,新数组的空间大小);//将源数组的数据存入指定空间大小的新数组中
Arrays.sort(数组名称);//字面量类型 升序排列数组元素
//Arrays.equals()
System.out.println(Arrays.equals(num1, num2));
//手动扩容 Arrays.copyOf
String[] copyOf = Arrays.copyOf(str, 5);
System.out.println("copyOf:"+Arrays.toString(copyOf));
System.out.println(copyOf==str);//false
对数组元素按照指定的自然顺序进行排列,(升序,降序)
原理: 将相邻的两个元素进行比较,最大的值放在右端。N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数。
所有的字面量类型的数组可以使用排序来完成。
int[] array = {89,20,68,10}; length-1轮
第一轮:
指针指向第一个元素89,与20比较 发现 89>20 交换位置 20,89,68,10
指针指定第二个元素89,与68比较,发现89>68 交换位置 20,68,89,10
指针指定第二个元素89, 与10比较,发现89>10 交换位置 20,68,10,89
第二轮:
指针指向第一个元素20,与68比较 20<68 不交换 20,68,10,89
指针指向第一个元素68 与10比较 68>10 交换 20,10,68,89
第三轮:
指针指向第一个元素20,与10比较 20>10 交换 10,20,68,89
for(int i=1,len=array.length;i<len;i++){
for(int index=0;index<len-i;index++){
if(array[index]>array[index+1]){
//交换位置
int temp = array[index];
array[index] = array[index+1];
array[index+1] = temp;
}
}
}
从第一个元素开始,分别与后面的元素相比较,找到最小的元素与第一个元素交换位置;从第二个元素开始,分别与后面的元素相比较,找到剩余元素中最小的元素,与第二个元素交换;重复上述步骤,直到所有的元素都排成由小到大为止。
数据量过大的话,使用冒泡排序效率比较低,因为一个元素会参与多次比较 。
交换次数会少于冒泡排序。
最小的元素与当前第几个元素做交换。
最小元素,以及最小元素的索引位置。
int[] array = {89,20,68,10};
第一轮:
指针指向第一个(误认为最小元素是第一个)89,
先于20比,89>20, 最小值变成20
20与68比较 68>20 最小值变成20
20再与10比较 20>10 最小值变成10
交换: 89 与10交换 结果: 10 20 68 89
第二轮:
指针指向第2个20
20先于68 再与89 最小值20
交换: 20与20交换 10 20 68 89
第三轮:
指针指向第三个68
与89
68与68交换 10 20 68 89
for(int i=0,len=array.length;i<len-1;i++){
int min = array[i];
int minIndex = i;
for(int j=i+1;j<len;j++){
//比较 找最小值 最小值的索引
if(min>array[j]){
min = array[j];
minIndex = j;
}
}
//交换位置
int temp = arry[i];
array[i] = min;
array[minIndex] = temp;
}
原理: 将指针指向某个==(第二个)==元素,假设该元素左侧的元素全部有序,将该元素抽取出来,然后按照从右往左的顺序分别与其左边的元素比较,遇到比其大的元素便将元素右移,直到找到比该元素小的元素或者找到最左面发现其左侧的元素都比它大,停止.此时会出现一个空位,将该元素放入到空位中,此时该元素左侧的元素都比它小,右侧的元素都比它大;指针向后移动一位,重复上述过程。每操作一轮,左侧有序元素都增加一个,右侧无序元素都减少一个。
int[] array = {89, 200, 68, 10,50};
第一轮:
指针指向第2个: 200 将200抽取 89,__,68,10,50
200先89比较 200>89 89不右移
200填补空位 89,200,68,10,50
第二轮:
指针指向第3个: 68 89,200,__,10,50
68先与200比较 200>68 200右移 89,__,200,10,50
68与89比较 89>68 89右移 __,89,200,10,50
68填补空位 68,89,200,10,50
第3轮:
指针指向第4个: 10
10,68,89,200,50
第4轮:
指针指向第5个: 50
10,50,68,89,200
for(int i=1,len=array.length;i<len;i++){
int temp = array[i];
int leftIndex = i-1;// 索引: 0-length-1
while(leftIndex>=0 && array[leftIndex]>temp){
array[leftIndex+1] = array[leftIndex];//右移
leftIndex--;//负数
}
array[leftIndex+1] = temp;
}
二维数组的元素全部都是一维数组。
1. 数据类型[][] 数组名称 = new 数据类型[m][n];//初始化二维数组
m:代表的二维数组的元素个数 length
n: 代表的是二维数组中一维数组的元素个数
2. 数据类型[][] 数组名称 = new 数据类型[m][];//常用
m:代表的二维数组的元素个数 length
3. 数据类型[][] 数组名称 = {{1,2,3},{3,4},{1}};//测试
数据类型[][] 数组名称 = new 数据类型[][]{{1,2,3},{3,4},{1}}
public static void main(String[] args) {
// 数据类型[][] 数组名称 = new 数据类型[m][n];
int[][] num = new int[5][3];
System.out.println("长度:"+num.length);
System.out.println(num);//[[I@1b6d3586
System.out.println(Arrays.toString(num));//[[I@4554617c, [I@74a14482, [I@1540e19d, [I@677327b6, [I@14ae5a5]
//Arrays 提供方法可以操作二维数组元素
System.out.println(Arrays.deepToString(num));//[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
//操作二维数组元素 index
//取值
// int[] array = num[0];
// System.out.println(array[0]);
int i1 = num[0][0];//获得第一个一维数组的第一个元素
//赋值
num[3][0] = 90;
num[1][2] = 100;
System.out.println(Arrays.deepToString(num));
//遍历
for(int i=0,len = num.length;i<len;i++){
for (int j = 0; j < num[i].length; j++) {
System.out.println("第"+(i+1)+"个一维数组的第"+(j+1)+"元素是:"+num[i][j]);
}
}
for (int[] ints : num) {
for (int anInt : ints) {
System.out.println(anInt);
}
}
}
需求:
动态录入班级数 以及每个班级的人数以及每个学生的成绩
遍历每个学生的成绩
public static void main(String[] args) {
// 1. 动态录入班级数 以及每个班级的人数以及每个学生的成绩
Scanner input = new Scanner(System.in);
System.out.println("录入班级数:");
int classCount = input.nextInt();
int[][] classArray = new int[classCount][];
for (int i = 0; i < classCount; i++) {
System.out.println("请录入第" + (i + 1) + "班级人数:");
int studentCount = input.nextInt();
//对二维数组元素初始化
classArray[i] = new int[studentCount];//重点
for (int j = 0; j < studentCount; j++) {
System.out.println("请录入第" + (i + 1) + "班级,第:" + (j + 1) + "学生的成绩:");
int score = input.nextInt();
//成绩存储到二维数组里面
classArray[i][j] = score;//NPE
}
}
// 2.遍历每个学生的成绩
int count1 = 1;//班级
for (int[] room : classArray) {
int count2 = 1;//学生
for (int score : room) {
System.out.println("第" + count1 + "个班级的第" + count2 + "个学生的成绩是:" + score);
count2++;
}
count1++;
}
}
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
其实这个书写完全等价与
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。
JDK1.5以后。出现了简化操作。… 用在参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
代码演示:
public class ChangeArgs {
public static void main(String[] args) {
int[] arr = { 1, 4, 62, 431, 2 };
int sum = getSum(arr);
System.out.println(sum);
// 6 7 2 12 2121
// 求 这几个元素和 6 7 2 12 2121
int sum2 = getSum(6, 7, 2, 12, 2121);
System.out.println(sum2);
}
/*
* 完成数组 所有元素的求和 原始写法
public static int getSum(int[] arr){
int sum = 0;
for(int a : arr){
sum += a;
}
return sum;
}
*/
//可变参数写法
public static int getSum(int... arr) {
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
}
tips: 上述add方法在同一个类中,只能存在一个。因为会发生调用的不确定性
注意:如果在方法书写时,这个方法拥有多参数,参数中包含可变参数,可变参数一定要写在参数列表的末尾位置。
public static void main(String[] args) {
}
有返回值类型:所有的数据类型都可以充当返回值类型
无返回值类型: void
方法名 (规范: 小写驼峰 目前唯一的)
形式参数(局部变量) 形参—> 规范 规则 方法有几个特定类型的参数
无参: ()
有参: 可以有1个参数 也可以有多个参数(使用,隔开)
可变参数: (数据类型… 参数名称) 出现参数的最后
方法体 { }
方法的完整格式
/*
参数列表的格式: 数据类型1 变量名1, 数据类型2 变量名2 ...
修饰符 返回值类型 方法名(参数列表) {
方法体;
return [值]; // []代表可能有也可能没有
}
详细介绍:
修饰符: public static
返回值类型: 我使用了这个功能, 我能得到什么
方法名: 合法的标识符
参数列表: 我使用了这个功能, 我需要提供什么
方法体 : 方法实现功能的代码
return : 返回
*/
方法定义的通用格式
public static 返回值类型 方法名(参数列表) {
方法体;
return 返回值; // 如果返回值类型为void可以省略
}
// 明确两个地方法: 返回值类型, 参数列表
主要是参考形式参数和返回值类型来进行分类: 方法与方法之间是同级的关系
1. 无参无返回值方法(测试)
2. 有参无返回值方法(相对较少)
3. 无参有返回值方法
4. 有参有返回值的方法(最多)
//创建
public static void exercise1(){
Scanner input = new Scanner(System.in);
//利用数组维护10个得分
int length = 3;
int[] scores = new int[length];
for (int i = 0; i < length; i++) {
System.out.println("请录入第" + (i + 1) + "个得分");
int score = input.nextInt();
scores[i] = score;
}
//最高分,去掉一个最低分
Arrays.sort(scores);//升序排列
System.out.println(scores);
}
/**
* 用户登录
*/
public static void userLogin(String username, String password) {
if (username == null || password == null) {
System.out.println("参数不能为null");
return;
}
if(!"admin".equals(username) || !"1234".equals(password) ){
System.out.println("登录失败");
return;
}
username = "administrator";
System.out.println("登录成功,欢迎你:"+username);
}
public static void main(String[] args) {
// 传参: 实际参数--> 实参--> 值(内存地址值)传递(如果参数是字面量数据类型 传递的就是数据)
//其它引用数据类型: 内存地址值
//调用登录的方法
String username = "admin";// string数据不可变
String pass = "1234";
userLogin(username,pass);
System.out.println(username+"====="+pass);
System.out.println("main结束。。。。。。。");
}
// public static void modifyArray(int[] num){
// //数组是引用数据类型 一定要提前预判 ==null 避免出现一些异常
// if (num==null){
// System.out.println("参数不能为null");
// return;
// }
// //length=0
// if (num.length==0){
// System.out.println("是空数组,无法修改");
// return;
// }
/**
* 不知道会传过来多少同类型的参数 使用可变参数替换
* 方法的形参数量一般不会超过4个,方法体代码量不要超过80行
*/
/**
* 用户注册--->提交数据(从页面拿数据都是String)
* @param id 用户id
* @param params 参数
*/
public static void userRegister(int id,String...params){
System.out.println(id);
System.out.println("params:"+Arrays.toString(params));//[Ljava.lang.String;@1b6d3586
System.out.println(params[0]);
}
public static void main(String[] args) {
System.out.println("main开始。。。。。。。");
//调用userRegister
//可变参数: 传参的时候>=0数据 就是一个数组
userRegister(1001,"jim","男","河南郑州");
System.out.println("main结束。。。。。。。");
}
/**
* 有返回值的方法 必须加 return 数据;
* @return
*/
public static boolean lucky(){
Scanner input = new Scanner(System.in);
System.out.println("请录入会员号:");
int cardNo = input.nextInt();
//随机生成4个幸运的数字 4位数字 1000-10000
for (int i = 0; i < 4; i++) {
int random = (int)(Math.random()*9000+1000);
if(cardNo==random){
return true;
}
}
return false;
}
public static void main(String[] args) {
System.out.println("main开始。。。。。。。");
//调用方法: 对于返回值: 可以接收 可以不接收
// boolean flag = lucky();
// if(flag){
// System.out.println("是幸运的会员");
// }else{
// System.out.println("不是");
// }
System.out.println(lucky());
// System.out.println(Arrays.toString(args));
System.out.println("main结束。。。。。。。");
}
public static boolean userLogin(String username, String password) {
if (username == null || password == null) {
System.out.println("参数不能为null");//红色
//错误的日志 没有控制台的概念 以后的项目都在服务器---> 日志框架--->
return false;
}
if(!"admin".equals(username) || !"1234".equals(password) ){
System.out.println("登录失败");
return false;
}
return true;
}
public static void main(String[] args) {
System.out.println("main开始。。。。。。。");
if(userLogin("admin","1234")){
System.out.println("success");
}else{
System.out.println("error");
}
//调用的其它类的方法
// Arrays.toString(new int[]{1,2,3});
System.out.println("main结束。。。。。。。");
}
在方法里面调用方法:
public static void exercise2(){
//dshhsdg
exercise1();
}
public static void main(String[] args) {
System.out.println("main开始。。。。。。。");
//执行exercise1的逻辑
exercise2();//调用方法
System.out.println("main结束。。。。。。。");
}
//方法重载: 底层一些工具类的方法
//1. 方法名相同
//2. 参数不同
//3. 不考虑返回值以及修饰符
public static long getMax(long lon1, long lon2) {
return (lon1 > lon2) ? lon1 : lon2;
}
/**
* 求2个整数最大值
*
* @param num1 数字1
* @param num2 数据2
*/
public static int getMax(int num1, int num2) {
return (num1 > num2) ? num1 : num2;
}
采用面向对象的这种思想进行编写代码,称为面向对象编程。 OOP Programming
面向对象编程本质: 使用类去管理代码,使用对象处理(封装)数据。
具体体现3个特征:(抽象)
封装
继承
多态
程序设计:
面向过程 C 一步一步完成。
面向对象 java
程序中, 目的创建对象。先有类别,再有对象。
类与对象的关系:
可以基于一个模板(类别)创建n多个对象(实例)。
每个对象(实例)都有唯一的一个类与之相对应。
public class Student {
//类的组成部分
//信息: n个对象(实例)相似的东西 被抽象出来 存储在模板里面
//(全局)成员变量(属性 特征) ---> 类里面 方法之外 注意: 成员变量尽可能不要初始化
//语法: [访问权限修饰符 普通修饰符] 数据类型 变量名称 [= 数据];
public int id; //定义属性 或者叫成员变量
public String name;
public byte age;
public char gender;
public double score;
public String[] hobby;
//成员方法(功能 行为)
public void study(){
System.out.println(name+"在学习");
//遍历学生爱好
if(hobby==null){
return;
}
for (String s : hobby) {
System.out.println("爱好:"+s);
}
}
}
对象访问类的成员:
对象名.成员变量;
对象名.成员方法();
基于Student创建对象:
public static void main(String[] args) {
//1. 创建学生对象
//创建对象的语法: 类名 对象名(变量名) = new 类名();
Student liHongShuai = new Student(); // new Student() 对象 初始化一个对象(实例)
System.out.println(liHongShuai); //com.javasm.obj.Student@4554617c
//使用对象。 对象访问(调用)类的成员 对象名.成员变量;
liHongShuai.id = 1001;
liHongShuai.name = "李洪帅";
liHongShuai.age = 18;
liHongShuai.score = 89.5;
liHongShuai.gender = '女';
liHongShuai.hobby = new String[]{"game","码代码","music"};
System.out.println("------------------------------");
System.out.println("id:"+liHongShuai.id);
System.out.println("name:"+liHongShuai.name);
System.out.println("score:"+liHongShuai.score);
System.out.println("hobby:"+Arrays.toString(liHongShuai.hobby));
System.out.println("age:"+liHongShuai.age);
System.out.println("gender:"+liHongShuai.gender);
//访问成员方法 对象名.成员方法();
liHongShuai.study();//哪一个对象调用方法 方法体里面运用属性就是哪一个对象的信息
}
public class UserInfo {
public int id;
public String name;
public int age;
//用户的角色---> 自己创建的一个角色类
//成员变量不推荐初始化? 不会占用太多 内存资源
public Role userRole;//自定义的类对象
//public Role userRole = new Role(); 这样写 成员变量初始化了 会在堆内存产生 一个UserInfo的同时产生new Role
}
public class Role {
public int id;
public String roleName;
public String desc;
}
public static void main(String[] args) {
//创建用户对象---> 关联角色
//程序里面有() 肯定是方法 UserInfo()是一个方法
UserInfo userInfo = new UserInfo(); // 创建---使用----无用---回收 GC
System.out.println("----------------赋值-----------------");
userInfo.id = 10001;
userInfo.name = "admin";
userInfo.age = 20;
Role role = new Role(); //
role.id = 1;
role.roleName = "超级管理员";
userInfo.userRole = role;
System.out.println(userInfo.id);
System.out.println(userInfo.name);
System.out.println(userInfo.age);
System.out.println(userInfo.userRole.roleName);
}
public class UserInfo {
public int id;
public String name;
public int age;
public Role userRole;
//默认存在一个无参的构造方法
//构造方法的方法名与类名一致
//构造没有返回值类型
//方法重载
//1. 方法名必须相同
//2. 参数列表必须不同
//3.与修饰符,返回值没有关系
public UserInfo(){
//功能: 初始化成员变量数据
System.out.println("无参构造");
}
}
public static void main(String[] args) {
//jvm 加载类UserInfo.class
//执行new UserInfo()
//再将对象赋值给userInfo
UserInfo userInfo = new UserInfo();
System.out.println("userInfo:"+userInfo);
}
也是构造方法重载
在一个类里面 没有展示无参构造 类里的有参构造就会覆盖无参 就不能使用无参构造创建对象了
//在一个类里面 没有展示无参构造 类里面有有参构造
// 有参会覆盖掉无参 就不能使用无参构造创建对象了
//手动创建有参构造: 形式参数
// public UserInfo(int id1,String name1,int age1){
// name = name1;
// id = id1;
// age = age1;
// }
我们使用无参构造创建对象,还需要再次访问属性赋值操作:
// userInfo.id = 10;
能不能在创建对象的时候通过传参直接对属性赋值???
new UserInfo(1,"张三",20);可以利用有参构造创建
public UserInfo(int id, String name, int age, Role userRole) {
this.id = id;
this.name = name;
this.age = age;
this.userRole = userRole;
}
public UserInfo(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
UserInfo userInfo1 = new UserInfo(1,"张三",20);
System.out.println(userInfo1.name);
位置 | 赋值 | 作用域 | 内存 | 回收 | |
---|---|---|---|---|---|
成员变量 | 类里面 | 有默认值 | 整个类都可以方法 | 堆 | GC(垃圾回购) |
局部变量 | 方法体 | 必须要赋值 | {} | 基本: 栈 引用: 堆 | 方法运行完毕 栈内存数据全部清除 |
封装: 与数据(成员变量)有关. 目的: 限制程序对数据的访问。(取值以及赋值的操作)
修饰符: 修饰某些内容
public | 可以修饰类,成员方法/变量 | 修饰类: 项目里面的任意一个包内都可以访问 | 修饰成员方法/变量: 对象 |
private | 成员方法/变量 | 本类访问 | |
protected | 成员方法/变量 | 包内有效/子类有效 | |
默认 | 可以修饰类,成员方法/变量 | 包内有效 |
@标识名 生成set/get/构造/toString
1. 数据从哪来? 用户/商品
用户录入--->控制台/页面--->数据的规则(页面执行一些数据检验 js校验)--->
后端永远不要相信前台页面提交的数据--->后端校验(service层)--->实体校验--->数据要持久保存(磁盘)
--> 程序的运行流程--->set 这个数据就是很ok的数据了。
@Setter 赋值使用
@Getter 获取使用
@NoArgsConstrutor
@AllArgsConstrutor
@ToString
@NonNull // 赋值的时候不能为null
这些注解都是在编译期间有效,
- lombok 与 idea集成 在idea安装这个plugin 安装完毕 重启idea
file—>setting—>plugins—>lombok
- 下载lombok的jar https://projectlombok.org/download
- 将类库文件放到编译路径下
编译工作: idea—> javac—>在class文件需要将注解转换成set/get
java: 当前正在运行 的对象
public class Order {
private int id; // setUid/getUid ---> 底层的反射 封装开源框架==> 用户提交的数据自动装配成一个对象
private double money;
private String time;
public int getId() { //不推荐在set/get里面写功能逻辑 会增加排查问题的难度
return id; //如果说: set/get 写很少代码去解决很大问题的时候
}
public void setId(int id) {
//局部变量与成员重名 就近原则
// id = id;
System.out.println("this:"+this);
//哪个对象调用了方法 this就是那个对象
this.id = id;
this.a();
}
public void a(){
System.out.println("a.........................");
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
@Setter
@Getter
public class Student {
private int id;
private String name;
//构造
public Student(int id, String name) {
// this();//必须在第一行
this.id = id;
this.name = name;
}
public Student() {
//调用有参构造
this(1,"jim");
}
public Student(int id) {
this.id = id;
}
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.getId());
System.out.println(student.getName());
//this 充当返回数据
//当我们调用方法对对象的执行操作,还想返回当前这个对象 return this;
}
}
public class FixCarFactory {
// 名字、地址、电话
public String name;
public String address;
public String phone;
//可以获得车所有的属性 信息
public void fixCar(Car car){
System.out.println("修车开始。。。。。。");
System.out.println(name+"正在修理。。。。"+car.color+car.brand);
System.out.println("修车结束。。。。。。");
}
public FixCarFactory(String name, String address, String phone) {
this.name = name;
this.address = address;
this.phone = phone;
}
}
public class Car {
// 品牌 轮子数、颜色
public String brand;
public int wheelNum;
public String color;
//跑的行为
public void run(FixCarFactory factory) {
if (factory == null) {
System.out.println("参数不能为null");
return;
}
//应该判断是否够四个轮子,如果够四个轮子可以跑起,否则送去修车厂维修。
if (wheelNum < 4) {
factory.fixCar(this); // this充当实际参数
return;
}
System.out.println(brand + "在跑。。。。。");
}
public Car(String brand, int wheelNum, String color) {
this.brand = brand;
this.wheelNum = wheelNum;
this.color = color;
}
}
普通的修饰符:
修饰变量: 静态变量 类变量
修饰方法:静态方法 类方法
public class Car {
//成员(实例)变量(全局变量)
public int id;
private int carId;//本类访问
int count;//默认修饰 本包内友好
//静态变量(类变量)--->静态成员的数据可以让所有的对象共享
//静态的成员只初始化1次
//静态成员的生命周期与Class文件保持一致。
private static String name;//本类访问
public static String str ;
//public +static +final 都会作为工具类的成员变量(一般都用来常量)
//private +static 出现在的工具类里面 ---> 工具类里面的方法 几乎都是static修饰的
//Arrays Math
//所属 static: Class 非静态: 对象
public static void a(){
System.out.println("a...................");
System.out.println(str);
System.out.println(name);
//是否可以使用this?
// System.out.println(this); 不可以
//在静态方法里面 只能直接访问static修饰的成员变量
Car car = new Car();
car.count = 10;
}
public void c(){}
public void b(){
//可以访问任意的成员
System.out.println(name);
System.out.println(id);
this.c();
Car.a();
}
@Getter
@Setter
@NoArgsConstructor
public class Person {
private int id;
@NonNull
private String name;
private int age;
public void compareAge(Person person){
//this
//jdk1.7 提供一个工具类 Objects 校验数据是否是null
Objects.requireNonNull(person);
if(this.age>person.age){
System.out.println(this.name+"比"+person.getName()+"年龄要大");
}else if(this.getAge()==person.getAge()){
System.out.println(name+"与"+person.name+"同龄");
}else{
System.out.println(this.name+"比"+person.getName()+"年龄要小");
}
}
public static int count;//实例变量
public Person(int id, @NonNull String name, int age) {
//count++
count++;
this.id = id;
this.name = name;
this.age = age;
}
}
修饰代码块: 静态代码块 vs 普通代码块
```java
普通代码块:(方法体)
{
//逻辑
}
```
静态代码块:
在一个程序里面,创建的Animal对象始终都是同一个。
public class Animal {
public static Animal instance;//声明一个instance null
static {
instance = new Animal();
}
//普通代码块 默认执行---> 只要创建一次对象 就执行一次
{
System.out.println("普通代码块。。。。。");
}
//jvm只加载一次class static代码走1次
static {
//用户非常多
//场景: 在一个程序里面 使用的都是同一个的对象
//初始化静态成员变量
System.out.println("静态代码块");
}
private Animal(){
System.out.println("无参构造。。。。。");
}
}
继承:提高代码(程序)的复用性—> (重复性,相似的代码可以被封装到某一个(类)方法)
体现
语法
public class 类1 extends 类2{
}
// 子类与父类(派生类与基类的关系)
//类1: 子类
//类2: 父类
子类就拥有类父类的一些成员。(非private,以及父类构造)
子类也可以拥有独有的特性(成员)
注意: java 里的继承: 属于单根继承。
this: 当前对象
super: 父类对象标识
super在子类里面访问父类的成员变量和成员方法
@Override
public int[] bark(){
System.out.println(this.name+"在喵喵的叫"+this.count);
System.out.println(this.name+"在喵喵的叫"+super.count);
return null;
}
在子类构造里面调用父类构造最多(最常用)
this();
this(实参1,....);
子类构造调用父类的构造: //二者只能有一个 因为this(); 和 super(); 都要放在第一行
super();
super(实参1,....);
@Setter
@Getter
public class PM extends Employee {
private int exep;
private double bonus;
//出现方法重写 override 重载:Overload
@Override
public void show(){
//直接调用父类的show()
super.show();
System.out.println(exep+","+bonus);
}
/*
父类用private 定义属性后(不是protected) 子类想要继承父类的属性要通过setter方法间接获取
如下:
子类的无参构造与有参构造这么写
*/
public PM() {
//肯定通过逻辑执行了---> 默认存在
//调用父类构造 super
super();
System.out.println("4. pm子类的构造");
}
public PM(int id, String name, String gender, double salary, int exep, double bonus) {
//将形参的数据赋值给成员变量
super(id, name, gender, salary); // 只能出现在第一行
this.exep = exep;
this.bonus = bonus;
// this.setId(id); 可直接用 super(id, name, gender, salary); 替代
// setName(name);
// setGender(gender);
// setSalary(salary);
//调用父类的有参构造
}
}
public class PMTest {
public static void main(String[] args) {
PM pm = new PM();
pm.setName("pm"); //在主函数通过 set 赋值
SE se = new SE(1, "pm", "男", 90000, 50);
}
}
有层级关系: 继承
子类重写父类的方法
1. 子类重写的方法名/形参必须与父类的一致
2. 子类重写的方法的访问权限修饰符>=父类的
3. 子类重写的方法的返回值类型<=父类的
super.成员;
//出现方法重写 override 重载:Overload
@Override
public void show(){
//直接调用父类的show()
super.show();
System.out.println(exep+","+bonus);
}
2. 子类重写的方法的访问权限修饰符>=父类的
访问权限修饰符:private--->默认--->protected--->public
@AllArgsConstructor
public class Animal {
//属性: protected 没有封装的概念 推荐使用private
protected int id;
protected String name;
protected void bark(){
System.out.println(name+"在叫。。。。。");
}
}
public class Cat extends Animal {
//子类默认无参构造 默认调用父类无参构造
//父类没有提供无参构造 在子类里面也没有必要提供无参构造
// public Cat(){
// //显示调用父类的有参构造
// super(1,"cat");
// }
//重写父类的方法
//对子类/包内友好
@Override
public void bark(){
System.out.println(this.name+"在喵喵的叫");
}
public Cat(int id, String name) {
super(id, name);
}
}
public class Dog extends Animal {
//利用idea生成
@Override
public void bark() {
System.out.println(name+"汪汪的叫。。。。。");
}
public Dog(int id, String name) {
super(id, name);
}
}
3. 子类重写的方法的返回值类型<=父类的--->多态
子类重写的方法的返回值类型 与 父类的方法返回值类型肯定是有层级关系(继承)
@AllArgsConstructor
public class Animal {
//属性: protected 没有封装的概念 推荐使用private
protected int id;
protected String name;
protected Object bark(){
System.out.println(name+"在叫。。。。。");
return null;
}
}
public class Cat extends Animal {
@Override
public int[] bark(){
System.out.println(this.name+"在喵喵的叫");
return null;
}
public Cat(int id, String name) {
super(id, name);
}
}
前提: 有层级关系。(继承,实现)
作用: 提高程序(方法)的可扩展性的。----> 高内聚 低耦合
其实就是子类对象向上转型成父类引用。
public static double cal(Employee employee){
double salary = employee.getSalary();
return salary;
}
public static void main(String[] args) {
PM pm = new PM(1, "pm", "nan", 20000, 5, 3000);
SE se = new SE(2, "se", "nan", 10000, 5);
Sale sale = new Sale(3, "sale", "nan", 9000);
System.out.println("se:"+SalaryCal.cal(se));
System.out.println("sale:"+SalaryCal.cal(sale));
System.out.println("pm:"+SalaryCal.cal(pm));
System.out.println("----------------------------------");
Employee employee = new PM(1, "pm", "nan", 20000, 5, 3000);
System.out.println("pm:" + SalaryCal.cal(employee));
employee = new SE(2, "se", "nan", 10000, 5);
System.out.println("se:" + SalaryCal.cal(employee));
employee = new Sale(3, "sale", "nan", 9000);
System.out.println("sale:" + SalaryCal.cal(employee));
//2. 编译时数据类型与运行时数据类型不一致的时候(多态)--->创建对象
//编译时数据类型: jvm加载class =左边的类型Employee jvm加载是Employee.class
//运行时数据类型: =右边的类型 PM Sale SE
}
// Employee employee = se;
// Employee employee = new SE();
// Employee employee = new pm();
// Employee employee = new sale();
//1.父类的引用(对象)指向任意的一个子类的实例
//子类对象赋值给父类引用(对象)===>子类对象向上转型(多态)
//传参的时候出现的多态(传参的时候子类对象向上转型 )
PM的薪资发放有误:
public static double cal(Employee employee) {
//多态的弊端: 无法获得子类的特有成员
Objects.requireNonNull(employee);
//在多态的环境下:
// 访问属性---> 父类(无法获得子类的特有成员) jvm编译时数据类型
// 访问方法---->子类(重写) 运行时数据类型
double salary = employee.getSalary();
//employee是pm实例的话 分红 PM--->pm的对象
//向下转型--->employee转型成子类对象(强制转换)
//条件判断: employee 到底是否是PM的实例
// instanceof: 判断对象是否是一个类的实例
//不然有可能会出现//类型转换//的异常。
// "abc".equals()
// "a".equals('a')
if (employee instanceof PM) {
PM pm = (PM) employee;
salary += pm.getBonus();
}
// 人: 男人 女人
return salary;
}
父类定义的属性
子类继承过去的相同的属性
如果子类使用的方法都相同,没有子类特有的属性,那么可以直接定义一个方法类,在方法类中传入父类形参,不同子类可直接调用到这个方法类中
如果使用到子类的特有属性或者方法
需要判断,然后强转成子类,再进行操作
//有可能会出现类型转换的异常 ClassCastException
public class Methods {
public Methods() {
}
public static void methods(Ana ana){
Objects.requireNonNull(ana);
if(ana instanceof Snake){
Snake s = (Snake)ana;
s.eat();
}
if(ana instanceof Fish){
Fish f = (Fish)ana;
f.run();
}
}
}
//数组的元素是一个个的对象
//存储多个动物
// 数据类型[] 数组 = new 数据类型[];
Animal[] animals = new Animal[5];
System.out.println(Arrays.toString(animals));
animals[0] = new Cat(1,"加菲猫1");
animals[1] = new Cat(2,"加菲猫2");
animals[2] = new Cat(3,"加菲猫3");
animals[3] = new Dog(4,"旺财");
System.out.println(Arrays.toString(animals));
//遍历数组 bark()
for (Animal animal1 : animals) {
// Objects.requireNonNull(animal1);
if(animal1==null){
break;
}
animal1.bark();
抽象方法
使用abstract修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
抽象类
包含抽象方法的类就是抽象类。通过abstract方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
@Setter
@Getter
@AllArgsConstructor
public abstract class Monster {
//抽象类都是指定规则。
private String name;
private int blood;
private int attack;
private int defence;
//父类的普通方法: 针对于子类而言 可以重写/也可以不重写
//制定好的规则: 针对于Monster所有的子类,都必须重写attack/move 抽象类的子类都要重写
//将方法定义成一个抽象方法: 使用abstract关键字修饰的方法
//特征: 没有方法体,{}都没有
//如果一个类里面有抽象方法了 这个类肯定是抽象类
public abstract void attack();
public abstract void move();
}
public abstract class MyAbstract {
//组成部分
//MyAbstract 在普通类基础之上 又增加了组成部分
static{}
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public static void c(){}
private void a() {
}
public MyAbstract(int num) {
this.num = num;
}
public MyAbstract() {
}
//抽象方法
//abstract可以与哪些修饰符综合运用? 不能private
//抽象类里面也可以没有抽象方法
//规则
public abstract String b(int num); //增加了抽象方法
}
public abstract class Child extends MyAbstract{
//子类必须要重写父类规定的抽象方法
}
public static void main(String[] args) {
//创建子类对象
MyAbstract myAbstract = new Child(); //抽象父类不能实例化 MyAbstract myAbstract = new MyAbstract();
String str = (String) myAbstract.b(100); //b()是Child子类里的重写的抽象方法
System.out.println(str); //如果子类方法返回类型改变 需以子类为准 进行强转
//MyAbstract myAbstract = new Child();
//Object str = myAbstract.b(100);
//System.out.println(str);
//抽象类不能实例化
//作用: 1. 服务于子类对象的创建 2. 体现多态
}
//创建了一个子类
//匿名的抽象类对象
// MyAbstract myAbstract = new Child(); //也可以强行创建一个抽象父类对象 不过只能是匿名对象
MyAbstract myAbstract1 = new MyAbstract(){
public int count = 1000;//子类特有的成员
@Override
public Object b(int num) {
count+=num;
System.out.println("coun:"+count);
return new Object();
}
public void abc(){
System.out.println("abc.......");
}
};
System.out.println(myAbstract1.b(1));
\1. 有抽象方法的类只能定义成抽象类
\2. 抽象类不能实例化,即不能用new来实例化抽象类。
\3. 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
\4. 抽象类只能用来被继承。
\5. 抽象方法必须被子类实现。
接口全面地专业地实现了:规范和具体实现的分离。
接口和实现类不是父子关系,是实现规则的关系。
接口就是规范,定义的是一组规则
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。
如果一个抽象类没有字段,所有方法全部都是抽象方法:
就可以把该抽象类改写为接口:interface
。
interface Person {
void run();
String getName();
}
//interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。
//因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。
语法: public interface 接口名{//大写的驼峰
}
项目设计: 分层的概念
dao: data access object 数据访问对象(数据持久层)--->持久化保存数据(磁盘/缓存)--->操作数据库
service: 业务逻辑处理层
看模块--->用户模块---->UserDao--->UserDaoImpl
订单模块---->OrderDao---->OrderDaoImpl
以包名区分层:
package: com.javasm.dao
com.javasm.dao.impl
com.javasm.service
com.javasm.service.impl
在开发中,接口的命名方式只有2中: ***DAO(Dao) ***Service
接口的实现类命名也只有2种: ***DAOImpl / ***DaoImpl ***ServiceImpl
public interface MyInterface {
//接口里面: 所有的组成部分都是public修饰的
//接口里面没有变量 只有常量 NAME
String NAME = "admin";// 默认也是public static final
//1.常量: final修饰的变量 常量名全部大写(成员变量) 必须初始化且不可更改
//2.方法---> jdk1.8- 接口里面所有的方法都是抽象的方法 public abstract
//定义了3个抽象方法
void a();
String b();
int[] c(Object... object);
//3.没有构造 --->比抽象类还抽象
}
//interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。
//因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。
1. 类与接口之间:多实现 implements
类实现接口: 称为类接口的实现类。(一个接口可以有n个实现类)
java语言: 类第一的原则
public class 类1 extends 类2 implements 接口1,接口2,....{
}
public class MyInterfaceImpl implements MyInterface {
@Override
public void a() {
System.out.println("MyInterfaceImpl......a"+NAME);
}
@Override
public String b() {
System.out.println("MyInterfaceImpl......b");
return "b";
}
@Override
public int[] c(int... object) {
System.out.println("object:"+Arrays.toString(object));
System.out.println("MyInterfaceImpl......c");
return object;
}
}
//public class MyInterfaceImpl implements MyInterface,A {} 可以实现多个接口
2. 接口与接口之间: 多继承
一个接口可以继承多个接口
public interface 接口1 extends 接口2,接口3{
}
public interface MyInterface extends A,B {}
public class MyInterfaceTest {
public static void main(String[] args) {
MyInterfaceImpl myInterface = new MyInterfaceImpl(); //myInterface接口已实现
myInterface.c(1,2,3);
//在实现关系下, 也可以使用多态创建对象
MyInterface myInterface1 = new MyInterfaceImpl(); //MyInterfaceImpl继承了接口A B的方法
//父接口的引用指向任何一个实现类的对象
System.out.println(MyInterface.NAME);
myInterface1.a(); //可直接调用A接口方法 通过MyInterfaceImpl接口
}
}
jdk1.8+ ===> 变的与抽象类很相似
public interface NewInterface {
//接口里面所有的组成内容都是public修饰的
//常量
String PASS = "1234";
//抽象方法
void demo();
//普通的功能方法: default
default int common(String str) {
System.out.println("NewInterface......common-----" + str);
return 100;
}
//静态方法
static void staticMethod(int[] num) {
System.out.println("NewInterface......staticMethod----" + Arrays.toString(num));
}
}
public class NewInterfaceImpl extends Object implements NewInterface {
@Override
public void demo() {//对象.实例方法
System.out.println(PASS);
//调用common方法: 重写之后的逻辑
// System.out.println("common:"+this.common("abc"));
//调用父接口的common
// super--->父类对象的标识
System.out.println("super.common:"+NewInterface.super.common("aaa"));
//调用static方法
NewInterface.staticMethod(new int[]{1,2,3});
System.out.println("NewInterfaceImpl......demo");
}
//重写父接口的common
@Override
public int common(String str) {
System.out.println("NewInterfaceImpl......common");
return 11111;
}
}
abstract class | interface | |
---|---|---|
继承 | 只能extends一个class | 可以implements多个interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
修饰符: 最后的/最终的
1. 修饰变量(常量)
1.1 修饰成员变量
private static final int USER_DELETE_STATUS = -1;
public static final String USER_NAME = "";
public static final String USER_PASS = "";
1.2 修饰局部变量
public static void login(final String name,final String pass){
//很多逻辑
final int num= 100;
}
2. 修饰类
String/Math
final修饰类不能充当父类的。
3.修饰方法
在继承的关系下,子类不能重写父类里面的final修饰的方法
final (finally finalize 清垃圾用的)
面向接口编程:
写 interface
写接口:
写服务的(给其它项目暴露接口(服务路径))--->路径
支付宝支付,微信支付,银联支付
百度/高德
导包
创建对象
Scanner sc = new Scanner(System.in);
使用方法, 获取键盘录入的内容
int num = sc.nextInt();
// 导包
import java.util.Scanner; // Scanner, S是大写的
public class Demo01 {
public static void main(String[] args) {
// 创建对象, 实际上sc就是一个对象名(可以改变), 但是不建议修改
Scanner sc = new Scanner(System.in);
// 使用方法, 获取键盘录入的数字
int num = sc.nextInt();
// num这个数字, 就是通过键盘录入得到的 只读取空格之前的内容 光标依然在当前行没有换行
}
}
使用步骤:
导入包
import java.util.Random;
创建对象
Random r = new Random();
产生随机数
int num = r.nextInt(10);
解释: 10代表的是一个范围,如果括号写10,产生的随机数就是0-9,括号写20,参数的随机数则是0-19
Random练习-猜数字(应用)
需求:
程序自动生成一个1-100之间的数字,使用程序实现猜出这个数字是多少?
当猜错的时候根据不同情况给出相应的提示
A. 如果猜的数字比真实数字大,提示你猜的数据大了
B. 如果猜的数字比真实数字小,提示你猜的数据小了
C. 如果猜的数字与真实数字相等,提示恭喜你猜中了
示例代码:
package com.itheima.test;
import java.util.Random;
import java.util.Scanner;
/*
需求:
程序自动生成一个1-100之间的数字,使用程序实现猜出这个数字是多少?
当猜错的时候根据不同情况给出相应的提示
A. 如果猜的数字比真实数字大,提示你猜的数据大了
B. 如果猜的数字比真实数字小,提示你猜的数据小了
C. 如果猜的数字与真实数字相等,提示恭喜你猜中了
程序自动生成一个1-100之间的数字 : 真实的目标数字 -> 随机数
猜的数字 : 我们键盘录入的数字
使用键盘录入的数字, 和随机数进行比较 (大, 小, 相等)
*/
public class Test04_review {
public static void main(String[] args) {
// 创建键盘录入对象
Scanner sc = new Scanner(System.in);
// 创建随机数对象
Random r = new Random();
// 生成要猜测的数字(1 ~ 100)
int randomNumber = r.nextInt(100) + 1;
// 如果生成随机数的代码放到了循环的里面, 每一次循环都会产生一个新的随机数, 你要猜一辈子都猜不中
// 猜数字(键盘录入数字)的代码, 要放在循环中, 因为我们要多次猜数字, 不能只猜一次
// 加上while循环
// 无限循环, 不断的猜, 直到 猜中了, 就不猜了(结束循环)
while (true) {
// 键盘录入一个数字
System.out.println("请输入你猜测的数字: ");
int guessNumber = sc.nextInt();
// 可以对这个键盘录入的数字进行判断, 把错误的情况提示出来
// 错误的情况 小于1 或者 大于100
if (guessNumber < 1 || guessNumber > 100) {
System.out.println("数字不在(1 ~ 100)范围内!");
// 下面的判断就不要再执行了, 而是开始下一次循环
// 中止本次循环, 继续进行下一次循环
continue;
}
// 如果我猜测的数字 大于 随机数
if (guessNumber > randomNumber) {
// 提示: 大了
System.out.println("你猜的数据大了");
} else if (guessNumber < randomNumber) { // 如果我猜测的数字 小于 随机数
// 提示: 小了
System.out.println("你猜的数据小了");
} else {
// 相等 -> 猜中了!~
System.out.println("恭喜你!~猜中了!~");
// 猜中了, 就不猜了, 循环结束了~
break;
}
}
}
}
ArrayList 是大小可变的数组的实现,存储在内的数据称为元素。
数组的长度不可以发生改变。
但是ArrayList集合的长度是可以随意变化的。 ArrayList 中可不断添加元素,其大小也自动增长。
对于ArrayList来说,有一个尖括号代表泛型。
泛型:也就是装在集合当中的所有元素,全都是统一的什么类型。
***注意:泛型只能是引用类型,不能是基本类型。 ArrayList当中存储基本类型数据,必须使用基本类型对应的“包装类”
注意事项:
对于ArrayList集合来说,直接打印得到的不是地址值,而是内容。
如果内容是空,得到的是空的中括号:[]
基本格式:
ArrayList<String> list = new ArrayList<String>();
在JDK 7后,右侧泛型的尖括号之内可以留空,但是<>仍然要写。简化格式:
ArrayList<String> list = new ArrayList<>();
常用的方法:
对于元素的操作,基本体现在——增、删、查。常用的方法有:
public boolean add(E e) :将指定的元素添加到此集合的尾部。
//参数 E e ,在构造ArrayList对象时, 指定了什么数据类型,那么 add(E e) 方法中,只能添加什么数据 类型的对象。
public E remove(int index) :移除此集合中指定位置上的元素。返回被删除的元素。
public E get(int index) :返回此集合中指定位置上的元素。返回获取的元素。
public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。
public class Demo03ArrayListMethod {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
System.out.println(list); // []
// 向集合中添加元素:add
boolean success = list.add("柳岩");
System.out.println(list); // [柳岩]
System.out.println("添加的动作是否成功:" + success); // true
list.add("高圆圆");
list.add("赵又廷");
list.add("李小璐");
list.add("贾乃亮");
System.out.println(list); // [柳岩, 高圆圆, 赵又廷, 李小璐, 贾乃亮]
// 从集合中获取元素:get。索引值从0开始
String name = list.get(2);
System.out.println("第2号索引位置:" + name); // 赵又廷
// 从集合中删除元素:remove。索引值从0开始。
String whoRemoved = list.remove(3);
System.out.println("被删除的人是:" + whoRemoved); // 李小璐
System.out.println(list); // [柳岩, 高圆圆, 赵又廷, 贾乃亮]
// 获取集合的长度尺寸,也就是其中元素的个数
int size = list.size();
System.out.println("集合的长度是:" + size);
}
}
此类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法
* public static String toString(数组):将参数数组变成字符串(按照默认格式:[元素1, 元素2, 元素3...])
* public static void sort(数组):按照默认升序(从小到大)对数组的元素进行排序。
static boolean equals (int[ ] a, int[ ] a2): equals方法用来比较两个数组是不是相等。此方法的返回值是一个布尔值,由于这是一个static静态方法可以直接Arrays.点出equals方法。
static void fill (int [ ] a,int n) :fill方法是一个没有返回值的方法,可以把整形数值n分配给数组a中的每一个元素。
static int [ ] copyOf (int [ ] a , n): copyOf方法则是把原数组复制成新长度的数组
//将数组转换成集合
List list =Arrays.asList(1,2,3);
//首先声明一个数组
int[] num={1,2,3,4,5};
String s=Arrays.toString(num);
System.out.println(s); //打印后的结果为:[1, 2, 3, 4, 5]
//先定义长度为5的整形数组
int[] num={55,42,12,86,91};
//调用方法进行升序
Arrays.sort(num);
System.out.println(Arrays.toString(num));//结果为:[12, 42, 55, 86, 91]
int[] arr1={10,20,30,40,50};
int[] arr2={10,20,30,40,50};
int[] arr3={10,20,30,40};
boolean result1=Arrays.equals(arr1,arr2);
boolean result2=Arrays.equals(arr1,arr3);
System.out.println(result1);//结果为true
System.out.println(result2);//结果为false
//声明数组num[]
int[] nums={1,2,3,4,5};
Arrays.fill(nums,6);
System.out.println(Arrays.toString(nums));//结果为:[6, 6, 6, 6, 6]
int[] nums={1,2,3,4,5};
//把原数组变成长度为3的新数组
int[] newNums1=Arrays.copyOf(nums,3);
System.out.println(Arrays.toString(newNums1));//结果为:[1, 2, 3]
//把原数组变成长度为7的新数组
int[] newNums2=Arrays.copyOf(nums,7);
System.out.println(Arrays.toString(newNums2));//结果为:[1, 2, 3, 4, 5, 0, 0]
Math类的常用方法:
* 1. abs 绝对值
2. acos,asin,atan,cos,sin,tan 三角函数
3. sqrt 平方根
4. pow(double a, double b) a的b次幂
5. max(double a, double b) 取大值
6. min(double a, double b) 取小值
* 7. ceil(double a) 大于a的最小整数
* 8. floor(double a) 小于a的最大整数
9. random() 返回 0.0 到 1.0 的随机数
* 10. long round(double a) double型的数据a转换为long型(四舍五入)
11. toDegrees(double angrad) 弧度->角度
12. toRadians(double angdeg) 角度->弧度
//public static double abs(double a) :返回 double 值的绝对值。
double d1 = Math.abs(‐5); //d1的值为5
//public static double ceil(double a) :返回大于等于参数的最小的整数。
double d1 = Math.ceil(3.3); //d1的值为 4.0
double d2 = Math.ceil(‐3.3); //d2的值为 ‐3.0
//public static double floor(double a) :返回小于等于参数最大的整数。
double d1 = Math.floor(3.3); //d1的值为3.0
double d2 = Math.floor(‐3.3); //d2的值为‐4.0
//public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)
long d1 = Math.round(5.5); //d1的值为6.0
long d2 = Math.round(5.4); //d2的值为5.0
对基本数据类型包装。
为什么有基本数据类型,还对基本提供包装类?
作用: 都是用来定义整数,小数,布尔,单字符等数据。
基本数据类型: 栈 ---> 效率高,数据共享。
但是java语言是一门面向对象的语言,基本数据类型没有对象的概念。
为了丰富基本数据类型的操作,就提供相对应包装类。(肯定属性和方法和构造)
基本数据类型 | 包装类(null) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
pojo/实体类: 类的所有的属性不要再使用基本数据类型定义了。
所有的方法的形参和返回值也都使用包装类类型。(null)—> NPE
public class Test {
/** 测试Integer的用法,其他包装类与Integer类似 */
void testInteger() {
// 基本类型转化成Integer对象
Integer int1 = new Integer(10); // 通过new操作符创建Integer实例(不推荐使用,会有编译警告):
Integer int2 = Integer.valueOf(20); // 官方推荐这种写法 // 通过静态方法valueOf(int)创建Integer实例:
// Integer对象转化成int
int a = int1.intValue();
// 字符串转化成Integer对象 //底层都是parseInt
Integer int3 = Integer.parseInt("334");
Integer int4 = new Integer("999");
Integer n3 = Integer.valueOf("100"); // 通过静态方法valueOf(String)创建Integer实例:
// Integer对象转化成字符串
String str1 = int3.toString();
// 一些常见int类型相关的常量
System.out.println("int能表示的最大整数:" + Integer.MAX_VALUE);
}
public static void main(String[] args) {
Test test = new Test();
test.testInteger();
}
}
// 自动装箱: 基本数据类型的变量转换成包装类的实例
// 自动拆箱:包装类的实例转换成基本数据类型的数据
Integer num1 = 100;//包装类.valueOf(基本类型 变量)
int num2 = 100;//包装类对象转换成基本类型的数据 ***Value()
//Integer num1 = Integer.valueOf(100);
// int num2 = num1.intValue();
自动装箱过程是通过调用包装类的valueOf()方法实现的
自动拆箱过程是通过调用包装类的 xxxValue()方法实现的(xxx代表对应的基本数据类型,如intValue()、doubleValue()等)。
Integer num1 = Integer.valueOf(100);
Integer num1 = 100;
public static Integer valueOf(int i) {//i=100
if (i >= IntegerCache.low && i <= IntegerCache.high)
//判断数字i是否在low-high
return IntegerCache.cache[i + (-IntegerCache.low)];
//获得数组指定索引位置的Integer对象
return new Integer(i);//创建一个新的Integer的实例
}
IntegerCache: 整型缓存 静态的内部类
private static class IntegerCache {
static final int low = -128;
static final int high;//127
static final Integer cache[];
}
//如果数字i在-128~127这个区间,那么直接在数组进行比较,否则就需要创建新的Integer的实例,这时候必须要equals比较 ***
ShortCache
IntegerCache
LongCache
CharacterCache
比较
private static void demo4() {
Integer num3 = 200;
Integer num4 = 200;
System.out.println(num3.equals(num4));
System.out.println(Objects.equals(num3,num4));
System.out.println(num3.compareTo(num4));//0相等 -1 1 //compareTo比较返回的是 0相等 -1 1
System.out.println(Integer.compare(num3,num4));//0
}
Character(char value)
public static void main(String[] args) {
Character character = new Character((char) 97);
Character character1 = 'a';
char c = character1.charValue();//
// Character character2 = Character.valueOf('a');
//判断字符是否是数字还是字母
System.out.println(Character.isDigit(character));
System.out.println(Character.isDigit('1'));//true
//数字--->相对应进制的数据
System.out.println(Character.MIN_RADIX);//2
System.out.println(Character.MAX_RADIX);//36
//每个进制里面数据固定的:
//2 0-1
//8 0-7
//10 0-9
//16 0-F
//36 0-Z
char ch = 'F';
int num = ch;
System.out.println("num:" + (num - 48));//51 默认转换成10进制的数据
//int result = Character.digit(char ch,int radix); 将指定的字符转换成指定进制的数据
System.out.println(Character.digit(ch, 8));// 字符没有在指定进制里面的话 -1
//字母---> 大小写
System.out.println(Character.isLetter(ch));
if(Character.isLetter(ch)) {
System.out.println(Character.toLowerCase(Character.toUpperCase(ch)));
}
}
Object()
1. protected Object clone() 克隆--->创建对象
protected native Object clone() throws CloneNotSupportedException;
2. boolean equals(Object obj) 比较2个对象是否一致
public boolean equals(Object obj) {
return (this == obj);
}
3. protected void finalize() GC(守护线程)调用,回收对象
4. Class<?> getClass() 获得正在运行的类/接口/枚举类的class文件
public final native Class<?> getClass();
5. int hashCode() 获得对象的哈希码值
public native int hashCode();
6. String toString() 将对象转换成字符串数据
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//全限定名称: 包名+类名(路径)
//com.javasm.lang.User@74a14482
多线程:(线程之间的通信)
7.void notify() 在一个线程里面唤醒另外一个wait的线程 必须是同一个监视器(锁)
8.void notifyAll() 在一个线程里面唤醒所有的wait的线程
9.void wait() 让当前线程一直处于等待状态
10.void wait(long time) 让当前线程在time的时间内处于等待
对象比较:
必须重写 equals 和 hashcode
//每个对象都有一个hashcode
//2个对象的hashcode不一致 肯定2对象的数据也不等
//由于底层生成hashcode机制的问题 也会出现不同的对象 hashcode也可能相等 不能完全信任
String str1 = new String("Ma");
String str2 = new String("NB");
System.out.println(str1.hashCode());//2484
System.out.println(str2.hashCode());//2484
//两个对象的hashcode相等 一定不要认为这两个对象的数据也是一致的
//equals()
//jvm: 1.先使用hashcode比较(效率比较较快) 如果hashcode不一致 2个对象肯定是不等的 就不执行equals
//2.两个对象的hashcode相等 也不会完全信任 还会去执行equals
// jdk的规范: 重写equals以及hashcode
//Cloneable 标识 标记这个类的对象可以被克隆出来
public class User extends Object implements Cloneable{
//重写clone的方法
@Override
public User clone() {
try {
User user = (User) super.clone();
return user;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
private static void demo4() {
String[] hobby = {"code","game","music"};
User user1 = new User(1,"jim","1234",hobby);
User cloneUser = user1.clone();
System.out.println("user1:"+user1);
System.out.println("cloneUser:"+cloneUser);
System.out.println("-------------修改之后--------------------");
//修改user1数据: hobby
user1.getHobby()[0] = "swim";
user1.setName("张三");
user1.setId(100);
System.out.println("user1:"+user1);
System.out.println("cloneUser:"+cloneUser);
//浅克隆: 引用数据类型而言: 克隆的内存的地址值。 比如数组
//排除(包装类+String+基本类型)
//--->Integer(private static final value)
//--->String(private final char value[])
}
遍历式的copy:
//重写clone的方法
@Override
public User clone() {
try {
User user = (User) super.clone();
//克隆出来的对象
//hobby在新的一块内存里面 new
user.setHobby(Arrays.copyOf(this.hobby,this.hobby.length));
return user;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
//正在运行的Java应用程序中的类和接口
//类: 属性-->Filed 方法--->Method 构造--->Constructor 注解-->Annoation
//Class--->反射最基本的一个类---->动态的操作类的各种组成部分--->底层的框架封装
//1.获得Class类的对象getClass (不推荐 与对象的耦合度太高) 不推荐用的还少
// User user = new User();
// Class clazz = user.getClass();//clazz对象===> User.class
// System.out.println(clazz.toString());//class com.javasm.lang.User
// System.out.println(clazz.getName());//com.javasm.lang.User
// System.out.println(clazz.getSimpleName());//User--->数据库映射---->类与表的映射
//任意一个基本的类型/引用类型(默认的静态的属性 class) 也可以获得Class的实例
//2. 调用class属性(比较推荐,与类耦合度)--->本项目内自己创建一些类或者接口的话 用的多
// Class userClass = User.class;
// System.out.println(userClass);
// System.out.println(userClass.getSimpleName());
// System.out.println(userClass.getName());
Class integerClass = int.class;//装箱
System.out.println("integerClass:" + integerClass.toString());
//3. Class.forName("类/接口/枚举类路径");
// 推荐--->肯定不是本项目内的类/接口--->都是第三方的类库的类/接口 特定时候用
try {
Class userClass = Class.forName("com.javasm.lang.User");
System.out.println(userClass);
System.out.println(userClass.getSimpleName());
System.out.println(userClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
String数据不可变。 字符串的所有的字符数据 全都在char数组
常用构造
创建字符串的常见3+1种方式。
三种构造方法:
public String():创建一个空白字符串,不含有任何内容。
public String(char[] array):根据字符数组的内容,来创建对应的字符串。
public String(byte[] array):根据字节数组的内容,来创建对应的字符串。
一种直接创建:
String str = "Hello"; // 右边直接用双引号
public class Demo01String {
public static void main(String[] args) {
// 使用空参构造
String str1 = new String(); // 小括号留空,说明字符串什么内容都没有。
System.out.println("第1个字符串:" + str1);
// 根据字符数组创建字符串
char[] charArray = { 'A', 'B', 'C' };
String str2 = new String(charArray);
System.out.println("第2个字符串:" + str2);
// 根据字节数组创建字符串
byte[] byteArray = { 97, 98, 99 };
String str3 = new String(byteArray);
System.out.println("第3个字符串:" + str3);
// 直接创建
String str4 = "Hello";
System.out.println("第4个字符串:" + str4);
}
}
编码解码
byte[] bytes = {97,98,99,100};
String str3 = new String(bytes);
System.out.println("str3:"+str3);
System.out.println("默认的编码格式:"+Charset.defaultCharset());//UTF-8
try {
String str4 = new String(bytes,"GBK");// 指定GBK编码格式 解码字节数组的内容
System.out.println("str4:"+str4);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("---------------------------------------");
//手动解码的处理方式
//getBytes()
try {
byte[] bytes1 = "我们".getBytes("gbk");//编码 使用平台默认的编码格式对字符串进行编码操作 存储到字节数组中
System.out.println(Arrays.toString(bytes1));
String str5 = new String(bytes1);//默认UTF-8 ����
System.out.println("str5:"+str5);
String str6 = new String(bytes1,"GBK");
System.out.println("str6:"+str6);//鎴戜滑
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
1.String concat(String str) 在源字符串末尾进行拼接
2.+ 拼接
3.static String join(CharSequence delimiter, CharSequence... elements)
//CharSequence: 多态
//delimiter: 分隔符
//elements:可变参数 数组>0实参
使用delimiter将多个字符串组合在一起
private static void demo3() {
String str = "hello";
System.out.println(str + ",world");// StringBuilder.append() bytecode 插件
System.out.println(str);//值不可变
System.out.println(str.concat("abc"));//helloabc
System.out.println(str);//hello
System.out.println(String.join("-","a","b","c"));
}
1.int compareTo(String anotherString) 比较2个字符串是否相等 返回的是0 正数 负数
2.int compareToIgnoreCase(String str) 不区分大小写比较
3.boolean contains(CharSequence s) 判断字符串是否包含某个字符串数据
4.boolean equals(Object anObject)
5.boolean equalsIgnoreCase(String anotherString)
6.boolean isEmpty() 判断字符串是否是空串
7.boolean matches(String regex) 匹配字符串是否符合正则表达式的规则
8.boolean startsWith(String prefix) 判断字符串是否以***开头
9.boolean endsWith(String suffix) 判断字符串是否以***结尾
private static void demo4(String string) {
// if(string==null){
//
// }
// Objects.requireNonNull(string);
// if(string.isEmpty()){//判断字符串是否是空串
// //数组: length==0
// }
String str1 = "hello";
String str2 = "HELLO";
//验证码
System.out.println(str1.equals(str2));//false
System.out.println(str1.equalsIgnoreCase(str2));
System.out.println(str1.compareTo(str2));// 0 正数 负数
System.out.println(str1.compareToIgnoreCase(str2));
//contains
System.out.println(str1.contains("he"));
//endWith()---> 目录下所有doc文件
String fileName = "abc.doc";
if(fileName.endsWith("doc")){
System.out.println(fileName);
}
//startWith()
System.out.println("hello".startsWith("hello"));
//boolean matches(String regex) 匹配字符串是否符合正则的表达式规则(特殊符号组成)
System.out.println("hello".matches("hello"));
}
1.char charAt(int index) 获得指定索引处字符内容
2.String intern() 获得原字符串的副本
3.CharSequence subSequence(int beginIndex, int endIndex)
4.String substring(int beginIndex) 截取
5.String substring(int beginIndex, int endIndex)
6.String trim() 去除字符串左右2端的空格
private static void demo5(String str) {
Objects.requireNonNull(str);
if (str.isEmpty()) {
return;
}
System.out.println(str.length());// value.length
//charAt(int index) 获得指定索引处字符内容 index: 0--(length()-1)
char charAt = str.charAt(0);// value[index]
System.out.println(charAt);
//trim() 去除字符串左右2端的空格
String str1 = " hello ";
System.out.println(str1.trim());
System.out.println(str1.trim().equals(str));
// substring 截取
// String substring(int beginIndex) // value[beginIndex]--value[length-1]
// String substring(int beginIndex, int endIndex)// value[beginIndex]--value[endIndex-1] 包头不包尾
//hello
String substring = str.substring(2); //llo 从索引2到末尾 包括索引2
System.out.println(str.substring(1, 4)); //ell 从索引1到索引4 包括索引1
//String intern() 获得原字符串的副本
String string1 = "abc";
String string2 = new String("abc");
//比较的地址值
System.out.println("string1==string2:"+(string1==string2));//false
//string1.intern() 常量池的地址
System.out.println("string1.intern()==string2:"+(string1.intern()==string2));//false
//string2.intern() 常量池的地址
System.out.println("string1==string2.intern():"+(string1==string2.intern()));//true
}
1.byte[] getBytes() 编码(使用默认编码格式对字符串进行编码)
2.byte[] getBytes(Charset charset)
3.byte[] getBytes(String charsetName)
4.char[] toCharArray() 将字符串转换成字符数组
5.String[] split(String regex) 分割(通过特定的符号- #)
6.String[] split(String regex, int limit)
//limit: 限定
//0: 代表最后分割出来字符串是一个空串的话 直接过滤
//>0:代表数组的元素个数(还要考虑,分割,最多的元素个数)
private static void demo6(String hello) {
//页面提交过来: 3个数据(都是String)
String str = "game,code,music,";// 4个
//根据,分割
String[] hobby = str.split(",",5);
System.out.println(Arrays.toString(hobby));
//文件上传--->1827163255244#a.jpg
String fileName = "1827163255244#a.jpg";
System.out.println(fileName.split("#")[1]);
}
1.int indexOf(String str )
2.int indexOf(String str, int fromIndex)
3.int lastIndexOf(String str) 获得指定字符串最后一次出现的索引位置
4.int lastIndexOf(String str, int fromIndex)
private static void demo7() {
//UUID 工具类
String string = UUID.randomUUID().toString();
String name = "a.jpg";
name = string+"-"+name;
System.out.println(name);//833df795-3029-46bf-8495-58b7b5245f67-a.jpg
// String[] split = name.split("-");
// System.out.println(split[split.length - 1]);
//subString()
//-: 最后一个-索引位置
// System.out.println(name.lastIndexOf("-"));
System.out.println(name.substring(name.lastIndexOf("-")+1));
//int lastIndexOf(String str, int fromIndex)
System.out.println("abcbbfb".lastIndexOf("b",0));// 包尾 -1 没有找到 从"abcbbfb" 索引位置0 从右往左搜索
//indexOf()
System.out.println("abcbbfb".indexOf("d"));//第一次出现的索引位置 1
System.out.println("abcdbfb".indexOf("b",3));//包尾
}
1.String replace(char oldChar, char newChar)
2.String replaceAll(String regex, String replacement)
3.String replaceFirst(String regex, String replacement)
private static void demo8() {
String str = " a b c ";
System.out.println(str.trim());
System.out.println(str.replaceAll(" ",""));
System.out.println(str.replaceFirst(" ",""));
// System.out.println("abca".replace('a', 'f'));
//手机号: 19138012504 191****2504
// String phone = "19138012504";//11
// String s1 = phone.substring(0, 3);
// String s2 = phone.substring(7);
// String result = s1 + "****" + s2;
// System.out.println(result);
}
StringBuffer/Builder 字符串缓冲区,可以提高字符串的效率
这两个类的方法一模一样。
构造方法:
StringBuilder() 构造一个不带任何字符的字符串生成器,其初始容量为 16 个字符。
StringBuilder(String str) 构造一个字符串生成器,并初始化为指定的字符串内容。
public class Demo01StringBuilder {
public static void main(String[] args) {
//空参数构造方法
StringBuilder bu1 = new StringBuilder();
System.out.println("bu1:"+bu1);//bu1:""
//带字符串的构造方法
StringBuilder bu2 = new StringBuilder("abc");
System.out.println("bu2:"+bu2);//bu2:abc
}
}
public class StringBufferDemo {
public static void main(String[] args) {
demo1();
}
private static void demo1() {
String str = "abc";
// char[] value; 缓冲区
StringBuffer buffer = new StringBuffer("hello");
//String类里面的方法 buffer也都有
//1.拼接
buffer.append(",world");
System.out.println(buffer);
//2.插入
// buffer.insert(1,"aaa");
// System.out.println(buffer);
//3.删除指定索引位置的字符
buffer.deleteCharAt(0);
System.out.println(buffer);
//4. 删除指定位置到结束位置的字符数据
buffer.delete(0,buffer.indexOf(","));
System.out.println(buffer);
//5.翻转--->回文字符串---> level noon abcba reverse()
String str1 = "level";
StringBuffer reverse = new StringBuffer(str1).reverse();
System.out.println(str1.equals(reverse.toString()));// 想要比较就要转换成字符串
//StringBuffer 所有的功能方法 全部都是要synchronized(同步锁) 代表类肯定是线程安全的。
//6.public String toString():将当前StringBuilder对象转换为String对象。
//通过toString方法,StringBuilder对象将会转换为不可变的String对象
}
}
String vs StringBuffer /Builder
线程安全 | 效率 | 内存 | 值是否可变 | |
---|---|---|---|---|
String | 安全 | 一般 | 最多 | 值不可变 |
StringBuffer | 安全(同步) | 最慢 | 一个变量 | 可变 |
StringBuilder | 不安全 | 最快 | 一个变量 | 可变 |
使用格式
public enum 枚举类类名{
实例1,
实例2,
对象3,
....
//成员变量 成员方法
//枚举类的构造都是私有的
}
一周7天:
public enum DayOfWeek {
MONDAY("星期一"){
@Override
public void abc() {
System.out.println("重写过后的abc。。。。。");
}
},
TUESDAY("星期2"), //都要大写
WEDNESDAY("星期3"), //如果没有写无参构造函数 可以直接括号里面赋值
THURSDAY("星期4"),
FRIDAY("星期5"),
SATURDAY("星期6"),
SUNDAY("星期7");
//7个对象都是用过无参构造创建的
private String name;
//取值多一些
public String getName() {
return name;
}
public void abc() {
System.out.println("abc................"+name);
}
//构造
// DayOfWeek(){}
private DayOfWeek(String name) {
this.name = name;
}
}
//案例
public enum CodeEnum {
SUCCESS("success", 200),
ERROR("error", 500);
private String msg;
private Integer code;
public String getMsg() {
return msg;
}
public Integer getCode() {
return code;
}
CodeEnum(String msg, Integer code) {
this.msg = msg;
this.code = code;
}
}
public static ServerResponse success(T data) {
return new ServerResponse(CodeEnum.SUCCESS.getMsg(), CodeEnum.SUCCESS.getCode(), data);
}
//失败
public static ServerResponse error(String message) {
return new ServerResponse(CodeEnum.ERROR.getMsg(), CodeEnum.ERROR.getCode());
}
1.boolean matches(String regex) 匹配字符串是否符合正则表达式的规则
2.String replaceAll(String regex, String replacement)
3.String replaceFirst(String regex, String replacement)
4.String[] split(String regex)
概念: 由一些特殊的符号,通过特定的语法组成的字符串内容。
作用: 一般都是为了校验,匹配,替换字符串的文本数据。
Regex--->js--->页面 js(校验用户提交的数据是否合法)
语法: String regex = "^([字符内容]{次数})([字符内容]{次数})([字符内容]{次数})$";
(): 域段--->尤其替换
字符内容: 特殊符号,数字,字母组成的。
{}: 次数 {1} {1,} {1,4}
\: 转义
*: >=0 {0,}
+: >=1 {1,}
.: 除了\n任何单个字符
(): \n \1 第一个域段() \2 获得第n个域段的内容: $n $1代表第1个域段的内容
x|y:匹配x或者y
[a-zA-Z0-9]:匹配a-z
[0-9]: \d
[a-zA-Z0-9_]: \\w
注册:
用户名/密码
private static void demo9() {
//用户名: 第一个字符必须是大写字母,有且只有1个 小写字母(3-6) 特殊的符号 _ - 数字(1,4)
// String usernameRegex = "^([A-Z]{1})([a-z_-]{3,6})([0-9]{1,4})$";
String usernameRegex = "^([A-Z])([a-z_-]{3,6})(\\d{1,4})$";
String userName = "Lisa123";
//1.java.util.regex.Pattern
Pattern pattern = Pattern.compile(usernameRegex);
//2.获得比较器对象
Matcher matcher = pattern.matcher(userName);
//3.调用matcher的matches方法进行比对
System.out.println(matcher.matches());
System.out.println(userName.matches(usernameRegex));
}
private static void demo10() {
//手机号: 19138012504 191****2504
//String replaceAll(String regex, String replacement)
String phone = "19138012504";
String phone1 ="19912367890";
String phoneRegex = "^(\\d{3})(\\d{4})(\\d{4})$";
System.out.println(phone.replaceAll(phoneRegex, "$1****$3"));
}
代表的是特定的时间点。(年月日 时分秒)
常用的构造:
public Date()
:分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。public Date(long date)
:分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。tips: 由于我们处于东八区,所以我们的基准时间为1970年1月1日8时0分0秒。
常用的方法:
long getTime();
: 获得特定时间的毫秒数Instant toInstant()
: Date 转换成Instant private static void demo1() {
//创建date类的实例
Date date = new Date();
System.out.println(date);//Tue Jul 07 09:45:20 CST 2020
// 创建日期对象,把当前的毫秒值转成日期对象
System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
long time = date.getTime();
System.out.println(time);
//Date与 Instant 转换
Instant instant = date.toInstant(); //java.time包下的
System.out.println(instant);//utc时间 英国的 不是本时区
//后期开发中,文件名等唯一性的话
System.out.println(System.currentTimeMillis());
System.out.println(System.nanoTime());
System.out.println(UUID.randomUUID().toString());
Date date1 = new Date(date.getTime());
System.out.println(date1.equals(date));
}
格式化:按照指定的格式,从Date对象转换为String对象。
解析:按照指定的格式,从String对象转换为Date对象。
格式化类: Format —> (抽象类)----> DateFormat----> NumberFormat
public abstract class DateFormat extends Format
public class SimpleDateFormat extends DateFormat
1. 制定规则
2. 为子类服务
3. 体现多态
常用构造:
SimpleDateFormat(String pattern)
在指定的pattern格式下,通过SimpleDateFormat实例进行格式化和解析日期功能。
常用方法:
Date parse(String str); // 字符串解析成Date
String format(Date date);//Date 转换成 String
private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
public static Date strConvertToDate(String dateStr) {
Objects.requireNonNull(dateStr);
//1. 创建格式化日期对象
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
//2.将字符串解析成Date对象
Date date = null;
try {
date = dateFormat.parse(dateStr); // Date parse(String str); // 字符串解析成Date
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
public static String dateConvertToStr(Date date) {
Objects.requireNonNull(date);
//1.创建格式化日期对象
DateFormat dateFormat = new SimpleDateFormat(PATTERN);
//2. 格式化Date
return dateFormat.format(date); //String format(Date date);//Date 转换成 String
}
SimpleDateFormat 线程不安全
public class DateUtil {
private DateUtil() {
}
//在单线程内 将SimpleDateFormat作为一个成员变量使用的话 是没有任何问题的
//在多线程的环境下,将SimpleDateFormat作为一个成员变量使用的话,必然会出先数据不安全(线程不安全)
//原因: SimpleDateFormat 本身就是一个线程不安全的类
//StringBuffer(底层的所有的方法全部都是同步的)/StringBuilder(局部)
//解决方案:
//1. 对每个方法都加synchronized---> 效率慢(只有一个线程可以执行这个方法===>单线程)
//2. 建议为每个线程创建单独的格式实例---> 只要每个线程都有一个DateFormat对象 肯定不会出现安全问题--->(既可以解决安全 又可以提高效率)
//3.ThreadLocal--->每个线程创建单独的格式实例(不可变)--->每个DateFormat对象的创建 获取 删除都交给了ThreadLocal管理
private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final ThreadLocal FORMAT_THREAD_LOCAL = new ThreadLocal(){
//创建对象
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(PATTERN);
}
};
public static Date strConvertToDate(String dateStr) {
Objects.requireNonNull(dateStr);
//1. 创建格式化日期对象
//2.将字符串解析成Date对象
Date date = null;
try {
date = FORMAT_THREAD_LOCAL.get().parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
public static String dateConvertToStr(Date date) {
Objects.requireNonNull(date);
//1.创建格式化日期对象
//2. 格式化Date
return FORMAT_THREAD_LOCAL.get().format(date);
}
}
Calendar为抽象类,由于语言敏感性,Calendar类在创建对象时并非直接创建,而是通过静态方法创建,返回子类对象,如下:
Calendar静态方法
public static Calendar getInstance()
:使用默认时区和语言环境获得一个日历import java.util.Calendar;
public class Demo06CalendarInit {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
}
}
常用方法:
1. static Calendar getInstance() 获得当前默认时区的时间
2. int get(int field) 获得指定时间单位的数据
3. void set(int field, int value)
4. void set(int year, int month, int date)
5. void set(int year, int month, int date, int hourOfDay, int minute)
6. Date getTime();
7. void setTime(Date date)
8. abstract void add(int field, int amount)
//get方法用来获取指定字段的值
public class CalendarUtil {
public static void main(String[] args) {
// 创建Calendar对象
Calendar cal = Calendar.getInstance();
// 设置年
int year = cal.get(Calendar.YEAR);
// 设置月
int month = cal.get(Calendar.MONTH) + 1;
// 设置日
int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
System.out.print(year + "年" + month + "月" + dayOfMonth + "日");
}
}
//set方法用来设置指定字段的值
public class Demo07CalendarMethod {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2020);
System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2020年1月17日
}
}
//add方法可以对指定日历字段的值进行加减操作,如果第二个参数为正数则加上偏移量,如果为负数则减去偏移量
public class Demo08CalendarMethod {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2018年1月17日
// 使用add方法
cal.add(Calendar.DAY_OF_MONTH, 2); // 加2天
cal.add(Calendar.YEAR, -3); // 减3年
System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2015年1月18日;
}
}
//Calendar中的getTime方法并不是获取毫秒时刻,而是拿到对应的Date对象
public class Demo09CalendarMethod {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
Date date = cal.getTime();
System.out.println(date); // Tue Jan 16 16:03:09 CST 2018
}
}
注意:
西方星期的开始为周日,中国为周一。
在Calendar类中,月份的表示是以0-11代表1-12月。
日期是有大小关系的,时间靠后,时间越大。
获得指定时区UTC的时间
1. static Instant now() //也是通过静态方法创建
2. static Instant now(Clock clock)
Clock(抽象类)
3. Instant plus(TemporalAmount amountToAdd)
将指定的时间量增加至实例中
TemporalAmount(接口)---> Duration/Period
4. Instant plus(long amountToAdd, TemporalUnit unit)
TemporalUnit(接口)--->ChronoUnit(枚举) 时间单位
5.int get(TemporalField field)
获得指定时间属性的数据
TemporalField(接口)--->ChronoField
6. long until(Temporal endExclusive, TemporalUnit unit)
//计算2个时间间隔
Instant now1 = Instant.now();//2020-07-07T03:40:01.611Z
Instant now2 = Instant.now().plus(30, ChronoUnit.DAYS);//2020-08-07T03:40:01.611Z
System.out.println(now1);
System.out.println(now2);
long until = now1.until(now2, ChronoUnit.DAYS);
System.out.println(until);
获得当前系统的时间
LocalDate 年月日
LocalTime 时分秒
常用方法:
static LocalDateTime now()
static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute)
private static void demo1() {
//LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = LocalDateTime.of(2020, Month.JANUARY, 1, 12, 20, 30);
System.out.println("localDateTime:" + localDateTime);
System.out.println("localDateTime1:" + localDateTime1);
System.out.println(localDateTime.get(ChronoField.YEAR));
System.out.println(localDateTime.getYear());
//运算
localDateTime = localDateTime.plusDays(10);
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getMonthValue());//7
System.out.println(localDateTime.getMonth());
System.out.println(localDateTime.getDayOfWeek());
//修改指定属性的数据
localDateTime = localDateTime.with(ChronoField.YEAR,2019);
System.out.println(localDateTime);
}
DateTimeFormatter ----------> jk1.8 提供的新的格式化类
static DateTimeFormatter ofPattern(String pattern)
------------> 同样要通过静态方法创建
1. 格式化Instant
public static String instantToStr(Instant instant){
//instant:UTC 需要转换成本时区
return formatter.withZone(ZoneId.systemDefault()).format(instant);
}
2.格式化LocalDateTime
2.1 String 转换成 LocalDateTime
private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN);
public static LocalDateTime strConvertToLocal(String dateStr){
Objects.requireNonNull(dateStr);
//字符串转换成哪个类的实例 就用哪个类parse的方法
return LocalDateTime.parse(dateStr,formatter);
}
2.2 LocalDateTime 转 String
public static String localConvertToStr(LocalDateTime localDateTime){
return formatter.format(localDateTime);
}
NumberFormat是所有数字格式的抽象基类.
//DecimalFormat 是格式化十进制数,可以对任何地区的数字解析和格式化。
//DecimalFormat(String pattern)
String format()
1. 四舍五入(小数点) Math.round()
2. 百分数
3. 金钱--->app,网站--->账户余额
private static void demo3() {
//金钱
NumberFormat numberFormat = new DecimalFormat("##,###.##");
String result = numberFormat.format(1234567788.7898); //1,234,567,788.79
System.out.println("result:"+result);
}
private static void demo2() {
//增长率百分数
NumberFormat numberFormat = new DecimalFormat(".##%");
String result = numberFormat.format(1.56789); 156.79%
System.out.println("result:"+result);//
}
private static void demo1() {
// 四舍五入(小数点) .#
NumberFormat numberFormat = new DecimalFormat("#.###"); //保留三位
String result = numberFormat.format(1234.56676); //1234.567
System.out.println("result:"+result);
}
加密解密
1. 用户密码--->密文加密
2. 路径---> 小说网站--->路径是可以加密
3. 路径---->参数--->加密
可逆的。加密,也可以解密
Base64.Decoder 用于解码字节数据的解码器
Base64.Encoder 用于编码字节数据的编码器。
private static boolean login(String pass) {
Objects.requireNonNull(pass);
//查询到了存储在磁盘里面的密文数据
//1.获得Base64解码器
String encodePass = "MTIzNA==";
Base64.Decoder decoder = Base64.getDecoder();
//2.解码
byte[] decode = decoder.decode(encodePass);
//字节数组转换成字符串
if (!new String(decode).equals(pass)) { //模拟将加密密码解密后与原密码进行比较
return false;
}
return true;
}
/**
* 加密(编码)
* @String encoding 编码格式
* @param sourcePass 原密码
* @return
*/
private static String register(String sourcePass, String encoding) {
//字符串转换成字节数组与编码有关
Objects.requireNonNull(sourcePass);
//1.获得Base64编码器 Encoder是Base64的静态成员(静态内部类)
Base64.Encoder encoder = Base64.getEncoder();
//2.编码
//字符串转换字节数组
return encoder.encodeToString(sourcePass.getBytes(Charset.forName(encoding)));
}
MD5 SHA-256 SHA-1 不可逆的 (只能加密,不能解密)
public abstract class MessageDigest extends MessageDigestSpi
该MessageDigest类为应用程序提供消息摘要算法的功能.
static MessageDigest getInstance(String algorithm)
盐值: salt(特殊的字符串)
BigInteger(byte[] val)
BigInteger(int signum, byte[] magnitude)
private static final String SALT = "^java#%*_"; //加盐值
private static String register(String pass, String encoding) {
Objects.requireNonNull(pass);
try {
//1.创建了信息摘要对象
pass = pass+SALT; //在后面直接加上
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
//2. 更新信息摘要对象(将密码的数据提交到messageDigest)
if (encoding == null || encoding.equals("")) {
messageDigest.update(pass.getBytes());
} else {
messageDigest.update(pass.getBytes(Charset.forName(encoding)));
}
//3. 加密处理
byte[] digest = messageDigest.digest();
//4.处理字节数组
// System.out.println(new String(digest,Charset.forName(encoding)));
//字节转16进制的数据--->16进制肯定是一个数值型的
// --> int byte short long
//--> Integer Byte Short Long---> 没有相对应的方法可以将字节数组转换成一个包装类型的数据
//---> BigInteger
// BigInteger bigInteger = new BigInteger(1,digest); //-1 代表负数 0 代表0 1 代表正数
// System.out.println(bigInteger);//10进制的数据
// //转换操作
// System.out.println(bigInteger.toString(16).toUpperCase()); //转字符串 --->转大写
return new BigInteger(1, digest).toString(16).toUpperCase();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
集合的作用: 理论上存储多个不同类型的元素数据。(实际开发中 存储相同类型的元素)
集合元素类型: 都是引用类型。
所有的集合:(存储,遍历)
集合存储数据: 数据从哪里来的??---->数据库查询来的
分为2类: Collection Map
Collection 存储单值元素 value
Map: 存储一组元素 key----value
<>: 泛型的标志 可以修饰类(泛型类) 修饰接口(泛型接口)
: 参数化类型 (限定集合元素的数据类型)----> 集合元素类型单一 A-Z //< >里放 A-Z的大写字母都可以
Collection的元素本身就是无序的(没有索引位置)
tips:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
增删:
*boolean add(E e) 将元素数据添加集合对象中
boolean addAll(Collection extends E> c)
*boolean remove(Object o) 根据元素数据删除
boolean removeAll(Collection> c)
default boolean removeIf(Predicate super E> filter) 删除多个符合条件的元素 lamda
*void clear() 删除/清空集合里面所有的元素
遍历:
//增强for循环
default void forEach(Consumer super T> action) 遍历集合元素 lamda
Iterator iterator() 获得集合对象的迭代器对象(遍历集合元素) --->集合的所有的元素都在迭代器对象
Iterator接口的常用方法如下:
1. boolean hasNext() 判断光标后面是否有更多的元素需要遍历
2. E next() 获得光标之后的元素
3. default void remove() 删除集合元素
判断:
*boolean contains(Object o) 判断集合对象中是否包含一个元素
boolean containsAll(Collection> c)
*boolean isEmpty() 集合是否是空
*int size() 获得集合元素个数
*`public Object[] toArray()`: 把集合中的元素,存储到数组中。
default Stream parallelStream() 获得集合对象的Stream实例--->集合可以执行并行化操作 lamda
public class Demo1Collection {
public static void main(String[] args) {
// 创建集合对象
// 使用多态形式
Collection<String> coll = new ArrayList<String>(); //创建对象的时候 new的必须是实现类
// 使用方法
// 添加功能 boolean add(String s)
coll.add("小李广");
coll.add("扫地僧");
coll.add("石破天");
System.out.println(coll);
// boolean contains(E e) 判断o是否在集合中存在
System.out.println("判断 扫地僧 是否在集合中"+coll.contains("扫地僧"));
//boolean remove(E e) 删除在集合中的o元素
System.out.println("删除石破天:"+coll.remove("石破天"));
System.out.println("操作之后集合中元素:"+coll);
// size() 集合中有几个元素
System.out.println("集合中有"+coll.size()+"个元素");
// Object[] toArray()转换成一个Object数组
Object[] objects = coll.toArray();
// 遍历数组
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
// void clear() 清空集合
coll.clear();
System.out.println("集合中内容为:"+coll);
// boolean isEmpty() 判断是否为空
System.out.println(coll.isEmpty());
}
}
//如何使用Iterator迭代集合中元素:
public class IteratorDemo {
public static void main(String[] args) {
// 使用多态方式 创建对象
Collection<String> coll = new ArrayList<String>();
// 添加元素到集合
coll.add("串串星人");
coll.add("吐槽星人");
coll.add("汪星人");
//遍历
//使用迭代器 遍历 每个集合对象都有自己的迭代器
Iterator<String> it = coll.iterator();
// 泛型指的是 迭代出 元素的数据类型
while(it.hasNext()){ //判断是否有迭代元素
String s = it.next();//获取迭代出的元素
System.out.println(s);
}
//使用增强for遍历
for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
System.out.println(s);
}
}
}
}
//tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法
//将会发生java.util.NoSuchElementException没有集合元素的错误。
元素是否有序(索引) | 元素是否可重复 | |
---|---|---|
List | 有序(即元素的存入顺序和取出顺序一致) | 可重复 |
Set | 无序 | 元素唯一 |
独有的功能方法:
void add(int index, E element) 指定索引位置添加元素数据
E get(int index) 获得指定索引位置的元素数据 0--size()-1
ListIterator listIterator() 获得集合迭代器对象
E remove(int index) 删除指定索引元素数据并获得旧值
E set(int index, E element) 修改指定索引的元素并返回修改之前的数据
List subList(int fromIndex, int toIndex) 对截取数组增加,删除,修改会高度还原到原数组上.
Object[] toArray()
T[] toArray(T[] a)(推荐)
public class ListDemo {
public static void main(String[] args) {
// 创建List集合对象
List list = new ArrayList();
// 往 尾部添加 指定元素
list.add("图图");
list.add("小美");
list.add("不高兴");
System.out.println(list);
// add(int index,String s) 往指定位置添加
list.add(1,"没头脑");
System.out.println(list);
// String remove(int index) 删除指定位置元素 返回被删除元素
// 删除索引位置为2的元素
System.out.println("删除索引位置为2的元素");
System.out.println(list.remove(2));
System.out.println(list);
// String set(int index,String s)
// 在指定位置 进行 元素替代(改)
// 修改指定位置元素
list.set(0, "三毛");
System.out.println(list);
// String get(int index) 获取指定位置元素
// 跟size() 方法一起用 来 遍历的
for(int i = 0;i
底层数据结构 | 效率 | 线程安全 | |
---|---|---|---|
* ArrayList | 动态数组 | 查询最快 | 不安全 |
LInkedList | 双向链表 | 删除 新增效率最快 | 不安全 |
Vector | 动态数组 | 都慢 | 安全 |
链表:
元素数据 引用(内存地址值)—(上一个元素的引用 下一个元素的引用)
单向链表: 元素数据 下一个元素的引用
双向链表: 元素数据 上一个元素的引用 下一个元素的引用
常用的构造:
ArrayList() 构造一个初始容量为十的空列表。(1.5扩容)
ArrayList(Collection extends E> c)
ArrayList(int initialCapacity) //10
initialCapacity: (存储的元素个数/负载因子+1)
//创建List集合对象
ArrayList list = new ArrayList<>();//初始化容量10
//1.新增元素
list.add(100);
list.add(200);
list.add(200);
list.add(300);
list.add(2);
list.add(3);
list.add(0,1);// >=0 <=size
System.out.println(list);
//2.获得
Integer num = list.get(0);//elementData[index]
System.out.println(num);
//3.删除
System.out.println(list.remove(1));
System.out.println(list);
//4.修改
System.out.println(list.set(0, 11));
System.out.println(list);//[11, 200, 200, 300, 2, 3]
List list1 = list.subList(0, 3);//包头不包尾
System.out.println(list1);
//对截取数组增加 删除 修改 会 高度还原到原数组上的
System.out.println("list1:"+list1);
System.out.println("list:"+list);
// list1.remove(0);
// list.add(0,12);//modCount==size failfast
//集合 转换数组
// Object[] toArray()
Object[] toArray = list.toArray();
System.out.println("toArray:"+Arrays.toString(toArray));
for (Object o : toArray) { //这样转换的数组 要想遍历Integer的话 还需要强转
//手动强制
Integer num1 = (Integer) o;
}
// T[] toArray(T[] a)(推荐)
Integer[] integers = list.toArray(new Integer[0]);
System.out.println("toArray:"+Arrays.toString(integers));
遍历
private static void demo2() {
List list = new ArrayList<>(10);
//遍历
list.add("abc");
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
list.add("abc");
//1.普通循环
// for (int i = 0,size = list.size(); i < size; i++) {
// System.out.println(list.get(i));
// }
//2.增强for
// for (String s : list) {
// System.out.println(s);
// }
//3.迭代器
// Iterator iterator = list.iterator();
// while(iterator.hasNext()){
// String next = iterator.next();
// System.out.println(next);
// }
//4.listIterator
// ListIterator listIterator = list.listIterator();
// while (listIterator.hasNext()) {
// String next = listIterator.next();
// System.out.println(next);
//
// }
//5.forEach
list.forEach((String str)->{
System.out.println(str);
});
}
遍历修改
private static void demo3() {
ArrayList list = new ArrayList<>(10);
//遍历
list.add("abc");
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
list.add("abc");
System.out.println("删除之前:"+list);
//删除元素都是abc
// list.forEach((String str)->{
// if("abc".equals(str)){
// //找到了元素 删除
// list.add("123"); 遍历期间forEach删除元素会报错 修改一般不会 可能会出现
// }
// });
// for (int i = 0; i < list.size(); i++) { for循环都可以
// if("abc".equals(list.get(i))
// list.remove("abc");
// }
// }
// Iterator iterator = list.iterator(); ***
// System.out.println(iterator); //java.util.ArrayList$Itr@4141d797
// while(iterator.hasNext()){
// String next = iterator.next();
// if("abc".equals(next)){
// iterator.remove(); 迭代器不能用list的remove 只能用iterator.remove();
// }
// }
// System.out.println("删除之后:"+list);// Exception in thread "main" java.util.ConcurrentModificationException
// for (String s : list) { //迭代器实现的
// if("abc3".equals(s)){
// //找到了元素 删除
// list.remove(s); //只能删倒数第二个元素
// }
// }
Iterator iterator = list.iterator();
for(;iterator.hasNext();){
String next = iterator.next();//cursor
if("abc3".equals(next)){
//找到了元素 删除
list.remove(next); //只能删倒数第二个元素
}
}
System.out.println("删除之后:"+list);
}
LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。
理论上可以存储无数个元素。
队列: 排队---> 先进先出
栈: fifo 先进后出
LinkedList()
LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:
* `public void addFirst(E e)`:将指定元素插入此列表的开头。
* `public void addLast(E e)`:将指定元素添加到此列表的结尾。
* `public E getFirst()`:返回此列表的第一个元素。
* `public E getLast()`:返回此列表的最后一个元素。
* `public E removeFirst()`:移除并返回此列表的第一个元素。
* `public E removeLast()`:移除并返回此列表的最后一个元素。
* `public E pop()`:从此列表所表示的堆栈处弹出一个元素。
* `public void push(E e)`:将元素推入此列表所表示的堆栈。
* `public boolean isEmpty()`:如果列表不包含元素,则返回true。
LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList link = new LinkedList();
//添加元素
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
System.out.println(link);
// 获取元素
System.out.println(link.getFirst());
System.out.println(link.getLast());
// 删除元素
System.out.println(link.removeFirst());
System.out.println(link.removeLast());
while (!link.isEmpty()) { //判断集合是否为空
System.out.println(link.pop()); //弹出集合中的栈顶元素
}
System.out.println(link);
}
}
Vector()
Vector(int initialCapacity) //10
Set集合的元素是无序的(index) 元素唯一的
tips:Set集合取出元素的方式可以采用:迭代器、增强for
数据结构 | 线程安全 | 元素是否可以为null | |
---|---|---|---|
HashSet | 哈希表(HashMap) | 不安全 | 可以为null |
LinkedHashSet | 链表+哈希表 | 不安全 | 可以为null |
TreeSet | 红黑树 | 不安全 | 不能为null |
此类实现Set接口,由哈希表(实际为HashMap实例)支持. 元素无序,(顺序)
HashSet() 构造一个新的空集合; 背景HashMap实例具有默认初始容量(16)和负载因子(0.75)。
HashSet(int initialCapacity) (存储的元素个数/负载因子)+1
HashSet(Collection<? extends E> c) (List集合去重)
public class HashSetDemo {
public static void main(String[] args) {
demo1();
}
private static void demo1() {
HashSet<Integer> hashSet = new HashSet<>();
//新增元素
System.out.println(hashSet.add(100));//true
System.out.println(hashSet.add(100));//false
System.out.println(hashSet.add(1));
System.out.println(hashSet.add(10));
System.out.println(hashSet.add(null));
System.out.println(hashSet.add(10));
System.out.println(hashSet.toString());
//其它的方法与Collection一模一样
//3种遍历方法
for (Integer integer : hashSet) {
System.out.println(integer);
}
System.out.println("------------------------");
Iterator<Integer> iterator = hashSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("------------------------");
hashSet.forEach((num)->{
System.out.println(num);
});
}
}
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
创建自定义Student类
/*
HashSet存储自定义类型元素
set集合报错元素唯一:
存储的元素(String,Integer,...Student,Person...),必须重写hashCode方法和equals方法
要求:
同名同年龄的人,视为同一个人,只能存储一次
*/
public class HashSetDemo2 {
public static void main(String[] args) {
//创建集合对象 该集合中存储 Student类型对象
HashSet<Student> stuSet = new HashSet<Student>();
//存储
Student stu = new Student("于谦", 43);
stuSet.add(stu);
stuSet.add(new Student("郭德纲", 44));
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("郭麒麟", 23));
stuSet.add(stu); //不重写 相同名字跟年龄的都会存进去
for (Student stu2 : stuSet) {
System.out.println(stu2);
}
}
}
执行结果:
Student [name=郭德纲, age=44]
Student [name=于谦, age=43]
Student [name=郭麒麟, age=23]
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。
演示代码如下:
哈希表和链表实现了Set接口,具有可预测的迭代次序(插入顺序与遍历顺序是一致的)
LinkedHashSet() 构造一个具有默认初始容量(16)和负载因子(0.75)的新的,空的链接散列集
LinkedHashSet(int initialCapacity)
构造一个具有指定初始容量和默认负载因子(0.75)的新的,空的链接散列集
public class LinkedHashSetDemo {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add("bbb");
set.add("aaa");
set.add("abc");
set.add("bbc");
Iterator it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
结果:
bbb
aaa
abc
bbc
基于TreeMap。 元素都是有序的(会按照自然顺序排列)--->要求元素类型必须有排序规则(Comparable)
TreeSet()
TreeSet(Comparator super E> comparator) 外部比较器(排序规则)
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet<>();//默认是按照升序排列的
treeSet.add(100);
treeSet.add(1);
treeSet.add(2);
System.out.println(treeSet);
}
}
存储一组元素。 有key 有value K V 都是map集合的参数化类型。
将key映射到value上。 可以根据key获得value值
map集合的key是唯一的。value可以重复。
底层数据结构 | 线程安全 | k/v是否可以为null | |
---|---|---|---|
HashMap |
哈希表 | 否 | 都可以为null |
LinkedHashMap |
哈希表+链表 | 否 | 都可以为null |
TreeMap |
红黑树 | 否 | k不能为null V:可以 |
HashTable |
哈希表 | 安全(悲观锁) | K/V都不能为null |
ConcurrentHashMap |
锁分段技术/CAS(乐观锁) | 安全 | K/V都不能为null |
static interface Map.Entry<K,V> 维护map集合里面每组元素的
void clear() 清空map集合所有的元素
boolean containsKey(Object key) 判断map集合是否包含指定的key
boolean containsValue(Object value) 判断map集合是否包含指定的value
V get(Object key) 根据key获得v
default V getOrDefault(Object key, V defaultValue)
boolean isEmpty()
V put(K key, V value) 新增一组元素
V remove(Object key) 删除
default boolean remove(Object key, Object value)
default V replace(K key, V value)
default boolean replace(K key, V oldValue, V newValue)
int size()
通过查看Map接口描述,看到Map有多个子类,这里我们主要讲解常用的HashMap集合、LinkedHashMap集合。
tips:Map接口中的集合都有两个泛型变量
,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量 的数据类型可以相同,也可以不同。
HashMap(int initialCapacity)
private static void demo1() {
//在正常的开发中,map的key的数据类型:String居多====> json串--->属性名称(全部都是字符串): 属性值
HashMap hashMap = new HashMap<>(16);
//1.存储元素
hashMap.put(1, "a");
hashMap.put(10, "b");
hashMap.put(100, "c");
hashMap.put(1, "ccccc");
System.out.println(hashMap.size());// map的key重复的时候 value会被覆盖。
//2.获取map元素
String s = hashMap.get(1);
System.out.println(s);
//3.删除
// System.out.println(hashMap.remove(1));//ccccc
// System.out.println(hashMap.remove(1,"ccccc"));
//4.修改
System.out.println(hashMap.replace(1, "10"));
//5.判断
System.out.println(hashMap.containsKey(1));
System.out.println(hashMap.containsValue("10"));
System.out.println(hashMap);
}
Set> entrySet()
遍历map集合的方法(获得map集合的每一组k/V转换成一个个entry对象 并存储到set集合中)
default void forEach(BiConsumer super K,? super V> action) 遍历map集合
Set keySet() 遍历map集合方法(获得map集合里面所有的key 并把key存储到set)
Collection values() 获得map集合的所有的v
黑马:
Map集合遍历键找值方式
键找值方式:即通过元素中的键,获取键所对应的值
分析步骤:
1. 获取Map中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键。方法提示:`keyset()`
2. 遍历键的Set集合,得到每一个键。
3. 根据键,获取键所对应的值。方法提示:`get(K key)`
public class MapDemo01 {
public static void main(String[] args) {
//创建Map集合对象
HashMap map = new HashMap();
//添加元素到集合
map.put("胡歌", "霍建华");
map.put("郭德纲", "于谦");
map.put("薛之谦", "大张伟");
//获取所有的键 获取键集
Set keys = map.keySet();
// 遍历键集 得到 每一个键
for (String key : keys) {
//key 就是键
//获取对应值
String value = map.get(key);
System.out.println(key+"的CP是:"+value);
}
}
}
黑马:
Map集合遍历键值对方式
键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。
操作步骤与图解:
1. 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回。方法提示:entrySet()。
2. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象。
3. 通过键值对(Entry)对象,获取Entry对象中的键与值。 方法提示:getkey() getValue()
public class MapDemo02 {
public static void main(String[] args) {
// 创建Map集合对象
HashMap map = new HashMap();
// 添加元素到集合
map.put("胡歌", "霍建华");
map.put("郭德纲", "于谦");
map.put("薛之谦", "大张伟");
// 获取 所有的 entry对象 entrySet
Set> entrySet = map.entrySet();
// 遍历得到每一个entry对象
for (Entry entry : entrySet) {
// 解析
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"的CP是:"+value);
}
}
}
private static void demo2() {
HashMap hashMap = new HashMap<>(16);
hashMap.put(1, "a");
hashMap.put(10, "b");
hashMap.put(100, "c");
hashMap.put(0, "c");
hashMap.put(null, "ccccc");
hashMap.put(2, null);
//遍历
//forEach遍历 需要重写方法
hashMap.forEach(new BiConsumer() {
@Override
public void accept(Integer key, String value) {
System.out.println("key:"+key+",value:"+value);
}
});
//forEach遍历 -----lambda 简化方式
hashMap.forEach((key, value)->{
System.out.println("key:"+key+",value:"+value);
});
//jdk1.8之前-
// hashMap.entrySet(); entry:键值对 推荐
Set> entries = hashMap.entrySet();//等同遍历set集合了
Iterator> iterator = entries.iterator();
while(iterator.hasNext()){
Map.Entry entry = iterator.next();// k v
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
//keySet()
Set keySet = hashMap.keySet();//map的key
keySet.forEach(key->{
System.out.println("key:"+key+",value:"+hashMap.get(key));
});
//Collection values() java.util.HashMap$Values
Collection values = hashMap.values();
System.out.println(values);
}
我们知道HashMap保证成对元素唯一,并且查询速度很快,可是成对元素存放进去是没有顺序的,那么我们要保证有序,还要速度快怎么办呢?
在HashMap下面有一个子类LinkedHashMap,它是链表和哈希表组合的一个数据存储结构。
public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
map.put("邓超", "孙俪");
map.put("李晨", "范冰冰");
map.put("刘德华", "朱丽倩");
Set<Entry<String, String>> entrySet = map.entrySet();
for (Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
结果:
邓超 孙俪
李晨 范冰冰
刘德华 朱丽倩
TreeMap(Comparator super K> comparator)
TreeMap()
private static void demo4() {
TreeMap map = new TreeMap<>();
//无参构造创建对象 在底层comparator = null; 自定义比较器(自定义的排序规则)
map.put(1, "a");
map.put(10, "b");
map.put(100, "c");
map.put(1, "c");
map.put(2, null);
System.out.println(map);
}
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:public static
:往集合中添加一些元素。
public static void shuffle(List> list) 打乱顺序
:打乱集合顺序。
public static
:将集合中元素按照默认规则排序。
public static
:将集合中元素按照指定规则排序。
static List synchronizedList(List list) :将集合变成线程安全的方法
static
代码演示:
private static void demo2() {
List<Integer> list = new ArrayList<>(10);
Collections.addAll(list, 2, 10, 30, 1, 101, 100);
System.out.println("排序之前:" + list);
Collections.sort(list);// 要求集合元素类型必须实现Comparable接口
System.out.println("排序之后:" + list);//默认按照升序排列 //默认按照集合元素类型规定的额排序规则 Integer.compareTo()
}
//[2,10,30,1,101,100]
//[1,2,10,30,100,101]
private static List<String> myList;//线程安全
private static void demo1() {
List<String> list = new ArrayList<>(10);
Collections.addAll(list, "abc", "11", "123", "a", "b");
System.out.println(list);
//将线程不安全的实例转换成线程安全的对象
myList = Collections.synchronizedList(list);
}
}
一种是比较死板的采用java.lang.Comparable
接口去实现
一种是灵活的当我需要做排序的时候在去选择的java.util.Comparator
接口完成
//public static void sort(List list):将集合中元素按照默认规则排序
//Comparable`接口去实现
public class CollectionsDemo2 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
//排序方法
Collections.sort(list);
System.out.println(list);
}
}
//[aba, cba, nba, sba]
//当你想改变默认的排序方式
// public int compare(String o1, String o2):比较其两个参数的顺序
//Comparator`接口完成
public class CollectionsDemo3 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
//排序方法 按照第一个单词的降序
Collections.sort(list, new Comparator() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) - o1.charAt(0);
}
});
System.out.println(list);
}
}
//[sba, nba, cba, aba]
Comparable和Comparator两个接口的区别
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
Comparable
private static void demo3() {
//List集合元素可以重复 list集合的元素类型没有必要重写equals和hashcode
List userList = new ArrayList<>(10);
Collections.addAll(userList,
new User(1, "jim1"),
new User(1, "jim1"),
new User(8, "jim3"),
new User(8, "jim4"));
System.out.println("排序之前:" + userList);
Collections.sort(userList);
System.out.println("排序之后:" + userList);
}
@Setter
@Getter
@AllArgsConstructor
@ToString
public class User implements Comparable {
private Integer id;
private String name;
@Override
public int compareTo(User user) {
Objects.requireNonNull(user);
if (this == user) {
return 0;//代表2个对象是相等的
}
//比较规则 看属性 根据id升序排列
//id一致 name也参与比较(降序)
int result = this.id.compareTo(user.id);
if (result == 0) {
result = user.name.compareTo(this.name);
}
return result;//0 -1 1
}
}
@Setter
@Getter
@AllArgsConstructor
@ToString
public class User{
private Integer id;
private String name;
}
private static void demo4() {
List userList = new ArrayList<>(10);
Collections.addAll(userList,
new User(1, "jim1"),
new User(10, "jim11"),
new User(8, "jim3"),
new User(7, "jim4"));
System.out.println("排序之前:" + userList);
//TreeSet TreeMap 有参构造 Comparator 自定义外部比较器规则
Collections.sort(userList, new Comparator() {
@Override
public int compare(User user1, User user2) {
int result = user1.getId().compareTo(user2.getId());
if(result==0){
result = user1.getName().compareTo(user2.getName());
}
return result;
}
});
//lambada 简化格式
Collections.sort(userList, (user1, user2) -> {
int result = user1.getId().compareTo(user2.getId());
if (result == 0) {
result = user1.getName().compareTo(user2.getName());
}
return result;
});
//
System.out.println("排序之后:" + userList);
}
HashSet/LinkedHashSet 无法排序
TreeSet 排序
private static void demo4() {
//分支
TreeSet treeSet = new TreeSet<>();
treeSet.add(new User(1, "jim7"));
treeSet.add(new User(1, "jim1"));
treeSet.add(new User(8, "jim7"));
treeSet.add(new User(8, "jim4"));
System.out.println(treeSet);
}
public class User implements Comparable {
//public class User{
private Integer id;
private String name;
@Override
public int compareTo(User user) {
Objects.requireNonNull(user);
if (this == user) {
return 0;//代表2个对象是相等的
}
return user.name.compareTo(this.name);//0 -1 1
}
}
//lambda简写
private static void demo5() {
TreeSet<User> treeSet = new TreeSet<>((User o1, User o2)->{
return o2.getName().compareTo(o1.getName());
});
treeSet.add(new User(1, "jim7"));
treeSet.add(new User(1, "jim1"));
treeSet.add(new User(8, "jim7"));
treeSet.add(new User(8, "jim4"));
System.out.println(treeSet);
}
Map的key排序 TreeMap
1. Map的key存储的是自定义的类对象
public static void main(String[] args) {
TreeMap treeMap = new TreeMap<>((user1,user2)->{
return user1.getName().compareTo(user2.getName());
});
treeMap.put(new User(1, "jim7"),1);
treeMap.put(new User(1, "jim1"),1);
treeMap.put(new User(8, "jim7"),1);
treeMap.put(new User(8, "jim4"),1);
System.out.println(treeMap);
}
public class GenericDemo2 {
public static void main(String[] args) {
Collection list = new ArrayList();
list.add("abc");
list.add("itcast");
// list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
// 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
Iterator it = list.iterator();
while(it.hasNext()){
String str = it.next();
//当使用Iterator控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
System.out.println(str.length());
}
}
}
tips : 泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
定义格式:
修饰符 class 类名<代表泛型的变量> { }
使用泛型: 即什么时候确定泛型。
//举例自定义泛型类
public class MyGenericClass {
//没有MVP类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
private MVP mvp;
public void setMVP(MVP mvp) {
this.mvp = mvp;
}
public MVP getMVP() {
return mvp;
}
}
//使用:
public class GenericClassDemo {
public static void main(String[] args) {
// 创建一个泛型为String的类
MyGenericClass my = new MyGenericClass();
// 调用setMVP
my.setMVP("大胡子登登");
// 调用getMVP
String mvp = my.getMVP();
System.out.println(mvp);
//创建一个泛型为Integer的类
MyGenericClass my2 = new MyGenericClass();
my2.setMVP(123);
Integer mvp2 = my2.getMVP();
}
}
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
例:
public class MyGenericMethod {
public void show(MVP mvp) {
System.out.println(mvp.getClass());
}
public MVP show2(MVP mvp) {
return mvp;
}
}
//使用格式:调用方法时,确定泛型的类型
public class GenericMethodDemo {
public static void main(String[] args) {
// 创建对象
MyGenericMethod mm = new MyGenericMethod();
// 演示看方法提示
mm.show("aaa");
mm.show(123);
mm.show(12.45);
}
}
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
使用格式:
//1、定义类时确定泛型的类型
例如
public class MyImp1 implements MyGenericInterface {
@Override
public void add(String e) {
// 省略...
}
@Override
public String getE() {
return null;
}
}
//此时,泛型E的值就是String类型。
//2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
例如
public class MyImp2 implements MyGenericInterface {
@Override
public void add(E e) {
// 省略...
}
@Override
public E getE() {
return null;
}
}
//确定泛型:
public class GenericInterface {
public static void main(String[] args) {
MyImp2 my = new MyImp2();
my.add("aa");
}
}
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
public static void getElement(Collection> coll){}
//这个类定义时用的问号 这样子创建对象的时候只要不是同一个对象 可随意定义数据类型
//遍历时使用次数也比较多
//?代表可以接收任意类型
public static void main(String[] args) {
Collection list1 = new ArrayList();
getElement(list1);
Collection list2 = new ArrayList();
getElement(list2);
}
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
类型名称 extends 类 > 对象名称
只能接收该类型及其子类
泛型的下限:
类型名称 super 类 > 对象名称
只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
城市级联:
一个省份有多个城市:
可以根据省份获得所有的城市信息
public static void main(String[] args) {
//存储数据 到集合
List city = new ArrayList<>(10);
Collections.addAll(city, "郑州市", "洛阳市", "郑州市1", "郑州市2", "郑州市3");
//可以根据省份获得所有的城市信息: ===> map: 根据key获得value
Map> province = new HashMap<>(16);
province.put("hn", city);
city = new ArrayList<>(10);
Collections.addAll(city, "石家庄市", "保定市", "保定1", "保定2", "保定3");
province.put("he", city);
System.out.println(province.get("he"));
}
List集合元素去重
1.List 转换 Set(利用set构造)
2.Stream的distinct
跟下面代码关系不大
public static void main(String[] args) {
//Stream: 支持并行化 操作集合元素
//除了迭代器,我们能否在遍历集合元素期间 对集合进行新增 删除 修改等操作?
//不可以。 ConcurrentModificationException
//并发?
//并行?
//正在吃饭 有人打电话
//1. 边吃饭 边打电话 并行
//2. 吃了一口饭 与别人打电话 并发 cpu 上下文的切换时间 短---->项目中: 高并发(集群 负载均衡)
//在遍历过程中 删除符合条件的多个元素数据
List userList = new ArrayList<>(10);
Collections.addAll(userList,
new User(1, "jim1"),
new User(10, "jim11"),
new User(8, "jim3"),
new User(7, "jim4"));
System.out.println(userList);
// userList.parallelStream().distinct()
//删除name中包含1的对象 //不支持 报错
// userList.forEach(user->{
// if(user.getName().contains("1")){
// userList.remove(user);
// }
// });
//1.获得集合对象的Stream实例(集合的所有的元素存储到Stream的对象中)
Stream userStream = userList.parallelStream();
userStream.filter(new Predicate() {
@Override
public boolean test(User user) {
return !user.getName().contains("1");//过滤下来名字里面包含1
}
});
userStream = userStream.filter(user -> !user.getName().contains("1"));
//将过滤下来的元素 存储到集合里面去(收集起来)
userList = userStream.collect(Collectors.toList());
//简写
userList = userList.parallelStream().filter(user -> !user.getName().contains("1")).collect(Collectors.toList());
// System.out.println(userStream.count());
System.out.println(userList);
}
java.io.File
类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
//文件和目录路径名的抽象表示。
File(String pathname) :通过将给定的**路径名字符串**转换为抽象路径名来创建新的 File实例。
File(String parent, String child) :从**父路径名字符串和子路径名字符串**创建新的 File实例
File(File parent, String child) parent :从**父抽象路径名和子路径名字符串**创建新的 File实例
//只是创建了实例 文件夹跟文件还没创建出来显示
// 文件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
// 文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通过父路径和子路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
// 通过父级File对象和子路径字符串
File parentDir = new File("d:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);
小贴士:
1. 一个File对象代表硬盘中实际存在的一个文件或者目录。
2. 无论该路径下是否存在文件或者目录,都不影响File对象的创建。
public static void main(String[] args) {
/*
static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar 与系统有关的路径分隔符。
static String separator 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
static char separatorChar 与系统有关的默认名称分隔符。
操作路径:路径不能写死了
C:\develop\a\a.txt windows
C:/develop/a/a.txt linux
"C:"+File.separator+"develop"+File.separator+"a"+File.separator+"a.txt"
*/
String pathSeparator = File.pathSeparator;
System.out.println(pathSeparator);//路径分隔符 windows:分号; linux:冒号:
String separator = File.separator;
System.out.println(separator);// 文件名称分隔符 windows:反斜杠\ linux:正斜杠/
}
/*
路径:
绝对路径:是一个完整的路径
以盘符(c:,D:)开始的路径
c:\\a.txt
C:\\Users\itcast\\IdeaProjects\\shungyuan\\123.txt
D:\\demo\\b.txt
相对路径:是一个简化的路径
相对指的是相对于当前项目的根目录(C:\\Users\itcast\\IdeaProjects\\shungyuan)
如果使用当前项目的根目录,路径可以简化书写
C:\\Users\itcast\\IdeaProjects\\shungyuan\\123.txt-->简化为: 123.txt(可以省略项目的根目录)
注意:
1.路径是不区分大小写
2.路径中的文件名称分隔符windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠
*/
public String getAbsolutePath()
:返回此File的绝对路径名字符串。 无论路径是绝对的还是相对的,getAbsolutePath方法返回的都是绝对路径
public String getPath()
:将此File转换为路径名字符串。
public String getName()
:返回由此File表示的文件或目录的名称。 获取的就是构造方法传递路径的结尾部分(文件/文件夹)
public long length()
:返回由此File表示的文件的长度。
方法演示,代码如下:
public class FileGet {
public static void main(String[] args) {
File f = new File("d:/aaa/bbb.java");
System.out.println("文件绝对路径:"+f.getAbsolutePath());
System.out.println("文件构造路径:"+f.getPath());
System.out.println("文件名称:"+f.getName());
System.out.println("文件长度:"+f.length()+"字节");
File f2 = new File("d:/aaa");
System.out.println("目录绝对路径:"+f2.getAbsolutePath());
System.out.println("目录构造路径:"+f2.getPath());
System.out.println("目录名称:"+f2.getName());
System.out.println("目录长度:"+f2.length());
}
}
输出结果:
文件绝对路径:d:\aaa\bbb.java
文件构造路径:d:\aaa\bbb.java
文件名称:bbb.java
文件长度:636字节
目录绝对路径:d:\aaa
目录构造路径:d:\aaa
目录名称:aaa
目录长度:4096
/*
public long length() :返回由此File表示的文件的长度。
获取的是构造方法指定的文件的大小,以字节为单位
注意:
文件夹是没有大小概念的,不能获取文件夹的大小
如果构造方法中给出的路径不存在,那么length方法返回0
*/
API中说明:length(),表示文件的长度。但是File对象表示目录,则返回值未指定。
public boolean exists()
:此File表示的文件或目录是否实际存在。public boolean isDirectory()
:此File表示的是否为目录。public boolean isFile()
:此File表示的是否为文件。方法演示,代码如下:
public class FileIs {
public static void main(String[] args) {
File f = new File("d:\\aaa\\bbb.java");
File f2 = new File("d:\\aaa");
// 判断是否存在
System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists());
System.out.println("d:\\aaa 是否存在:"+f2.exists());
// 判断是文件还是目录
System.out.println("d:\\aaa 文件?:"+f2.isFile());
System.out.println("d:\\aaa 目录?:"+f2.isDirectory());
}
}
输出结果:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目录?:true
public boolean createNewFile()
:当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。public boolean delete()
:删除由此File表示的文件或目录。public boolean mkdir()
:创建由此File表示的目录。public boolean mkdirs()
:创建由此File表示的目录,包括任何必需但不存在的父目录。方法演示,代码如下:
public class FileCreateDelete {
public static void main(String[] args) throws IOException {
// 文件的创建
File f = new File("aaa.txt");
System.out.println("是否存在:"+f.exists()); // false
System.out.println("是否创建:"+f.createNewFile()); // true
System.out.println("是否存在:"+f.exists()); // true
// 目录的创建
File f2= new File("newDir");
System.out.println("是否存在:"+f2.exists());// false
System.out.println("是否创建:"+f2.mkdir()); // true
System.out.println("是否存在:"+f2.exists());// true
// 创建多级目录
File f3= new File("newDira\\newDirb");
System.out.println(f3.mkdir());// false
File f4= new File("newDira\\newDirb");
System.out.println(f4.mkdirs());// true
// 文件的删除
System.out.println(f.delete());// true
// 目录的删除
System.out.println(f2.delete());// true
System.out.println(f4.delete());// false
}
}
API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。
File file = new File("a.txt");//在22这个项目里面有一个a.txt的文件
System.out.println(file.toString());
//操作文件的方法
System.out.println("文件的上一次修改时间:"+file.lastModified());//长整型毫秒数
System.out.println("文件的上一次修改时间:"+new Date(file.lastModified()));//长整型毫秒数
System.out.println("文件权限:"+file.canRead());
System.out.println("文件权限:"+file.canWrite());
System.out.println("修改文件权限:"+file.setReadOnly());//只读
System.out.println("修改文件权限:"+file.setWritable(false));
创建文件:
File file = new File("day16/src/com/javasm/file/abc.txt");
try {
if(!file.exists()){
System.out.println("创建文件成功");
file.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles()
:返回一个File数组,表示该File目录中的所有的子文件或目录。
public class FileFor {
public static void main(String[] args) {
File dir = new File("d:\\java_code");
//获取当前目录下的文件以及文件夹的名称。
String[] names = dir.list();
for(String name : names){
System.out.println(name);
}
//获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
File[] files = dir.listFiles();
for (File file : files) {
System.out.println(file);
}
}
}
小贴士:
调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。
//获取当前目录下的所有文件以及文件夹的名称
public static void main(String[] args) {
String path = "day16/src";//目录的路径
demo5(new File(path), "|-");
}
private static void demo3(File file, String separtor) {
File[] files = file.listFiles();
for (File child : files) {
if (child.isDirectory()) {
//继续查找子级目录和文件
//方法递归: 自己调用自己
System.out.println(separtor + child.getName());
demo3(child, "| " + separtor);
} else {
System.out.println(separtor + child.getName());
}
}
}
String[] list(FilenameFilter filter)
File[] listFiles(FileFilter filter)
//FileFilter: 接口(文件过滤器类)--->过滤掉不符合条件的文件
File[] listFiles(FilenameFilter filter)
public static void main(String[] args) {
String path = "day16/src";//目录的路径
demo5(new File(path), "|-");
}
// listFiles(FileFilter filter) 过滤 需要重写accept方法
private static void demo5(File file, String s) {
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File child) {//child 就是子级目录/文件
//判断child是目录还是文件
if(child.isDirectory()){
return true;
}
return child.getName().endsWith("java");
}
});
// listFiles(FileFilter filter) 过滤 lambda简写
File[] files = file.listFiles(child -> {
if (child.isDirectory()) {
return true;
}
return child.getName().endsWith("txt");
});
//listFiles(FilenameFilter filter) 过滤
File[] files = file.listFiles((dir, name)->{//dir: 父级目录 name: 文件/目录名称
File child = new File(dir, name);
if (child.isDirectory()) {
return true;
}
return child.getName().endsWith("txt");
});
for (File child : files) {
String fileName = child.getName();
if (child.isDirectory()) {
//继续查找子级目录和文件
//方法递归: 自己调用自己
System.out.println(s + fileName);
demo5(child, "| " + s);
} else {
System.out.println(s + fileName);
}
}
}
Java中I/O操作主要是指使用java.io
包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据
根据数据的流向分为:输入流和输出流。
其他设备
上读取到内存
中的流。 Input内存
中写出到其他设备
上的流。 Outpu格局数据的类型分为:字节流和字符流。
流媒体
计算机里面,所有的文件都是二进制的文件。所有的文件内容都是以字节形式进行存储的。
文本,图片,压缩,音频,都可以通过字节流来进行操作。
【InputStream】
public abstract class InputStream extends Object implements Closeable
//所有字节输入流的类
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public int available()
:获得流对象有效的字节数(文件内容大小file.length( ) )public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。public abstract int read()
: 从输入流读取数据的下一个字节。public int read(byte[] b)
:从输入流对象中读取b.length个字节内容存储字节数组 返回读到的有效的字节数读到末尾返回-1public int read(byte[] b, int off, int len )
:从输入流对象中读取len个字节内容,从指定off索引开始存储字节数组 返回读到的有效的字节数读到末尾返回-1小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
。
构造举例,代码如下:
public class FileInputStreamConstructor throws IOException{
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
// 使用文件名称创建流对象
FileInputStream fos = new FileInputStream("b.txt");
}
}
read
方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
,代码使用演示: public static void main(String[] args) {
demo2("day16/src/abc.txt");
}
private static void demo1(String path) {
InputStream inputStream = null;
try {
//1.创建输入流对象
inputStream = new FileInputStream(path);//将文件的数据都转存inputStream
//2.读取数据
int read = inputStream.read();//一次读取一个字节 //read返回ASCII值 需要char强转一下
System.out.println("read:"+(char)read);
int read1 = inputStream.read();//一次读取一个字节
System.out.println("read1:"+(char)read1);
int read2 = inputStream.read();//一次读取一个字节
System.out.println("read2:"+read2);
-------------------------------------------------------------------------------------------------------------------------
//循环改进读取方式 不要将 result = inputStream.read()放外面 跑两次浪费资源
int result = 0;
while ((result = inputStream.read()) != -1) {
System.out.print((char) result);
}
System.out.println();
System.out.println("success");
-------------------------------------------------------------------------------------------------------------------------
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源 close()
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
read(byte[] b)
,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
,代码使用演示: private static void demo2(String path) {
long begin = System.currentTimeMillis();
//jdk1.7+ 提供了更加优雅的方式释放流对象 try...with...resources 不需要再close关闭资源了
//JDK7优化后的`try-with-resource` 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象
//流 implements Closeable
//字节流读取文本(字符数据)--->有可能会出现乱码
// try (创建流对象语句,如果多个,使用';'隔开) {
// 读写数据
// } catch (IOException e) {
// e.printStackTrace();
//}
try (
//1.创建流对象
InputStream inputStream = new FileInputStream(path);
) {
//2. 读取数据
System.out.println("inputStream.available():" + inputStream.available());
byte[] bytes = new byte[1024];//length: 1024的整数倍 缓冲
int len = 0;
while ((len = inputStream.read(bytes)) != -1) {
//每次读取一个字节内容 将内容又存储到bytes len: 代表读取到的有效的字节个数 -1
System.out.print(new String(bytes, 0, len));
//如果是System.out.println(new String(bytes));
//可能如果最后一次读取时,只读取一部分,数组中,上次读取的数据没有被完全替换,那上次读取时没有被替换的可能又要重新打印出来
}
long end = System.currentTimeMillis();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
小贴士:
使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。
read(byte[] b, int off, int len )
public static void main(String[] args) {
demo3("day16/src/abc.txt");
}
private static void demo3(String path) {
try (
//1.创建流对象
InputStream inputStream = new FileInputStream(path);
) {
//2.读取数据
byte[] bytes = new byte[10];
//off 代表是byte的索引 从第几个索引开始将内容存储数组
// int len = inputStream.read(bytes, 0, bytes.length);
// //len: 有效的字节个数
// System.out.println(len);
// System.out.println(Arrays.toString(bytes));
int len1 = 0;
while((len1=inputStream.read(bytes,0,bytes.length))!=-1){
System.out.println(new String(bytes,0,len1));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
【OutputStream】
public abstract class OutputStream extends Object implements Closeable, Flushable
//所有字节输出流的父类
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public abstract void write(int b)
:将指定的字节输出流。小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。try-with-resource就不需要了
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。
public FileOutputStream(File file, boolean append)
创建文件输出流以写入由指定的 File对象表示的文件 默认是 false不拼接 想要拼接 boolean append
输入true
public FileOutputStream(String name, boolean append)
创建文件输出流以指定的名称写入文件
true
表示追加数据,false
表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
public class FileOutputStreamConstructor throws IOException {
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos = new FileOutputStream(file);
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("b.txt");
}
}
写出字节数据
write(int b)
方法,每次可以写出一个字节数据,代码使用演示:public class OutPutStreamDemo {
public static void main(String[] args) {
demo1("day16/src/a.txt");
//文件200个字节
// read() 一次读一个 读200个字节 输出200次
// read(byte[200]) 一次读一次 在存到byte数组 --->一次读10个 输出1次
}
//对于所有输出流而言 文件不存在 默认自动创建(createNewFile())
private static void demo1(String path) {
try (
//1.创建字节输出流对象
OutputStream outputStream = new FileOutputStream(path,true);
// 默认是 false不拼接 想要拼接 输入true
) {
------------------------------------------------------------------------------------------------------------------------
System.out.println("success");
//2. 写入数据
outputStream.write('a');
outputStream.write(97);
outputStream.write('\n');
outputStream.write('1');
outputStream.write("java".getBytes());
outputStream.write('\n');
outputStream.write("我们".getBytes());//6个字节 //两个内容都写入了 没有被覆盖
------------------------------------------------------------------------------------------------------------------------
System.out.println("success");
byte[] bytes = "我们".getBytes();//编码---> 6个字节
System.out.println(Arrays.toString(bytes));
outputStream.write(bytes); //直接将数组内容全部输出
outputStream.write(bytes,0,3); //public void write(byte[] b, int off, int len)
//每次写出从off索引开始,len个字节
//一个字三个字节 缺一个字节可能就是乱码
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt",true);
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b);
// 关闭资源
fos.close();
}
}
文件操作前:cd
文件操作后:cdabcde
-------------------------------------------------------------------------------------------------------------------------
//写出换行
Windows系统里,换行符号是`\r\n` 。把
以指定是否追加续写了,代码使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 定义字节数组
byte[] words = {97,98,99,100,101};
// 遍历数组
for (int i = 0; i < words.length; i++) {
// 写出一个字节
fos.write(words[i]);
// 写出一个换行, 换行符号转成数组写出
fos.write("\r\n".getBytes());
}
// 关闭资源
fos.close();
}
}
输出结果:
a
b
c
d
e
回车符`\r`和换行符`\n` :
回车符:回到一行的开头(return)。
换行符:下一行(newline)。
系统中的换行:
Windows系统里,每行结尾是 `回车+换行` ,即`\r\n`;
Unix系统里,每行结尾只有 `换行` ,即`\n`;
Mac系统里,每行结尾是 `回车` ,即`\r`。从 Mac OS X开始与Linux统一。
在开发中,经常会遇见资源上传或者下载功能。==>等同于将一个地方的资源复制到本机==>读到资源写入本地的文件中。
参照FileCopy.java
public class FileCopy {
public static void main(String[] args) {
// String sourcePath = "day16/src/com/javasm/io/FileCopy.java";//源文件路径
// String targetPath = "F:\\";//F:\\目标文件
String sourcePath = "F:\\tools\\IDE\\idea\\ideaIU-2020.1.1.exe";
String targetPath = "F:\\";
filCopy2(new File(sourcePath), new File(targetPath));//0
// filCopy3(new File(sourcePath), new File(targetPath));//30
}
private static void filCopy2(File sourceFile, File parent) {
long begin = System.currentTimeMillis();
try (
InputStream inputStream = new FileInputStream(sourceFile);
OutputStream outputStream = new FileOutputStream(new File(parent, sourceFile.getName()))
) {
//读取源文件数据 read--->InputStream
int len = 0;
byte[] bytes = new byte[1024 * 10];
while ((len = inputStream.read(bytes)) != -1) {
//写入目标文件中 write--->OutPutStream
outputStream.write(bytes, 0, len); //这个是读完了 然后将读完了的内容写入一次 推荐使用这个
}
long end = System.currentTimeMillis(); //获取最后结束的毫秒时间
System.out.println("success:" + (end - begin)); //计算了下开始到结束用了多少时间
//200个字节 读200次 写1次
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 文件复制
*
* @param sourceFile 源文件
* @param parent 目标文件父级目录
*/
private static void filCopy1(File sourceFile, File parent) {
long begin = System.currentTimeMillis();
try (
InputStream inputStream = new FileInputStream(sourceFile);
OutputStream outputStream = new FileOutputStream(new File(parent, sourceFile.getName()))
) {
//读取源文件数据 read--->InputStream
int len = 0;
while ((len = inputStream.read()) != -1) { //这个是每次读完都要写入一次
//写入目标文件中 write--->OutPutStream
outputStream.write(len);
}
long end = System.currentTimeMillis();
System.out.println("success:" + (end - begin));
//200个字节 读200次 写200次
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedInputStream: 高效字节输入流 read
BufferedOutputStream:高效字节输出流 write
为什么称为高效? 底层自带了一个缓冲区: byte[] buff = new byte[8192];
new byte[1024] --->我们自定义长度如果>8192 按照我们自定义区执行了
BufferedInputStream(InputStream in)
BufferedOutputStream(OutputStream out)
public static void main(String[] args) {
String sourcePath = "F:\\tools\\IDE\\idea\\ideaIU-2020.1.1.exe";
String targetPath = "F:\\";
filCopy3(new File(sourcePath), new File(targetPath));//30
}
private static void filCopy3(File sourceFile, File parent) {
//创建高效流对象
long begin = System.currentTimeMillis();
try (
//高效字节输入流对象
FileInputStream inputStream = new FileInputStream(sourceFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
//获得源文件的名称 F:\ideaIU-2020.1.1.exe
FileOutputStream outputStream = new FileOutputStream(new File(parent, sourceFile.getName()));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
) {
int len = 0;
byte[] bytes = new byte[1024 *5]; //小于8192 按照8192缓存运行
while ((len = bufferedInputStream.read(bytes)) != -1) {//8192
bufferedOutputStream.write(bytes, 0, len);
}
long end = System.currentTimeMillis();
System.out.println("success:" + (end - begin));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
字节流读取文本文件时,遇到中文字符时,可能不会显示完整的字符( 乱码 ),因为一个中文字符可能占用多个字节存储。
所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件
Reader(抽象类)---->read
【Reader】
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
: 从输入流读取一个字符 读到末尾-1public int read(char[] cbuf)
: 读取cbuf.length个字符 返回有效的字符个数 读到末尾-1public abstract int read(char[] cbuf, int off, int len)
: 返回有效的字符个数 读到末尾-1java.io.FileReader
类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
小贴士:
字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
idea中UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据。
构造方法
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
public class FileReaderConstructor throws IOException{
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileReader fr = new FileReader(file);
// 使用文件名称创建流对象
FileReader fr = new FileReader("b.txt");
}
}
读取字符数据
public class RederDemo {
public static void main(String[] args) {
demo1("day17/src/com/javasm/io/RederDemo.java");
}
private static void demo1(String filePath) {
try (
//1.创建流对象---> 输入流 read 前提:文件要存在
Reader reader = new FileReader(filePath);
) {
//2.读取数据
// int read = reader.read();
// System.out.println((char)read);//UTF-8
//3.循环读取
// int len = 0;
// while((len=reader.read())!=-1){
// System.out.print((char)len);
// }
char[] chars = new char[1024];
//4.带参读取
/* int len = 0;
while ((len=reader.read(chars))!=-1){
//将有效的字符个数转换成字符串
System.out.print(new String(chars,0,len));
}*/
//5.带多个参读取
int len = 0;
while((len = reader.read(chars,0,chars.length))!=-1){
System.out.print(new String(chars,0,len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
【Writer】
Writer(抽象类)----->write
public abstract class Writer extends Object implements Appendable, Closeable, Flushable
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
Writer append(CharSequence csq)
在文件的末尾一次写一个字符串 。void write(int c)
写入单个字符。void write(char[] cbuf)
写入字符数组。abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。void write(String str)
写入字符串。void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。FileWriter(String fileName, boolean append)
: false: 不追加(覆盖) true: 末尾追加当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
public class FileWriterConstructor {
public static void main(String[] args) throws IOException {
// 使用File对象创建流对象
File file = new File("a.txt");
FileWriter fw = new FileWriter(file);
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("b.txt");
}
}
public class WriterDemo {
public static void main(String[] args) {
}
private static void demo1(String path) {
try (
//1.创建字符输出流对象
Writer writer = new FileWriter(path, true);
) {
//2.写入一些数据
writer.write(97);
writer.write('\n');
writer.write("abc".toCharArray());
writer.write('\n');
writer.write("局域网的元素c".toCharArray(), 0, 3);
writer.write('\n');
writer.write("胡有多少个电视柜湿地公园");
writer.write('\n');
//append
writer.append("abc");
System.out.println("success");
} catch (IOException e) {
e.printStackTrace();
}
}
}
1. 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
2. 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。
3.try-with-resource就自动写入了
### 关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要`flush` 方法了。
* `flush` :刷新缓冲区,流对象可以继续使用。
* `close `:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
代码使用演示:
```java
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据,通过flush
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第2个字符,写出成功
fw.flush();
// 写出数据,通过close
fw.write('关'); // 写出第1个字符
fw.close();
fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
fw.close();
}
}
```
> 小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。 try-with-resource就不需要了
private static void copy1(File sourceFile, File targetFile) {
//利用字符流实现
try (
Reader reader = new FileReader(sourceFile);
FileWriter writer = new FileWriter(targetFile);
) {
//循环读写
int len = 0;
char[] chars = new char[1024];
while ((len = reader.read(chars)) != -1) {
//一次写一个字符串
// writer.write(chars,0,len);
writer.write(new String(chars, 0, len));
}
//flush()---> 缓冲区(满的时候 或者close)
System.out.println("success");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedReader----> 装饰了基本的字符输入流---->readLine()
public class BufferedReader extends Reader
BufferedReader(Reader in)
String readLine() 读取到末尾 null
Bufferedwriter
//取集合里面的数据 例如"name = 张三" 这样的格式
private static void demo2() {
try (
//1.创建高效的字符输入流
BufferedReader bufferedReader = new BufferedReader(new FileReader("day17/src/a.txt"));
) {
//2.读取数据---> 将属性名称和属性值都获得并一起存储 (可以通过属性名称获得属性值)
Map params = new HashMap<>(16);
String content = "";//代表读取的每一行内容
while ((content = bufferedReader.readLine()) != null) { // String readLine() 读取到末尾 null
String[] split = content.split("="); //通过split 将K V分别取出来
params.put(split[0], split[1]);
}
params.forEach((k, v) -> {
System.out.println("k:" + k + ",v:" + v);
});
System.out.println(params.get("name"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
------------------------------------------------------------------------------------------------------------------
//从别的地址文件的内容 复制过来 通过高效字符流
public class FileCopyDemo {
public static void main(String[] args) {
//将源文件内容复制到目标文件中
File sourceFile = new File("D:\\idea_workspace\\22\\day17\\src\\com\\javasm\\io\\FileCopyDemo.java");
File targetFile = new File(new File("D:\\"), sourceFile.getName());
copy2(sourceFile, targetFile);
// demo2();
}
private static void copy2(File sourceFile, File targetFile) {
try (
//创建高效字符输出/输入流对象
BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile));
) {
String content = "";
while ((content = reader.readLine()) != null) {
writer.write(content);
writer.newLine();
}
System.out.println("success");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
IO流里面,涉及到设计模式: 装饰者设计模式。
功能: 只要是针对字面量类型实现读写操作(基本+string). 前提: 必须先写,再去读取。
场景: 保存一个基本类型的数据。
字节流的子类。 DataInputStream —> read
public class DataInputStream extends FilterInputStream implements DataInput
DataInputStream(InputStream in)
String readUTF()
public class DataOutputStream extends FilterOutputStream implements DataOutput
DataOutputStream(OutputStream out)
void writeByte(int v)
void writeChar(int v)
void writeDouble(double v)
void writeInt(int v)
void writeUTF(String s)
保存用户基本信息
private static void demo1(String path) {
try (
//创建数据输出流对象
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(path));
) {
dataOutputStream.writeInt(1001); //写入什么类型 读取就得是什么类型
dataOutputStream.writeUTF("张三");
dataOutputStream.writeByte(20);
dataOutputStream.writeChar('男');
System.out.println("写入成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void demo2(String path) {
try (
//1.创建数据输入流对象 直接DataInputStream 父类体现多态不可行
DataInputStream dataInputStream = new DataInputStream(new FileInputStream(path));
) {
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readUTF());//EOFException end of file
System.out.println(dataInputStream.readByte());
System.out.println(dataInputStream.readChar());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
字节流的子类。
序列化流是扩展了基本的字节流以及数据流的功能。读写对象的数据。前提: 先写 再去读取
序列化: writeObject() 类必须要实现java.io.Serializable (标识—> 签名(版本的id))
反序列化: readObject()
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants
ObjectOutput:
public interface ObjectOutput extends DataOutput, AutoCloseable
ObjectOutputStream(OutputStream out) 将Java对象的原始数据类型写出到文件,实现对象的持久存储
void writeObject(Object obj)
public class ObjectDemo {
public static void main(String[] args) {
demo2("day17/src/user.info");
}
private static void demo1(String path) { //序列化
//需求: 持久化保存一个对象的信息(序列化)
try (
//创建对象输出流对象
ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(path));
) {
UserInfo userInfo = new UserInfo(1, "张三", "1234", 20,null);
//将对象写入文件中
objectOutput.writeObject(userInfo);
System.out.println("序列化成功。。。。。。。");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void demo2(String path) { //反序列化
//读取对象的信息
try (
//创建反序列化对象
ObjectInput objectInput = new ObjectInputStream(new FileInputStream(path));
) {
//读取对象的数据
UserInfo userInfo = (UserInfo) objectInput.readObject();// 相同版本(编译一次)的Class文件
System.out.println(userInfo);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {//??? 字节码文件找不到 如果对象有修改或编译 class文件版本号会变 这时候反序列化失效
e.printStackTrace(); //除非一开始就提供序列号
//java.io.InvalidClassException: com.javasm.io.UserInfo;
//local class incompatible: stream classdesc serialVersionUID = 5430996563714593682, local class
}
}
}
//当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:
//该类的序列版本号与从流中读取的类描述符的版本号不匹配
// 该类包含未知数据类型
// 该类没有可访问的无参数构造方法
//Serializable 接口给需要序列化的类,提供了一个序列版本号。`serialVersionUID` 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable {
// 加入序列版本号
private static final long serialVersionUID = 5526334495220612258L;
//标识class文件版本的唯一性:
// 没有serialVersionUID:如果修改源码文件,会重新被编译,会随机生成新的serialVersionUID的数据。反序列化可能会出现异常。
// 反之,即使修改源码文件,重新编译,但是serialVersionUID还是固定的,唯一的。
private Integer id;
private String name;
//在开发中,安全级别较高的数据,可以不被序列化。
//该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
//transient 瞬时的 (内存不可见性)
private transient String pass;
private Integer age;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
private String[] hobby;
}
提示版本的id的生成:
字符流的子类。 Reader Writer
两种不同的流进行转换,字节流 字符流
** 字节流转换字符流的桥梁: InputStreamReader----> read
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
需求:
读取万维网的资源(数据)。
read()---->InputStream/Reader
BufferedReader----> readLine()
InputStreamReader(InputStream in) 创建一个使用默认字符集的字符流
InputStreamReader(InputStream in, Charset cs)
InputStreamReader(InputStream in, String charsetName) 创建一个指定字符集的字符流
public class ReaderDemo2 {
public static void main(String[] args) throws IOException {
// 定义文件路径,文件为gbk编码
String FileName = "E:\\file_gbk.txt";
// 创建流对象,默认UTF8编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
// 创建流对象,指定GBK编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
// 定义变量,保存字符
int read;
// 使用默认编码字符流读取,乱码
while ((read = isr.read()) != -1) {
System.out.print((char)read); // ��Һ�
}
isr.close();
// 使用指定编码字符流读取,正常解析
while ((read = isr2.read()) != -1) {
System.out.print((char)read);// 大家好
}
isr2.close();
}
}
字节流转换字符流
public class NovelDemo {
public static void main(String[] args) {
String path = "https://read.qidian.com/chapter/VU7CVG-4sOTlwGcoSQesFQ2/QcgZbUHqW8pMs5iq0oQwLQ2";
demo2(path);
}
private static void demo1(String path) {
try (
//1.创建高效字符输入流对象--->万维网的资源(起点的服务器的资源)----> URL(统一资源定位符)--->代表万维网资源
//URL(String spec)
//目的: 普通的字符输入流对象(需要将字节流转换成字符流对象)
BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(path).openStream(), Charset.forName("utf-8")));
BufferedWriter writer = new BufferedWriter(new FileWriter("a.txt")); //拉入的内容放进那个文件
) {
String content = "";
while ((content = reader.readLine()) != null) {
if (content.contains("")) {
content = reader.readLine().replaceAll(""
, "\n"); 将<p>替换成换行
writer.write(content);
break;
}
}
System.out.println("下载成功。。。。。");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
字符流转换成字节流: OutPutStreamWriter
OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
public class OutputDemo {
public static void main(String[] args) throws IOException {
// 定义文件路径
String FileName = "E:\\out.txt";
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
// 写出数据
osw.write("你好"); // 保存为6个字节
osw.close();
// 定义文件路径
String FileName2 = "E:\\out2.txt";
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
// 写出数据
osw2.write("你好");// 保存为4个字节
osw2.close();
}
}
字符流转换成字节流
private static void demo2(String path) {
try (
BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(path).openStream(), Charset.forName("utf-8")));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
//指定到控制台上 字符流转换成字节流
) {
String content = "";
while ((content = reader.readLine()) != null) {
if (content.contains("")) {
content = reader.readLine().replaceAll(""
, "\n");
writer.write(content);//写字符 字符 转字节
break;
}
}
System.out.println("下载成功。。。。。");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
* Properties
是HashTable的子类。 Map的实现类。 键值对。key----value
存储一组元素,属性名称----属性值 (属性集)
与配置文件有关: 加载读取配置文件的数据 。 要求配置文件内容: key=value
配置文件名: *.properties (资源文件 resources)
构造方法
public Properties()
:创建一个空的属性列表。
基本的存储方法
public Object setProperty(String key, String value)
: 保存一对属性。
public String getProperty(String key)
:使用此属性列表中指定的键搜索属性值。
public Set stringPropertyNames()
:所有键的名称的集合。
public class ProDemo {
public static void main(String[] args) throws FileNotFoundException {
// 创建属性集对象
Properties properties = new Properties();
// 添加键值对元素
properties.setProperty("filename", "a.txt");
properties.setProperty("length", "209385038");
properties.setProperty("location", "D:\\a.txt");
// 打印属性集对象
System.out.println(properties);
// 通过键,获取属性值
System.out.println(properties.getProperty("filename"));
System.out.println(properties.getProperty("length"));
System.out.println(properties.getProperty("location"));
// 遍历属性集,获取所有键的集合
Set strings = properties.stringPropertyNames();
// 打印键值对
for (String key : strings ) {
System.out.println(key+" -- "+properties.getProperty(key));
}
}
}
输出结果:
{filename=a.txt, length=209385038, location=D:\a.txt}
a.txt
209385038
D:\a.txt
filename -- a.txt
length -- 209385038
location -- D:\a.txt
user.properties
// 要求配置文件内容: key=value
# 属性key---属性值 properties默认编码格式iso-8859-1 #是注释
name=张三
后面学习中,开发中: web 项目 maven项目 boot项目,肯定是在服务器上运行的
服务器里面都是资源文件,class文件(classpath)
private static void demo2() {
//读取user.properties--->read
// 创建属性集对象
Properties properties = new Properties();
try {
// 加载文本中信息到属性集
properties.load(new FileInputStream("day17/src/user.properties"));
-------- --------------------------------------------------------------------------------------------------------------
//web 项目 maven项目 boot项目 服务器里面都是资源文件,class文件(classpath)
//指定加载的文件路径(jvm里面类加载器 ClassLoader==编译路径的根路径)
//编译路径下的资源文件(src==编译路径的根路径/)
// class文件--->Class类--->获得Class对象的方式
//PropertiesDemo.class.getClassLoader() 获得了编译路径的根路径
properties.load(PropertiesDemo.class.getClassLoader().getResourceAsStream("./user.properties"));
// properties.load(PropertiesDemo.class.getResourceAsStream("../../../user.properties"));
//如果没有getClassLoader()获得编译路径的根路径 要自己../往上找 此时是项目下
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(properties.getProperty("user.name"));
//String 转 int
System.out.println(Integer.parseInt(properties.getProperty("user.age")));
}
//小贴士:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔
public class PropUtil {
private PropUtil() {
}
private static Properties properties;
private static final String FILE_NAME = "user.properties";
static {
properties = new Properties();
try {
properties.load(PropUtil.class.getClassLoader().getResourceAsStream(FILE_NAME));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 根据key获得value
*
* @param key
* @return
*/
public static String getValue(String key) {
if (key == null) {
return properties.getProperty(key, "");
}
return properties.getProperty(key);
}
}
线程
术语解释
// 并发与并行
**并发**:指两个或多个事件在**同一个时间段内**发生。 例: 你吃了一口饭 (间隔的时间--->线程的上下文的切换时间) 你去打电话了 (交替执行)
并发:(在以后项目中)----> (有多个请求)--->服务器(负载均衡 服务器集群
**并行**:指两个或多个事件在**同一时刻**发生(同时发生) 例:(嘴巴): 你一边吃饭 一边打电话
// 同步 vs 异步
同步请求:
服务器完全响应,才能去做另外一件事情。一致等待wait
异步请求:(第二阶段---> 页面异步刷新 ajax)
不等服务器的完全响应,可以做很多的其它的事情。---> 异步任务(队列)
// 阻塞 vs 非阻塞
阻塞: IO (堵车)---> 传统IO BIO(blocking IO)----> 同步阻塞的io
Scanner 一致等待 知道获得合适的数据
非阻塞:(有数据)---> NIO
同步阻塞,相当于一个线程在等待。
同步非阻塞,相当于一个线程在正常运行。
异步阻塞,相当于多个线程都在等待。
异步非阻塞,相当于多个线程都在正常运行。
// 进程 vs 线程
进程: (相对于系统)--->应用程序 cpu
Android: 卡 多进程的。
IOS: 不卡 单进程
线程:(在进程的基础)
一个进程里面有多个线程
WECHAT: 数据请求 网络连接
B站: 看视频 发弹幕 评论 声音
线程也看cpu: 单核: 就是单线程 多核: 多线程
线程 Thread —>
线程与线程之间是相互独立的。会共享进程里面的数据。
jvm可以支持多线程。 java程序运行,至少有2个线程存在。 main线程 GC线程(守护线程)
* Thread
public class Thread extends Object implements Runnable
Java虚拟机允许应用程序同时执行多个执行线程
每个线程都有优先级。 具有较高优先级的线程优先于优先级较低的线程执行。
static class Thread.State 线程的状态(静态内部类)
static int MAX_PRIORITY 线程最大的级别10
static int MIN_PRIORITY 线程最小的级别1
static int NORM_PRIORITY 线程默认级别5
Thread()
Thread(Runnable target) 通过Runnable实现类构建线程
Thread(Runnable target, String name)
Thread(String name)
static Thread currentThread() 获得当前正在运行的线程
long getId() 获得唯一的标识 1
String getName() 获得线程的名称
int getPriority() 获得线程的优先级别
void setName(String name) 修改线程名称
void setPriority(int newPriority) 修改线程级别
Thread.State getState() 获得线程的状态
void interrupt() 中断线程
boolean isAlive() 判断线程是否是活着的
boolean isDaemon() 判断线程是否是守护线程
void join() 等待当前线程死亡(当前线程执行完毕之后 其它线程才有机会抢占cpu)
void join(long millis)
void run() 线程的运行逻辑
void setDaemon(boolean on) 一定要在启动线程之前标记
static void sleep(long millis) 让当前线程休眠指定的时间 线程调度
static void sleep(long millis, int nanos)
void start() 启动线程(执行run)
static void yield() 当前线程放弃抢占cpu 立马处于就绪的状态
void wait() 当前线程一直等待(睡过去了) --->有同一个监视器(锁)
void notify()/void notifyAll() 唤醒睡过去的线程
void wait(long timeout) 当前线程再指定时间内等待 超时了会自动醒过来
void wait(long timeout, int nanos)
* 多线程
优势: 提高了cpu的利用率问题。
弊端: 由于线程与线程之间是相互独立,
很可能会出现线程安全问题。(死锁)—>程序不停,程序不会继续执行
SimpleDateFormat.pare()/format()—>数据
继承Thread
Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
模拟:
同时下载多张网图。---> 网上资源---> copy--->读写--->字节流
/**
* 下载功能
* https://www.javasm.cn/static/upload/image/20200525/1590377569484285.png
* https://www.javasm.cn/static/upload/image/20200219/1582127034814192.jpg
* https://www.javasm.cn/static/upload/image/20180702/1530526129232132.png
*
* @param target 父级路径 day18/demo/images/
* @param url 网上图片路径
*/
public class DownloadFileThread extends Thread {
private String url;
private String targetDirectory;
//线程逻辑在run方法: 重写父类的run方法
@Override
public void run() {
//调用工具类下载图片的资源
System.out.println(DownloadFileUtil.download(url, targetDirectory) + "下载成功");
}
public DownloadFileThread(String url, String targetDirectory, String threadName) {
super(threadName);
this.url = url;
this.targetDirectory = targetDirectory;
}
}
public static String download(String url, String target) {
//IO读写数据(读写小说)
Objects.requireNonNull(url);
//url 最后一个/内容
String fileName = url.substring(url.lastIndexOf("/") + 1);
try (
//高效字节流
BufferedInputStream inputStream = new BufferedInputStream(new URL(url).openStream());
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(new File(target, fileName)))
) {
//循环读写
int len = 0;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return fileName;
}
public static void main(String[] args) {
//1.创建3个线程对象
// * https://www.javasm.cn/static/upload/image/20200525/1590377569484285.png
// * https://www.javasm.cn/static/upload/image/20200219/1582127034814192.jpg
// * https://www.javasm.cn/static/upload/image/20180702/1530526129232132.png
String url = "https://www.javasm.cn/static/upload/image/20200525/1590377569484285.png";
String directory = "day18/demo/images/";
DownloadFileThread thread1 = new DownloadFileThread(url, directory, "线程1");
url = "https://www.javasm.cn/static/upload/image/20200219/1582127034814192.jpg";
DownloadFileThread thread2 = new DownloadFileThread(url, directory, "线程2");
url = "https://www.javasm.cn/static/upload/image/20180702/1530526129232132.png";
DownloadFileThread thread3 = new DownloadFileThread(url, directory, "线程3");
//2. 启动线程 start()
thread1.start();
thread2.start();
thread3.start();
}
实现Runnable
@FunctionalInterface
public interface Runnable
Runnable接口应由任何类实现,其实例将由线程执行
步骤如下:
1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
的线程对象。
3. 调用线程对象的start()方法来启动线程。
案例:
火车站卖票: 很多窗口都可以卖固定票数 50 3个线程(窗口)
public class SaleTicketRunnable implements Runnable {
private int ticket = 50;//3个线程共享50张票
@Override
public void run() {//运行状态
//开启多个线程 执行的一个线程逻辑
//50张票
//获得当前正在运行的线程: 获得线程的名称
for (int i = 1; i <= 50; i++) {//i 控制有可能会出现一个卖50张票的特殊情况的
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
ticket--;
//线程调度
try {
Thread.sleep(400);//休眠 继续执行下面的一些逻辑
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
//创建3个窗口(线程)
SaleTicketRunnable saleTicketRunnable = new SaleTicketRunnable();
//Thread
Thread window1 = new Thread(saleTicketRunnable, "窗口1");//新建状态
//Thread window1 = new Thread(new saleTicketRunnable, "窗口1"); 如果这么写 没有共用一个对象
//那么类里面 private static int ticket = 50; 票数要定义为静态的 保证共用这个50张票
Thread window2 = new Thread(saleTicketRunnable, "窗口2");
Thread window3 = new Thread(saleTicketRunnable, "窗口3");
window1.start();//就绪状态
window2.start();//就绪状态
window3.start();//就绪状态
}
实现Runnable接口比继承Thread类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
* 实现Callable
Callable+Future(FutureTask)
public class FutureTask<V> extends Object implements RunnableFuture<V>
FutureTask(Callable<V> callable)
案例:
火车站卖票: 很多窗口都可以卖固定票数 50 3个线程(窗口)
public class SaleTicketCallable implements Callable<String> {
private int ticket = 50;//3个线程共享50张票
@Override
public String call() throws Exception { //call===run
for (int i = 1; i <= 50; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
ticket--;
//线程调度
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return "ok";
}
}
public static void main(String[] args) {
SaleTicketCallable saleTicketCallable = new SaleTicketCallable();
//3个窗口(线程)干3件事情(任务) 几个线程就创建几个,方便拿数据
FutureTask<String> futureTask1 = new FutureTask<>(saleTicketCallable);
FutureTask<String> futureTask2 = new FutureTask<>(saleTicketCallable);
FutureTask<String> futureTask3 = new FutureTask<>(saleTicketCallable);
//要runnable的实例
Thread window1 = new Thread(futureTask1, "窗口1");
Thread window2 = new Thread(futureTask2, "窗口2");
Thread window3 = new Thread(futureTask3, "窗口3");
window1.start();//就绪状态
window2.start();//就绪状态
window3.start();//就绪状态
}
线程池
使用线程池中线程对象的步骤:
1. 创建线程池对象。
2. 创建Runnable接口子类对象。(task)
3. 提交Runnable接口子类对象。(take task)
4. 关闭线程池(一般不做)。
public static void main(String[] args) {
SaleTicketCallable saleTicketCallable = new SaleTicketCallable();
//3个窗口(线程)干3件事情(任务)
FutureTask<String> futureTask1 = new FutureTask<>(saleTicketCallable);
FutureTask<String> futureTask2 = new FutureTask<>(saleTicketCallable);
FutureTask<String> futureTask3 = new FutureTask<>(saleTicketCallable);
//利用线程池来完成--->创建3个线程
//ThreadPoolExecutor
// 创建线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(3);//pool-1-thread-3
executorService.execute(futureTask1);
executorService.execute(futureTask2);
executorService.execute(futureTask3);
}
* 线程安全
多线程的环境下,且有共同使用的资源(数据—>同一个变量)
安全与效率 二者都是不可兼得的。 局部变量
线程安全的类: StringBuffer Vector HashTable java.time.*
监视器对象(锁(互斥锁 悲观锁 同步锁)对象)—> 任意一个对象都可以充当监视器对象 Object
保证每个线程都能正常执行原子操作,Java引入了线程同步机制 :
- 自动加锁synchronized
- 同步代码块
- 同步方法
- 手动加锁 Lock
自动加锁
synchronized
多个线程使用同一个锁对象。
- 同步代码块
在方法体里面使用同步代码块解决安全问题。----> 变量数据改变的程序中
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1. 锁对象 可以是任意类型。
2. 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
(BLOCKED)。
@Override
public void run() {//运行状态
//开启多个线程 执行的一个线程逻辑
//50张票
//获得当前正在运行的线程: 获得线程的名称
for (int i = 1; i <= 50; i++) {//i 控制有可能会出现一个卖50张票的特殊情况的
synchronized ("锁") {//锁对象 加锁
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
ticket--;
//线程调度
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}//释放锁
}
同步方法
在方法的定义上面 使用synchronized进行修饰
public synchronized void abc(){}
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
@Override
public void run() {//运行状态
//开启多个线程 执行的一个线程逻辑
//50张票
//获得当前正在运行的线程: 获得线程的名称
for (int i = 1; i <= 50; i++) {//i 控制有可能会出现一个卖50张票的特殊情况的
saleTicket();
}
}
private synchronized void saleTicket() {//上锁
// 锁对象是什么? 当前类对象(this/saleTicketRunnable)
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
ticket--;
//线程调度
try {
TimeUnit.MILLISECONDS.sleep(500);// 不会
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}//释放锁
手动加锁 Lock
public class ReentrantLock extends Object implements Lock, Serializable
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
public class SaleTicketCallable implements Callable<String> {
private int ticket = 50;//3个线程共享50张票
private static final Lock LOCK = new ReentrantLock();//锁对象
@Override
public String call() throws Exception {//call===run
for (int i = 1; i <= 50; i++) {
LOCK.lock();//上锁
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
ticket--;
//线程调度
TimeUnit.MILLISECONDS.sleep(500);
}
LOCK.unlock();//手动释放锁
}
return "ok";
}
}
死锁(了解)
前提: (程序不停 但是也不执行 阻塞状态)
1. 多线程的环境
2. 有多把锁(至少2个)(交叉锁+嵌套锁)
3. 有共享的资源(至少2个)
父亲: 有零花钱 想要儿子的成绩单
儿子:有成绩单 想要父亲的零花钱
public class Father extends Thread {
@Override
public void run() {
synchronized (Locks.FATHER_LOCK) {
System.out.println("父亲有零花钱");
synchronized (Locks.SON_LOCK) {
System.out.println("父亲想要儿子的成绩单");
}
}
}
}
public class Son extends Thread {
@Override
public void run() {
synchronized (Locks.SON_LOCK) {
System.out.println("儿子有成绩单");
synchronized (Locks.FATHER_LOCK) {
System.out.println("儿子想要父亲的零花钱");
}
}
}
}
public class Locks {
public static final String FATHER_LOCK = "father";
public static final String SON_LOCK = "son";
}
public static void main(String[] args) {
new Father().start();
new Son().start();
}
线程通信
与锁对象有关,synchronized
Object.wait() Object.notify()
体现在: 生产者 与 消费者模式 (现象/问题)
生产者: 一直生产(提交)数据—> 池子(缓冲区)
消费者:一直消费数据-----> 从池子里面获得数据
** 消息队列: RocketMQ **
线程间通信
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
为什么要处理线程间通信:
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源:
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— **等待唤醒机制。**
什么是等待唤醒机制
这是多个线程间的一种协作机制。就是在一个线程进行了规定操作后,就进入等待状态 wait(), 等待其他线程执行完他们的指定代码过后 再将其唤醒 (notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。
>注意:
>哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
调用wait和notify方法需要注意的细节
1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
双十一:
很多用户(生产者)提交订单----> 订单池(排队+队列) (有一定的量) 不能一直生产 集合
支付宝处理了订单(用户使用支付宝支付)---->订单池 不能一直消费
public class OrderPool {
private static List orderPool = Collections.synchronizedList(new ArrayList<>(10));//订单池
//最多存储30个
/**
* 生产者
*/
public synchronized static void produceOrder() {// 锁对象? 类锁
try {
if (orderPool.size() == 30) {
//池子满了 就不能生产 当前线程等待
OrderPool.class.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
if (orderPool.size() < 30) {
//生产一个订单
orderPool.add("order");// 通知消费者进行消费
System.out.println(Thread.currentThread().getName()+"提交了一个订单,目前池子里面:" + orderPool.size());
TimeUnit.MILLISECONDS.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
OrderPool.class.notify();//在这个线程里面唤醒另外一个线程
}
/**
* 消费者
*/
public synchronized static void consumeOrder() {
//1.判断orderPool是否有订单 有的话才能消费 没有的话等待
try {
if (orderPool.size() <= 0) {
//当前线程等待
OrderPool.class.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.有订单--->移除第一个订单---> 池子里面就有多余的空间---->生产者就可以生产
orderPool.remove(0);
System.out.println("支付宝处理了一个订单,目前池子里面还有:" + orderPool.size());
try {
TimeUnit.MILLISECONDS.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
OrderPool.class.notifyAll();
}
}
public class UserProduceOrderThread extends Thread {
@Override
public void run() {
//一直生产订单
while (true) {
OrderPool.produceOrder();
}
}
public UserProduceOrderThread(String name){
super(name);
}
}
public class AiPayOrderThread extends Thread {
@Override
public void run() {
//一直消费订单
while (true) {
OrderPool.consumeOrder();
}
}
}
public class Test {
public static void main(String[] args) {
UserProduceOrderThread produceOrderThread = new UserProduceOrderThread("user1");
produceOrderThread.start();
UserProduceOrderThread produceOrderThread1 = new UserProduceOrderThread("user2");
produceOrderThread1.start();
AiPayOrderThread payOrderThread = new AiPayOrderThread();
payOrderThread.start();
}
}
线程状态(生命周期)
线程状态
导致状态发生条件
NEW(新建)
线程刚被创建,但是并未启动。还没调用start方法
Runnable(可 运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器
Blocked(锁阻 塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态
Waiting(无限 等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时 等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait
Teminated(被 终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
停止线程
public class StopThread extends Thread {
private int count = 0;
private boolean flag = true;
@Override
public void run() {
while (flag) { //当flag为true的时候运行
if (count == 10) {
break;
}
System.out.println(Thread.currentThread().getName() + "-----count:" + (++count));
try {
TimeUnit.MILLISECONDS.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stopThread() { //创建停止线程的方法 将flag赋值false
this.flag = false;
}
public StopThread(String name) {
super(name);
}
}
public class Test {
public static void main(String[] args) {
//启动线程
StopThread stopThread = new StopThread("stop");
stopThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main......"+i);
//当i==10 结束线程
if(i==10){
//stopThread 结束
stopThread.stopThread(); //想停止的时候直接调用stopThread()方法 将flag赋值false
}
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test1 {
public static void main(String[] args) {
StopThread a = new StopThread("a");
StopThread b = new StopThread("b");
StopThread c = new StopThread("c");
a.start();
try {
a.join(); //a后面有join()方法后 a运行完毕 后面才能开始
} catch (InterruptedException e) {
e.printStackTrace();
}
b.start();
try {
b.join();//等待当前线程死亡
} catch (InterruptedException e) {
e.printStackTrace();
}
c.start();
}
}
单例模式
23种设计模式之一。
在一个进程里面 有且只有一个对象。
- 类的构造私有 2. 提供静态方法 / 属性
1. 饿汉模式
public class Singleton {
//1.构造私有(只能本类访问)
private Singleton() {
}
//2.提供静态的方法
private static Singleton singleton = new Singleton();
// jvm加载 class 就会创建 Singleton对象 ---> 饿汉 不会出现线程安全的问题 (没有体现lazy loading的特性)
public static Singleton getInstance() {
return singleton;
}
}
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+Singleton.getInstance()+"=========="+i);
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
for (int i = 11; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+Singleton.getInstance()+"=========="+i);
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
2.懒汉模式
public class Singleton1 {
//1.构造私有(只能本类访问)
private Singleton1() {
}
//2.提供静态的方法
private static Singleton1 singleton;// 声明一个对象 null
// 体现出来lazy loading的特性 懒加载 但是有线程安全的问题
//synchronized 方法 效率很低
public synchronized static Singleton1 getInstance() {
if (singleton == null) {
singleton = new Singleton1();
}
return singleton;
}
}
Thread-1com.javasm.danli.Singleton1@710a52b==========11
Thread-0com.javasm.danli.Singleton1@377839d0==========0
。。。。。
public class Singleton1 {
//1.构造私有(只能本类访问)
private Singleton1() {
}
//2.提供静态的方法
private volatile static Singleton1 singleton;// 声明一个对象 null
// 体现出来lazy loading的特性 懒加载 但是有线程安全的问题
//synchronized 方法 效率很低
//推荐使用double-check方法实现安全
//JMM JAVA memory model 无序性 jvm/cpu(指令重排) 1. 分配空间 2. 赋值 3. 初始化
// volatile: 有序性 原子性 限制指令重排 (线程可见性)---> 写之前先读
//缓存一致性。
public static Singleton1 getInstance() {
if (singleton == null) {
synchronized ("abc") {
if (singleton == null) {
singleton = new Singleton1();
}
}
}
return singleton;
}
}