javase IO流简单梳理

说到io流必须先介绍File类

File类

是什么

  • 一个File对象代表硬盘或网络中可能存在的一个文件或者文件目录(俗称文件夹),与平台无关。万事万物皆对象。

  • File对象可以作为参数传递给流的构造器。

  • 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。

怎么构造

  • public File(String pathname) :以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
  • public File(String parent, String child) :以parent为父路径,child为子路径创建File对象。
    //参数1:一定是一个文件目录
    //参数2:可以是一个文件,也可以是一个文件目录
  • public File(File parent, String child) :根据一个父File对象和子文件路径创建File对象

关于路径:

  • **绝对路径:**从盘符开始的路径,这是一个完整的路径。
  • **相对路径:**相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
    • IDEA中,main中的文件的相对路径,是相对于"当前工程"
    • IDEA中,单元测试方法中的文件的相对路径,是相对于"当前module"

一些方法

获取文件和目录基本信息

public String getName() :获取名称
public String getPath() :获取路径
public String getAbsolutePath():获取绝对路径
public File getAbsoluteFile():获取绝对路径表示的文件public String getParent():获取上层文件目录路径。若无,返回null

public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值

public void test1() {
    File file1 = new File("hello.txt");
    System.out.println(file1.getName());
    System.out.println(file1.getPath());
    System.out.println(file1.getAbsolutePath());
    System.out.println(file1.getAbsoluteFile());
    System.out.println(file1.getParent());
    System.out.println(file1.getAbsoluteFile().getParent());
    System.out.println(file1.length());
    System.out.println(file1.lastModified());
    //hello.txt 在该模块下
    //hello.txt
    //hello.txt
    //C:\Users\86151\Downloads\07_授课代码\07_授课代码\07_授课代码\JavaSECode\chapter15_io_teacher\hello.txt
    //C:\Users\86151\Downloads\07_授课代码\07_授课代码\07_授课代码\JavaSECode\chapter15_io_teacher\hello.txt(调用了toString方法)
    //null
    //C:\Users\86151\Downloads\07_授课代码\07_授课代码\07_授课代码\JavaSECode\chapter15_io_teacher
    //19
    //1670807448137
    }

    @Test
    public void test2() {
    File file1 = new File("D:\\io\\io1");
    System.out.println(file1.getName());
    System.out.println(file1.getPath());
    System.out.println(file1.getAbsolutePath());
    System.out.println(file1.getAbsoluteFile());
    System.out.println(file1.getParent());
    System.out.println(file1.getAbsoluteFile().getParent());
    System.out.println(file1.length());
    System.out.println(file1.lastModified());
    //        io1
    //        D:\io\io1
    //        D:\io\io1
    //        D:\io\io1
    //        D:\io
    //        D:\io
    //        0
    //        0
}

列出目录的下一级

public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

@Test
public void test3() {
    //public String[] list()
    File file1 = new File("C:\\Users\\86151\\Downloads\\commons-io-2.6-bin");
    String[] fileArr = file1.list();
    for (String s : fileArr) {
        System.out.println(s);
    }

    System.out.println();
    //public File[] listFiles()
    File[] files = file1.listFiles();
    for (File f : files) {
        System.out.println(f);
	//			System.out.println(f.getName());
    }
    //        commons-io-2.6-javadoc.jar
    //        commons-io-2.6-sources.jar
    //        commons-io-2.6-test-sources.jar
    //        commons-io-2.6-tests.jar
    //        commons-io-2.6.jar
    //        docs
    //        LICENSE.txt
    //        NOTICE.txt
    //        RELEASE-NOTES.txt
    //
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\commons-io-2.6-javadoc.jar
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\commons-io-2.6-sources.jar
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\commons-io-2.6-test-sources.jar
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\commons-io-2.6-tests.jar
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\commons-io-2.6.jar
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\docs
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\LICENSE.txt
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\NOTICE.txt
    //        C:\Users\86151\Downloads\commons-io-2.6-bin\RELEASE-NOTES.txt

}

File类的重命名功能

public boolean renameTo(File dest):把文件重命名为指定的文件路径。
举例:
file1.renameTo(file2):要想此方法执行完,返回true。要求:
file1必须存在,且file2必须不存在,且file2所在的文件目录需要存在。

@Test
public void test4() {
    File file1 = new File("hello.txt");

    File file2 = new File("d:\\io\\abc.txt");

    boolean renameSuccess = file1.renameTo(file2);
    System.out.println(renameSuccess ? "重命名成功" : "重命名失败");
}

判断功能的方法

public boolean exists() :此File表示的文件或目录是否实际存在。

public boolean isDirectory() :此File表示的是否为目录。
public boolean isFile() :此File表示的是否为文件。
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏

@Test
public void test5() {
    File file1 = new File("d:\\io\\abc.txt");
    System.out.println(file1.exists());
    System.out.println(file1.isDirectory());
    System.out.println(file1.isFile());
    System.out.println(file1.canRead());
    System.out.println(file1.canWrite());
    System.out.println(file1.isHidden());

    System.out.println();
    File file2 = new File("d:\\ioo");
    System.out.println(file2.exists());
    System.out.println(file2.isDirectory());
    System.out.println(file2.isFile());
    System.out.println(file2.canRead());
    System.out.println(file2.canWrite());
    System.out.println(file2.isHidden());

}

创建、删除功能

public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false。
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建。
public boolean delete() :删除文件或者文件夹
删除注意事项:① Java中的删除不走回收站。② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。

 @Test
public void test6() throws IOException {
    File file1 = new File("d:\\io\\hello.txt");
    //测试文件的创建、删除
    if (!file1.exists()) {
        boolean isSuccessed = file1.createNewFile();
        if (isSuccessed) {
            System.out.println("创建成功");
        }
    } else {
        System.out.println("此文件已存在");


        System.out.println(file1.delete() ? "文件删除成功" : "文件删除失败");
    }
}

@Test
public void test7(){
    //前提:d:\\io文件目录存在,io2或io3目录是不存在的。
    File file1 = new File("d:\\io\\io2");

    System.out.println(file1.mkdir());//true

    File file2 = new File("d:\\io\\io3");

    System.out.println(file2.mkdirs()); //true
}

@Test
public void test8(){
    //前提:d:\\io文件目录存在,io2或io3目录是不存在的。
    File file1 = new File("d:\\io\\io2\\io4");

    System.out.println(file1.mkdir());//false

    File file2 = new File("d:\\io\\io3\\io5");

    System.out.println(file2.mkdirs()); //true
}

@Test
public void test9(){
    File file1 = new File("d:\\io\\io3");

    System.out.println(file1.delete());
}

IO流

是什么

  • Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行,可以看做是一种数据的流动。

  • 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

  • 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

流的分类

  • 按数据的流向不同分为:输入流输出流

    • 输入流 :把数据从其他设备上读取到内存中的流。
      • 以InputStream、Reader结尾
    • 输出流 :把数据从内存 中写出到其他设备上的流。
      • 以OutputStream、Writer结尾
  • 按操作数据单位的不同分为:字节流(8bit)字符流(16bit)

    • 字节流 :以字节为单位,读写数据的流。
      • 以InputStream、OutputStream结尾
    • 字符流 :以字符为单位,读写数据的流。
      • 以Reader、Writer结尾
  • 根据IO流的角色不同分为:节点流处理流

    • 节点流:直接从数据源或目的地读写数据

    • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

  • Java的IO流都是从如下4个抽象基类派生的。

(抽象基类)输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter
  • 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

常用的节点流:

  • 文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
  • 字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter
    • 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。

常用处理流:

  • 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
    • 作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率。
  • 转换流:InputStreamReader、OutputStreamReader
    • 作用:实现字节流和字符流之间的转换。
  • 对象流:ObjectInputStream、ObjectOutputStream
    • 作用:提供直接读写Java对象功能

节点流之字符流 FileReader\FileWriter

是什么

Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。不能操作图片,视频等非文本文件。

常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py等

注意:.doc、.xls、.ppt这些都不是文本文件。

字符输入流:Reader

java.io.Reader抽象类是表示用于读取字符流的所有类的父类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public int read(): 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。
  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。
  • public int read(char[] cbuf,int off,int len):从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1
  • public void close() :关闭此流并释放与此流相关联的任何系统资源。

注意:当完成流的操作时,必须调用close()方法,释放系统资源,否则会造成内存泄漏。

字符输出流:Writer

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void write(int c) :写出单个字符。
  • public void write(char[] cbuf) :写出字符数组。
  • public void write(char[] cbuf, int off, int len) :写出字符数组的某一部分。off:数组的开始索引;len:写出的字符个数。
  • public void write(String str) :写出字符串。
  • public void write(String str, int off, int len) :写出字符串的某一部分。off:字符串的开始索引;len:写出的字符个数。
  • public void flush() :刷新该流的缓冲。
  • public void close() :关闭此流。

注意:当完成流的操作时,必须调用close()方法,释放系统资源,否则会造成内存泄漏。

FileReader

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

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

java.io.FileWriter 类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区。

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。
  • FileWriter(File file,boolean append): 创建一个新的 FileWriter,指明是否在现有文件末尾追加内容。

怎么用

FileReader \ FileWriter 的使用
执行步骤:

  • 第1步:创建读取或写出的File类的对象

  • 第2步:创建输入流或输出流

  • 第3步:具体的读入或写出的过程。

    读入:read(char[] cbuffer)
    写出:write(String str) / write(char[] cbuffer,0,len)

  • 第4步:关闭流资源,避免内存泄漏

注意
输出流不关闭输出不了

  • 因为涉及到流资源的关闭操作,所以出现异常的话,需要使用try-catch-finally的方式来处理异常

  • 对于输入流来讲,要求File类的对象对应的物理磁盘上的文件必须存在。否则,会报FileNotFoundException

  • 对于输出流来讲,File类的对象对应的物理磁盘上的文件可以不存在。

    如果此文件不存在,则在输出的过程中,会自动创建此文件,并写出数据到此文件中。
    如果此文件存在,使用 FileWriter(File file) 或 FileWriter(File file,false):输出数据过程中,会新建同名的文件对现有的文件进行覆盖。FileWriter(File file,true) : 输出数据过程中,会在现有的文件的末尾追加写出内容。

**举例:**读取hello.txt文件中的字符数据,并显示在控制台上

//实现方式2:在方式1的基础上改进,使用try-catch-finally处理异常。保证流是可以关闭的
    @Test
    public void test2() {
        FileReader fr = null;
        try {
            //1. 创建File类的对象,对应着物理磁盘上的某个文件
            File file = new File("hello.txt");
            //2. 创建FileReader流对象,将File类的对象作为参数传递到FileReader的构造器中
            fr = new FileReader(file);
            //3. 通过相关流的方法,读取文件中的数据
            /*
             * read():每次从对接的文件中读取一个字符。并将此字符返回。
             * 如果返回值为-1,则表示文件到了末尾,可以不再读取。
             * */
            int data;
            while ((data = fr.read()) != -1) {
                System.out.println((char) data);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4. 关闭相关的流资源,避免出现内存泄漏
            try {
                if (fr != null)
                    fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

//实现方式2:调用read(char[] cbuf),每次从文件中读取多个字符
    @Test
    public void test3() {
        FileReader fr = null;
        try {
            //1. 创建File类的对象,对应着物理磁盘上的某个文件
            File file = new File("hello.txt");
            //2. 创建FileReader流对象,将File类的对象作为参数传递到FileReader的构造器中
            fr = new FileReader(file);
            //3. 通过相关流的方法,读取文件中的数据
            char[] cbuf = new char[5];
            /*
             * read(char[] cbuf) : 每次将文件中的数据读入到cbuf数组中,并返回读入到数组中的
             * 字符的个数。
             * */
            int len; //记录每次读入的字符的个数
            while ((len = fr.read(cbuf)) != -1) {
                //处理char[]数组即可
                //错误:
//read方法可能并没有填满整个数组。因此,你可能会看到数组中的旧数据(如果它们没有被清空)或者额外的空白字符。
//                for(int i = 0;i < cbuf.length;i++){
//                    System.out.print(cbuf[i]);
//                }
                //错误:
//                String str = new String(cbuf);
//                System.out.print(str);
                //正确:
//                for(int i = 0;i < len;i++){
//                    System.out.print(cbuf[i]);
//                }
                //正确:
                String str = new String(cbuf, 0, len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4. 关闭相关的流资源,避免出现内存泄漏
            try {
                if (fr != null)
                    fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

使用FileWriter

@Test
    public void test03()throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 字符串
        String msg = "尚硅谷";

        // 写出字符数组
        fw.write(msg); //尚硅谷

        // 写出从索引1开始,2个字符。
        fw.write(msg,1,2);	// 硅谷

        // 关闭资源
        fw.close();
    }
    
    @Test
    public void test04(){
        FileWriter fw = null;
        try {
            //1. 创建File的对象
            File file = new File("personinfo.txt");
            //2. 创建FileWriter的对象,将File对象作为参数传递到FileWriter的构造器中
            //如果输出的文件已存在,则会对现有的文件进行覆盖
            fw = new FileWriter(file);
//            fw = new FileWriter(file,false);
            //如果输出的文件已存在,则会在现有的文件末尾写入数据
//            fw = new FileWriter(file,true);

            //3. 调用相关的方法,实现数据的写出操作
            //write(String str) / write(char[] cbuf)
            fw.write("I love you,");
            fw.write("you love him.");
            fw.write("so sad".toCharArray());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4. 关闭资源,避免内存泄漏
            try {
                if (fw != null)
                    fw.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

关于flush(刷新)

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

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

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

举例:

public class FWWriteFlush {
    //注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
    @Test
    public void test() 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();
    }
}

节点流之字节流 FileInputStream/FileOutputStream

为什么引入

如果我们读取或写出的数据是非文本文件,则Reader、Writer就无能为力了,必须使用字节流。

是什么

InputStream

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  • public int read(): 从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
  • public int read(byte[] b,int off,int len):从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。

说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。

OutputStream

java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void write(int b)将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。

说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。

FileInputStream

java.io.FileInputStream 类是文件输入流,从文件中读取字节。

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

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。

  • public FileOutputStream(File file):创建文件输出流,写出由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流,指定的名称为写出文件。
  • public FileOutputStream(File file, boolean append): 创建文件输出流,指明是否在现有文件末尾追加内容。

怎么用

执行步骤:

  • 第1步:创建读取或写出的File类的对象
  • 第2步:创建输入流或输出流
  • 第3步:具体的读入或写出的过程。
    读入:read(byte[] buffer)
    写出:write(byte[] buffer,0,len)
  • 第4步:关闭流资源,避免内存泄漏

注意点:

对于字符流,只能用来操作文本文件,不能用来处理非文本文件的。
对于字节流,通常是用来处理非文本文件的。但是,如果涉及到文本文件的复制操作,也可以使用字节流。

说明:
文本文件:.txt 、.java 、.c、.cpp、.py等
非文本文件:.doc、.xls 、.jpg 、.pdf、.mp3、.mp4、.avi 等

读取操作

public class FISRead {
    //注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
    @Test
    public void test() throws IOException {
        // 使用文件名称创建流对象
        FileInputStream fis = new FileInputStream("read.txt");
        // 读取数据,返回一个字节
        int read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        // 读取到末尾,返回-1
        read = fis.read();
        System.out.println(read);
        // 关闭资源
        fis.close();
        /*
        文件内容:abcde
        输出结果:
        a
        b
        c
        d
        e
        -1
         */
    }

    @Test
    public void test02()throws IOException{
        // 使用文件名称创建流对象
        FileInputStream fis = new FileInputStream("read.txt");
        // 定义变量,保存数据
        int b;
        // 循环读取
        while ((b = fis.read())!=-1) {
            System.out.println((char)b);
        }
        // 关闭资源
        fis.close();
    }
	
    @Test
    public void test03()throws IOException{
        // 使用文件名称创建流对象.
        FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
        // 定义变量,作为有效个数
        int len;
        // 定义字节数组,作为装字节数据的容器
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b))!=-1) {
            // 每次读取后,把数组变成字符串打印
            System.out.println(new String(b));
        }
        // 关闭资源
        fis.close();
        /*
        输出结果:
        ab
        cd
        ed
        最后错误数据`d`,是由于最后一次读取时,只读取一个字节`e`,数组中,
        上次读取的数据没有被完全替换,所以要通过`len` ,获取有效的字节
         */
    }

    @Test
    public void test04()throws IOException{
        // 使用文件名称创建流对象.
        FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
        // 定义变量,作为有效个数
        int len;
        // 定义字节数组,作为装字节数据的容器
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b))!=-1) {
            // 每次读取后,把数组的有效字节部分,变成字符串打印
            System.out.println(new String(b,0,len));//  len 每次读取的有效字节个数
        }
        // 关闭资源
        fis.close();
        /*
        输出结果:
        ab
        cd
        e
         */
    }
}
写入操作
public class FOSWrite {
    //注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
    @Test
    public void test01() throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        // 写出数据
        fos.write(97); // 写出第1个字节
        fos.write(98); // 写出第2个字节
        fos.write(99); // 写出第3个字节
        // 关闭资源
        fos.close();
      /*  输出结果:abc*/
    }

    @Test
    public void test02()throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        // 字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
        fos.write(b,2,2);
        // 关闭资源
        fos.close();
    }
    //这段程序如果多运行几次,每次都会在原来文件末尾追加abcde
    @Test
    public void test03()throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt",true);
        // 字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        fos.write(b);
        // 关闭资源
        fos.close();
    }
    
    //使用FileInputStream\FileOutputStream,实现对文件的复制
    @Test
    public void test05() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //1. 造文件-造流
            //复制图片:成功
//            fis = new FileInputStream(new File("pony.jpg"));
//            fos = new FileOutputStream(new File("pony_copy1.jpg"));

            //复制文本文件:成功
            fis = new FileInputStream(new File("hello.txt"));
            fos = new FileOutputStream(new File("hello1.txt"));

            //2. 复制操作(读、写)
            byte[] buffer = new byte[1024];
            int len;//每次读入到buffer中字节的个数
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
//                String str = new String(buffer,0,len);
//                System.out.print(str);
            }
            System.out.println("复制成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //3. 关闭资源
            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

节点流之缓冲流

是什么

缓冲流

  • 为了提高数据读写的速度,Java API提供了带缓冲功能的流类:缓冲流。

  • 缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

    • 字节缓冲流BufferedInputStreamBufferedOutputStream
    • 字符缓冲流BufferedReaderBufferedWriter
  • 缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用8192个字节(8Kb)的缓冲区),通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

怎么构造

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

代码举例:

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.jpg"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc_copy.jpg"));
  • 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(): 写一行行分隔符,由系统属性定义符号。
public class BufferedIOLine {
    @Test
    public void testReadLine()throws IOException {
        // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
        // 定义字符串,保存读取的一行文字
        String line;
        // 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {
            System.out.println(line);
        }
        // 释放资源
        br.close();
    }

    @Test
    public void testNewLine()throws IOException{
        // 创建流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
        // 写出数据
        bw.write("尚");
        // 写出换行
        bw.newLine();
        bw.write("硅");
        bw.newLine();
        bw.write("谷");
        bw.newLine();
        // 释放资源
        bw.close();
    }
}

说明:

  1. 涉及到嵌套的多个流时,如果都显式关闭的话,需要先关闭外层的流,再关闭内层的流

  2. 其实在开发中,只需要关闭最外层的流即可,因为在关闭外层流时,内层的流也会被关闭。

处理流之转换流

引入情况1:

使用FileReader 读取项目中的文本文件。由于IDEA设置中针对项目设置了UTF-8编码,当读取Windows系统中创建的文本文件时,如果Windows系统默认的是GBK编码,则读入内存中会出现乱码。

package com.atguigu.transfer;

import java.io.FileReader;
import java.io.IOException;

public class Problem {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("E:\\File_GBK.txt");
        int data;
        while ((data = fileReader.read()) != -1) {
            System.out.print((char)data);
        }
        fileReader.close();
    }
}

输出结果:
���

那么如何读取GBK编码的文件呢?

可以使用转换流

是什么

转换流是字节与字符间的桥梁!

  • InputStreamReader

    • 转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

    • 构造器

      • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
      • InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
    • 举例

      //使用默认字符集
      InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt"));
      //使用指定字符集
      InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
      
    • 示例代码:

      package com.atguigu.transfer;
      
      import java.io.FileInputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      
      public class InputStreamReaderDemo {
          public static void main(String[] args) throws IOException {
              // 定义文件路径,文件为gbk编码
              String fileName = "E:\\file_gbk.txt";
              //方式1:
              // 创建流对象,默认UTF8编码
              InputStreamReader isr1 = new InputStreamReader(new FileInputStream(fileName));
              // 定义变量,保存字符
              int charData;
              // 使用默认编码字符流读取,乱码
              while ((charData = isr1.read()) != -1) {
                  System.out.print((char)charData); // ��Һ�
              }
              isr1.close();
      		
              //方式2:
              // 创建流对象,指定GBK编码
              InputStreamReader isr2 = new InputStreamReader(new FileInputStream(fileName) , "GBK");
              // 使用指定编码字符流读取,正常解析
              while ((charData = isr2.read()) != -1) {
                  System.out.print((char)charData);// 大家好
              }
              isr2.close();
          }
      }
      
  • OutputStreamWriter

    • 转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

    • 构造器

      • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
      • OutputStreamWriter(OutputStream in,String charsetName): 创建一个指定字符集的字符流。
    • 举例:

      //使用默认字符集
      OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
      //使用指定的字符集
      OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
      
    • 示例代码:

      package com.atguigu.transfer;
      
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.OutputStreamWriter;
      
      public class OutputStreamWriterDemo {
          public static void main(String[] args) throws IOException {
              // 定义文件路径
              String FileName = "E:\\out_utf8.txt";
              // 创建流对象,默认UTF8编码
              OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
              // 写出数据
              osw.write("你好"); // 保存为6个字节
              osw.close();
      
              // 定义文件路径
              String FileName2 = "E:\\out_gbk.txt";
              // 创建流对象,指定GBK编码
              OutputStreamWriter osw2 = new OutputStreamWriter(new                     
                                                      FileOutputStream(FileName2),"GBK");
              // 写出数据
              osw2.write("你好");// 保存为4个字节
              osw2.close();
          }
      }
      

处理流之数据流对象流

是什么

如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文件中,那怎么办呢?

int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;

String name = "巫师";
Student stu = new Student("张三",23,89);

Java提供了数据流和对象流来处理这些类型的数据:

  • 数据流:DataOutputStream、DataInputStream
    • DataOutputStream:允许应用程序将基本数据类型、String类型的变量写入输出流中

    • DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String类型的变量。

  • 对象流DataInputStream中的方法:
  byte readByte()                short readShort()
  int readInt()                  long readLong()
  float readFloat()              double readDouble()
  char readChar()				 boolean readBoolean()					
  String readUTF()               void readFully(byte[] b)
  • 对象流DataOutputStream中的方法:将上述的方法的read改为相应的write即可。

  • 数据流的弊端:只支持Java基本数据类型和字符串的读写,而不支持其它Java对象的类型。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以重点介绍对象流ObjectOutputStream和ObjectInputStream。

  • 对象流:ObjectOutputStream、ObjectInputStream

    • ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储。
    • ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写出的基本数据类型的数据和对象进行读入操作,保存在内存中。

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

一些方法

ObjectOutputStream中的构造器:

public ObjectOutputStream(OutputStream out) : 创建一个指定的ObjectOutputStream。

FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);

ObjectOutputStream中的方法:

  • public void writeBoolean(boolean val):写出一个 boolean 值。
  • public void writeByte(int val):写出一个8位字节
  • public void writeShort(int val):写出一个16位的 short 值
  • public void writeChar(int val):写出一个16位的 char 值
  • public void writeInt(int val):写出一个32位的 int 值
  • public void writeLong(long val):写出一个64位的 long 值
  • public void writeFloat(float val):写出一个32位的 float 值。
  • public void writeDouble(double val):写出一个64位的 double 值
  • public void writeUTF(String str):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。根据字符的值,将字符串 s 中每个字符转换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入流中与将它作为 Object 写入流中明显不同。 如果 s 为 null,则抛出 NullPointerException。
  • public void writeObject(Object obj):写出一个obj对象
  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源

ObjectInputStream中的构造器:

public ObjectInputStream(InputStream in) : 创建一个指定的ObjectInputStream。

FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);

ObjectInputStream中的方法:

  • public boolean readBoolean():读取一个 boolean 值

  • public byte readByte():读取一个 8 位的字节

  • public short readShort():读取一个 16 位的 short 值

  • public char readChar():读取一个 16 位的 char 值

  • public int readInt():读取一个 32 位的 int 值

  • public long readLong():读取一个 64 位的 long 值

  • public float readFloat():读取一个 32 位的 float 值

  • public double readDouble():读取一个 64 位的 double 值

  • public String readUTF():读取 UTF-8 修改版格式的 String

  • public void readObject(Object obj):读入一个obj对象

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源

对象序列化机制

是什么

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

  • 序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

  • 反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。

image-20220503123328452

序列化机制的重要性

序列化是 RMI(Remote Method Invoke、远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。

序列化的好处,在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

3、实现原理

  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制。方法为:

    • public final void writeObject (Object obj) : 将指定的对象写出。
  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制。方法为:

    • public final Object readObject () : 读取一个对象。

怎么实现序列化

如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现java.io.Serializable 接口Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

  • 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
  • 静态(static)变量的值不会序列化。因为静态变量的值不属于某个对象。

举例1:

package com.atguigu.object;

import org.junit.Test;

import java.io.*;

public class ReadWriteDataOfAnyType {
    @Test
    public void save() throws IOException {
        String name = "巫师";
        int age = 300;
        char gender = '男';
        int energy = 5000;
        double price = 75.5;
        boolean relive = true;

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat"));
        oos.writeUTF(name);
        oos.writeInt(age);
        oos.writeChar(gender);
        oos.writeInt(energy);
        oos.writeDouble(price);
        oos.writeBoolean(relive);
        oos.close();
    }
    @Test
    public void reload()throws IOException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat"));
        String name = ois.readUTF();
        int age = ois.readInt();
        char gender = ois.readChar();
        int energy = ois.readInt();
        double price = ois.readDouble();
        boolean relive = ois.readBoolean();

        System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive);

        ois.close();
    }
}

举例2:

package com.atguigu.object;

import java.io.Serializable;

public class Employee implements Serializable {
    //static final long serialVersionUID = 23234234234L;
    public static String company; //static修饰的类变量,不会被序列化
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化

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

    public static String getCompany() {
        return company;
    }

    public static void setCompany(String company) {
        Employee.company = company;
    }

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getAge() {
        return age;
    }

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

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

package com.atguigu.object;

import org.junit.Test;

import java.io.*;

public class ReadWriteObject {
    @Test
    public void save() throws IOException {
        Employee.setCompany("尚硅谷");
        Employee e = new Employee("小谷姐姐", "宏福苑", 23);
        // 创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
        // 写出对象
        oos.writeObject(e);
        // 释放资源
        oos.close();
        System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
    }

    @Test
    public void reload() throws IOException, ClassNotFoundException {
        // 创建反序列化流
        FileInputStream fis = new FileInputStream("employee.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        // 读取一个对象
        Employee e = (Employee) ois.readObject();
        // 释放资源
        ois.close();
        fis.close();

        System.out.println(e);
    }
}

举例3:如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。

package com.atguigu.object;

import org.junit.Test;

import java.io.*;
import java.util.ArrayList;

public class ReadWriteCollection {
    @Test
    public void save() throws IOException {
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee("张三", "宏福苑", 23));
        list.add(new Employee("李四", "白庙", 24));
        list.add(new Employee("王五", "平西府", 25));
        // 创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat"));
        // 写出对象
        oos.writeObject(list);
        // 释放资源
        oos.close();
    }

    @Test
    public void reload() throws IOException, ClassNotFoundException {
        // 创建反序列化流
        FileInputStream fis = new FileInputStream("employees.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        // 读取一个对象
        ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject();
        // 释放资源
        ois.close();
        fis.close();

        System.out.println(list);
    }
}

反序列化失败问题

问题1:

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

问题2:

当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型

解决办法:

Serializable 接口给需要序列化的类,提供了一个序列版本号:serialVersionUID 。凡是实现 Serializable接口的类都应该有一个表示序列化版本标识符的静态变量:

static final long serialVersionUID = 234242343243L; //它的值由程序员随意指定即可。
  • serialVersionUID用来表明类的不同版本间的兼容性。简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。
  • 果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成若类的实例变量做了修改,serialVersionUID 可能发生变化。因此,建议显式声明。
  • 如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。
package com.atguigu.object;

import java.io.Serializable;

public class Employee implements Serializable {
    private static final long serialVersionUID = 1324234L; //增加serialVersionUID
    
    //其它结构:略
}

标准输入、输出流

  • System.in和System.out分别代表了系统标准的输入和输出设备
  • 默认输入设备是:键盘,输出设备是:显示器
  • System.in的类型是InputStream
  • System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
  • 重定向:通过System类的setIn,setOut方法对默认设备进行改变。
    • public static void setIn(InputStream in)
    • public static void setOut(PrintStream out)

举例:

从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。

System.out.println("请输入信息(退出输入e或exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = null;
try {
    while ((s = br.readLine()) != null) { // 读取用户输入的一行数据 --> 阻塞程序
        if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
            System.out.println("安全退出!!");
            break;
        }
        // 将读取到的整行字符串转成大写输出
        System.out.println("-->:" + s.toUpperCase());
        System.out.println("继续输入信息");
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (br != null) {
            br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

拓展:

System类中有三个常量对象:System.out、System.in、System.err

查看System类中这三个常量对象的声明:

public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;

奇怪的是,

  • 这三个常量对象有final声明,但是却初始化为null。final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗?
  • 这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗?
  • 这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?
final声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法。
public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}
public static void setErr(PrintStream err) {
    checkIO();
    setErr0(err);
}
public static void setIn(InputStream in) {
    checkIO();
    setIn0(in);
}
private static void checkIO() {
    SecurityManager sm = getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("setIO"));
    }
}
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);

打印流

  • 实现将基本数据类型的数据格式转化为字符串输出。

  • 打印流:PrintStreamPrintWriter

    • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出


    • PrintStream和PrintWriter的输出不会抛出IOException异常

    • PrintStream和PrintWriter有自动flush功能

    • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。

    • System.out返回的是PrintStream的实例

  • 构造器

    • PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
    • PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
    • PrintStream(OutputStream out) :创建新的打印流。
    • PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。 autoFlush如果为 true,则每当写入 byte 数组、调用其中一个 println 方法或写入换行符或字节 (‘\n’) 时都会刷新输出缓冲区。
    • PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建新的打印流。
    • PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的新打印流。
    • PrintStream(String fileName, String csn) :创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
  • 代码举例1

package com.atguigu.systemio;

import java.io.FileNotFoundException;
import java.io.PrintStream;

public class TestPrintStream {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream("io.txt");
        ps.println("hello");
        ps.println(1);
        ps.println(1.5);
        ps.close();
    }
}

  • 代码举例2
PrintStream ps = null;
try {
    FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt"));
    // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
    ps = new PrintStream(fos, true);
    if (ps != null) {// 把标准输出流(控制台输出)改成文件
        System.setOut(ps);
    }
    for (int i = 0; i <= 255; i++) { // 输出ASCII字符
        System.out.print((char) i);
        if (i % 50 == 0) { // 每50个数据一行
            System.out.println(); // 换行
        }
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (ps != null) {
        ps.close();
    }
}

  • 代码举例3:自定义一个日志工具
/*
日志工具
 */
public class Logger {
    /*
    记录日志的方法。
     */
    public static void log(String msg) {
        try {
            // 指向一个日志文件
            PrintStream out = new PrintStream(new FileOutputStream("log.txt", true));
            // 改变输出方向
            System.setOut(out);
            // 日期当前时间
            Date nowTime = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String strTime = sdf.format(nowTime);

            System.out.println(strTime + ": " + msg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
public class LogTest {
    public static void main(String[] args) {
        //测试工具类是否好用
        Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
        Logger.log("调用了TeamView的addMember()方法");
        Logger.log("用户尝试进行登录,验证失败");
    }
}

本文参考了尚硅谷宋红康老师的讲义

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值