关于IO流(java日志十三)

本文介绍了Java中的File类,包括构造方法和常用方法,并详细讲解了IO流的概念,包括字节流和字符流的使用,以及如何进行文件的读写操作。同时,文章强调了流关闭的重要性,并提到了缓冲在提高IO效率中的作用。
摘要由CSDN通过智能技术生成


前言

在开始讲IO之前,先介绍一下File,接下来的内容里,IO的主要操作对象是File。
提示:在讲IO之前,你可能需要一点异常处理的知识,可以移步去看我的上一篇博客,链接如下
Java异常处理

File的构造方法

File(String pathname):根据给定的路径名创建File对象。路径名可以是绝对路径或相对路径。
File(String parent, String child):根据给定的父路径和子路径创建File对象。父路径可以是目录的路径,子路径可以是文件名或目录名。
File(File parent, String child):根据给定的父目录和子路径创建File对象。父目录是一个File对象,子路径可以是文件名或目录名。

File中常用的几个方法

boolean exists():检查文件或目录是否存在。返回布尔值,存在返回true,不存在返回false。
boolean isFile():判断当前File对象是否表示一个文件。如果是文件,则返回true,否则返回false。
boolean isDirectory():判断当前File对象是否表示一个目录。如果是目录,则返回true,否则返回false。
String getName():获取文件或目录的名称。返回一个字符串,表示文件或目录的名称。
String getPath():获取文件或目录的路径。返回一个字符串,表示文件或目录的绝对路径。
String getParent():获取文件或目录的父级目录。返回一个字符串,表示父级目录的路径。
long length():获取文件的大小。返回一个长整型数值,表示文件的字节数。
boolean canRead():检查文件是否可读。如果文件可读,则返回true,否则返回false。
boolean canWrite():检查文件是否可写。如果文件可写,则返回true,否则返回false。
boolean createNewFile():创建一个新的空文件。如果文件已经存在,则返回false;如果文件成功创建,则返回true
boolean mkdir():创建一个新的目录。如果目录已经存在,则返回false;如果目录成功创建,则返回true。
boolean delete():删除文件或目录。如果文件或目录删除成功,则返回true;否则返回false

注意

在写入文件地址时/和\\作用相同

File f=new File("C:/Users/user/Desktop/TEST1.txt");
File f=new File("C:\\Users\\user\\Desktop\\TEST1.txt");
效果一样,\为转义字符,\\效果等同/

当我们要使用一个文件时最好用 exists()方法判断一下

File f=new File("C:/Users/user/Desktop/TEST1.txt");

因为使用上述构造方法后,再对该文件进行操作时,会主动new一个文件出来,有时这并不符合我们的预期。


一、IO流介绍

I即Input:数据输入,内存中读入硬盘上的数据,这个过程叫read。
O即Output:数据输出,从内存中往硬盘上写数据,这个过程叫wirte。
流:相当于是为了为了联系数据而建立的一个管道。

Output 输出 write
Input 输入 read
内存
硬盘

Java的I/O库主要分为两个模块:字节流和字符流。
字符流和字节流之间的区别
1.处理对象不同:字节流处理二进制数据(任何形式的数据,包括视频,录音,文本等),每次读取以1byte为单位;而字符流处理文本数据,每次读取以一个字符为单位(只能读取纯文本)。

2.码表不同:字节流是以字节为单位进行输入输出的,它们不关心数据是什么,操作的都是二进制文件。字符流是以字符为单位进行输入输出的,每次读取以一个字符为单位。

3.效率不同:字符流比字节流效率低,字符流需要进行编码和解码操作。

最重要的一点流一旦打开,一定要关闭!!!!!

二、IO流的使用

1.字节流

字节流是用于处理字节数据的输入和输出流。Java中常用的字节流类有InputStream和OutputStream以及它们的具体实现类。下面是一些常用的字节流方法:

InputStream类的常用方法

int read():从输入流中读取一个字节并返回其对应的整数值(0-255),如果到达流的末尾则返回-1。
int read(byte[] buffer):从输入流中读取一定数量的字节到给定的字节数组中,并返回实际读取的字节数,如果到达流的末尾则返回-1。
int available():返回可以从输入流中读取的剩余字节数。
void close():关闭输入流,释放相关资源。
skip(long n):从输入流中跳过指定数量的字节并丢弃。该方法将输入流的位置前进指定的字节数,跳过不读取,返回实际跳过的字节数。如果无法跳过指定数量的字节(例如到达流的末尾),则可能返回较少的字节数。
mark(int readLimit):在当前位置设置一个标记。该方法允许标记输入流的当前位置,以便稍后可以使用reset()方法将流的位置重置到标记的位置。readLimit参数指定可以在不失效标记的情况下从流中读取的最大字节数。
reset():将流的位置重置到最后一次调用mark()方法时的标记位置。该方法用于将输入流的位置重新设置为之前设置的标记位置,以便重新读取数据。

OutputStream类的常用方法

void write(int b):将一个字节写入输出流。
void write(byte[] buffer):将字节数组中的数据写入输出流。
void flush():刷新输出流,将缓冲区中的数据立即写入目标设备。
void close():关闭输出流,释放相关资源。

介绍几个需要方法,知道大体作用即可:

  • String中的getBytes()

源码如下:

    public byte[] getBytes() {
        return StringCoding.encode(value, 0, value.length);
    }

作用:调用静态方法encode将String编码为字节序列,将结果存储到一个新的字节数组中,返回结果字节数组。


  • StringBuilder中的append方法
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

作用:将所得到的字符str追加到StringBuilder类对象中所指的字符串,调用的是父类AbstractStringBuilder的append方法。


实例

我们这里采用字符输入输出
思路:
输入: 输入采用字符串存储,然后调用getBytes()方法转换为字节数组,再使用字节流。
输出:用byte数组去存储输入流,然后调用String的一个构造方法将byte数组中的一部分内容转换为String,然后调用StringBuilder中的append方法将其内容追加到StringBuilder的对象后面。
具体实现:

import java.io.*;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        File f=new File("C:\\Users\\user\\Desktop\\TEST1.txt");
        Main m=new Main();
        System.out.println("程序开始运行!!!");
        FileOutputStream fos=null;
        FileInputStream fis=null;
        m.wirte(f,fos);
        m.read(f,fis);
        System.out.println("程序运行结束!!!");
    }
    public void wirte(File f,FileOutputStream fos){
        try {
            fos = new FileOutputStream(f, true);
            Scanner in = new Scanner(System.in);
            System.out.println("请输入你在在文本中输入的内容:");
            String x = in.nextLine();
            byte[] arr = x.getBytes();
            fos.write(arr);
            System.out.println("输入完毕!!!");
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                fos.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    public void read(File f,FileInputStream fis){
        try {
            fis = new FileInputStream(f);
            StringBuilder sb = new StringBuilder();
            byte arr[] = new byte[(int) f.length()];
            System.out.println("开始读取文本内容:");
            int len;
            while ((len = fis.read(arr)) != -1) {
                sb.append(new String(arr, 0, len));
            }
            System.out.println("文本内容:");
            System.out.println(sb);
            System.out.println("文本内容读取完毕!!!");
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                fis.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

测试一下
在这里插入图片描述


注意易错

  • 读取
while(fis.read(arr)!=-1){
            len=fis.read(arr);
            if(len>=0) {
                sb.append(new String(arr, 0, len));
            }
        }

ERROR
本人一开始就是这样写得,觉得思路也没有问题,但,错啦!!!
why?
问题出在循环内部的读取操作上。每次调用 fis.read(arr) 方法都会读取一定数量的字节并将其存储在 arr 数组中,然后返回实际读取的字节数。然而,在此代码中,在 while 循环条件和循环体内部都调用了 fis.read(arr) 方法,这会导致每次循环实际读取两次,造成部分数据的丢失。

  • 要注意强制转换长度
 byte arr[]=new byte[(int)f.length()];

数组的长度是一个非负整数,范围从0到Integer.MAX_VALUE(即2^31 - 1),而File的length()方法的返回值是long。

关于读

读里面有这样一个方法int read(byte[] buffer)
类型参数是一个数组,这个传入的数组怎么理解,当我们使用int read()方法时,每次读取读取的是一个字节,byte数组有两种含义

   byte []all=new byte[(int)f.length()];

当我们定义这样这样的byte数组时,我们期望的是用all这个数组存储f这个文件里面的所有东西,这时候这里的all相当于一个缓存,将f里面的数据,缓存进all这一个数组里面,然后通过这个数组随时随地对数据访问。

   byte []all=new byte[1024];

我们有时也会看到这样的,这么写的话,常常是对文件内容进行操作,比如复制等,
1024byte=1KB,这里开了一个长度为1024的数组,相当于管道的宽度为1KB,每次执行1KB的文件。例如这样做:

byte []all=new byte[1024];
while(fis.read(all)!=-1){
    fos.write(all);
}

关于流的关闭

每次流使用完后都要关闭,要遵循两个原则
1.先开后关,后开先关;
2.每个流的关闭要在一个单独的try-catch语句中,为了避免其中一个流关闭的异常,而影响了接下来的流的关闭

2.字符流

字符流是用于处理字符数据的输入和输出流。Java中常用的字节流类有Reader和Writer以及它们的具体实现类。下面是一些常用的字节流方法:

Reader类的常用方法

int read():从输入流中读取一个字符并返回其对应的整数值(0-65535),如果到达流的末尾则返回-1。
int read(char[] buffer):从输入流中读取一定数量的字符到给定的字符数组中,并返回实际读取的字符数,如果到达流的末尾则返回-1。
int read(char[] buffer, int offset, int length):从输入流中读取指定数量的字符到给定的字符数组的指定位置中,并返回实际读取的字符数,如果到达流的末尾则返回-1。
void close():关闭输入流,释放相关资源。

Writer类的常用方法

void write(int c):将一个字符写入输出流。
void write(char[] buffer):将字符数组中的数据写入输出流。
void write(char[] buffer, int offset, int length):将字符数组中指定范围的数据写入输出流。
void write(String str):将字符串写入输出流。
void flush():刷新输出流,将缓冲区中的数据立即写入目标设备。
void close():关闭输出流,释放相关资源。

实例

还是一样举个例子说明一下,不过字符流处理起来相对简单

import java.io.*;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        File f=new File("C:/Users/user/Desktop/TEST1.txt");
        Main m=new Main();
        f.length();
        System.out.println("程序开始运行!!!");
        FileWriter fw=null;
        FileReader fr=null;
        m.wirte(f,fw);
        m.read(f,fr);
        System.out.println("程序运行结束!!!");
    }
    public void wirte(File f,FileWriter fw){
        try {
            fw = new FileWriter(f, true);
            System.out.println("请输入你要输入的内容");
            Scanner in = new Scanner(System.in);
            String str = in.nextLine();
            fw.write(str);
            System.out.println("输入成功!!!!");
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                fw.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    public void read(File f,FileReader fr){
        try {
            fr = new FileReader(f);
            System.out.println("以下是文本内容:");
            char arr[] = new char[(int) f.length()];
            fr.read(arr);
            String str = String.valueOf(arr);
            System.out.println(str);
            System.out.println("读取成功!!!!");
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try{
                fr.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

疑问:输出行末有空格

但是有一个问题,在打印时末尾会莫名其妙出现很多空格,为什么?
出现多余空格的原因是在使用FileReader和read()方法读取字符数据时,没有指定字符编码。默认情况下,FileReader使用平台默认的字符编码来读取文件,可能会导致字符编码不匹配的问题。

可以使用trim()方法除去空格

String str = String.valueOf(arr);
String str = String.valueOf(arr).trim(); // 去除末尾空格

这样输出就符合我们的预期了。


3.缓冲

缓冲(Buffering)是在输入和输出操作中使用缓冲区(Buffer)来提高数据传输效率的一种技术。在IO操作中,数据通常会通过缓冲区进行读取和写入,而不是直接从源读取或直接写入到目标。

当不使用缓冲流时:
输入流:从数据源(如文件、网络连接等)读取数据,并将数据逐个字节或字符地传递给应用程序。
输出流:将应用程序中的数据逐个字节或字符地写入到目标位置(如文件、网络连接等)。
这个过程是逐个字节或字符地进行读取和写入,每次读取或写入一个字节或字符,然后将其传输给目标位置。这种方式也称为逐字节(逐字符)传输。

使用缓冲流时
缓冲流内部维护了一个缓冲区,可以一次性读取或写入多个字节或字符,减少了频繁的读写操作,从而提高了IO的效率。

简单点来讲,把数据传输的过程比作搬砖,不使用缓冲流时,砖头是一块块搬来搬去,但使用的缓冲流后,我们相当于有了一个板车,把砖头放在板车里面,然后集中起来一起去运输,就减少了运输的次数,提高了效率。

随便打开BufferedOutputStream的源码查看一下其中的一个write方法,很简单,
if (count >= buf.length) { flushBuffer(); }
关键就是这句话,什么意思,当写入的数据长度超过缓冲区的的容量,那么刷新缓冲区,把数据写入磁盘。在其他的写入操作里面都会有这一类型的判断。

    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }

下面是非缓冲流和·缓冲流的对应关系:

非缓冲流(原始流\裸流)缓冲流
InputStreamBufferedInputStream
OutputStreamBufferedOutputStream
ReaderBufferedReader
WriterBufferedWriter

举个例子

import java.io.*;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        File f = new File("C:\\Users\\user\\Desktop\\TEST1.txt");
        Main m = new Main();
        System.out.println("程序开始运行!!!");
        m.write(f);
        m.read(f);
        System.out.println("程序运行结束!!!");
    }
    public void write(File f) {
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        System.out.println("开始输入文本内容");
        try {
            fos = new FileOutputStream(f, true);
            bos = new BufferedOutputStream(fos);
            Scanner in=new Scanner(System.in);
            String data = in.nextLine();
            bos.write(data.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("文本内容输入结束");
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public void read(File f) {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        System.out.println("开始打印文本内容");
        try {
            fis = new FileInputStream(f);
            bis = new BufferedInputStream(fis);
            byte[] all = new byte[(int)f.length()];
            int len;
            String str=null;
            while ((len = bis.read(all)) != -1) {
                str = new String(all, 0, len);
            }
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("文本内容打印结束");
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

我们随便选取一个文本复制一下,对比一下两者之间的速率

import java.io.*;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        File f = new File("C:\\Users\\user\\Desktop\\TEST.txt");
        File df=new File("C:\\Users\\user\\Desktop\\TEST1.txt");
        Main m = new Main();
        m.quickcopy(f,df);
        m.copy(f,df);
        System.out.println("程序运行结束!!!");
    }
    public void quickcopy(File f,File df) {
        FileReader fr = null;
        FileWriter fw = null;
        BufferedReader br = null;
        BufferedWriter bw= null;
        try {
            fr=new FileReader(f);
            fw=new FileWriter(df);
            br=new BufferedReader(fr);
            bw=new BufferedWriter(fw);
            String all=null;
            long start=System.currentTimeMillis();
            while ((all = br.readLine()) != null) {
                bw.write(all);
                bw.flush();
            }
            long end=System.currentTimeMillis();
            System.out.println("缓冲流共用时:"+(end-start)+"ms");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        //单独try——catch语句关闭,先开后关
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public void copy(File f,File df) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr=new FileReader(f);
            fw=new FileWriter(df);
            int a;
            long start1=System.currentTimeMillis();
            while ((a = fr.read()) != -1) {
                fw.write(a);
            }
            long end1=System.currentTimeMillis();
            System.out.println("非缓冲流共用时:"+(end1-start1)+"ms");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

下面是运行时间比较
在这里插入图片描述
很明显,缓冲流要快的多。

总结

本文简单介绍了File的构造方法,File中常用的几个方法以及IO的字节流,字符流以及缓冲流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值