Java小白第四天
写在前面
本文是边看黑马b站视频边写的一片笔记, 文中大多图片都来自黑马视频. 旨在巩固学习以及方便后续查阅和供广大朋友们学习, 感谢黑马视频分享
01 异常
异常是程序在编译和运行的过程中出现的错误
01 异常体系
编译时异常 – 感恩有你
----------------------------------------------------------------- ---------------------------------------------------------------
02 异常的处理流程
01 异常的默认处理流程
- 默认会在出现异常的代码那里自动的创建一个异常对象
- 异常会在方法中异常点抛出异常给调用者, 调用者最终会抛出异常给jvm虚拟机
- 虚拟机接收到异常对象后, 先在控制台直接输出异常栈信息数据
- 然后直接从当前的异常点停止程序
- 程序运行停止 – 所以异常需要处理, 避免程序停止
02 编译时异常处理流程
编译时异常是编译阶段就出错的, 必须处理, 否则代码无法通过编译
编译时异常三种处理方式
- 出现异常直接抛给调用者, 调用者继续向上抛, 最终交给虚拟机处理, 和默认处理没有区别
- 出现异常时, 捕获异常自己处理
- 出现异常时, 抛给调用者捕获异常处理
方式一
方式二 监视捕获异常 – 谁出现谁处理
方式三: 交给需要处(调用者)捕获异常处理, 调用者可以知悉当前的操作是否成功, 这种方式更加友好
03 运行时异常
- 运行时异常编译阶段不会出错, 运行时报错, 所以编译阶段不处理也可以, 运行时异常是自动抛出的
- 规范: 建议在最外层调用处集中捕获处理
使用异常捕获, 使得程序更加健壮, 即使用户输入非法字符, 程序也不会停止
public static void exceptionIsGood(){
Scanner sc = new Scanner(System.in);
System.out.println("请输入定价: ");
while (true) {
try {
String priceStr = sc.nextLine();
double price = Double.valueOf(priceStr);
if (price >= 0){
System.out.println("定价 " + price);
break;
}
} catch (Exception e) {
//e.printStackTrace();
System.out.println("输入非法数字, 请重新输入");
}
}
}
03 自定义异常
01 自定义编译时异常
- 定义一个异常类继承
Exception
- 重写构造器
- 在出现异常的地方用
throw new
自定义对象抛出, 还需在方法声明上手动抛出, 交给调用者处理, 或自己捕获
作用: 编译时异常是在编译阶段就报错
代码示例
// 创建异常类
package com.zxq.innnerClasss.exception;
public class IllegalAgeException extends Exception{
public IllegalAgeException(){
}
public IllegalAgeException(String message){
super(message); // 调用 Exception 的有参构造方法, 提供该异常的说明信息
}
}
// 在方法中使用自定义编译时异常类
public static void testAge(int age) throws IllegalAgeException{
if (age < 0 || age > 200){
/*
使用自定义异常 -- 编译时异常
1. 异常类 extends Exception
2. throw new 创建异常对象
3. 编译时异常不会自动抛出, 需要手动抛出
说明:
1. throw: 在方法内部直接创建一个异常对象, 并从此点抛出
2. throws: 在方法声明处, 抛出方法内部需要手动抛出的异常
*/
throw new IllegalAgeException(age + " " + " is illegal!!!");
}else{
System.out.println("年龄合法, 推荐购买商品");
}
}
// 调用方法, 处理方法抛出的自定义异常类
public static void main(String[] args) {
//exceptionIsGood();
try {
testAge(-12);
} catch (IllegalAgeException e) {
e.printStackTrace();
}
}
// main方法执行结果
com.zxq.innnerClasss.exception.IllegalAgeException: -12 is illegal!!!
at com.zxq.innnerClasss.exception.Test.testAge(Test.java:30)
at com.zxq.innnerClasss.exception.Test.main(Test.java:13)
02 自定义运行时异常
不需要提醒的异常, 定义为运行时异常
- 定义一个异常类继承
RuntimeException
- 重写构造器
- 在出现异常的地方用
throw new
自定义对象抛出, 不会强制捕获
代码示例
// 创建异常类 extends RuntimeException
package com.zxq.innnerClasss.exception;
public class IllegalRuntimeException extends RuntimeException{
public IllegalRuntimeException(){
}
public IllegalRuntimeException(String message){
super(message);
}
}
// 在方法中 throw new 创建异常类对象
public static void testRuntimeException(){
throw new IllegalRuntimeException("我是运行时异常哦!!!");
}
// 调用方法 -- 不强制对运行时异常进行捕获处理
public static void testRuntimeException(){
throw new IllegalRuntimeException("我是运行时异常哦!!!");
}
// 输出结果
Exception in thread "main" com.zxq.innnerClasss.exception.IllegalRuntimeException: 我是运行时异常哦!!!
at com.zxq.innnerClasss.exception.Test.testRuntimeException(Test.java:21)
at com.zxq.innnerClasss.exception.Test.main(Test.java:15)
04 try – catch – final
final: 用于资源的收尾工作
finally: 即使try / catch 语句中有return语句, finally 语句中的内容也会执行, 如果此时 finally 语句中有 return , 返回的就是finally中的 return
02 日志
日志: 记录程序中运行过程的信息, 并需要对日志信息进行存储, 便于查询
日志技术的优势
- 可以将系统执行的信息选择性的记录到指定的位置(控制台 文件 数据库)
- 可以以开关的形式控制是否记录日志, 无序修改源码
- 日志和程序可以分开跑, 提高程序的性能
日志体系结构
主要学习 Logback
01 Logback快速入门
Logback快速入门
代码中使用: 创建 Logback 日志对象使用
输出到控制台
输出到文件
配置日志输出关联位置
Logback日志级别设置
03 File 和 IO 流
- File 定位文件: 删除 获取文件本身信息等操作
- 但是 file 不能读写文件内容
- 读写文件数据
- 使用 IO 流读写硬盘中的文件
即, file 定位操作文件, io流读写文件
本节内容
01 file 类概述
- file 类在
java.io.File
包下, 代表操作系统的文件对象(文件 文件夹) - file 类提供了诸如: 定位文件, 获取文件本身的信息 删除文件 创建文件(文件夹)功能
补充: 反斜杠的三种形式以及课程代码示例
绝对路径和相对路径
02 file 类的常用 API
note:
getPath()
: 获取的是创建该file
对象时, 传入的路径length()
: 该方法返回文件的大小, 需要注意的是, 文件夹使用此方法无用, 因为文件夹没有大小, 文件夹内部文件的大小使用其他方法获得
上述API代码示例
note:
createNewFile()
: 创建的文件, 是创建文件对象时, 传入的文件, 是对象的方法
note
- 只能遍历当前文件夹对象下的一级文件对象
03 方法递归
- 方法直接调用自己或间接调用自己的形式称为方法递归 (recursion)
- 递归如果没有控制终止条件, 会出现递归死循环, 导致栈内存溢出现象
递归三要素
- 递归公司
- 递归终结点
- 递归方向
难点: 非规律化递归
在某个文件夹中搜索某一个文件或文件夹 代码完整实现
package com.it;
import java.io.File;
/*
启动 exe 程序
*/
public class ExeStart {
public static String filepathName;
public static String dir = "A:/test/test1";
public static String fileName = "button_hover.png";
public static void main(String[] args) {
File f = new File(dir);
System.out.println(f.exists());
search(fileName,f);
System.out.println("拿到了" + filepathName);
File exe = new File(filepathName);
// try {
// Runtime r = Runtime.getRuntime();
// r.exec(exe.getAbsolutePath());
// }catch (Exception e){
// System.out.println("启动失败");
// }
}
public static int test(int n){
if (n == 0){
return 0; // 终止条件
}
return n + test(n-1); // 递归公式 与 方法
}
public static void search(String filename, File searchScope){
String[] list = searchScope.list();
for (String currentFileName : list){
if (currentFileName.equals(filename)){
String filePath = searchScope.getAbsolutePath() + "/" + filename;
filepathName = filePath;
System.out.println(filePath);
}
}
File[] files = searchScope.listFiles();
for (File file : files){
if (file.isFile()){
continue;
}
if (file.list().length == 0){
continue;
}
search(filename,file);
}
}
}
04 字符集
字节是字节, 字符是字符, 一个汉字表示一个字符, 一个英文字母也表示一个字符, 不同字符占用不同字节, 一个字节8个位, 字节是 nbyte
- 计算机底层只能以二进制存储数据
- 以一个二进制数据表示人类字符, 这套表示规则就是字符集
Unicode
万国码
介绍
知识储备
- 不同字符集在计算机底层存储字符的不同方式
- 英文和数字等在任何国家的字符集中都占一个字节
- GBK 编码中一个中文占两个字节
- UTF-8 编码中一个中文一般占3个字节
- 编码前后的字符集要一致, 否则会出现中文乱码
- 英文和数字在任何国家的编码中都不会乱码, 即所有字符集都要兼容 ASCII 码
01 字符集的编码和解码
都位于 String
类中
代码示例
// 编码方法 与 解码构造器
public static void test01() throws UnsupportedEncodingException {
// 编码
String str = "abc我爱你中国";
byte[] bytes = str.getBytes();// 指定字符集可以传参, 默认为以系统字符集形式编码
byte[] gbks = str.getBytes("GBK"); // 指定编码
System.out.println(bytes.length);
System.out.println(Arrays.toString(bytes)); // 查看底层字符对应编码
String str2 = new String(gbks); // gbks 是以 GBK 编码的, 默认解码以系统 UTF-8 解码 会出现乱码
System.out.println(str2); // abc�Ұ����й�
// 指定解码字符集
String str3 = new String(gbks, "GBK");
System.out.println(str3); // abc我爱你中国
}
05 IO流 – 读写文件数据
字节输入输出流 每次操作一个或一组字节 使用 read
或 write
FileInputStream
: 硬盘 -> 内存read
FileOutputStream
: 内存 - > 硬盘write
特点
- 字节流适合做一切文件数据的拷贝(音视频, 文本)
- 字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读, 写)
01 文件输入流: FileInputStream
- 把磁盘文件中的数据以字节的形式读取到内存中
note:
- 创建文件输入对象的第二种方式, 封装了根据文件路径创建
file
对象的步骤, 本质上同第一种方法一样 - 读取的字节使用
(char)97 -> a
将read
方法读取到的字节int
转换为字符 - 一般, 读取字节数组大小为1kb, 即1024
- 读取到的字节数组就可以用字符集的方法解码了
buffer
数组的残留问题, 代码如下, 解决方案 apinew String(buffer, startindex, endindex)
, 或每次清空数组
// read(byte[] buffer) 桶的残留问题
/*
当定义 byte[] buffer = new byte[3];
时, 使用 read 方法, 每次读取3个字节,
当最后一次读取不足 buffer 的长度时,
由于上一次读取的buffer中的数据有残留, 所以, 上一次读取的部分数据会残留在最后一次获取的buffer数组中
*/
public static void test02() throws Exception {
FileInputStream fis = new FileInputStream("file/src/2.txt");
byte[] buffer = new byte[3];
int index = 10;
while (index > 0) {
System.out.println(index);
int i = fis.read(buffer);
System.out.println(i);
System.out.println(Arrays.toString(buffer));
index--;
}
}
// read(byte[] buffer) 桶的残留问题 解决代码 但是有乱码问题 可以用来转移数据
public static void test03() throws Exception {
FileInputStream fis = new FileInputStream("file/src/2.txt");
byte[] buffer = new byte[3];
int len = 0;
while((len = fis.read(buffer)) != -1){
String str = new String(buffer, 0 ,len); // 关键是这构造器
System.out.println(str);
}
}
解决上诉读取乱码问题
一次将文件所有字节都读入内存, jdk有官方api
02 字节输出流 FileOutputStream
把内存中的数据以字节的形式输出到硬盘文件中
note:
- 输出目的地: 如果不存在, 会自动创建
fos.flush()
: 输出流, 写完数据后, 调用该方法, 将数据刷新到文件中, 这是因为, 字节输出流写数据时, 可能将数据在缓冲中处理, 导致数据写入失败fos.close()
: 释放流,释放资源, 自带刷新- 写入中文, 可以先使用字符集处的api将中文字符串转换为字符数组, 将字符数组写入文件
- 换行符写入:
"\r\n".getBytes()
"\r\n" 兼容性更强
- 只带有字节输出目的地的字节输出流称作覆盖管道流, 这是因为创建时, 会自动清空文件中内容
- 追加字节输出流, 将第二个参数
append
设为true
06 关闭流较完善代码
package com.it.demo;
import java.io.*;
/*
比较完善的释放流的try-catch-finally代码
*/
public class CloseStreamCode {
public static void closeStream(){
// 流定义在try外边, 这样在try外面也能访问到
InputStream is = null; // 字节输入流
OutputStream os = null; // 字节输出流
try {
// 此处代码有问题时, 关闭流时, 会出现空指针异常
// 所以, 关闭流之前, 需要判空
// 创建流
is = new FileInputStream("path");
os = new FileOutputStream("path");
// 此处代码有问题时, 即位于创建流与关闭流之间的代码出问题时
// 会导致关闭流无法实现
// 所以关闭流写在finally语句中, 无论代码走try语句还是catch语句, 都会执行finally语句
// 且不论什么情况都必须执行finally语句 除非关掉虚拟机 System.exit(3);
// 不在此处关闭
// is.close();
// os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
// 判空再关闭
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
01 jdk7和jdk9释放资源的一些改进![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6d27c9f0ba2375cc5a9cd67150c64cd8.png)
jdk7代码示例
jdk9代码示例
注意事项
- **不论是jdk7还是jdk9还是一开始写的释放资源的代码, 执行完try-catch-finally语句之后, 流已经被关闭, 所以一般来说只要保证在try-catch-finally语句中能访问到资源的对象即可, **
02 什么是资源对象
jdk7和jdk9的该进, 只能在try()小括号中放置资源对象, java中实现了 Closeable/AutoCloseable
接口的类对象是资源对象, 自定义的类实现该接口, 也可以使用改进代码处理
07 FileReader extends Reader 字符输入流
代码示例
08 FileWriter extends Writer 字符输出流
写字符同写字节一样, 会自动创建文件
同样. 写数据记得刷新
04 IO 流进阶
- 缓冲流
- 转换流 – 解决乱码问题
- 序列化 – 把 java 对象长久保存
- 打印流 – 输出数据
- IO框架
01 缓冲流
- 缓冲流也称为高效流
- 作用: 缓冲流自带缓冲区, 可以提高原始字节流 字符流读写数据的性能
- 将数据读取或写入到内存的缓冲区中, 然后由底层再去操作
01 字节缓冲流
代码示例
public static void test01() throws Exception {
FileInputStream fis = new FileInputStream("A\\demo");
// 缓冲流需要原始流作为参数
BufferedInputStream bis = new BufferedInputStream(fis);
// 默认缓冲区大小 DEFAULT_BUFFER_SIZE = 8192 8kb
FileOutputStream fos = new FileOutputStream("A\\demo\\outputStreamDemo.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos); // 源码直接写了 8192
}
02 字符缓冲流
代码示例 – 按照行读文本内容
// 按照 行 读文本内容
public static void getPerLine() throws Exception {
try (FileReader fr = new FileReader("file/src/2.txt");
// 缓冲流
BufferedReader br = new BufferedReader(fr); // defaultCharBufferSize = 8192
) {
String line;
while ((line = br.readLine()) != null){
System.out.println(line); // 这里需要换行
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
代码示例
public static void getPerLine() throws Exception {
try (FileReader fr = new FileReader("file/src/2.txt");
// 缓冲流
BufferedReader br = new BufferedReader(fr); // defaultCharBufferSize = 8192
FileWriter fw = new FileWriter("file/src/3.txt");
BufferedWriter bw = new BufferedWriter(fw);
) {
String line;
while ((line = br.readLine()) != null){
System.out.println(line); // 这里需要换行
bw.write(line);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
02 转换流
当代码编码与文件编码不一致时, 出现乱码
作用: 以指定编码的格式处理字节流为字符流
01 字符输入转换流
使用字节输入流作为参数, 以指定编码读取字节输入流, 为字符输入流, 然后, 传入字符输入流, 处理数据
代码示例 – 字符输入转换流
// 转换流的使用
public static void test02(){
try(
// 使用字节流读取文件内容
FileInputStream fis = new FileInputStream("file/src/2.txt");
// 使用转换流按照指定字符集读取原始流指向的文本
// 原始字节流 ---> 转换流 ---> 指定字符集的字符流
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
// 使用 BufferedReader 输出数据
BufferedReader br = new BufferedReader(isr);
)
{
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
}catch (Exception e){
e.printStackTrace();
}
}
02 字符输出转换流
将数据以指定字符集/编码输出到文件
代码-流程-示例
03 序列化对象
作用: 以内存为基准, 把内存中的对象存储到磁盘文件中去, 称为对象序列化
使用到的流是对象字节输出流: ObjectOutputStream
– 高级流
01 ObjectOutputStream – 序列化对象到文件中
序列化对象: 将对象序列化到文件中保存
注意事项
- 序列化的对象的类需要实现接口
Serializable
构造器和方法
代码示例
实现接口 Serializable
02 对象反序列化
对象反序列化
- 使用到的流是对象字节输入流:
ObjectInputStream
- 作用: 以内存为基准, 把存储到磁盘文件中去的对象数据恢复到内存中的对象, 称为对象反序列化
对象序列化 与 反序列化 代码示例
package com.it.demo.serialization;
import java.io.Serializable;
/*
实现序列化与反序列化必须实现 类 Serializable ,
*/
public class Student implements Serializable {
// 声明序列化的版本号
private static final long serialVersionUID = 1;
// 序列化与反序列化时的版本号应一致
private String name;
private String age;
private transient String password; // 使用 transient 关键字, 在序列化时, 将次属性的值设为 null
// Student{name='张三', age='12', password='null'}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", password='" + password + '\'' +
'}';
}
public Student(String name, String age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.it.demo.serialization;
import java.io.*;
/*
对象反序列化
使用 对象字节输入流
*/
package com.it.demo.serialization;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/*
对象序列化到文件中
使用 对象字节输出流
*/
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 1. 创建学生对象
Student s = new Student("张三", "12","123456");
// 2. 创建原始流
FileOutputStream fos = new FileOutputStream("file/src/2.txt");
// 2. 创建对象字节输出流
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 调用方法
oos.writeObject(s);
oos.close();
}
}
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建流
FileInputStream fis = new FileInputStream("file/src/2.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
System.out.println(o);
}
}
04 打印流
作用: 打印流可以实现方便 高效的输出数据到文件
打印流一般指: PrintStream
PrintWriter
代码示例
小知识 – 打印流重定向
05 Properties
- 是一个 Map 集合, 但一般不当集合使用
- 核心作用:
Properties
代表的是一个属性文件, 可以把自己对象中的键值对信息存入到一个属性文件中去- 属性文件: 后缀
.properties
结尾的文件, 内容key=value
, 做配置文件使用
代码示例 – 存数据
代码示例 – 读数据
06 框架
01 commons-io
总结
IO流
-
字节流
- 操作所有类型文件 操作文本文件
-
字符流
操作所有类型文件 操作文本文件
输入 输出 输入 输出
接口 InputStream Outputstream Reader Writer
原始流 FileInputStream FileOutputStream FileReader FileWriter
把文件以字节的形式读到内存中 把文件以字节的形式输出到硬盘 把文件以字符的形式读入内存 把文件以字符的形式输出到硬盘
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter 内存与缓冲区(8kb)进行交互, 效率高
需要原始流readLine()按行读取 newLine()换行操作 readLine()按行读取 newLine()换行操作
转换流 InputStreamReader OutputStreamWriter 以指定编码的格式处理字节流为字符流,第二个参数指定编码集 需要原始流
序列化与反序列化 ObjectInputStream ObjectOutputStream 序列化的对象的类需要实现接口Serializable
, 需要原始流readObject() writerObject()
打印流 PrintStream PrintWriter 可以输出语句重定向 需要传入原始流
以字节形式输出任何类型数据print() println() 带换行 以字符形式输出任何类型数据print() println() 带换行
Properties 是一个Map集合用于.properties文件读写 需要原始流
框架 commos-io
05 多线程
01 多线程的创建
Thread
01 继承 Thread
代码示例
package com.it.createThred;
/*
创建多线程方式一
1. 创建 MyThread 类, 并继承 Thread
2. 重写 run 方法
这样 MyThread 类就是一个子线程, main方法是主要线程
3. 在main方法中, 创建 MyThread 对象
4. 调用 start() 方法, 启动子线程
*/
/*
问题:
1.
启动子线程, 调用 start 方法, 然后运行子线程对应类中的 run 方法
如果, 直接调用 run 方法, 就不是多线程
2.
子线程的相关代码写在主线程的最前面, 否则又成了单线程的执行过程
不足:
虽然这种方式代码简单,
但是子线程 继承 了Thread类, 导致后续功能扩展不便
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程" + i);
}
}
}
// test
package com.it.createThred;
public class Test {
public static void main(String[] args) {
/*
子线程的相关代码写在主线程的最前面, 否则又成了单线程的执行过程
*/
MyThread myThread = new MyThread();
myThread.start(); // 启动子线程
for (int i = 0; i < 10000; i++) {
System.out.println("主线程" + i);
}
}
}
02 实现 Runnable
代码示例
package com.it.createThred.twoWay;
/*
线程创建方式二
1. 实现 Runnable 接口
2. 重写 run 方法
3. 封装 MyThread 对象, 到 Thread 构造器中, 创建 Thread 对象t
4. 调用t的start方法, 启动子线程
*/
/*
不足:
1. 编程多一层对象包装
2. 无法接收线程的返回对象, 因为 run 方法没有返回值, 这是方式一和二的通病
*/
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程" + i);
}
}
}
// test
package com.it.createThred.twoWay;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread).start(); // 启动子线程
for (int i = 0; i < 100; i++) {
System.out.println("主线程" + i);
}
}
}
03 Callable 和 FutureTask
优点: 可以得到线程执行的结果
package com.it.createThred.threeWay;
/*
创建线程方式三:
1. 得到任务类对象
1. 创建线程类, 实现 Callable 接口
2. 重写 call 方法
3. 使用 FutureTask 封装 Callable 为线程对象
2. 把线程任务对象交给 Thread 处理
3. 调用 start 方法, 启动线程
线程执行完毕后, 通过 FutureTask 的 get 方法获取任务执行的结果
*/
import java.util.concurrent.Callable;
/*
说明
1. 获取子线程执行完毕的结果的代码位于 主线程 中
2. 当执行到对应代码处时, 如果子线程没有执行完毕, 会等待子线程的执行
3. 同时, 位于此处代码之后的代码也需要等待
*/
public class MyThread implements Callable<String> {
private int n;
public MyThread(int n){
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < n; i++) {
sum = sum + i;
}
return "子线程 " + n + " 的和" + " " + sum;
}
}
// test
package com.it.createThred.threeWay;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread(10000);
// 创建 FutureTask
FutureTask<String> ft = new FutureTask<>(myThread);
// 传入 Thread
Thread t = new Thread(ft);
t.start();
System.out.println("我一定要执行");
// 获取 子线程 返回值
try {
String s = ft.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("我执行了吗");
}
}
02 Thread常用方法
03 线程安全
- 多个线程同时操作同一个共享资源时, 可能会出现的业务安全问题, 称为线程安全问题
- 原因
- 存在多线程并发
- 同时访问并修改共享资源
04 线程同步
线程同步的核心思想
- 加锁 : 把共享资源加锁, 每次只能一个线程进入访问, 当前线程访问完毕解锁后, 其他线程才能进来
- 通过加锁, 实现多个线程先后依次访问共享资源
01 方式一 : 同步代码块
- 作用: 把出现线程安全问题的核心代码上锁
格式
synchronized(同步锁对象){
//共享资源代码
}
+ 同步锁对象
+ 理论上可以使用任意一个唯一的对象即可, 如 "heima" , 字符串字面量, 但是这种方式, 容易影响其他线程
+ 锁对象的规范要求
+ 规范上: 建议使用共享资源作为锁对象
+ 对于实例方法建议使用 this 作为锁对象 -- 共享资源: 共用一个实例对象
+ 对于静态方法建议使用字节码 (类名.class) 对象作为锁对象 -- 共享资源: 共用一个类
代码示例
02 方式二 同步方法
- 作用: 把出现线程安全问题的核心方法给上锁
- 每次只能进入一个线程, 执行完毕后自动解锁, 其他线程才可以进入
格式
修饰符 synchronized 返回值类型 方法名称(形参列表){
// 共享资源的代码
}
代码示例
03 方式三 Lock锁
官方示例
代码示例
05 线程通信
- 线程通信就是线程间相互发送数据, 线程通信通常通过共享一个数据的方式实现
线程间通信, 即消费者和生产者通信, 需要自己等待, 唤醒对方, 下图是一些对应的api
位于 Object 类中, 说明任意一个类都可以执行这些操作
但是, 应使用当前同步锁对象调用- 实例方法用 this
- 静态方法用 类名.
06 线程池
- 线程池就是一个可以复用线程的技术
- 因为创建新线程的开销是很大的, 这样会严重影响系统的性能
JDK5.0 起提供了代表线程池的接口: ExecutorService
获得线程池对象有两种方式: - 方式一 使用
ExecutorService
的实现类ThreadPoolExecutor
自创建一个线程池对象 - 方式二 使用
Executors
线程池的工具类 调用方法返回不同特点的线程池对象
01 方式一
创建线程池对象并使用
新任务拒绝策略
代码示例
package com.it.useThreadPool;
import java.util.concurrent.Callable;
public class MyThreadCal implements Callable<String> {
private int n;
public MyThreadCal(int n){
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < n; i++) {
sum = sum + n;
}
return Thread.currentThread().getName() + " 的结果是 " + sum;
}
}
--------------------------------
package com.it.useThreadPool;
import java.util.concurrent.Callable;
public class MyThreadCal2 implements Callable<String> {
private int n;
public MyThreadCal2(int n){
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < n; i++) {
sum = sum + n;
}
Thread.sleep(10000);
return Thread.currentThread().getName() + " 的结果是 " + sum;
}
}
--------------------
package com.it.useThreadPool;
public class MyThreadRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "--> 输出 --> " + i);
}
}
}
-------------------
package com.it.useThreadPool;
import java.util.concurrent.*;
public class ThreadDemo01 {
public static void main(String[] args) {
//test01();
test02();
}
/*
使用 ExecutorService 的实现类 ThreadPoolExecutor 创建线程池, 执行多个线程任务 Runnable
*/
public static void test01(){
Runnable target = new MyThreadRun();
ThreadPoolExecutor pools = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
pools.execute(target);
pools.execute(target);
pools.execute(target);
pools.execute(target);
pools.execute(target);
}
/*
使用 ExecutorService 的实现类 ThreadPoolExecutor 创建线程池, 执行多个线程任务 Callable
*/
public static void test02(){
Callable target1 = new MyThreadCal(10);
Callable target2 = new MyThreadCal(10);
Callable target3 = new MyThreadCal(10);
Callable target4 = new MyThreadCal2(10);
ThreadPoolExecutor pools = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
try {
long start = System.currentTimeMillis();
System.out.println(pools.submit(target1).get());
System.out.println(pools.submit(target2).get());
System.out.println(pools.submit(target4).get());
System.out.println(pools.submit(target3).get());
long end = System.currentTimeMillis();
System.out.println(end - start); // 10009
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
02 方式二
可能存在的问题
07 定时器
01 Timer
Timer
定时器是有问题的如下图
代码示例
02 ScheduledExecutorService
Timer and ScheduleExecutorService 的代码示例
package com.it.useThreadPool.executors;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.zip.DataFormatException;
public class TimerDemo {
public static void main(String[] args) {
//testTimer();
testScheduleExecutor();
}
private static void testScheduleExecutor() {
// 基于线程池 某个任务的执行情况不会影响其他定时任务的执行
// ScheduleExecutorService是为了弥补timer的缺陷, 内部为线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3); // 共有3个线程池
// 多个任务使用不同的线程池
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "a");
}
},0,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "b");
}
},0,2, TimeUnit.SECONDS);
}
public static void testTimer(){
/*
Timer 是单线程, 处理多个任务按照顺序执行, 存在延时与设置定时器的时间有出入
当其中一个任务出现异常时, 会导致所有任务无法执行
*/
// 创建 Timer 对象
Timer timer = new Timer();
// 调用方法
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "aaa" + new Date());
}
},2,2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "bbb" + new Date());
}
},2,2000);
}
}
08 并发与并行
09 线程的声明周期
06 网络编程
网络通信模式有两种:
- CS: Client-Server
- BS: Browser-Server
01 网络通信三要素
三要素
- IP地址: 设备(计算机)在网络中的地址, 是设备的唯一标识
- 端口: 应用程序在设备(计算机)中唯一的标识
- 协议: 数据在网络中传输的规则, 常见的协议有UDP和TCP
01 ip
InetAddress 操作 IP 的相关类
代码示例
02 端口号
- 端口号: 标识正在计算机设备上运行的进程(程序), 被规定为一个 16 位的二进制, 范围是 0-65535
- 端口类型
- 周知端口 0-1023 (HTTP - 80, FTP - 21)
- 注册端口: 1024 - 49151 分配给用户进程或某些应用程序
- 动态端口: 49152 - 65535
03 协议
UDP
01 UDP
- UDP 是一种无连接 不可靠传输的协议
- 将数据源ip 目的地ip 和端口以及数据封装成数据包, 大小限制在64kb内, 直接发送数据包
- 发送端发送数据包
- 接收端接收数据包, 可以接收多个发送端的数据, tcp默认只能接收一个客户端
多发多收
代码示例
package com.it.UDP;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
/*
UDP
1. 将数据打成一个包发送给接收端
2. 不会确认对方是否收到, 不太可靠, 高效
3. 应用: 语音视频通话 弹幕
相关类
1. 厨师: DatagramSocket 发送接收端
2. 盘子: DatagramPacket 数据包
*/
public class ClientDemo {
public static void main(String[] args) throws Exception{
sendTest();
}
/*
发送端
*/
public static void sendTest() throws Exception {
// 创建 DatagramSocket 发送端对象 自带默认端口
DatagramSocket sendSocket = new DatagramSocket();
// 创建 DatagramPacket 使用数据包对象封装数据
/*
byte buf[],
int offset,
int length,
InetAddress address,
int port
*/
byte[] buf ={0}; // 大小为 64kb
String msg;
Scanner sc = new Scanner(System.in);
DatagramPacket sendPacket = new DatagramPacket(buf,buf.length, InetAddress.getLocalHost(),6666);
while (true){
System.out.println("请说:");
msg = sc.nextLine();
if ("exit".equals(msg)){
System.out.println("退出");
sendSocket.close();
break;
}
buf = msg.getBytes();
sendPacket.setData(buf);
sendSocket.send(sendPacket);
}
}
}
package com.it.UDP;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Date;
public class ServerDemo {
public static void main(String[] args) throws Exception{
receiveTest();
}
private static void receiveTest() throws Exception {
// 创建 DatagramSocket
DatagramSocket receiveSocket = new DatagramSocket(6666);
// 创建数据包接收数据
byte[] buf = new byte[1024*64];
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
while (true) {
// 等待接收数据
receiveSocket.receive(receivePacket);
// 取出数据
int len = receivePacket.getLength();
String msg = new String(buf,0,len);
System.out.println("接收到来自 ip " + receivePacket.getAddress() + " 的数据: " + msg + " " + new Date());
}
}
}
01 UDP通信 - 广播 组播
代码示例
02 TCP 通信
TCP协议
- TCP是一种面向连接 安全 可靠的传输数据的协议
- 采用"三次握手"方式, 点对点通信, 是可靠的
- 在连接中可进行大数据量的传输
代码示例
note
- 使用
while
语句处, 接收数据与发送数据步伐不一致, 可能报错, 这是因为客户端已经关闭, 服务端也需要关闭
TCP - 服务端不可以同时接收多个客户端的数据, 这是因为服务端是单线程的, 每次只能处理一个客户端的Socket消息
使用线程池处理多个客户端
部分代码示例
TCP通信实战案例 - 即时通信
- 即时通信, 指一个客户端的消息发出去, 其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想
- 服务端需要把在线的 Socket 管道存储起来
- 一旦收到一个消息就要推送给其他管道
07 辅助知识
01 单元测试
- 单元测试: 针对最小的功能单元编写测试代码, java 程序最小的功能单元是方法, 因此, 单元测试就是针对 java 方法的测试, 进而检查方法的正确性
代码示例
一些常用注解
02 反射
反射概述
- 反射是指对于任何一个 Class 类, 在运行的时候可以直接拿到这个类的字节码文件, 获取类的全部成分
- 反射的第一步都是先得到编译后的 Class 类对象, 然后就可以得到 Class 的全部成分
代码示例
代码示例
代码示例
代码示例
代码示例
01 反射的作用
反射的作用 - 绕过编译阶段为集合添加数据
- 反射是作用在运行时的技术, 此时集合的泛型将不能产生约束了, 此时可以为集合存入其他任意类型的元素
- 泛型只是在编译阶段可以约束集合中元素的数据类型, 在编译成 Class 文件进入运行阶段的时候, 其真实类型都是 ArrayList 了, 泛型相当于被擦除了
代码示例
02 反射的作用 - 通用框架的底层原理
代码示例
02 注解
- 注解 (Annotation) 又称 java 标注, 是 JDK5 引入的一种注释机制
- java 语言中的 类 构造器 方法 成员变量 参数 等都可以被注解进行标注
01 自定义注解
自定义注解格式
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
注意事项
- value 属性, 如果只有一个 value 属性的情况下, 使用 value 属性的时候可以省略 value 名称不写
- 但是如果有多个属性, 且多个属性没有默认值, 那么 value 名称是不能省略的
02 元注解
- 元注解: 就是注解的注解
@Target
约束自定义注解只能在哪些地方使用@Retention
申明注解的生命周期
03 注解解析
04 动态代理
- 代理: 主要是对对象的行为额外做一些辅助操作
- 实现动态代理的步骤
- 必须存在接口
- 被代理的对象需要实现接口
- 使用 proxy 类提供的方法, 处理原对象的方法
- 执行流程
- 先走向代理
- 代理可以实现原方法额外的一些辅助工作
- 触发原对象的方法并执行
- 返回代理, 由代理负责后续处理即返回结果给方法的调用者
- 优点
- 可以在不改变方法源码的情况下, 实现对方法功能的增强, 提高代码的复用, 降低冗余
- 简化了编程工作 提高了开发效率
- 提高了软件的可扩展性
- 可以为被代理对象的所有方法做代理
- 非常灵活, 支持任意接口类型(使用泛型)的实现类对象做代理, 也可以直接为接口本身做代理
如何创建代理对象
- java 中代理的代表类是:
java.lang.reflect.Proxy
Proxy
提供了一个静态方法, 用于为对象产生一个代理对象返回
代码示例
非常灵活, 支持任意接口类型(使用泛型)的实现类对象做代理, 也可以直接为接口本身做代理
08 XML
XML 可扩展标记语言 extensible markup language
特点
- xml 是纯文本 默认使用 utf-8 可嵌套
- xml 内容经常被当成消息进行网络传输, 或者作为配置文件用于存储系统的信息
- xml 文件第一行必须是声明内容
- xml 根标签只能有一个
01 文档约束
不要随便写, 要有规矩
01 DTD
代码示例
02 schema
代码示例
02 xml 解析
- SAX 解析
- DOM 解析
DOM 解析文档对象模型
01 Dom4j 解析 xml 文件
代码示例
03 xml 检索技术 – XPath
Xpath
- XPath 在解析 xml 文档方面提供了独树一帜的路径思想, 更加优雅 高效
- XPath 使用
路径表达式
来定位 xml 文档中的元素节点或属性节点
09 补充
01 工厂设计模式
- 不使用 new 创建对象, 使用工厂模式创建对象, 属于创建型模式, 提供了一种获取对象的方式
- 工厂模式可以封装对象创建的细节, 如对对象进行加工和数据注入, 创建的对象更加具体
- 可以实现类与类之间的解耦
02 装饰模式
作用: 在不改变原类的基础上, 动态地扩展一个类的功能 1:1 加强
优点类似代理