【Java_Senior】六、输入输出流

本文详细介绍了Java中的File类,包括创建File对象、获取文件属性和操作文件的方法。接着,讨论了IO流的基本概念,如字节流和字符流,以及FileReader和FileWriter的使用。还提到了缓冲流BufferedReader和BufferedWriter的效率提升作用,以及转换流InputStreamReader和OutputStreamWriter在不同编码间的转换。此外,文章讲解了标准输入输出流System.in和System.out,以及对象流的序列化和反序列化机制。最后,简要提及了RandomAccessFile的特性及其在文件操作中的应用,并介绍了JavaNIO和NIO.2作为File的增强版在处理文件系统时的优势。

IO

相对路径:在module同层,code文件夹下的文件。
绝对路径:从磁盘到最后的路径(完全体)

File对象的三种创建方法:

        File file = new File("hello.txt");
        
        File file1 = new File("D:\\Learning_Java\\Java_basics\\HighLevel\\6_IO\\code", "hello.txt");
        
        File file2 = new File("D:\\Learning_Java\\Java_basics\\HighLevel\\6_IO\\code");
        File file3 = new File(file2, "hello.txt");
    }

File

File的常用功能1

  1. public String getAbsolutePath():获取绝对路径
  2. public String getPath() :获取路径
  3. public String getName() :获取名称
  4. public String getParent():获取上层文件目录路径。若无,返回null
  5. public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
  6. public long lastModified() :获取最后一次的修改时间,毫秒值
  7. public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
  8. public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
  9. public boolean renameTo(File dest):把文件重命名为指定的文件路径,dest文件不能在硬盘中存在
    @Test
    public void test1(){
        File file = new File("hello.txt");

        System.out.println(file.getAbsolutePath());     //获取绝对路径
        System.out.println(file.getPath());
        System.out.println(file.getName());
        System.out.println(file.getParent());
        System.out.println(file.length());  //字节数
        System.out.println(new Date(file.lastModified()));

        System.out.println("********************************************************************************************");
        File file1 = new File("D:\\Learning_Java\\Java_basics\\课件笔记源码资料\\新建文件夹\\1_课件\\第2部分:Java高级编程\\尚硅谷_宋红康_第13章_IO流");

        String[] list = file1.list();
        for(String s: list){
            System.out.println(s);
        }

        System.out.println("********************************************************************************************");

        File[] files = file1.listFiles();
        for(File f: files){
            System.out.println(f);
        }
        System.out.println("********************************************************************************************");

        File file2 = new File("test.txt");
        File fileTest = new File("fileTest.java");
        file2.renameTo(fileTest);   //更改成功,若银盘中存在fileTest则更改不成功
    }

File类的常用功能2

  1. public boolean isDirectory():判断是否是文件目录
  2. public boolean isFile() :判断是否是文件
  3. public boolean exists() :判断是否存在
  4. public boolean canRead() :判断是否可读
  5. public boolean canWrite() :判断是否可写
  6. public boolean isHidden() :判断是否隐藏

创建方法

  1. public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
  2. public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。
    如果此文件目录的上层目录不存在,也不创建。
  3. public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建

注:File类只设计文件及文件目录的创建删除以及属性获取等方法,如果涉及文件具体内容的读取修改等就必须使用IO流。

IO流

字节流(8bit、1Byte、图片,视频)、字符流(16bit、1char、文本)
输入流、输出流
节点流:直接作用于数据的传输。处理流:作用于节点流

字节输入流、输出流: InputStream、OutputStream
字符输入流、输出流: Reader、Writer
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DHpec8yS-1672725825886)(Snipaste_2022-09-11_20-07-54.png)]

具体的流

FileReader

FileReader读入数据的小例子:
使用.read()方法

        FileReader fr = null;
        //必须使用try-catch-finally结构进行操作,否则可能导致fr.close()操作不关闭,导致资源浪费(严重)
        try {
            //先创建一个父类的File对象
            File file = new File("hello.txt");

            //通过File对象创建具体的FileReader对象
            fr = new FileReader(file);

            //调用.read()方法进行遍历文件
            int data = fr.read();   //该方法会返回文件中的字符型数据,由int接收会接收为ASCII码,若文件已经读取到末尾,则会返回-1
            while(data != -1){
                System.out.print((char)data);
                data = fr.read();   //再次调用会自动往后执行
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //流文件不能交给JVM垃圾回收机制进行回收,资源必须进行手动关闭
        try {
            if(fr != null)
                fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

使用.read(char[])方法读入数据

    public void test1() {
        FileReader hello = null;
        try {
            // 创建File对象
            File file = new File("hello.txt");

            //字符型对象的读取选用FileReader类
            hello = new FileReader(file);

            //使用.read(char[])方法,每次读入char[]长度个字符并轮巡读入,若文件到达末尾则返回-1
            char[] cbuf = new char[5];
            int len;
            //.read(char[])方法每次读入了几个数据会作为返回值返回,这样也便于遍历了
            while ((len = hello.read(cbuf)) != -1) {
                for (int i = 0; i < len; i++) {
                    System.out.print(cbuf[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try{
            if(hello != null){
                hello.close();
            }
        }catch(IOException e){
            e.printStackTrace();
        }

    }

注:

//String应该这样写,代表从0处开始取,每次取len个
String str = new String(cbuf, 0, len);

FileWriter

FileWriter允许文件不存在,其会直接创建一个新文件。
.write()方法,会对文件进行写入,默认直接覆盖源文件。
也可以在FileWriter对象的创建时调用不覆盖的构造器,使文件在写入时不覆盖源文件。

    public void test2(){
        //File对象的创建
        FileWriter fw = null;
        try {
            File file = new File("hello1.txt");

            //FileWriter的创建,true代表不覆盖,默认值为覆盖
            fw = new FileWriter(file, true);

            //写入操作
            fw.write("I have a dream!\n");
            fw.write("You have a dream too!\n");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fw != null)
                fw.close();
        }
    }
    }

FileReader与FileWriter联合进行复制操作

    public void test3(){
        FileReader fr = null;
        FileWriter fw = null;
        try {
            File srcFile = new File("hello1.txt");
            File destFile = new File("hello2.txt");

            fr = new FileReader(srcFile);
            fw = new FileWriter(destFile);

            char[] cbuf = new char[5];
            int len;
            while((len = fr.read(cbuf)) != -1){
                fw.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fr != null){
                    fr.close();
                }
                if(fw != null)
                    fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

注:字符流Reader、Writer不可以操作图片

缓冲流 BufferXxxXxx

BufferFileReader、BufferFileWriter
BufferInputStream、BufferOutputStream
一个使用Buffered处理流进行复制非文本数据的小例子。
Buffered通过构造缓冲区,待缓冲区满再进行写入的方式减少写入的次数,以达到提高效率的目的。

    public void copyWithBuffered(String srcPath, String destPath) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            //造文件
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);
            //造字节流,BufferXxx是只能作用于节点流的处理流
            //先造节点流
            FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(destFile);
            //造处理流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            //执行操作
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭流:
            //由外层向内层关闭,先关闭外层,再关闭内层。
            //BufferedXxx的关闭会帮助我们关闭内层的节点流
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

BufferdReader、BufferedWriter操作小例子,复制一个文件
注意:BufferedXxxx都会在写入操作之后自动进行一个flush()操作,意为清空缓冲区。

    @Test
    public void test2(){
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new FileReader(new File("hello.txt")));
            bw = new BufferedWriter(new FileWriter(new File("BufferHello.txt")));
//            常规方法
//            char[] cbuf = new char[5];
//            int len;
//            while((len = br.read(cbuf)) != -1){
//                bw.write(cbuf, 0, len);
//            }

            //String和ReadLine()方法
            //ReadLine()方法会一次读取一行数据,其不会将换行符读入,所以生成的文件可能都在同一行。
            String data;
            while((data = br.readLine()) != null){
//                bw.write(data);     //不提供换行符
                bw.write(data + "\n");  //手动添加
//                bw.newLine();     //或这样加一个换行符
            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bw != null){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

转换流InputStreamReader、OutputStreamWriter

InputStreamReader、OutputStreamWriter是处理流的一种,要作用于节点流。
InputStreamReader是解码:字节—>字符
OutputStreamWriter是编码:字符—>字节
两者的抽象类都是字符流,其可以使用Reader与Writer的方法。


/*
这是一个使用字节流以及装换流实现读取文本文件的程序
*/

    @Test
    public void test3() throws IOException {
        FileInputStream fis = new FileInputStream("hello.txt");     //不创建File对象直接在这里传入路径,会调用重载的构造器在构造器内创建一个File对象
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");

        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf))!= -1){
            String str = new String(cbuf, 0, len);
            System.out.print(str);
        }

    }
    @Test
    /*
        使用转换流实现将以utf-8编码的文本文件重新编码为以gbk编码的文本文件的程序。
     */
    public void test4() throws IOException {
        File file1 = new File("hello.txt");
        File file2 = new File("hello_gbk.txt");

        FileInputStream fis = new FileInputStream(file1);
        FileOutputStream fos = new FileOutputStream(file2);

        InputStreamReader isr = new InputStreamReader(fis, "utf-8");    //使用UTF-8进行解码
        OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");    //使用gbk进行编码

        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            String str = new String(cbuf, 0, len);
            osw.write(str);
        }

        isr.close();
        osw.close();
    }

标准输入输出流System.in,System.out

System.in 标准输入流,从键盘输入,其属于InputStream类
System.out 标准输出流,从控制台输出,其属于PrintStream类,该类继承于OutputStream

/*
使用标准输入输出流通过转换流实现从控制台输入小写字母,在控制台输出对应大写字母的功能
*/

    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(System.in);   //将字节流System.in转换为字符流Reader类型
        BufferedReader br = new BufferedReader(isr);    //将Reader字符流转换为速度更快的BufferedReader缓冲流

        while(true){
            String data = br.readLine();
            if("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
                System.out.println("程序结束");
                break;
            }
            String upper = data.toUpperCase();
            System.out.println(upper);
        }

        br.close();
    }

此外,还有打印流、数据流、对象流等处理流,他们都应该被套在对应的节点流上并有着自己独特的功能,与BufferedXxxx类似。
数据流可以将内存中的基本数据类型、字符串等变量写出到文件中。
对象流又可以将对象输出到文件。

对象流ObjectOutputStream、ObjectInputStream

定义

对象流可以实现将内存中的对象(或基本类型的数据)存储到文件中,以实现内存中对象的持久化(该对象所属的类必须是可序列化的)

序列化:将内存中对象保存在文件中(持久化)
**使用ObjectOutputStream
反序列化:将文件中的数据还原为对象(反持久化)
**使用ObjectInputStream

注:只有可序列化的类所实例化的对象才可以被系列化

序列化机制:序列化机制允许将内存中的Java对象持久化的保存到文件中或通过网络进行传输,并通过反序列化机制将接收到的持久化文件转换为Java对象。

关于对象可的序列化的必要条件

一、对象必须实现Serializable接口或Externalizable接口才可以进行序列化。

Serializable接口是一个标签接口,其没有任何需要实现的抽象方法。
但如果对象想要序列化,在实现Serializable接口的同时还要创建其独有的全局常量SerializableVersionUID–序列化版本号:public static final long SerializableVersionUID = 45674789461654L;(此为举例)
关于SerializableVersionUID,必须显式的在类中声明,若不声明,类也会自动创建,但类会在进行修改时自动重新生成不同的SerializeableVersionUID,故必须显式的对SerializeableVersionUID进行显式的定义(会报错)。

二、类中的所有属性也必须是可序列化的
否则序列化过程会失败,报SerializeException异常。

三、成员变量不能用static、transient修饰
被static和transient修饰的成员变量无法被序列化,其不会报错,但无法被序列化存储在文件中。
transient可以被简单理解为,不允许序列化的属性,可以使用transient关键字修饰,令其不可被序列化

举例实践

一、序列化的过程举例:
实现将String通过序列化机制持久化并保存到.dat文件中的举例:

    @Test
    public void testObjectOutputStream(){
        //注:流所特有的必须进行的异常处理操作。
        ObjectOutputStream oos = null;
        try {
            //创建序列化流的对象
            oos = new ObjectOutputStream(new FileOutputStream("ObjectTest.dat"));

            //调用对象流将String类型的对象进行持久化
            oos.writeObject(new String("清河"));
            oos.flush();    //显示的刷新一下
            oos.writeObject(new Person("李华", 18));  //自定义类的序列化写入
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null) {
                //.close()操作所特有的try/catch操作
                try {
                    oos.close();    //流所特有的关闭操作
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

二、反序列化机制的过程举例:
将刚刚通过序列化生成的.dat文件反序列化为相应的对象。

    @Test
    public void testObjectInputStream(){
        ObjectInputStream ois = null;
        try {   //使用try/catch结构来避免IO出错的情况
            //创建一个反序列化的输入对象流
            ois = new ObjectInputStream(new FileInputStream("ObjectTest.dat"));

            Object obj = ois.readObject();  //进行反序列化的读取
            String str = (String)obj;   //将反序列化后得到的Object对象转换为我们已知的String对象

            Object obj1 = ois.readObject();     //进行自定义类的反序列化读取
            Person p1 = (Person) obj1;

            System.out.println(str);    //输出反序列化生成的String对象
            System.out.println(p1);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                if(ois != null){
                ois.close();    //流同一需要进行关闭操作
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

三、自定义一个序列化的类
一个可序列化的类必须实现Serializable接口并定义序列化版本号serializableVersionUID来实现一个自定义的可序列化的类。

public class Person implements Serializable {       //要实现序列化就必须实现Serializable接口
    public static final long serializableVersionUID = 45674896465489L;      //必须生成的序列化版本号

    private String name;
    private int age;

    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

结果为:

清河
Person{name='李华', age=18}

随机存取流 RandomAccessFile

定义

特点

  1. RandomAccessFile流既可以作为输入流,又可以作为输出流。但如果想实现输入和输出也必须生成两个RandomAccessFile来分别进行输入与输出。
  2. RandomAccessFile对文件进行覆盖时,会按照顺序从前往后覆盖,而不是直接覆盖文件,也不是向后添加(见举例二)。

参数

第一个参数是所读取的文件/要写入的文件。第二个参数是文件的读取模式。
r:只读。rw:读写。rwd:读立刻写。

RandomAccessFile raf1 = new RandomAccessFile(new File("告白之夜.mp3"), "r");

常用方法

.seek()实现操作指针位置的改变(见例三)。

举例实践

一、使用RandomAccessFile进行复制操作

    @Test
    public void RandomAccessFileT1(){
        RandomAccessFile raf1 = null;    //定义raf1作为输入流
        RandomAccessFile raf2 = null;   //定义raf2作为输出流
        try {
            raf1 = new RandomAccessFile(new File("告白之夜.mp3"), "r");
            raf2 = new RandomAccessFile(new File("告白之夜1.mp3"), "rw");

            byte[] buffer = new byte[1024]; //将文件转换为数据并存储在byte中。
            int len;    //定义len以记录buffer的长度为输出做准备
            /**
             * .read()参数为将读取的同时将文件存入的地方
             * .read()返回值为读取的流的长度。
             * .read()若返回-1则代表文件为空。(第一个位置为0)
             * .write()参数依次分别为:从buffer写入,从第0个位置开始写入,写入len的长度,根据.write()的构造器来判断要写入的数据类型
            */
            while((len = raf1.read(buffer)) != -1){
                raf2.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(raf1 != null) {
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(raf2 != null) {
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
上述例子会将“告白之夜.mp3”文件复制一份,其名称为“告白之夜1.mp3”

二、对于RandomAccessFile进行文件覆盖的理解

//test.txt文件覆盖之前如下
abcdefg
    @Test
    public void RandomAccessFileT2(){
        RandomAccessFile raf1 = null;
        try {
            raf1 = new RandomAccessFile("test1.txt", "rw"); //以读写的方式打开test1.txt
            raf1.write("xyz".getBytes());   //将xyz写入test1.txt
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(raf1 != null) {
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
//test.txt在覆盖后文件内容为
xyzdefg

三、使用.seek()方法对文件操作索引进行更改

    @Test
    public void RandomAccessFileT3() throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("test1.txt", "rw");
        raf1.seek(3);   //从三号索引开始进行操作
        raf1.write("NNN".getBytes());
        raf1.close();
    }
//test1.txt的最新内容
xyzNNNg

四、如何对RandomAccessFile流对文件执行插入操作

    @Test
    public void RandomAccessFileT4() throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("test1.txt", "rw");
        raf1.seek(3);

        StringBuilder bud = new StringBuilder((int)new File("test1.txt").length());
        byte[] byt = new byte[30];
        int len;
        while((len = raf1.read(byt)) != -1){
            bud.append(new String(byt, 0, len));    //byt是要存入String的数组,0是从byte数组索引的开始位置,len是存储的byte的长度。
        }
        //注意此时指针会到移到最后,还需要通过.seek()方法将指针移回索引3的位置
        raf1.seek(3);
        raf1.write("JJJ".getBytes());

        raf1.write(bud.toString().getBytes());

        raf1.close();
    }
//Test1.txt的内容会变更为
xyzJJJNNNg

NIO、NIO.2

定义

NIO、NIO.2是java7基于File的缺点升级的新的读取文件系统的方式,其弥补了很多File方式读取文件路径的不足,比如对异常信息的详细返回等。

//使用File创建对象
import java.io.File;
File file = new File("index.html");

//使用NIO创建对象
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get("index.html");

注:Paths、Files是一些工具类,其中有创建Path对象的方法以及操作文件的方法,具体在框架中在进行详细说明

Errors loading geometries: • for link 'base_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/base_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/base_link.STL' • for link 'left_shou1_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_shou1_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_shou1_link.STL' • for link 'left_shou2_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_shou2_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_shou2_link.STL' • for link 'left_tui_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_tui_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_tui_link.STL' • for link 'left_wheel_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_wheel_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/left_wheel_link.STL' • for link 'right_shou1_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/right_shou1_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/right_shou1_link.STL' • for link 'right_shou2_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/right_shou2_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/right_shou2_link.STL' • for link 'right_tui_link': Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/right_tui_link.STL' Could not load mesh resource 'package://turn_on_minz_robot/meshes/brushless_senior_diff/right_tui_link.STL' • for link
10-09
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值