Java知识点07——输入/输出(File类、IO流、序列化、NIO)

Java知识点07——输入/输出(File类、IO流、序列化、NIO)

声明:

  1. 该资料来自自己整理。
  2. 参考、摘抄书籍 疯狂 Java讲义(第五版) 李刚©著

一、File 类

1.1 概述


java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

1.2 构造方法

  • public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
  • public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
  • public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。
  • public File(URI uri) :通过将指定的file:URI转换为抽象路径名来创建新的File实例。

举例说明:
个人的身份证号就是URN,个人的家庭地址就是URL,URN可以唯一标识一个人,而URL可以告诉邮递员怎么把货送到你手里。

  • 构造举例,代码如下:
import java.io.File;
import java.io.IOException;
public class FileDemo {
    public static void main(String[] args) throws IOException {
        //File(String pathname)  将指定路径名转换成一个File对象
        File file = new File("D:\\1.txt");//只是创建该文件对象(并没有创建该文件)!!!
        System.out.println(file);
        file.createNewFile();//真正根据该对象创建文件(如果路径存在的话)(如果路径不存在则必须创建,否则报错)
        //创建文件夹
        File fileDir = new File("D:\\a");
        //判断该file1文件对象是否存在,如果不存在就创建该对象
        if (!fileDir.exists()){
            System.out.println("该路经是否创建成功?"+fileDir.mkdir());
        }
        //File(String parent,String child) 根据指定的父路径和文件路径创建File对象
        File file1 =  new File("D:\\a","1.txt");
        System.out.println(file1);
        //D盘下没有a文件夹
        //file1.createNewFile();//IOException: 系统找不到指定的路径。
        file1.createNewFile();
        //File(File parent,String child) 根据指定的父路径对象和文件路径创建File对象
        File parent = new File("D:\\a");
        File file2 = new File(parent, "1.txt");
        System.out.println(file2);
        File file3 = new File(new File("D:\\a"),"1.txt");
        System.out.println(file3);
    }
}

小贴士:

  1. 一个File对象代表硬盘中实际存在的一个文件或者目录。
  2. 无论该路径下是否存在文件或者目录,都不影响File对象的创建。

1.3 访问文件和目录

1.3.1 访问文件名相关方法

import java.io.File;
import java.io.IOException;
public class FileDemo01 {
    public static void main(String[] args) throws IOException {
        //创建文件夹
        File fileDir = new File("D:\\a");
        //判断该file1文件对象是否存在,如果不存在就创建该对象
        if (!fileDir.exists()){
            System.out.println("该路经是否创建成功?"+fileDir.mkdir());
        }
        //File(String parent,String child) 根据指定的父路径和文件路径创建File对象
        File file1 =  new File("D:\\a","1.txt");
        System.out.println(file1);
        file1.createNewFile();//创建该文件 1.txt //如果该路径不存在就直接创建会报错
        System.out.println("file1.getName():"+file1.getName());
        System.out.println("file1.getPath():"+file1.getPath());
        System.out.println("file1.getAbsoluteFile():"+file1.getAbsoluteFile());
        System.out.println("file1.getAbsolutePath():"+file1.getAbsolutePath());
        System.out.println("file1.getParent():"+file1.getParent());
        System.out.println("file1.renameTo():"+file1.renameTo(new File("D:\\a\\2.txt")));
    }
}


1.3.2 文件检测相关方法

在UNIX/Linux/BSD等系统上,如果路径名开头是一条斜线(/),则说明File对象对应一个绝对路径;在Windows等系统上,如果路径开头是盘符,则说明它是一个绝对路径。

import java.io.File;
import java.io.IOException;
public class FileCheckDemo {
    public static void main(String[] args) throws Exception {
        isExists();
    }
    //Boolean exists();判断文件是否存在
    public static void isExists() throws IOException {
        new File("D:\\b").mkdir();
        File file = new File("D:\\b\\a.txt");
        file.createNewFile();
        System.out.println("file:"+file);
        System.out.println("对象对应的文件是否存在?"+file.exists());//true
        System.out.println("file对象对应的文件是否可读?"+file.canRead());
        System.out.println("file对象对应的文件是否可写?"+file.canWrite());
        System.out.println("file对象对应的文件是否是文件?"+file.isFile());
        System.out.println("file对象对应的文件是否是目录?"+file.isDirectory());
        System.out.println("file对象对应的文件是否是绝对路径?"+file.isAbsolute());
    }
}

1.3.3 获取常规文件信息

import java.io.File;
import java.io.IOException;
public class FileCheckDemo {
    public static void main(String[] args) throws Exception {
        isExists();
    }
    //Boolean exists();判断文件是否存在
    public static void isExists() throws IOException {
        new File("D:\\b").mkdir();
        File file = new File("D:\\b\\a.txt");
        file.createNewFile();
        System.out.println("file对象对应的文件的最后修改时间:"+file.lastModified());
        System.out.println("file对象对应的文件内容长度:"+file.length());
    }
}

1.3.4 文件操作相关方法

import java.io.File;
import java.io.IOException;
public class FileOperationDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("D:\\a\\fileDemo.txt");
        //当file对象对应的文件不存在时创建该文件(创建成功返回true,否则返回false)
        if (!file.exists()){
            new File("D:\\a").mkdir();
        }
        file.createNewFile();
        //删除file对象所对应的文件或路径
        System.out.println("删除file对象对应的文件是否成功?"+file.delete());
        //在指定的临时文件目录中创建一个临时空文件
        File.createTempFile("cht",".tmp", new File("D:\\a"));
        //当Java虚拟机退出时,删除file对象所对应的文件
        file.deleteOnExit();

        //相对路径创建文件
        System.out.println("======相对路径创建File文件======");
        File file2 = new File("fileRelativeTest.txt");
        file2.createNewFile();
        System.out.println("file2对象的绝对路径为:"+file2.getAbsolutePath());
    }
}


createNewFile()

delete()

createTempFile(String prefix,String suffix,File director)

相对路径创建文件

1.3.5 目录操作相关方法


public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

import java.io.File;
public class FileDirectorOperation {
    public static void main(String[] args) {
        File file = new File("D:\\a\\b");
        //如果file对应的目录不存在就创建该目录
        System.out.println("file对象是否存在?"+file.exists());
        if (!file.exists()){
            file.mkdir();
        }

        File file2 = new File("D:\\a");
        //列出所有file对象的所有文件名和目录名
        System.out.println("======"+file2.getAbsolutePath()+"路径下对应的所有文件和目录======");
        String[] fileList = file2.list();
        for (String fileName:fileList){
            System.out.println(fileName);
        }
        //列出系统所有的根目录
        System.out.println("======系统的所有根目录======");
        File[] roots = File.listRoots();
        for (File root:roots){
            System.out.println(root);
        }
    }
}

  • 当使用相对路径的File对象来获取父路径时可能引起错误,因为该方法返回File对象所对应的目录名、文件名里最后一个子目录名、子文件名删除后的结果。
  • API中说明:length(),表示文件的长度。但是File对象表示目录,则返回值未指定。
  • API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。

1.2 文件过滤器

1.2.1 普通方法

import java.io.File;
public class SearchFile {
    public static void main(String[] args) {
        File dir = new File("D:\\a");
        printFilesName(dir);
    }
    //打印
    private static void printFilesName(File dir) {
        //获取子文件和目录
        File[] files = dir.listFiles();
        //判断是否null,防止空指针异常
        if (files != null){
            for (File f:files){
                if (f.isFile() && f.getName().endsWith(".tmp")){
                    System.out.println("文件名:"+f.getName()+" ,文件绝对路径:"+f.getAbsolutePath());
                }else{
                    printFilesName(f);
                }
            }
        }
    }
}

1.2.2 匿名内部类方法

import java.io.File;
import java.io.FilenameFilter;
public class FilenameFilterTest {
    public static void main(String[] args) {
        File file = new File("D:\\a");
        //数据源文件必须是要.tmp类型
        File[] dataFiles = file.listFiles(new FilenameFilter() {
            //以a开头,后缀名为.tmp的将被选出来,其余被过滤掉
            @Override
            public boolean accept(File dir, String name) {
                String fileName = name.toLowerCase();
                if (fileName.endsWith(".tmp")){
                    if (fileName.startsWith("a")){
                        return true;
                    }
                }
                return false;
            }
        });
        //遍历File[]
        for (File f:dataFiles){
            System.out.println(f.getName());
        }
    }
}

1.2.3 Lambda表达式优化

import java.io.File;
import java.io.FilenameFilter;
public class FilenameFilterTest {
    public static void main(String[] args) {
        File file = new File("D:\\a");
        //Lambda表达式优化
        File[] files = file.listFiles(((dir, name) -> name.endsWith(".tmp")
                && name.startsWith("a")));
        for (File f:files){
            System.out.println(f.getName());
        }
    }
}

1.3 案例

1.3.1 递归打印多级目录

分析:多级目录的打印,就是当目录的嵌套。遍历之前,无从知道到底有多少级目录,所以我们还是要使用递归实现。

代码实现

import java.io.File;
public class RecursionPrintDirectory {
    public static void main(String[] args) {
        //创建file对象
        File dir = new File("D:\\a");
        //调用打印目录方法
        printDir(dir);
    }
    //打印目录的方法
    private static void printDir(File dir) {
        //获取子文件和目录
        File[] files = dir.listFiles();
        /**
         * 判断:当是文件时,打印该文件的绝对路径
         *      当是目录时,继续调用打印目录的方法,形成递归调用
         */
        for (File f:files){
            if (f.isFile()){
                System.out.println("文件:"+f.getName()+" ,路径为:"+f.getAbsolutePath());
            }else{
                System.out.println("目录:"+f.getName()+" ,路径为:"+f.getAbsolutePath());
                //继续遍历,调用printDir()形成递归
                printDir(f);
            }
        }
    }
}

二、IO流

2.1 什么是IO

Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

2.2 IO分类

1.2.1、输入流/输出流

输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

根据数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。InputStream or OutputStream
  • 字符流 :以字符为单位,读写数据的流。Reader or Writer

1.2.2 字节流/处理流

1.2.3 IO的流向说明

  • 输入流/输出流 以内存为基准

1.2.4 流的概念模型


1.2.5 字节流 / 字符流

  • 字节流和字符流操作方式几乎完全一样,区别只在于操作的数据单元不同
    字节流操作的数据单元是8位的字节,字符流操作的数据单元是16位的字符
  • 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

2.3 输入流(FileInputStream、FileReader)

2.3.1 FileInputStream(字节输入流)

构造方法:

  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException

常用方法:

  • int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)。
  • int read(byte[] b):从输入流中最多读取 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数。
  • int read(byte[] b,int offset,int lenght):从输入流中最多读取 length 个字节的数据,并将其存储在字符数组 b 中,放入数组 b 中时,并不是从数组起点开始,而是从 offset 位置开始,返回实际读取的字节数。

直到read(byte[] b)方法返回-1,即表明到了输入流的结束点。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamTest {
    public static void main(String[] args) throws IOException {
        //创建字节输入流
        FileInputStream fis = new FileInputStream("D:\\a\\FileInputStreamTest.txt");
        //创建一个长度为1024的“容器”(可以理解为盛放字节流的容器)
        byte[] b = new byte[1024];//java中一个汉字占3个字节
        //用于保存实际读取的字节数
        int hasRead = 0;
        //使用循环来重复“取水”过程
        while( (hasRead = fis.read(b)) > 0 ){
            //取出 “容器” 中的水滴(字节),将字节数组转换成字符串输入
            System.out.println(new String(b,0,hasRead));
        }
        //关闭文件输入流,放在finally块里更安全
        fis.close();
    }
}


在这里插入图片描述

2.3.2 FileReader(字符输入流)

java.io.FileReader 类是读取字符文件的类。构造时使用系统默认的字符编码和默认字节缓冲区。

小贴士:

  1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
    idea中UTF-8
  2. 字节缓冲区:一个字节数组,用来临时存储字节数据。

构造方法:

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。

常用方法:

  • int read():从输入流中读取单个字符,返回所读取的字符数(字节数据可直接转换为int类型)。
  • int read(char[] c):从输入流中最多读取 c.length 个字符的数据,并将其存储在字符数组 c 中,返回实际读取的字符数。
  • int read(char[] c,int offset,int lenght):从输入流中最多读取 length 个字符的数据,并将其存储在字符数组 c 中,放入数组 c 中时,并不是从数组起点开始,而是从 offset 位置开始,返回实际读取的字符数。

直到read(char[] cbuf)方法返回-1,即表明到了输入流的结束点。

import java.io.FileReader;
import java.io.IOException;
public class FileReaderTest {
    /**
     * 程序里打开的文件IO资源不属于内存里的资源,
     * 垃圾回收机制无法回收该资源,所以应该显式地关闭文件IO资源
     * Java 7 改写了所有的IO资源类,它们都实现了AutoCloseable接口,
     * 因此都可以通过自动关闭资源的try语句来关闭这些IO流。
     */
    public static void main(String[] args) {
        try(FileReader fr = new FileReader("D:\\a\\FileReader.txt")){
            char[] c = new char[32];
            int hasRead = 0;
            while ((hasRead = fr.read(c)) > 0){
                System.out.println(new String(c,0,hasRead));
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

2.4 输出流(FileOutputStream、FileWriter)

构造方法:

  • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了。默认是false

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

常用方法:

  • void write(int c)将指定的字节/字符输入到输出流中,其中 c 既可以代表字节,也可以代表字符。
  • void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到指定输出流中。
  • void write(byte[]/char[] buf,int offset,int length):将字节数组/字符数组中从 offset 位置开始,长度为 length的字节/字符输出到输出流中。
  • void write(String str):将 str 字符串里包含的字符输出到指定输出流中。
  • void write(String str,int offset,int length):将 str 字符串里从 offset 位置开始,长度为 length 的字符输出到指定输出流中。
  • void flush() :刷新该流的缓冲。
  • void close() :关闭该流(会先自动刷新该流的缓冲,然后关闭)。

2.4.1 FileOutputStream(字节输出流)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest {
    public static void main(String[] args) {
        try(
            //创建字节输入流
            FileInputStream fis = new FileInputStream("D:\\a\\FileOutputStream.txt");
            //创建字节输出流
            //当指定路径下的文件不存在时会自动创建
            FileOutputStream fos = new FileOutputStream("D:\\a\\NewFileOutputStream.txt",true);
        ){
            byte[] b = new byte[32];//相当于一个缓冲区
            int hasRead = 0;
            while ((hasRead = fis.read(b)) > 0){
                //每读取一次,即写入文件输出流;读了多少,就写多少
                fos.write(b,0,hasRead);
            }
            //前提要FileOutputStream(String name,boolean append)append的参数要为true
            byte[] bAppendByte = "这里是追加的信息。。。".getBytes();
            fos.write(bAppendByte);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

2.4.2 PrintStream(字节打印流)

继承树
java.lang.Object 
	java.io.OutputStream 
		java.io.FilterOutputStream 
			java.io.PrintStream 

*java.io.PrintStream:打印流
    PrintStream为其他输出流添加了功能,使他们能够方便地打印各种数据值表示形式。
PrintStream特点:
    1.只负责数据的输出,不负责数据的读取
    2.与其他输出流不同,PrintStream永远不会抛出异常IOException
    3.有特有的方法,print,println
        void print(任意类型的值)
        void println(任意类型的值并换行)

构造方法:

  • PrintStream(File file):输出的目的地是一个文件

  • PrintStream(OutputStream out):输出的目的地是一个字节输出流

  • PrintStream(String fileName):输出的目的地是一个文件路径

继承自父类的方法:

  • public void close(): 关闭此输出流并释放与此流相关联的任何系统资源
  • public void flush(): 刷新此输出流并强制任何缓冲流的输出字节被写入
  • public void write(byte[] b): 将 b.leng 字节从指定的字节数组写入此输出流
  • public void write(byte[] b,int off,int len): 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。
  • public abstract void write(int b): 将指定的字节写入此流。

注意:
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97–>a
如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97–>97

import java.io.FileNotFoundException;
import java.io.PrintStream;
public class PrintStreamTest01 {
    public static void main(String[] args) {
        //创建打印流PrintStream对象,构造方法中绑定要输出的目的地
        try (PrintStream ps = new PrintStream("D:\\a\\PrintStream.txt")){
            //如果使用继承自父类的的方法write写数据,那么查看数据的时候会查询编码表97-->a
            ps.write(97);
            ps.println();//换行
            //如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97-->97
            ps.println(97);
            ps.println('a');
            ps.println("hello");
            ps.println(true);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2.4.3 FileWriter(字符输出流)

import java.io.FileWriter;
import java.io.IOException;
public class FileWriterTest {
    public static void main(String[] args) {
        try(FileWriter fw = new FileWriter("D:\\a\\FileWriter.txt")){
            fw.write("锦瑟 - 李商隐\r\n");
            fw.write("锦瑟无端五十弦,一弦一柱思华年\r\n");
            fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃\r\n");
            fw.write("沧海月明珠有泪,蓝田日暖玉生烟\r\n");
            fw.write("此情可待成追忆,只是当时已惘然\r\n");
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}


  • 回车符\r和换行符\n
    • 回车符:回到一行的开头(return)。
    • 换行符:下一行(newline)。
  • 系统中的换行:
    • Windows系统里,每行结尾是 回车+换行 ,即\r\n
    • Unix系统里,每行结尾只有 换行 ,即\n
    • Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。

小贴士:

使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。

2.4.4 PrintWriter(字符打印流)

继承树:
java.lang.Object 
	java.io.Writer 
		java.io.PrintWriter 

构造函数:

  • PrintWriter(File file):使用指定的文件创建一个没有自动行刷新的新PrintWriter。
  • PrintWriter(File file, Charset charset): 使用指定的文件和字符集创建一个没有自动行刷新的新PrintWriter。

常用方法:

  • print(String str):向文件写入一个字符串。
  • print(char[] ch):向文件写入一个字符数组。
  • print(char c):向文件写入一个字符。
  • print(int i):向文件写入一个int型值。
  • print(long l):向文件写入一个long型值。
  • print(float f):向文件写入一个float型值。
  • print(double d):向文件写入一个double型值。
  • print(boolean b):向文件写入一个boolean型值。

例子跟PrintStream类似

2.5 输入输出流体系

2.5.1 处理流

功能:它可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操作。

import java.io.*;
public class PrintStreamTest {
    public static void main(String[] args) {
        try(
            //创建节点流(默认不能追加输出流中的信息 append 参数为false)
            FileOutputStream fos = new FileOutputStream("D:\\a\\FileOutputStream.txt");
            //创建处理流
            PrintStream ps = new PrintStream(fos);//参数为一个节点流
        ){
            //使用不能追加字节流的处理流执行输出 //会覆盖文件的内容!!!!!
            ps.println("输出普通字符串"); //不是在控制台输出,而是写入到指定的文件中
            //直接使用处理流输出对象
            ps.println(new PrintStreamTest());//不是在控制台输出,而是写入到指定的文件中
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}


2.5.2 输入输出流中常用的流分类

注意:

  • 规则:如果进行输入/输出的内容时文本内容,则应该考虑使用字符流;如果进行输入/输出的内容是二进制内容,则应该考虑使用字节流。
  • 增加缓冲流功能后需要使用 flush()才可以将缓冲区的内容写入实际的物理节点。
/**
 * 用字符串作为物理节点的字符输入/输出流的用法
 */
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
public class StringNodeTest {
    public static void main(String[] args) {
        String src = "从现在开始,做一个上进的人\n喂马,劈柴,周游世界\n告诉他们im fine";
        char[] buffer = new char[32];//创建一个字符数组,作为缓冲区
        int hasRed = 0;
        try(StringReader sr = new StringReader(src)){
            //采用循环方式读取字符串存入到buffer字符数组中
            while ((hasRed = sr.read(buffer)) > 0){//判断读取到的有效字节数
                System.out.println(new String(buffer,0,hasRed));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try(
           //创建StringWriter时,实际上是以一个StringBuffer作为输出节点
           //使用指定的初始字符串缓冲区大小
           StringWriter sw = new StringWriter(20)
        ){
            sw.write("这是一个美丽的地方\n");//实际是将str写入到指定的字符串缓冲区中
            sw.write("湖北理工学院\n");
            sw.write("慈湖湖畔\n");
            System.out.println("-------下面是sw字符串节点里的内容-------");
            System.out.println(sw.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.5.3 转换流

  • InputStreamReader:将字节流输入流转换成字符输入流。
  • OutputStreamWtirer:将字节输出流转换成字符输出流。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class KeyinTest {
    public static void main(String[] args) {
        try (
            //将System.in对象转成Reader对象
            InputStreamReader reader = new InputStreamReader(System.in);
            //将普通的Reader包装成BufferedReader
            BufferedReader br = new BufferedReader(reader);
        ){
            String line = null;
            //采用循环方法来逐行地读取
            while((line = br.readLine()) != null){
                //如果读取的字符串是 exit ,则程序退出
                if (line.equals("exit")){
                    System.exit(1);
                }
                //打印读取的内容
                System.out.println("输入内容为:"+line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.5.4 缓冲流

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

2.5.4.1 字节缓冲流

构造方法:

  • public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。

构造举例,代码如下:

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

效率测试:

查询API,缓冲流读写方法与基本的流是一致的

基本流,代码如下:

public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	FileInputStream fis = new FileInputStream("jdk9.exe");
        	FileOutputStream fos = new FileOutputStream("copy.exe")
        ){
        	// 读写数据
            int b;
            while ((b = fis.read()) != -1) { //从此输入流中读取一个字节的数据。
                fos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
    }
}

十几分钟过去了...
  1. 缓冲流,代码如下:
public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
	     	BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
        ){
        // 读写数据
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流复制时间:"+(end - start)+" 毫秒");
    }
}

缓冲流复制时间:8016 毫秒

如何更快呢?

使用数组的方式,代码如下:

public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
      	// 记录开始时间
        long start = System.currentTimeMillis();
		// 创建流对象
        try (
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
		 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
        ){
          	// 读写数据
            int len;//每次读取的字节长度
            byte[] bytes = new byte[8*1024];//设置一个大的数组充当(缓冲区)
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0 , len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
    }
}
缓冲流使用数组复制时间:666 毫秒
2.5.4.2 字符缓冲流

构造方法:

  • public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
  • public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

构造举例,代码如下:

// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

特有方法:

字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。

  • BufferedReader:public String readLine(): 读一行文字。
  • BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。

readLine方法演示,代码如下:

public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
      	 // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
		// 定义字符串,保存读取的一行文字
        String line  = null;
      	// 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {
            System.out.print(line);
            System.out.println("------");
        }
		// 释放资源
        br.close();
    }
}

newLine方法演示,代码如下:

public class BufferedWriterDemo throws IOException {
    public static void main(String[] args) throws IOException  {
      	// 创建流对象
		BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
      	// 写出数据
        bw.write("我是");
      	// 写出换行
        bw.newLine();
        bw.write("程序");
        bw.newLine();
        bw.write("员");
        bw.newLine();
		// 释放资源
        bw.close();
    }
}

2.6 重定向标准输入/输出

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
/**
 * 重定向标准输入流,将System.out的输入重定向到文件输出,而不是在屏幕上输出
 */
public class RedirectOut {
    public static void main(String[] args) {
        PrintStream ps = null;
        try {
            //一次性创建PrintStream输出流
            ps = new PrintStream(new FileOutputStream("D:\\a\\out.txt"));
            //将标准输出重定向到ps输出流
            System.setOut(ps);
            //向标准输出输出一个字符串
            System.out.println("普通字符串");
            //向标准输出输出一个对象
            System.out.println(new RedirectOut());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if (ps != null){
                ps.close();
            }
        }
    }
}

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;
/**
 * 重定向标准输入,从而将System.in重定向到指定文件,而不是键盘
 */
public class RedirectIn {
    public static void main(String[] args) {
        FileInputStream fis = null;
        Scanner sc = null;
        try {
            fis = new FileInputStream("D:\\a\\in.txt");
            //将标准输入流重定向到fis输入流
            System.setIn(fis);
            //使用System.in创建Scanner对象,用于获取标准输入
            sc = new Scanner(System.in);
            //只把回车作为分隔符
            sc.useDelimiter("\n");
            //判断是否还有下一个输入项
            while (sc.hasNext()){
                System.out.println("键盘输入的内容是:"+sc.next());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if (fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (sc != null){
                sc.close();
            }
        }
    }
}

2.7 案例

2.7.1 图片复制

public class Copy {
    public static void main(String[] args) throws IOException {
        // 1.创建流对象
        // 1.1 指定数据源
        FileInputStream fis = new FileInputStream("D:\\test.jpg");
        // 1.2 指定目的地
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");

        // 2.读写数据
        // 2.1 定义数组(相当于缓冲区,暂时存放读取的字节)
        byte[] b = new byte[1024];
        // 2.2 定义长度
        int len;
        // 2.3 循环读取
        while ((len = fis.read(b))!=-1) {
            // 2.4 写出数据
            fos.write(b, 0 , len);
        }

        // 3.关闭资源
        fos.close();
        fis.close();
    }
}

流的关闭原则:先开后关,后开先关

  1. 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
  2. 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。

2.7.2 关闭和刷新

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

  • flush :刷新缓冲区,流对象可以继续使用。
  • close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

代码使用演示:

public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();
      
      	// 写出数据,通过close
        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
        fw.close();
    }
}

小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。

三、序列化

3.1 序列化含义




建议:程序创建的每个 JavaBean 类都实现 Serializable。

3.2 使用对象流实现序列化

3.2.1 Serializable 接口

使用Serializable来实现序列化非常简单,主要让目标类实现Serializable标记接口即可,无须实现任何方法。

一旦某个类实现了Serializable接口,该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象:

序列化:
​1.创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其它节点流的基础之上。
    //创建ObjectOutputStream输出流
	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));2.调用ObjectOutputStream对象的writeObject()方法输出序列化对象。
	//将一个Person对象输出到输出流中
    oos.writeObject(per);

反序列化:
​1.创建一个ObjectInputStream输入流,这个流是一个处理流,所以必须及案例在其它节点流的基础之上。
    //创建一个ObjectInputStream输入流
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));2.调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道Java对象的类型,则可以将对象强制类型转换成其真实的类型。
    //从输入流中读取一个Java对象,并将其强制类型转换为Person类
    Person p = (Person)ois.readObject();

序列化案例:

import java.io.Serializable;
/**
 * 实例化的Person bean类
 */
@Data
public class Person implements Serializable {//必须实现Serializable接口
    private String name;
    private int age;
}
/**
 * 使用ObjectOutputStream将一个Person对象写入磁盘
 */
public class WriteObject {
    public static void main(String[] args) {
        //创建一个ObjectOutputStream输出流(放入可自动关闭io资源的try语句中)
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a\\object.txt"))){
            Person per = new Person("孙悟空",500);
            //将per对象写入到输出流中
            oos.writeObject(per);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


反序列化案例:

/**
 * 使用ObjectInputStream将磁盘中序列化的二进制流反序列化为Person类的实例
 */
public class ReadObject {
    public static void main(String[] args) {
        //创建一个ObjectInputStream输入流
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a\\object.txt"));){
            //从输入流中读取一个Java对象,并将其强制类型转换为Person类
            Person p = (Person) ois.readObject();
            System.out.println("名字为:"+p.getName()+",年龄为:"+p.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

3.2.2 Externalizable 接口

待完善

3.3 对象引用的序列化


@Data
public class Teacher implements Serializable {
    private String name;
    private Person student;
}


3.3.1 防止重复序列化

  • 所有保存到磁盘中的对象都有一个序列化编号。
  • 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)被序列化过,系统才会将该对象转换成字节序列并输出。
  • 如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象。

public class WriteTeacher {
    public static void main(String[] args) {
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a\\teacher.txt"))){
            Person per = new Person("孙悟空",500);
            Teacher t1 = new Teacher("唐僧",per);
            Teacher t2 = new Teacher("菩提祖师",per);
            //将对象写入输出流
            oos.writeObject(t1);
            oos.writeObject(t2);
            oos.writeObject(per);
            oos.writeObject(t2);//重复写只会存在第一次写的那个
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class ReadTeacher {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a\\teacher.txt"))){
            //依次读取 ObjectInputStream 输入流中的4个对象
            Teacher t1 = (Teacher) ois.readObject();
            Teacher t2 = (Teacher) ois.readObject();
            Person p = (Person) ois.readObject();
            Teacher t3 = (Teacher) ois.readObject();
            System.out.println("t1 的 student 引用和 p 是否相同:"+(t1.getStudent() == p));
            System.out.println("t2 的 student 引用和 p 是否相同:"+(t2.getStudent() == p));
            System.out.println("t2 的 t3 是否是同一个对象:"+(t2 == t3));
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意:

如果多次序列化同一个Java对象时,只有第一次序列化时才会把Java对象转换成字节序列输出。(后续如果更改了该对象的变量的值,重新调用 writeObject()方法时,程序只会输出前面的序列化编号,即更改的实例变量的值还是之前的值,不是更改之后的值)

3.4 自定义序列化

@Data
public class TransientPerson implements Serializable {
    private String name;
    private transient int age;
}

//测试类
public class TransientTest {
    public static void main(String[] args) {
        try (
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a\\transient.txt"));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a\\transient.txt"))
        ){
            TransientPerson per = new TransientPerson("孙悟空",500);
            oos.writeObject(per);
            TransientPerson p = (TransientPerson) ois.readObject();
            System.out.println(p.getName()+","+p.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

备注:transient 关键字只能用于修饰实例变量,不可修饰Java程序中的其他成分。

四、NIO(New IO)

4.1 使用 Buffer(缓冲)

Buffer的主要作用是装入数据,然后输出数据

得到一个XxxBuffer对象


Buffer 读入数据后的示意图:

4.2 使用 Channel(通道)


获取 Channel :

@Test
void fileChannelTest(){
    File file = new File("/Users/jpdev/Desktop/deploy.txt");
    try {
        //创建 FileInputStream,以该文件输入流创建 FileChannel
        FileChannel inChannel = new FileInputStream(file).getChannel();
        //以文件输入流创建 FileChannel,用于控制输出
        FileChannel outChannel = new FileOutputStream("/Users/jpdev/Desktop/TemporaryFiles/a.txt").getChannel();

        //将FileChannel里的全部数据映射成 ByteBuffer
        MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
        //直接将buffer 里的数据全部输出 (文件复制)
        outChannel.write(buffer);
        
        //再次调用buffer的clear()方法,复原limit、position的位置
        buffer.clear();
        //使用 UTF-8 的字符集来创建解码器
        Charset charset = Charset.forName("UTF-8");
        //创建解码器
        CharsetDecoder decoder = charset.newDecoder();
        //使用解码器将ByteBuffer转换成CharBuffer
        CharBuffer charBuffer = decoder.decode(buffer);
        //CharBuffer 的 toString 方法可以获取对应的字符串
        System.out.println(charBuffer);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

4.3 字符集和 Charset


常见中文相关编码

4.4 文件锁

待完善

4.5 Path、Paths、Files

待完善

4.6 FileVisitor 遍历文件和目录

待完善

4.7 WatchService监控文件变化

待完善

4.8 访问文件属性


FileAttributeView 是其他 XxxAttributeView 的父接口。

public class AttributeViewTest {
    public static void main(String[] args) throws IOException {
        //以指定的路径来创建Path对象
        Path path = Paths.get("D:\\a\\AttributeViewTest.txt");
        System.out.println("path里包含的路径数量:"+path.getNameCount());
        System.out.println("path的根路径:"+path.getRoot());
        System.out.println("path的绝对路径:"+path.toAbsolutePath());

        System.out.println("====== BasicFileAttributeView ======");
        //获取访问基本属性的BasicFileAttributeView
        BasicFileAttributeView basicView = Files.getFileAttributeView(path,BasicFileAttributeView.class);
        //获取访问基本属性的BasicFileAttributes
        BasicFileAttributes basicAttribs = basicView.readAttributes();
        //访问文件基本属性
        System.out.println("创建时间:"+new Date(basicAttribs.creationTime().toMillis()));
        System.out.println("最后访问时间:"+new Date(basicAttribs.lastAccessTime().toMillis()));
        System.out.println("最后修改时间:"+new Date(basicAttribs.lastModifiedTime().toMillis()));
        System.out.println("文件大小:"+basicAttribs.size()+" bytes");

        System.out.println("====== FileOwnerAttributeView ======");
        //访问文件属性著信息的FileOwnerAttributeView
        FileOwnerAttributeView ownerView = Files.getFileAttributeView(path,FileOwnerAttributeView.class);
        //获取用户所属信息
        System.out.println("该文件所属用户:"+ownerView.getOwner());
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值