学习视频链接:https://www.bilibili.com/video/BV1fh411y7R8?p=1&vd_source=1635a55d1012e0ef6688b3652cefcdfe
(本文出现的程序和图片参考视频)
目录
线程的基本使用(两种方法)
补充一下程序、进程和线程之间的关系:进程是操作系统对于一个正在运行的程序的抽象,进程是操作系统资源分配的基本单位;我们编写的源程序在被编译之后只不过是位于磁盘的静态的二进制数据,他们需要被加载到内存,运行在进程的上下文中;线程由进程创建,是进程的一个实体,进程可进一步细化为线程,一个进程可以拥有多个线程,线程是一个程序内部的一条执行路径。
注:main是主线程,线程里面可以在开子线程,并且主线程不需要等到子线程执行完之后才退出,是进程等到全部线程执行完之后才退出。
1.继承Thread类,重写run方法
继承了Thread类之后,就可以将所需要执行的程序写到run方法中等待调用,这里要清楚的是run方法本身就只是一个普通的方法,真正使用到多线程要到main中调用start方法,而start方法里面是本地方法start0(这个start0是底层方法,由JVM调用),start0会用多线程的执行方式来执行run方法中的内容。
所以从表明上看,run方法是给我们装要用多线程实现的内容的,但是多线程执行方式的开启可不是我们能管的
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建 Cat 对象,可以当做线程使用
Cat cat = new Cat();
cat.start();//启动线程-> 最终会执行 cat(start方法是继承Thread类的方法)
System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字 main
for(int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
//让主线程休眠
Thread.sleep(200);
}
}
}
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们需要重写 run 方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的 run 方法
class Cat extends Thread {
int times = 0;
@Override
public void run() {//重写 run 方法,写上自己的业务逻辑
while (true) {
System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break;//当 times 到 80, 退出 while, 这时线程也就退出.. }
}
}
}
}
2.实现 Runnable 接口
为什么会有这种方法呢?因为Java是单继承机制,如果一个子类也想要实现多线程的执行,显然是不可能再继承Thread类了,这里就可以通过实现Runnable 接口,然后实现Runnable 接口里的run方法。
在调用时有一个注意点:如果一个类没有继承Thread类,那么那个类也不会有start方法,也就是不能直接用类名.start来开启多线程,这里就有一个新的设计模式——静态代理模式(下面模拟执行流程)
//线程代理类 实现Runnable接口,模拟代理模式,
//这里的ThreadProxy是模拟实际的Thread类对传入的 实现Runnable接口的类的run方法 的执行方式
class ThreadProxy implements Runnable {
private Runnable target = null;
@Override
public void run() {//第四步调用本类的run方法
if (target != null) {
target.run();//第五步,此时的target已经不是空了,就动态绑定target的run方法,这里的执行就是多线程的执行方式
//这个传进来的target就是我们实现了Runnable接口的那个类,就是通过这种方式调用我们重写的run方法
}
}
public ThreadProxy(Runnable target) {//第一步先调用构造器绑定target
this.target = target;
}
public void start() {//第二步调start方法
start0();
}
public void start0() {//第三步调用start0方法
run();//这是ThreadProxy的run
}
}
1.ThreadProxy的作用就是拉起一个跟main主线程并行的子线程 然后这个子线程执行传入的实现了runnable的类的run方法,所以叫代理
2.使用继承Thread类的方法的缺点就是数据不能共享,只有将数据定义为static时才可以共享数据,而实现Runnable接口的好处就是数据可以共享,因为Runnable的方式是用同一个对象
//多线程售票
public class myCode {
public static void main(String[] args) throws InterruptedException {
SellTicket sellTicket1 = new SellTicket(100);
new Thread(sellTicket1).start();//开启上述对象的第一个线程
new Thread(sellTicket1).start();//开启上述对象的第二个线程
new Thread(sellTicket1).start();//开启上述对象的第三个线程
}
}
class SellTicket implements Runnable{
private int ticket;
@Override
public void run() {
while (true) {
if (ticket <= 0) {
System.out.println("门票销售结束!");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票"
+ " 剩余票数:" + --ticket);
}
}
public SellTicket(int ticket) {
this.ticket = ticket;
}
}
线程终止
- 线程完成任务后自动退出
- 通过线程A使用标记变量来控制线程B的run方法退出,即通知方式
线程的常用方法
- setName
- getName
- start:开启线程
- setPriority:更改线程优先级
- getPriority:获取线程优先级
- sleep:当前线程休眠【静态方法】
- interrupt:当前线程打断
- yield:让出cpu给其他线程(给的时间不确定,也不确保一定会给成功)
- join:线程插队,插队成功后一定先执行完插队线程的全部任务(这里就不会有轮流占用CPU了)
public class myCode {
public static void main(String[] args) throws InterruptedException {
//顺便提醒一下sleep是有可能出现被打断的异常的,所以需要处理异常,要么像现在main这样抛出异常,要么try catch处理
Test test = new Test();
test.start();
test.setName("小明");
for (int i = 0; i < 20; i++) {
System.out.println("main主线程工作中... 计数:" + i);
if (i == 4) {
test.interrupt();
}
Thread.sleep(500);
}
}
}
class Test extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "在吃第" + i +"个包子");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "的休眠被中断,需要继续执行下一轮程序");
}
}
}
}
用户线程和守护线程
用户线程(工作线程):当线程任务完成后自动退出和以通知方式结束的线程
守护线程:一般为工作线程服务,用户线程结束后自动结束(没有设置成守护线程的话都是用户线程)
MyDaemonThread dt = new MyDaemonThread();
dt.setDaemon(true);//将dt设置为守护线程
dt.start();//先设置为守护线程,再开启线程,不要颠倒顺序
线程状态(矩形框里面的)
线程同步机制
主要解决的是多个线程同时进入run方法,然后卖了同一张票(前面的售票问题)
public class myCode {
public static void main(String[] args) throws InterruptedException {
SellTicket sellTicket1 = new SellTicket(100);
new Thread(sellTicket1).start();//开启上述对象的第一个线程
new Thread(sellTicket1).start();//开启上述对象的第二个线程
new Thread(sellTicket1).start();//开启上述对象的第三个线程
}
}
class SellTicket implements Runnable{
private int ticket;
private boolean loop = true;
private synchronized void sell() {//对同一个对象的多个线程才能使用同步
if (ticket <= 0) {
loop = false;
System.out.println("门票销售结束!");
return;
}
try {
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票"
+ " 剩余票数:" + --ticket);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
public SellTicket(int ticket) {
this.ticket = ticket;
}
}
互斥锁
前面的synchronized就是实现线程同步的方法,而它的本质就是为当前对象套上一个对象互斥锁,具体用法看代码吧
1.上面的“线程同步机制”里的代码就是将synchronized加在普通方法上,实际上就是加锁在this(调用方法的对象上)
2.锁加在代码块上,不过锁实际上也是加在this上
public class myCode {
public static void main(String[] args) throws InterruptedException {
SellTicket sellTicket1 = new SellTicket(100);
new Thread(sellTicket1).start();//开启上述对象的第一个线程
new Thread(sellTicket1).start();//开启上述对象的第二个线程
new Thread(sellTicket1).start();//开启上述对象的第三个线程
}
}
class SellTicket implements Runnable{
private int ticket;
private boolean loop = true;
private void sell() {//对同一个对象的多个线程才能使用同步
synchronized (this) {
if (ticket <= 0) {
loop = false;
System.out.println("门票销售结束!");
return;
}
try {
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票"
+ " 剩余票数:" + --ticket);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
public SellTicket(int ticket) {
this.ticket = ticket;
}
}
3.当然也可以不加在this上如下面的新定义的object,注意这里是实现Runnable的才能这样,继承Thread的就不是同一个对象了
public class myCode {
public static void main(String[] args) throws InterruptedException {
SellTicket sellTicket1 = new SellTicket(100);
new Thread(sellTicket1).start();//开启上述对象的第一个线程
new Thread(sellTicket1).start();//开启上述对象的第二个线程
new Thread(sellTicket1).start();//开启上述对象的第三个线程
}
}
class SellTicket implements Runnable{
private int ticket;
private boolean loop = true;
Object object = new Object();
private void sell() {//对同一个对象的多个线程才能使用同步
synchronized (object) {
if (ticket <= 0) {
loop = false;
System.out.println("门票销售结束!");
return;
}
try {
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票"
+ " 剩余票数:" + --ticket);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
public SellTicket(int ticket) {
this.ticket = ticket;
}
}
4.在静态方法或者静态代码块上加锁,将相当于将锁加在类上
public class myCode {
public static void main(String[] args) throws InterruptedException {
SellTicket sellTicket1 = new SellTicket(100);
new Thread(sellTicket1).start();//开启上述对象的第一个线程
new Thread(sellTicket1).start();//开启上述对象的第二个线程
new Thread(sellTicket1).start();//开启上述对象的第三个线程
}
}
class SellTicket implements Runnable{
private int ticket;
private boolean loop = true;
Object object = new Object();
//注意!!!,看这两个函数m1、m2,下面的sell不是重点
public synchronized static void m1() {
//加锁在 SellTicket.class
}
public static void m2() {
//注意如果是加载静态方法里面的代码块,就要写SellTicket.class,而不是前面那样写this
synchronized (SellTicket.class) {
//这是一个代码块
}
}
private void sell() {//对同一个对象的多个线程才能使用同步
synchronized (object) {
if (ticket <= 0) {
loop = false;
System.out.println("门票销售结束!");
return;
}
try {
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票"
+ " 剩余票数:" + --ticket);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
public SellTicket(int ticket) {
this.ticket = ticket;
}
}
死锁和释放锁(释放锁的时机上面那个彩图也有,出了中间那个蓝色的框框就是释放锁)
死锁就不说明了,说明一下释放锁的时机:
- 当前线程的同步方法、同步代码块执行完成【上完厕所就得出来】
- 当前线程在同步方法、同步代码块遇到break、return【厕所报警了,要出去】
- 当前线程在同步方法、同步代码块出现未处理的Error或Exception【上厕所忘带手机,要出去拿手机】
- 当前线程在同步方法、同步代码块中执行线程对象的wait方法,线程就会暂停并释放【】
不释放锁的时机
- 当前线程在执行同步方法、同步代码块,程序调用Thread.sleep()、Thread.yield()暂停当前线程的执行,但不会退出【厕所里睡着了】
- 调用suspend、resume
IO流
创建文件的方法(三种)
public static void main(String[] args) {
}
//方式 1 new File(String pathname)
@Test
public void create01() {
String filePath = "e:\\news1.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式 2 new File(File parent,String child) //根据父目录文件+子路径构建
//e:\\news2.txt
@Test
public void create02() {
File parentFile = new File("e:\\");
String fileName = "news2.txt";
//这里的 file 对象,在 java 程序中,只是一个对象
//只有执行了 createNewFile 方法,才会真正的,在磁盘创建该文件
File file = new File(parentFile, fileName);
try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
//方式 3 new File(String parent,String child) //根据父目录+子路径构建
@Test
public void create03() {
String parentPath = "e:\\";
String fileName = "news4.txt";
File file = new File(parentPath, fileName);
try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}
获取文件的信息
@Test
public void info() {
//先创建文件对象
File file = new File("e:\\news1.txt");
//调用相应的方法,得到对应信息
System.out.println("文件名字=" + file.getName());
//getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
System.out.println("文件绝对路径=" + file.getAbsolutePath());
System.out.println("文件父级目录=" + file.getParent());
System.out.println("文件大小(字节)=" + file.length());
System.out.println("文件是否存在=" + file.exists());//T
System.out.println("是不是一个文件=" + file.isFile());//T
System.out.println("是不是一个目录=" + file.isDirectory());//
)
流的分类
节点流举例
FileInputStream 和FileOutputStream 应用实例
package File;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class f1 {
public static void main(String[] args) {
}
@Test
public void readFile01() {
String filePath = "D:\\Java\\text1.txt";//自己去新建一个text1.txt文件,随便写点字母
int readData = 0;
FileInputStream fileInputStream = null;//引入FileInputStream类
try {
//根据路径创建 FileInputStream对象,用于读取文件(这里的前提是文件已经存在,所以下面处理异常的方式就是随便写的)
fileInputStream = new FileInputStream(filePath);//这里会产生 文件找不到 的异常,所以try catch处理一下
//从该输入流读取一个字节的数据,如果没有输入可用,此方法将阻止。
//如果返回-1 , 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char) readData);//转成 char 显示
// 如果待读数据是汉字或者另类的字符,就会被拆成一个个字节后转成char显示,就会变成乱码(解决方案就是用字符流读取)
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();//这里有可能出现异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void readFile02() {
String filePath = "D:\\Java\\text1.txt";
byte[] buf = new byte[8]; //定义字节数组,一次读取 8 个字节
int readLen = 0;
FileInputStream fileInputStream = null;//引入FileInputStream类
try {
//创建 FileInputStream 对象,用于读取文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多 b.length 字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
//如果读取正常, read返回实际读取的字节数,如果返回-1表示读取完毕,
while ((readLen = fileInputStream.read(buf)) != -1) {//将字节数组传进去
System.out.print(new String(buf, 0, readLen));//构造字符串显示
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void writeFile() {
String filePath = "D:\\Java\\writeTest.txt";//输出的文件名,下面调方法时,如果在目录没找到这个文件,就会根据这个名字创建一个文件
FileOutputStream fileOutputStream = null;//创建 FileOutputStream 对象
try {
//1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
//2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
//fileOutputStream.write('H');
//写入字符串
String str = "ABC,world!";
/*
write(byte[] b, int off, int len) 将 len 字节从位于偏移量 off 的指定字节数组写入此文件输出流
*/
fileOutputStream.write(str.getBytes(), 0, 3);//注意这个是逐个字节进行写入,所以才需要将字符串转成字节数组
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();//关闭文件流有可能会出异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class f1 {
public static void main(String[] args) {
//结合FileInputStream和FileOutputStream复制粘贴文件
String srcFilePath = "D:\\Java\\copyTest.jpg";//读取文件的路径
String destFilePath = "D:\\Java\\copyTestOut.jpg";//输出文件的路径
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(srcFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
//不加TRUE的原因:下面整个文件读取都是在一个流里的,不存在覆盖的问题,当然如果又来一个新的FileOutputStream,不加TRUE就会覆盖
byte[] buf = new byte[1024];//定义一个字节数组,提高读取效率,一次读取1kb
int readLen = 0;
while ((readLen = fileInputStream.read(buf)) != -1) {
//读取到就写入到文件,通过fileOutputStream(边读边写)
fileOutputStream.write(buf, 0, readLen);//这里是一定要用readLen,避免输入无意义字节
// 例如1026个,第一次读取1024,剩下就应该只读2个,而不是2个有意义的+1022个无意义的破坏数据
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭输入流和输出流,释放资源
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileReader 和 FileWriter 应用案例
package File;
import org.junit.jupiter.api.Test;
import java.io.FileReader;
import java.io.IOException;
public class f1 {
public static void main(String[] args) {
}
@Test
public void readFile01() {
String filePath = "D:\\Java\\text2.txt";//自己去新建一个text2.txt文件,随便写点东西(字母、汉字、符号都写一点)
FileReader fileReader = null;
int data = 0;
//1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用 read, 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();//文件流一定记得关了,不关的话数据是写不进去的
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void readFile02() {
String filePath = "D:\\Java\\text2.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
//1. 创建 FileReader 对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用 read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package File;
import java.io.FileWriter;
import java.io.IOException;
public class f1 {
public static void main(String[] args) {
String filePath = "D:\\Java\\writeTest.txt";
//创建 FileWriter 对象
FileWriter fileWriter = null;
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath);
//FileWriter默认是覆盖写入方式,如果在一个流里就不会覆盖(例如下面的就不会覆盖)
// 3) write(int):写入单个字符
fileWriter.write('H');
// 4) write(char[]):写入指定数组
fileWriter.write(chars);
// 5) write(char[],off,len):写入指定数组的指定部分
fileWriter.write("东八区背景".toCharArray(), 0, 3);
// 6) write(string):写入整个字符串
fileWriter.write(" 你好北京~");
fileWriter.write("天子守国门");
// 7) write(string,off,len):写入字符串的指定部分
fileWriter.write("上海天津", 0, 2);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//fileWriter.flush();
//关闭文件流,等价 flush() + 关闭,不close就相当于数据没写
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("程序结束...");
}
}
节点流和处理流的区别
- 节点流就是底层流,直接和数据源相接(前面的四个就是例子);
- 处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法完成输入输出;
- 处理流使用修饰器设计模式,不会直接与数据源相连
处理流的功能:
1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率;
2.操作的便捷:处理流提供一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活(多态+动态绑定)
多态+动态绑定:这里回顾一下两者的理解,多态就是父类A的引用 指向 子类B的对象,动态绑定就是如果B类中重写了A类的部分方法,那么用指向B类的 A类引用在实际调用方法时用的就是B类的方法,当然B类自己的方法(A类没有的),指向B类的 A类引用是不能调用直接调用的,必须向下转型为B类,例如下面的演示:
package File;
public class temp {
public static void main(String[] args) {
A a = new A();//父类引用指向父类对象 OK
System.out.println(a.aNum);//指向父类对象的父类引用调用自己的属性 OK
A b = new B();//父类引用指向子类对象--->多态 OK
System.out.println(b.aNum);//指向子类对象的父类引用调用继承父类的属性 OK
System.out.println(b.bNum);//指向父类对象的父类引用调用子类独有的属性 NO
B b_2 = (B)b;//将父类引用向下转型
System.out.println(b_2.bNum);//显式地向下转型后,调用子类独有的属性 OK
a.print();//指向父类对象的父类引用变量调用父类的方法 OK 输出"A的方法"
b.print();//指向子类对象的父类引用变量自动调用 子类 对父类方法重写后的 方法--->动态绑定 OK 输出"B重写A的方法"
b.print_B();//指向子类对象的父类引用变量调用 非重写父类的方法 NO
b_2.print_B();//显式地向下转型后,调用 非重写父类的方法 OK 输出"B专属"
//即如果想要 指向子类对象的父类引用不显式地向下转型去转换类型,想要利用动态绑定机制,
//就必须在父类也弄一个这样的方法,子类就变成重写方法,就可以通过运行类型去调用对应类型的方法
}
}
class A {
public int aNum;
public void print() {
System.out.println("A的方法");
}
}
class B extends A {
public int bNum;
public void print() {
System.out.println("B重写A的方法");
}
public void print_B() {
System.out.println("B专属");
}
}
在清楚上述的概念后就可以理解处理流的构成了,处理流的目的就是将对不同数据源的相同操作统一,例如对文件的读取和对数组的读取,现在需要只使用一个读取方法就能做到对任意数据源的读取:
1.底层的节点流对文件读取方法和对数组读取方法是固定的,我们只能用这些方法,不能修改这些方法;
2.如果想要扩展功能的话,就需要新建一个类,并且我们不仅仅是对一种节点流的功能进行扩展,而是多所有节点流进行扩展,那就可以用到抽象父类来接收所有实现抽象类的节点流子类,调用节点流来完成操作,再将不同的操作组合成新的面向用户更容易直接理解的操作就是处理流的目的。
处理流-BufferedReader 和 BufferedWrite
BufferedReader 和 BufferedWrite属于字符流,按照字符进行数据读取,在关闭处理流时只需要关闭外层流
package File;
import org.junit.jupiter.api.Test;
import java.io.*;
public class f1 {
public static void main(String[] args) {
}
@Test
public void read_() throws Exception {
String filePath = "D:\\Java\\writeTest.txt";
//创建 bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
String line; //按行读取, 效率高
//1. bufferedReader.readLine() 是按行读取文件
//2. 当返回 null 时,表示文件读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动去关闭 节点流
bufferedReader.close();
}
@Test
public void write_() throws Exception {
String filePath = "D:\\Java\\writeTest.txt";
//创建 BufferedWriter
//说明:
//1. new FileWriter(filePath, true) 表示以追加的方式写入
//2. new FileWriter(filePath) , 表示以覆盖的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello!");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("hello2!");
bufferedWriter.newLine();
bufferedWriter.write("hello3!");
bufferedWriter.newLine();
//关闭外层流即可,传入的new FileWriter(filePath)会在底层关闭
bufferedWriter.close();//没close之前都是一个流
}
}
处理流-BufferedInputStream 和 BufferedOutputStream
BufferedInputStream和BufferedOutputStream是字节流,在创建BufferedInputStream时会创建一个内部缓冲区数组,BufferedOutputStream可以将多个字节写入底层输出流,不必对每次字节写入调用底层系统
package File;
import org.junit.jupiter.api.Test;
import java.io.*;
public class temp {
public static void main(String[] args) {
}
@Test
public void Test() {
String srcFilePath = "D:\\Java\\text2.txt";
String destFilePath = "D:\\Java\\text22.txt";
//创建 BufferedOutputStream 对象 BufferedInputStream 对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//因为 FileInputStream 是 InputStream 子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环的读取文件,并写入到 destFilePath
byte[] buff = new byte[1024];
int readLen = 0;
//当返回 -1 时,就表示文件读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}
System.out.println("文件拷贝完毕~~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象流-ObjectInputStream 和 ObjectOutputStream
序列化就是保存数据时,将数据的值和数据类型都要保存,反序列化就是恢复数据时将数据的值和数据类型都恢复;
如果需要一个对象是可序列化的,那么其类就必须要求可序列化,即要么实现Serializable接口(这个接口的实现不用实现方法),要么实现Externalizable接口(这个接口里面有方法需要实现);
小细节:
1.读写顺序要一致;
2.序列化的类中建议添加 SerialVersionUID以提高版本的兼容性;
3.序列化对象时,默认将里面所有属性都进行序列化,但除了 static 或 transient 修饰的成员;
4.序列化对象时,要求里面属性的类型也需要实现序列化接口;
5.序列化具备可继承性,即如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
package File;
import org.junit.jupiter.api.Test;
import java.io.*;
public class temp {
public static void main(String[] args) {
}
@Test
public void Test() throws IOException {
String filePath = "D:\\Java\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到D:\Java\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("爱心茶多酚");//String
//保存一个 dog 对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
@Test
public void Test2() throws Exception {
// 1.创建流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Java\\data.dat"));
// 2.读取, 注意顺序
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject());
// 3.关闭
ois.close();
System.out.println("以反序列化的方式读取(恢复)ok~");
}
}
class Dog implements Serializable{
private String name;
private int age;
private String birth;
private String color;
public Dog(String name, int age, String birth, String color) {
this.name = name;
this.age = age;
this.birth = birth;
this.color = color;
}
}
标准输入输出流(System.in和System.out)
System.in的编译类型是InputStream,运行类型是BufferInputStream
System.out的编译类型是printStream,运行类型是printStream
转换流-InputStreamReader 和 OutputStreamWriter(字节流转换为字符流)
在默认情况下按照UTF-8进行读取,字节流可指定编码方式,转换流就是将字符流转为字节流,字节流再按特定的编码方式转为字符流
InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)
OutputStreamReader:Writer的子类,可以将OutputStream(字节流)包装成Writer(字符流)
打印流-PrintStream 和 PrintWrite
package File;
import org.junit.jupiter.api.Test;
import java.io.*;
public class temp {
public static void main(String[] args) {}
@Test
public void Test() throws Exception {
//PrintStream字节流
System.setOut(new PrintStream("D:\\Java\\writeTest.txt"));
System.out.println("hello");
}
@Test
public void Test2() throws Exception {
//PrintWriter字符流
PrintWriter printWriter = new PrintWriter(new FileWriter("D:\\Java\\writeTest.txt"));
printWriter.print("死锁");
printWriter.close();
}
}