系列文章目录
前言
本文我们将介绍IO流和多线程,IO流用对文件内容进行操作或对文件属性进行操作,多线程用于多个线程同时对数据进行处理。在下攸攸太上,背靠人山人海,享受海阔天空。
一、IO流
1. IO流介绍
I: input输入(从程序外输出到到程序里)
O: output输出(从程序里输出到到程序外)
流(通道): 输送方法
2. File工具类
1、分析参与者,涉及到文档对象。提取一个类,确定类名:File(文档类File类)
2、分析参与者的属性
3、分析参与者的行为(我们不太关注File的行为)
Windows系统的地址分隔符为\\,Linix系统的地址分隔符为/
File.separator:自动获得适合当前系统的分隔符
2.1 操作文件
常用方法代码:
File file = new File("D:" + File.separator + "for_codes\\for_java\\JiRuanJava\\240729_Demo1\\240729.txt");
System.out.println("获得文件名:" + file.getName());
System.out.println("判断文件是否存在:" + file.exists());
System.out.println("判断文件是否是目录(文件夹)(返回true是文件夹):" + file.isDirectory());
System.out.println("判断是否是文件:" + file.isFile());
System.out.println("获得文件大小:" + file.length());
//System.out.println("删除文件+判断:" + file.delete() + ":" + file.exists());
System.out.println("判断上一级目录(文件夹):" + file.getParent());
System.out.println("创建(需要throws IOException):" + file.createNewFile());
System.out.println("是否隐藏:" + file.isHidden());
System.out.println("文件的大小:" + file.length());
System.out.println("是否存在:" + file.exists());
System.out.println("文件是否可读:" + file.canRead());
System.out.println("文件是否可写:" + file.canWrite());
运行结果:
相对路径:
相对路径:相对于当前代码文件位置的路径".\\“代表包含本文件的文件夹下的”..\\"代表包含包含本文件的文件夹的文件夹。
绝对路径:
从C盘开始往下捋,显示途中所有的文件夹
2.2 操作目录(文件夹)
2.2.1 创建删除目录
代码:
public static void main(String[] args) throws IOException {
new Test().getFileInfo("D:\\Program Files");
}
public void getFileInfo(String file){
File file2 = new File(file);
File[] files = file2.listFiles();
for (File file3: files){
if (file3.isDirectory()){
getFileInfo(file3.getPath());
}
System.out.println(file3);
}
}
运行结果:
只删除了Three,多层删除需要循环
2.2.2 模拟杀毒软件(读取所有文件)
代码:
new Test().getFileInfo("D:\\Program Files");
}
public void getFileInfo(String file){
File file2 = new File(file);
File[] files = file2.listFiles();
for (File file3: files){
if (file3.isDirectory()){
getFileInfo(file3.getPath());
}
System.out.println(file3);
}
}
提示:递归占用内存,可能造成死循环,不好理解,一般不使用
File类提供的方法,都是用来获得文件/目录的基本信息,没有提供存储信息等操作文件内部信息的方法。
下面IO流可以操作文件内部信息。
3. IO流如何读写文件
IO流的分类:
根据流的方向:输入流;输出流
根据流承载信息量的大小:字节流(8位:处理不了汉字);字符流(16位)
根据流的功能:节点流(无其它功能,只管运送);处理流(有其他功能)
4. 使用IO流进行读写文件
4.1 挨个读取数据
代码:
public static void main(String[] args) throws IOException {
//1:获得信息来源对象(获得水龙头)
File file = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\240729.txt");
//2:创建IO流对象(拿出水管,连接水龙头)
FileInputStream fileInputStream = new FileInputStream(file);
//3:读取信息(洒水)
int i = fileInputStream.read();
while (i != -1){//没有下一个了read到的就是-1
System.out.print((char)i);
i = fileInputStream.read();//read每次只读一个字节,每次read自动跳到下一个
}
//4:关闭流资源对象
fileInputStream.close();
}
运行结果:
如果不进行赋值操作,那么内侧循环为:
while (fileInputStream.read() != -1){//没有下一个了read到的就是-1
System.out.print((char)fileInputStream.read());//read每次只读一个字节,每次read自动跳到下一个
}
运行结果:
结果会跳一个读一个(因为每次read操作IO流都跳到下一个,我们循环条件里那个read也跳)
4.2 读取所有数据
因为硬盘不能多次读取,会更快的到寿命,为了不那么多次读取,我们使用一次读取多个的read()方法
也称为缓冲区
代码:
public static void main(String[] args) throws IOException {
File file = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\240729.txt");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[1024];
int i = fileInputStream.read(bytes);
while (i != -1){
a:for (byte b:bytes
) {
System.out.print((char)b);
if ((char)b == 0)
break a;
}
i = fileInputStream.read();
System.out.println(i);
}
fileInputStream.close();
}
运行结果:
4.1 输入输出【复制】数据(图片)
代码:
public static void main(String[] args) throws IOException {
//图片视频音频都是用字节流
//获得数据源对象
File fileIn = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\PP.jpg");
File fileOut = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\One\\Two\\PP.jpg");
//获得流对象
FileInputStream fileIS = new FileInputStream(fileIn);
FileOutputStream fileOS = new FileOutputStream(fileOut);
//读写信息
byte[] bytes = new byte[1024];
int i = fileIS.read(bytes);
while (i != -1){
fileOS.write(bytes);
i = fileIS.read(bytes);
}
//关闭资源(后开先关)
fileOS.close();
fileIS.close();
System.out.println("🆗");
}
运行结果:
复制成功
5. 字节流字符流区别
5.1 字节流
代码:
public static void main(String[] args) throws IOException {
//字节输入流
//获得数据源对象
File fileIn = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\240729.txt");
//获得流对象
FileInputStream fileIS = new FileInputStream(fileIn);
//读写信息
byte[] bytes = new byte[1024];
int i = fileIS.read(bytes);
while (i != -1){
a:for (byte b:bytes
) {
System.out.print((char)b);
if ((char)b == 0)
break a;
}
i = fileIS.read(bytes);
}
//关闭资源(后开先关)
fileIS.close();
System.out.println("🆗");
}
运行结果:
原因: 汉字是16位的,读取八字节相当于只读取一半,所以出现乱码
5.2 字符流
5.2.1 字符流输入
代码:
public static void main(String[] args) throws IOException {
//字符输入流
//获得数据源对象
File fileIn = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\240729.txt");
//获得流对象
FileReader fileIS = new FileReader(fileIn);
//读写信息
char[] chars = new char[1024];//每次读取1024个字符
int i = fileIS.read(chars);
while (i != -1){
a:for (char b:chars
) {
System.out.print(b);
if (b == 0)
break a;
}
i = fileIS.read(chars);
}
//关闭资源(后开先关)
fileIS.close();
System.out.println("ok");
}
输出结果:
5.2.2 字符流输出
代码:
public static void main(String[] args) throws IOException {
//获得数据源对象
//模拟微信聊天信息存储功能
File fileIn = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\240729.txt");
FileWriter fileWriter = new FileWriter(fileIn, true);
Scanner scanner = new Scanner(System.in);
System.out.println("请输入想输入的信息,输入exit退出:");
String str = scanner.next();
while (!"exit".equals(str)){
System.out.println("请输入想输入的信息,输入exit退出:");
fileWriter.write("\n" + str);
str = scanner.next();
}
fileWriter.close();
}
输入:
输出结果:
以上介绍的所有流都是节点流
7. 处理流
7.1 处理流与节点流对比
处理流:有特殊功能;连接节点流对象
节点流:无特殊功能;直接连接信息源对象
7.2 处理流分类
缓冲流:自带缓冲区
转换流:字节流转换位字符流
对象流:内存对象存到文件中
数据流:在存取信息时,可以保留信息的类型
打印流:System.out.*下的对象
7.3 对象流(序列化和反序列化)
输入输出代码:
public static void main(String[] args) throws IOException, ClassNotFoundException {
//使用对象流:将内存信息读取到外部文件里称为序列化
//外部文件内对象信息读取到内存中称为反序列化
File file = new File("D:\\for_codes\\for_java\\JiRuanJava\\240729_Demo1\\obj.txt");
//必须用字节
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
Student student = new Student(1, "李四",12, "男");
objectOutputStream.writeObject(student);
//当有(同方向)多层流对象时,关闭最里层对象就可以
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Student student1 = (Student)objectInputStream.readObject();
System.out.println(student1);
objectInputStream.close();
}
输出:
文件内:
idea输出:
为什么以字节流输出文件,为什么字节流能够成功获取汉字数据?
二、多线程
1. 线程介绍
程序:是为了完成特定任务,使用编程语言编写的一组指令的集合,是一段静态的代码。
进程:程序的执行过程。正在运行的程序,进程作为资源分配的单位。
线程:进程的细化,是一个程序内部的一条执行路径。
举例
单线程:一个工人造完车轮造车身,造完车身造车窗
多线程:一个工人多条流水线同时造车轮、车身、车窗,完成之后组装在一起
2. 并行、并发介绍
并行:多个CPU同时执行多个任务(一般电脑不支持)
并发:一个CPU“同时”执行多个任务(采用时间片切换,本文中主要使用并发)
3. 创建多线程的三种方式
3.1 第一种:继承Thread父类
线程类:只有线程类能开辟新的线程
main方法所在的线程为主线程
1、创建线程类
2、创建线程对象【新生状态】
3、线程对象.start()【就绪状态】
4、CPU给资源【运行状态】
如果调用run(),程序会以:一个线程结束后,开始另一个线程,所以跟单线程没差别
启动线程效率高,占用了父类的位置,资源需要加静态
售票例子:
有是张车票,三个窗口,同时售票。
售票窗口类:
public class SellerThread extends Thread {
static int number = 10;
@Override
public void run() {
while (number >= 1)
System.out.println(getName() + "的窗口\t卖出第" + (11 - number--) + "票");
}
}
主函数类:
public static void main(String[] args) {
SellerThread seller1 = new SellerThread();
seller1.setName("麻辣张婆");
seller1.start();
SellerThread seller2 = new SellerThread();
seller2.setName("沉默王爷");
seller2.start();
SellerThread seller3 = new SellerThread();
seller3.setName("大嗓门张姨");
seller3.start();
}
运行结果:
可以看出所有的票都是乱七八糟的重复的, 这是因为我们一个线程还没做输出语句,执行流就被切换到下一个了
3.2 第二种:实现Runnable接口
启动线程对象效率低,没有占用父类位置,共享资源的能力强,不用加静态
Seller类:
public class Seller implements Runnable {
int number = 10;
@Override
public void run() {
while (number >= 1){
System.out.println(Thread.currentThread().getName() + "的窗口\t卖出第" + (11 - number--) + "票");
}
}
}
主函数:
public static void main(String[] args) {
Seller seller1 = new Seller();
Thread tSeller1 = new Thread(seller1, "麻辣张婆");
tSeller1.start();
Thread tSeller2 = new Thread(seller1, "沉默王爷");
tSeller2.start();
Thread tSeller3 = new Thread(seller1, "大嗓门张姨");
tSeller3.start();
}
运行结果:
3.3 第三种:实现Callable接口
run方法返回值为void,不能抛出异常,Callable接口能解决
Sell类:
public class Sell implements Callable {
static int number = 100;
@Override
public Object call() throws Exception {
while (number >= 1)
System.out.println(Thread.currentThread().getName() + "的窗口\t卖出第" + (101 - number--) + "票");
return 0;
}
}
主函数:
public static void main(String[] args) {
Sell sell = new Sell();
FutureTask futureTask = new FutureTask(sell);
FutureTask futureTask1 = new FutureTask(sell);
Thread thread = new Thread(futureTask);
Thread thread1 = new Thread(futureTask1);
thread.start();
thread1.start();
}
运行结果:
4. 常用方法
4.1 start()
启用当前线程,表面上叫start,其实不过是披着start皮的run
4.3 run()
run()方法里面是线程要执行的内容
4.3 getName()
对象.getName()获得当前线程对象的名字
如果开发者调用了无参构造器,程序自动设置线程名:Thread-0/1/2(主线程固定为main)
开发者使用有参构造器,参数的值就是线程的名字
4.4 setName()
开发者使用setName()方法对线程名赋值
4.5 Thread.currentThread()
获得当前正在运行的线程对象
4.6 setPriority()
改变该线程对象的优先级
代码:
public static void main(String[] args) {
Seller seller1 = new Seller();
Thread tSeller1 = new Thread(seller1, "麻辣张婆");
tSeller1.start();
Thread tSeller2 = new Thread(seller1, "沉默王爷");
tSeller2.setPriority(1);
tSeller2.start();
Thread tSeller3 = new Thread(seller1, "销售王张姨");
tSeller3.setPriority(10);
tSeller3.start();
}
结果:
4.7 join()
一个线程调用了join()方法,这个线程会被执行,结束后才会去执行其他线程
start()方法后,join()才有效
4.8 sleep()
在被其他资源占用时,sleep的时间继续向下算吗?继续往下算,是实际时间的毫秒数,不是该线程在执行的时间片段相加
4.8 setDaemon()
主线程死亡,伴随线程也会跟着一起死亡(会有一些延迟)
NumberThread类
public class NumberThread extends Thread {
@Override
public void run() {//第二条执行路线的内容
super.run();
for (int i = 1; i < 1001; i++) {
System.out.println(super.getName() + "\t\ti = " + i);
}
}
}
主函数:
public static void main(String[] args) throws InterruptedException {
NumberThread num = new NumberThread();
num.setDaemon(true);//在start()方法前
num.start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "\t\ti = " + i);
}
}
结果:
之后就结束了
5. 多线程的生命周期
以方法作为临界点
1、使用线程构造器>创建线程对象>新生状态
2、创建线程对象.start()>进入就绪状态>有资格没资源
3、线程对象.run()>进入运行状态>有资格有资源
4、分三种情况
——情况1、时间片段内执行完,死亡状态
——情况2、时间片段内没执行完,重返就绪状态
——情况3、时间片段内出现突发事件,阻塞状态,突发事件处理完返回就绪状态
6. 同步
锁的范围要尽量小,不然可能出现死锁
6.1 同步代码块
在代码体里,把所有客官想同步的代码放到同步代码块里
Seller类:
public class Seller implements Runnable {
int number = 1000;
@Override
public void run() {
while (true){
synchronized (Seller.class){//必须是多个线程用的是同一把锁(同步监视器)引用数据类型
if (number >= 1)
System.out.println(Thread.currentThread().getName() + "的窗口\t卖出第" + (1001 - number--) + "票");
}
if (number == 0){
break;
}
}
}
}
6.2 同步方法
在代码体里,把所有客官想同步的代码提取出来放到同步方法里
如果用继承Thread类的方式,则同步方法和资源都需要加上static关键字
Seller类:
public class Seller implements Runnable {
int number = 1000;
@Override
public void run() {
while (true){
if (number == 0)
break;
else
sellT();
}
}
public synchronized void sellT() {
if (number != 0)
System.out.println(Thread.currentThread().getName() + "的窗口\t卖出第" + (1001 - number--) + "票");
}
}
6.3 Lock锁
如果用继承Thread类的方式,则Lock锁和资源都需要加上static关键字
Seller类:
public class Seller implements Runnable {
int number = 1000;
Lock lock = new ReentrantLock();//创建一个锁对象
@Override
public void run() {
while (true) {
lock.lock();
if (number >= 1)
System.out.println(Thread.currentThread().getName() + "的窗口\t卖出第" + (1001 - number--) + "票");
lock.unlock();
if (number == 0) {
break;
}
}
}
}
7.线程通信
线程通信是使两个线程之间能知道下一步该谁做,做完下一步有该谁做下下一步。
重要的有两个方法和一个变量
wait方法使此线程等待,等到其他线程完成他该完成的,
notify方法通知所有线程我运行完了,你们谁用资源啊
变量,作为类似红绿灯的作用,从两个线程的角度都能看到,红灯线程1走,绿灯线程2走。
我描述的可能不太清晰,直接上例子吧!
题目:
Printer类:
public class Printer {
private int index = 1;
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public synchronized void print(int i) throws InterruptedException {
if (index % 3 == 0){
wait();
}
System.out.print(i);
index++;
notifyAll();//唤醒所有等待的线程
}
public synchronized void print(char c) throws InterruptedException {
if (index % 3 != 0){
wait();
}
System.out.println(c);
index++;
notifyAll();
}
}
NumberPrint类:
public class NumberPrint extends Thread{
private Printer p;//成员变量,属性
public NumberPrint(Printer p){
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <= 52; i++) {
//p.print(i);
try {
p.print(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
类:
public class LetterPrinter extends Thread {
private Printer p;
public LetterPrinter(Printer p){
this.p = p;
}
@Override
public void run() {//65 91
for (char c = 'A'; c <= 'Z'; c++) {
try {
p.print(c);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Test类:
public class Test {
public static void main(String[] args) {
Printer p = new Printer();
NumberPrint num = new NumberPrint(p);
LetterPrinter l = new LetterPrinter(p);
num.start();
l.start();
}
}
结果:
总结
本文讲述了Java中如何对文件或目录进行操作,使用了IO流技术。讲述了多线程如何进行,请各位客官哇,总结好难辩。
也不能怪我啦,毕竟我是外星人的嘞。