4:File类与IO流

File类

1:疑问一:

	public static void main(String[] args) {
        //File f = new File("A:" + File.separator + "TEXT" + File.separator + "11.docx");
        //File f1 = new File("A:" + File.separator + "TEXT" + File.separator + "11.docx");
        File f = new File("A:\\TEXT\\11.docx");
        File f1 = new File("A:\\TEXT\\11.docx");
        System.out.println(f.exists());
        System.out.println(f1.exists());
        System.out.println(f == f1);
        System.out.println(f.equals(f1)); // 比较的是两个文件的路径是否一致
    }
/*
true
true
false
true
*/
File f = new File("A:\\TEXT\\11.docx");// 创建一个引用通过传进来的路径指向磁盘中的文件。

1:引入:

文件:

内存中存放的数据在计算机关机后就会消失。要长久保存数据,就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索,引入了“文件”的概念。一篇文章、一段视频、一个可执行程序,都可以被保存为一个文件,并赋予一个文件名。操作系统以文件为单位管理磁盘中的数据。 一般来说,文件可分为文本文件、视频文件、音频文件、图像文件、可执行文件等多种类别,这是从文件的功能进行分类的。从数据存储的角度来说,所有的文件本质上都是一样的,都是由一个个字节组成的,归根到底都是 0、1 比特串。不同的文件呈现出不同的形态(有的是文本,有的是视频等等)

文件夹/目录

成千上万个文件如果不加分类放在一起,用户使用起来显然非常不便,因此又引入了树形目录(目录也叫文件夹)的机制,可以把文件放在不同的文件夹中,文件夹中还可以嵌套文件夹,这就便于用户对文件进行管理和使用

盘符上的文件—》封装为对象—》对象属于File类的对象–》有了这个对象,我们程序就可以直接操纵这个对象,通过这个对象获取文件的各种信息,还可以对文件进行创建 ,删除。

2:对文件进行操作

	public static void main(String[] args) throws IOException {
     // File.separator:获取当前操作系统的拼接符。
     // File f = new File("A:" + File.separator + "TEXT" + File.separator + "11.docx");
     // File f1 = new File("A:" + File.separator + "TEXT" + File.separator + "11.docx");
     File f = new File("A:\\TEXT\\11.docx");
     File f1 = new File("A:\\TEXT\\11.docx");
     File f2 = new File("A:\\TEXT\\12.docx"); // 随便写的一个目录
     // 文件是否可读/文件是否可写
     System.out.println(f.canRead()); // true
     System.out.println(f.canWrite()); // true
     System.out.println(f2.canWrite()); // false
     System.out.println(f2.canRead()); // false

     // 获取文件的名字
     System.out.println(f.getName()); // 11.docx
     System.out.println(f1.getName()); // 11.docx
     System.out.println(f2.getName()); // 12.docx

     // 获取当前文件的上级目录
     System.out.println(f.getParent()); // A:\TEXT
     System.out.println(f2.getParent()); // A:\TEXT

     // 判断当前目录下的东西是否是一个文件
     System.out.println(f.isFile()); // true
     System.out.println(f2.isFile()); // false

     // 判断当前目录下的东西是否是一个目录
     System.out.println(f.isDirectory()); // false
     System.out.println(f2.isDirectory()); // false

     // 判断当前文件是否隐藏
     System.out.println(f.isHidden()); // F
     System.out.println(f2.isHidden()); // F

     // 当前文件的大小
     System.out.println(f.length()); // 14246
     System.out.println(f2.length()); // 0

     // 当前文件是否存在
     System.out.println(f.exists()); // true
     System.out.println(f2.exists()); // false

     if (f2.exists()) {
         f2.delete();
     } else {
         f2.createNewFile();
     }

     // 比较两个对象的地址是否一致
     System.out.println(f == f1); // false -> 堆中的两个不同对象

     // 比较两个对象对应的路径
     System.out.println(f.equals(f1)); // true : 两个对象的路径是一样的。

     // 根路径相关的
     System.out.println("绝对路径: " + f.getAbsolutePath()); // 真实的一个精准的,完整的路径
     System.out.println("相对路径: " + f.getPath()); // 有一个参照物,相对这个参照物的路径。
     System.out.println("tostring: " + f.toString()); // tostring跟相对目录一致

     //
     File f5 = new File("demo.txt");
     File f6 = new File("a/b/c/demo.txt");
     if (!f5.exists()) {
         f5.createNewFile();
     }
     System.out.println("绝对路径: " + f6.getAbsolutePath()); // 绝对路径: D:\java\javacode\src\MyJavaSE\a\b\c\demo.txt
     System.out.println("相对路径: " + f6.getPath()); // 相对路径: a\b\c\demo.txt
     System.out.println("tostring: " + f6.toString()); // tostring: a\b\c\demo.txt
     System.out.println("f5的绝对路径" + f5.getAbsolutePath()); // f5的绝对路径D:\java\javacode\src\MyJavaSE\demo.txt
 }

3:对目录/文件夹进行操作

对目录进行操作跟对文件进行操作差不多。

public static void main(String[] args) throws IOException {
     // File.separator:获取当前操作系统的拼接符。
     // File f = new File("A:" + File.separator + "TEXT" + File.separator + "11.docx");
     // File f1 = new File("A:" + File.separator + "TEXT" + File.separator + "11.docx");
     File f = new File("A:\\TEXT\\145");
     File f1 = new File("A:\\TEXT\\145");
     File f2 = new File("A:\\TEXT\\146"); // 随便写的一个目录
     // 文件是否可读/文件是否可写
     System.out.println(f.canRead()); // true
     System.out.println(f.canWrite()); // true
     System.out.println(f2.canWrite()); // false
     System.out.println(f2.canRead()); // false

     // 获取文件的名字
     System.out.println(f.getName()); // 145
     System.out.println(f1.getName()); // 145
     System.out.println(f2.getName()); // 146

     // 获取当前文件的上级目录
     System.out.println(f.getParent()); // A:\TEXT
     System.out.println(f2.getParent()); // A:\TEXT

     // 判断当前目录下的东西是否是一个文件
     System.out.println(f.isFile()); // f
     System.out.println(f2.isFile()); // false

     // 判断当前目录下的东西是否是一个目录
     System.out.println(f.isDirectory()); // t
     System.out.println(f2.isDirectory()); // false

     // 判断当前文件是否隐藏
     System.out.println(f.isHidden()); // F
     System.out.println(f2.isHidden()); // F

     // 当前文件的大小
     System.out.println(f.length()); // 0
     System.out.println(f2.length()); // 0

     // 当前文件是否存在
     System.out.println(f.exists()); // true
     System.out.println(f2.exists()); // false

     if (f2.exists()) {
         f2.delete();
     } else {
         f2.createNewFile();
     }

     // 比较两个对象的地址是否一致
     System.out.println(f == f1); // false -> 堆中的两个不同对象

     // 比较两个对象对应的路径
     System.out.println(f.equals(f1)); // true : 两个对象的路径是一样的。

     // 根路径相关的
     System.out.println("绝对路径: " + f.getAbsolutePath()); // 真实的一个精准的,完整的路径
     System.out.println("相对路径: " + f.getPath()); // 有一个参照物,相对这个参照物的路径。
     System.out.println("tostring: " + f.toString()); // tostring跟相对目录一致

     //
     File f5 = new File("demo");
     File f6 = new File("a/b/c/");
     if (!f5.exists()) {
         f5.createNewFile();
     }
     System.out.println("绝对路径: " + f6.getAbsolutePath()); // 绝对路径: D:\java\javacode\src\MyJavaSE\a\b\c
     System.out.println("相对路径: " + f6.getPath()); // 相对路径: \a\b\c
     System.out.println("tostring: " + f6.toString()); // tostring: \a\b\c
     System.out.println("f5的绝对路径" + f5.getAbsolutePath()); // f5的绝对路径D:\java\javacode\src\MyJavaSE\demo

 // 与目录有关的操作-------------------------------------------------------------------------------
     File file = new File("A:\\a\\b\\c");

     // 创建单层目录 -------------
     file.mkdir();

     // 创建多层目录
     file.mkdirs();

     // 目录的删除只会删除一层,并且这一层必须是空的才会被删除。
     //file.delete();

     // 当前目录下所有的文件和目录的名字放到String数组中。
     String[] str = file.list();
     System.out.println(Arrays.toString(str)); // [1.txt, 145.docx, 2]

     // 功能更加的强大:将当前目录下所有的文件和目录的放到File数组中。
     File[] files = file.listFiles();
     for (File F : files) {
         System.out.println(F.getName() + " ->  " + F.getAbsolutePath());
     }
     /*
     1.txt ->  A:\a\b\c\1.txt
     145.docx ->  A:\a\b\c\145.docx
     2 ->  A:\a\b\c\2`
      */
 }

IO流

1:引入:

形象理解:把IO流当作一根管子。

在这里插入图片描述

注意:按照功能又分为节点流与处理流

**节点流:**FileInputStream FileOutputStream FileReader FileWriter

**处理流:**BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter InputStreamReader OutputStreamWriter

读取文件数据 是以io流的方式,但是关于网络中的数据,同样也是io流,但是并不是file了,而是专门处理网络数据的socket和serversocket,以及之后nio中的channel

在这里插入图片描述

2:字符输入 / 出流FileReader 与 FileWriter

一次读取1个字符

	public static void main(String[] args) throws IOException {
     // 1: 有一个文件 -> 创建一个File文件
     File file = new File("A:\\test.txt");
     // 2: 有一根管子,将这个管子怼到文件中 -> 创建一个FileReader对象,
     FileReader fileReader = new FileReader(file);
     // 3:有一个”吸“的操作 --> 进行读取操作
     //    一次只会读一个字符。当读到文件的结尾处,那读到是-1.
     int n = fileReader.read();
     while (n != -1) {
         System.out.print((char) n);
         n = fileReader.read();
     }
     // 4:管子不用了,要关闭管子 --> 关闭流
     // 流,数据库,网络资源,靠jvm本身没有办法帮我们关闭,此时必须程序员手动关闭:
     fileReader.close();
     System.out.println();
     method();
 }

一次读取5个字符

	public static void method() throws IOException {
     // 1:创建一个File文件
     File file = new File("A:\\test.txt");
     // 2:创建一个FileReader对象
     FileReader fileReader = new FileReader(file);
     // 3: 进行读取操作
     char[] c = new char[5]; // 缓冲数组
     // 一次读取5个返回值是有效长度
     int len = fileReader.read(c);
     while(len != -1) {
         // 错误方式
         for (int i = 0; i < c.length; i++) {
             System.out.print(c[i]); // 肝死你们,草泥马们,
         }
         // 正确方式1:
         for (int i = 0; i < len; i++) {
             System.out.print(c[i]); // 肝死你们,草泥马
         }
         // 正确方式2:
         String str = new String(c);
         System.out.print(str);

         len = fileReader.read(c);
     }
     // 4:关闭流
     fileReader.close();
 }

写操作

	public static void method2() throws IOException {
     // 1:创建一个文件
     File file = new File("A:\\T.txt");
     // 2:创建一个FileWriter怼到文件中去
     FileWriter fileWriter = new FileWriter(file);
     // 3:写动作
     String str = "abcdefghijklmn";
     /*
     char[] c = str.toCharArray(); // 缓冲数组
     fileWriter.write(c);
      */
     fileWriter.write(str);
     // 4:关闭流
     fileWriter.close();
 }

文件的复制

	// 文件的复制
 public static void main(String[] args) throws IOException {
     // 一个源文件
     File f1 = new File("A:\\test.txt");
     // 一个目标文件
     File f2 = new File("A:\\T.txt");
     // 创建一个输入管和一个输出管
     FileReader fileReader = new FileReader(f1);
     FileWriter fileWriter = new FileWriter(f2);
     // 开始动作:读动作与写动作
     char[] c = new char[5]; // 缓冲数组
     int len = fileReader.read(c); // len:有效长度
     while (len != -1) {
         fileWriter.write(c, 0, len); //注意下标范围是[0,len]
         len = fileReader.read(c);
     }
     // 关闭流
     fileWriter.close();
     fileReader.close();
 }

文件流为什么要关闭

文件流在使用完毕后需要进行关闭操作,以释放系统资源保证数据的完整性。在某些编程语言中,文件流必须在使用完毕后手动进行关闭操作,否则文件流将一直占用系统资源,可能会导致程序出现异常或无法继续进行操作不释放 其他的线程就获取不到.

3:用try - catch - finally 处理异常

public static void main(String[] args) {
        // 源文件
        File f1 = new File("A:\\test.txt");
        // 目标文件
        File f2 = new File("A:\\T.txt");
        // 输入管和一个输出管
        FileReader fileReader = null;
        FileWriter fileWriter = null;
        try {
            fileReader = new FileReader(f1);
            fileWriter = new FileWriter(f2);
            // 开始动作:读动作与写动作
            char[] c = new char[5]; // 缓冲数组
            int len = fileReader.read(c); // len:有效长度
            while (len != -1) {
                fileWriter.write(c, 0, len);
                len = fileReader.read(c);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            // 注意两个流关闭的try-catch是分开写的。
            try {
                if(fileWriter != null) { // 防止空指针异常。
                    fileWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

4:几个常见的问题

(1):

文本文件:.txt .java .c .cpp —》建议使用字符流操作

非文本文件:.jpg, .mp3 , .mp4 , .docx , .ppt —》建议使用字节流操作

(2):

Q:警告:不要用字符流去操作非文本文件

A:字符流是用于处理文本数据的流,他们将数据视为字符序列,并通过读取与写入字符来操作数据。相比之下,非文本文件包含的是二进制数据,他们不是用字符表示的。因此如果你使用字符流去处理非文本文件,可能会带来如下几个问题。(1):数据的损坏:字符流会将数据读入内存,然后将其转化为字符序列,在这个过程中,如果数据中包含着不可转换为字符的字节,这些字节将被丢弃或被转换为其他的字符,这样就会导致数据的损坏。(2):效率低下:由于字符流会将二进制数据转化为字符序列,因此会产生额外的开销,这可能会导致处理非常大的二进制文件时效率降低。因此建议使用使用字节流来处理非文本文件,字节流直接读取与写入字节数据,不需要进行任何的转化,因此可以确保数据的完整性与准确性,并提高准确度。

(3):

Q:那字节流可以处理文本文件吗??

**A:字节流可以用于处理文本文件,虽然文本文件是由字符组成,但是在计算机内部,他们是用二进制数据表示的。字节流可以直接的读取与写入二进制数据,因此也可以用于处理文本文件。**当然如果你只需要处理文本文件,使用字符流会更加的方便与简单。因为字符流会自动的处理字符编码和换行符等文本特有的问题,**而字节流需要手动的处理这些问题。**然是如果你需要同时处理字符以及包含二进制数据的文件,或是需要处理非常大的文件,那么字节流是最合适的。

5:字节输入 / 出流FileInputStream 与 FileOutputStream

复制一份非文本文件

	public static void main(String[] args) throws IOException {
     // 1:创建一个源文件与一个目标文件
     File f1 = new File("A:\\TEXT\\1.1.jpg");
     File f2 = new File("A:\\TEXT\\1.2.jpg");
     // 2:创建字节流,读取字节流与写入字节流
     FileInputStream fileInputStream = new FileInputStream(f1);
     FileOutputStream fileOutputStream = new FileOutputStream(f2);
     // 3:进行传输动作
     // (1):文件是UTF-8进行存储的,所以英文字符占1个字节,中文字符占3个字节
     // (2):如果文件是文本文件,建议不要使用字节流进行读取,建议使用字符流。
     byte[] bytes = new byte[1024 * 6]; // 缓冲数组
     int len = fileInputStream.read(bytes); // 返回的是有效长度
     while(len != -1) {
         fileOutputStream.write(bytes,0 , len); // 注意这个地方的范围
         len = fileInputStream.read(bytes);
     }
     // 4:关闭流
     fileOutputStream.close();
     fileInputStream.close();
 }

6:缓冲字节流 BufferedInputStream 与 BufferedOutputStream

引入:

【1】读入一个字节,写出一个字节:

在这里插入图片描述

【2】利用缓冲字节数组:

在这里插入图片描述

【3】利用缓冲区:

在这里插入图片描述

实践

	public static void main(String[] args) throws IOException {
     // 1:创建一个源文件与一个目标文件
     File f1 = new File("A:\\TEXT\\1.1.jpg");
     File f2 = new File("A:\\TEXT\\1.2.jpg");
     // 2:创建字节流,读取字节流与写入字节流
     FileInputStream fileInputStream = new FileInputStream(f1);
     FileOutputStream fileOutputStream = new FileOutputStream(f2);
     // 3:功能加强:
     // FileInputStream外面套一个管子:BufferedInputStream
     // fileOutputStream外面套一个管子:BufferedOutputStream
     BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
     BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
     // 4:进行传输动作
     byte[] bytes = new byte[1024 * 20]; // 缓冲数组
     int len = bufferedInputStream.read(bytes); // 返回的是有效长度
     while(len != -1) {
         System.out.println(len);
         bufferedOutputStream.write(bytes,0,len);
         len = bufferedInputStream.read(bytes);
     }
     // 4:关闭流
     bufferedOutputStream.close();
     bufferedInputStream.close();
 }

总结:

		byte[] bytes = new byte[1024 * 6]; // 缓冲数组
		int len = fileInputStream.read(bytes); // 返回的是有效长度
		while(len != -1) {
         fileOutputStream.write(bytes,0 , len);
         len = fileInputStream.read(bytes);
      }

**这里 fileInputStream.read(bytes) 意思是:先从底层硬盘中一个字节一个字节的读到 bytes 数组中,当bytes数组满了或是文件读取完毕之后,再将bytes数组中的数据写入到目标文件中。读取与写入硬盘的总次数 == (文件字节的长度)/ bytes .length **

	    // 1:创建一个源文件与一个目标文件
     File f1 = new File("A:\\TEXT\\1.1.jpg");
     File f2 = new File("A:\\TEXT\\1.2.jpg");
     // 2:创建字节流,读取字节流与写入字节流
     FileInputStream fileInputStream = new FileInputStream(f1);
     FileOutputStream fileOutputStream = new FileOutputStream(f2);
     // 3:功能加强
     // FileInputStream外面套一个管子
     // fileOutputStream外面套一个管子
     BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
     BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
     // 4:进行传输动作
     byte[] bytes = new byte[1024 * 20]; // 缓冲数组
     int len = bufferedInputStream.read(bytes); // 返回的是有效长度
     while(len != -1) {
         bufferedOutputStream.write(bytes,0,len);
         len = bufferedInputStream.read(bytes);
     }
     // 4:关闭流
	   // 如果处理流包裹着节点流的话,那么其实只要关闭高级流(处理流),那么里面的字节流也会随之被关闭。
     bufferedOutputStream.close();
     bufferedInputStream.close();

这段代码中的缓冲数组可以理解为一个中转站,具体细节是这样的:读缓冲区的大小是8kb,每次从底层硬件文件中一次读8kb的字节到读缓冲区中。当缓冲区满了,然后将缓冲区中的数据放到缓冲数组中。当缓冲数组满了,就将缓冲数组中的数据放到写缓冲区中。当写缓冲区满了,就将写缓冲区的数据放到底层硬件的目标文件中。

字节流与缓冲流对比:

A:字节流:

数据是以字节为单位进行读写操作程序直接从硬件中读物或写入数据,一切文件在系统中都是以字节的形式保存的,无论你是文档文件、视频文件、音频文件…,需要读取这些文件都可以用FileInputStream去读取其保存在存储介质(磁盘等)上的字节序列,文件字节输入流的读取时,是直接同字节流中读取的。由于字节流是与硬件(存储介质)进行的读取,所以速度较慢。而CPU需要使用数据时通过read()、read(byte[])读取数据时就要受到硬件IO的慢速度限制。

在这里插入图片描述

B:缓冲流:

将一个一个的字节先存入到缓冲区中在JVM中会开辟一块缓冲区的内存空间,然后将文件中的数据读取到缓冲区中,直到读满这个缓冲,才会将缓冲区中的数据获取到程序中。
在JVM中会开辟一块缓冲区的内存空间,然后将程序中的数据写入到缓冲区中,直到写满这个缓冲,才会将缓冲区中的数据写入到文件中。

在这里插入图片描述

C:原理总结:

带缓冲的字节输入流:上面我们知道文件字节输入流的读取时,是直接同字节流中读取的。由于字节流是与硬件(存储介质)进行的读取,所以速度较慢。而CPU需要使用数据时通过read()、read(byte[])读取数据时就要受到硬件IO的慢速度限制。我们又知道,CPU与内存发生的读写速度比硬件IO快一个数量级,所以优化读写的思路就有了:在内存中建立缓存区,先把存储介质中的字节读取到缓存区中。CPU需要数据时直接从缓冲区读就行了,缓冲区要足够大,在被读完后又触发fill()函数自动从存储介质的文件字节内容中读取字节存储到缓冲区数组。
BufferedInputStream 内部有一个缓冲区,默认大小为8KB**,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源 (譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容返回给用户.由于从缓冲区里读取数据远比直接从存储介质读取速度快,所以BufferedInputStream的效率很高。**

7:缓冲字符流

	public static void main(String[] args) throws IOException {
     File f1 = new File("A:\\TEXT\\16.txt");
     File f2 = new File("A:\\TEXT\\15.txt");

     FileReader fileReader = new FileReader(f1);
     FileWriter fileWriter = new FileWriter(f2);
     BufferedReader bufferedReader = new BufferedReader(fileReader);
     BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

     /*
     // 方式一:缓冲数组
     char[] c = new char[1024];
     int len = bufferedReader.read(c);
     while (len != -1) {
         bufferedWriter.write(c, 0, len);
         len = bufferedReader.read(c);
     }
      */

     /*
     // 方式二:一个一个的读写
     int n = bufferedReader.read();
     while(n != -1) {
         bufferedWriter.write(n);
         n = bufferedReader.read();
     }
      */
     // 方式三:
     String str = bufferedReader.readLine();
     while(str != null) {
         bufferedWriter.write(str);
         // 另起一行,注意系统是不会给我们自动换行的,我们需要自己换行。
         bufferedWriter.newLine();
         str = bufferedReader.readLine();
     }
     bufferedWriter.close();
     bufferedReader.close();
 }

注意:read与readline方法的区别

read:读取单个字符/字节,是一个通用方法。 返回:作为一个整数(其范围从 0 到 65535 (0x00-0xffff))读入的字符,如果已到达流末尾,则返回 -1。

readLine:读取一个文本行,主要是一个用于读取文本的方法。通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行。 返回:包含该行内容的字符串不包含任何行终止符,如果已到达流末尾,则返回 null

8:转换流:inputstreamreader , outputstreamwriter

【1】转换流:作用:将字节流和字符流进行转换。

【2】转换流 属于 字节流还是字符流**?属于字符流**

InputStreamReader :字节输入流 —》字符的输入流

OutputStreamWriter : 字符输出流 --》字节的输出流

在这里插入图片描述

	public static void main(String[] args) throws IOException {
     File f1 = new File("A:\\TEXT\\16.txt");
     File f2 = new File("A:\\TEXT\\15.txt");

     FileInputStream fileInputStream = new FileInputStream(f1); //字节输入流
     // 转化为UTF-8类型的字符
     InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream , "utf-8"); // 字符转换流

     FileOutputStream fileOutputStream = new FileOutputStream(f2); // 字节输出流
     // 输出为UTF-8类型的字符
     OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream , "utf-8"); // 字符转换流

     char[] c = new char[1024 * 10];
     int len = inputStreamReader.read(c);
     while (len != -1) {
         outputStreamWriter.write(c , 0 , len);
         len = inputStreamReader.read(c);
     }

     outputStreamWriter.close();
     inputStreamReader.close();
 }

输入转换流:将源文件中的二进制内容转化成对应编码字符集的内容。

输出转换流:按照目标编码字符集,将字符转化成二进制内容。

假如源文件是UTF-8编码,目标文件是GBK编码。

(1):源文件按照字节的形式输入,然后按照UTF-8字符集的格式将二进制内容解析成对应的字符。

(2):将解析出来的字符按照GBK字符集的格式转化成对应的二进制形式进行输出。

9:System.in与System.out

System.in : “标准”输入流。—》默认情况下 从键盘输入

System.out :“标准”输出流。 —》默认情况下,输出到控制台。

package P1.package4;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Test7 {
 public static void main(String[] args) throws IOException {
     // 标准的输入流
     InputStream in = System.in; // 注意这是一个字节流
     // read方法等待键盘的录入,这是一个阻塞方法。
     // system.in这个管怼到键盘上,所以你从键盘上录入的化,就会从这个管到程序中。
     int n = in.read(); // a
     System.out.println(n); // 97
     // Scanner就是一个扫描器,扫描从这个管出来的数据。
     // Scanner scanner = new Scanner(System.in);
     File file = new File("A:\\TEXT\\16.txt");
     // 既然Scanner是扫描的作用,不一定非得扫 System.in进来的东西,还可以扫描其他管的内容:
     Scanner sc = new Scanner(new FileInputStream(file));// 自己输入流
     while(sc.hasNext()) {
         System.out.println(sc.nextLine()); // 读取下一个单词并返回这一单词。
     }
 }
}
/*
a
97
肝死你们,Fuck you !
111111111111111111111111。1111111111111111111111111111111111111。
0000000000。


111111111,11111111111,
000000。
*/
package P1.package4;

import java.io.PrintStream;

public class Test8 {
 public static void main(String[] args) {
     // 直接在控制台写出
     // printstream:打印流,一个管子插在控制台上
     PrintStream o = System.out;
     o.print("1");
     o.print("2");
     o.print("3");
     o.print("4");
     o.println("1");
     o.println("1");
     o.println("1");

     // 就相当于
     System.out.println("1");
     System.out.print("1");
 }
}

10:练习:键盘录入内容输出到文件中

在这里插入图片描述

	public static void main(String[] args) throws IOException {
     // 1: 先准备输入方向
     // 键盘录入
     InputStream in = System.in; // 属于字节流
     // 字节流 --> 字符流
     InputStreamReader inputStreamReader = new InputStreamReader(in); // 转换流
     // 字符流 --> 字符缓冲流
     BufferedReader bufferedReader = new BufferedReader(inputStreamReader); // 字符缓冲流

     // 2:再准备输出方向
     File file = new File("A:\\TEXT\\15.txt");
     FileWriter fileWriter = new FileWriter(file); // 字符写入流
     BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); // 字符缓冲流

     // 3:准备操作

     String s = bufferedReader.readLine();
     while(!s.equals("exist")) {
         bufferedWriter.write(s);
         bufferedWriter.newLine();
         s = bufferedReader.readLine();
     }

     // 4:关闭流
     bufferedWriter.close();
     bufferedReader.close();
 }

课上老师使用的是:键盘输入 / 字节流 --> 字符流 --> 字符缓冲流 --> 程序 --> 字符缓冲流 --> 字符流 --> 目标文件的写法。

我想试一下:键盘输入 / 字节流 --》字节缓冲流 --> 程序 --> 字节缓冲流 --> 字节流 --》目标文件的写法。

package P1.package4;

import java.io.*;

// 键盘录入内容输出到文件中
public class Test9 {

 public static void main(String[] args) throws IOException, InterruptedException {
     // 1: 先准备输入方向
     // 键盘录入
     InputStream in = System.in; // 属于字节流
     //File file1 = new File("A:\\TEXT\\188.txt");
     //FileInputStream fileInputStream = new FileInputStream(file1);
     // 字节流 --> 字节缓冲流
     BufferedInputStream bufferedInputStream = new BufferedInputStream(in);

     // 再准备输出方向
     File file = new File("A:\\TEXT\\122.txt");
     System.out.println(file.exists());
     //字节缓冲流
     BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file));

     // 进行操作
     byte[] bytes = new byte[1024];
     int len = bufferedInputStream.read(bytes); // 阻塞
     // System.out.println(len);
     int count = 0;
     while (len != -1) {
         bufferedOutputStream.write(bytes, 0, len); // 新线程阻塞
         // System.out.println(new String(bytes, 0, len));
         len = bufferedInputStream.read(bytes);
         System.out.println(len);
         count++;
         if (count == 3) {
             break;
         }
     }
     // 关闭流
     bufferedOutputStream.close();
     bufferedInputStream.close();
 }
}

11:对象流 ObjectInputStream 与 ObjectOutputStream

【1】:对象流

用于存储和读取基本数据类型数据或对象的处理流。

它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

【2】序列化和反序列化:

ObjectOutputStream 类 : 把内存中的Java对象转换成平台无关的二进制数据,从而允许把这种二进制数据持久地保存在磁盘上,或通过网络将这种二进制数据传输到另一个网络节点。----》序列化

用ObjectInputStream类 : 当其它程序获取了这种二进制数据,就可以恢复成原来的Java对象。----》反序列化

【3】代码:操作字符串对象:

进行序列化操作。

package P1.package4;

import java.io.*;

public class Test14 {
 public static void main(String[] args) throws IOException {
     ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("A:\\TEXT\\122.txt")));
     objectOutputStream.writeObject("肝死你们,草泥马");
     objectOutputStream.close();
 }
}

在这里插入图片描述

这玩意不是给人看的,是给程序看的。

进行反序列化操作。

package P1.package4;

import java.io.*;

public class Test14 {
 public static void main(String[] args) throws IOException, ClassNotFoundException {
     /*
     ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("A:\\TEXT\\122.txt")));
     objectOutputStream.writeObject("肝死你们,草泥马");
     objectOutputStream.close();
      */
     ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("A:\\TEXT\\122.txt")));
     System.out.println(objectInputStream.readObject());
     objectInputStream.close();
 }
}
// 肝死你们,草泥马

【4】代码:操作自定义类的对象:

package P1.package4;

import java.io.*;

public class Test14 {
 public static void main(String[] args) throws IOException, ClassNotFoundException {
     // 对象流
     ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("A:\\TEXT\\122.txt")));
     Person person = new Person("asfsdf" , 13 , 188.3);
     objectOutputStream.writeObject(person);
     objectOutputStream.close();

     /*
     ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("A:\\TEXT\\122.txt")));
     System.out.println(objectInputStream.readObject());
     objectInputStream.close();
      */
 }
}

class Person {
 // 属性
 private String name;
 private int age;
 private double height;

 // 构造器
 public Person(String name, int age, double height) {
     this.name = name;
     this.age = age;
     this.height = height;
 }

 // 方法
 @Override
 public String toString() {
     return "Student{" +
             "name='" + name + '\'' +
             ", age=" + age +
             ", height=" + height +
             '}';
 }
}

在这里插入图片描述

会报错误,显示的是没有序列化异常。

解决方法:你要序列化的那个对象所对应的类应该实现一个Serializable接口

在这里插入图片描述

接口内部,什么都没有,这种接口叫 标识接口。 起到标识作用,标识什么呢?只要实现这个接口的类的对象才能序列化,否则不可以。

【5】serialVersionUID:

假设刚开始的时候Person类里面我们没有toString方法,我们将person对象通过对象流写入到文件中,然后再通过反序列化读入到内存中,此时对Person对象进行修改重新加上toString方法,并打印该对象,会发现出现异常。

写入的person类

class Person implements Serializable{
 // 属性
 private String name;
 private int age;
 private double height;

 // 构造器
 public Person(String name, int age, double height) {
     this.name = name;
     this.age = age;
     this.height = height;
 }


}

重写反序列化

package P1.package4;

import java.io.*;

public class Test14 {
 public static void main(String[] args) throws IOException, ClassNotFoundException {


     /*
     ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("A:\\TEXT\\122.txt")));
     Person person = new Person("asfsdf" , 13 , 188.3);
     objectOutputStream.writeObject(person);
     objectOutputStream.close();
      */




     ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("A:\\TEXT\\122.txt")));
     System.out.println(objectInputStream.readObject().toString());
     objectInputStream.close();
 }
}
// 反序列化后修改后的person类
class Person implements Serializable{
 // 属性
 private String name;
 private int age;
 private double height;

 // 构造器
 public Person(String name, int age, double height) {
     this.name = name;
     this.age = age;
     this.height = height;
 }

 // 方法

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

}

在这里插入图片描述

会出现InvalidClassException异常。

出现异常的原因:

在这里插入图片描述

解决:给这个类 加入一个 序列号:serialVersionUID

在这里插入图片描述

凡是实现Serializable接口(标识接口)的类都有一个表示序列化版本标识符的静态常量:

➢private static final long serialVersionUID;

➢serialVersionUID用来表明类的不同版本间的兼容性简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。

➢如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。

➢简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

【6】序列化细节:

(1)被序列化的类的内部的所有属性,必须是可序列化的基本数据类型都是可序列化的

在这里插入图片描述

(2)static,transient修饰的属性 不可以被序列化。

package P1.package4;

import java.io.*;

public class Test14 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /*
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("A:\\TEXT\\122.txt")));
        Person person = new Person("asfsdf" , 13 , 188.3);
        System.out.println(person.toString()); // Student{name='asfsdf', age=13, height=188.3}
        objectOutputStream.writeObject(person);
        objectOutputStream.close();
         */
        // 因为static与transient修饰的属性不可以被序列化,所以这两个属性是写不进去的。

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("A:\\TEXT\\122.txt")));
        System.out.println(objectInputStream.readObject().toString());
        // Student{name='null', age=0, height=188.3}
        objectInputStream.close();

    }
}

class Person implements Serializable{
    private static final long serialVersionUID = -7774721154046748661L;
    // 属性
    private static String name;
    private transient int age;
    private double height;

    // 构造器
    public Person(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    // 方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HackerTerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值