File类与IO流

发布 | 2024-08-09 | JAVA

java.io.File类

File类是java.io包下代表与平台无关的文件和目录,也就是说如果希望在程序中操作文件和目录都可以通过File类来完成,File类能新建、删除、重命名文件和目录。

构造方法

序号方法描述
1public File(String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
2public File(String parent, String child)父路径名字符串和子路径名字符串创建新的 File实例。
3public File(File parent, String child)父抽象路径名和子路径名字符串创建新的 File实例。

注意:

  • 一个File对象代表硬盘或网络中可能存在的一个文件或者目录。
  • 无论该路径下是否存在文件或者目录,都不影响File对象的创建。

示例代码如下:

package com.atguigu.file;

import java.io.File;

public class FileObjectTest {
    public static void main(String[] args) {
        // 文件路径名
        String pathname = "D:\\aaa.txt";
        File file1 = new File(pathname);

        // 文件路径名
        String pathname2 = "D:\\aaa\\bbb.txt";
        File file2 = new File(pathname2);

        // 通过父路径和子路径字符串
        String parent = "d:\\aaa";
        String child = "bbb.txt";
        File file3 = new File(parent, child);

        // 通过父级File对象和子路径字符串
        File parentDir = new File("d:\\aaa");
        String childFile = "bbb.txt";
        File file4 = new File(parentDir, childFile);
    }
}

常用方法

1、获取文件和目录基本信息的方法

序号方法描述
1public String getName()返回由此File表示的文件或目录的名称。
2public long length()返回由此File表示的文件的长度。 如果此路径名表示一个目录,则返回值是不确定的。
3public long lastModified()返回File对象对应的文件或目录的最后修改时间(毫秒值)。
4public boolean exists()此File表示的文件或目录是否实际存在。
5public boolean isDirectory()此File表示的是否为目录。
6public boolean isFile()此File表示的是否为文件。
7public boolean isHidden()此File表示的是否为隐藏文件或目录。
8public boolean canExecute()测试应用程序是否可以执行此抽象路径名表示的文件。
9public boolean canRead()测试应用程序是否可以读取此抽象路径名表示的文件。
10public boolean canWrite()测试应用程序是否可以修改此抽象路径名表示的文件。

注意:如果File对象代表的文件或目录存在,则File对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(例如,时间、类型等)为File对象的属性赋值,否则除了路径和名称,File对象的其他属性将会保留默认值。

示例代码如下:

package com.atguigu.file;

import java.io.File;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

public class FileInfoMethod {
    public static void main(String[] args) {
        File f = new File("d:/aaa/bbb.txt");
        System.out.println("文件构造路径:"+f.getPath());
        System.out.println("文件名称:"+f.getName());
        System.out.println("文件长度:"+f.length()+"字节");
        System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()),ZoneId.of("Asia/Shanghai")));

        File f2 = new File("d:/aaa");
        System.out.println("目录构造路径:"+f2.getPath());
        System.out.println("目录名称:"+f2.getName());
        System.out.println("目录长度:"+f2.length()+"字节");
        System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()),ZoneId.of("Asia/Shanghai")));
    }
}

/*
输出结果:
文件构造路径:d:\aaa\bbb.java
文件名称:bbb.java
文件长度:636字节
文件最后修改时间:2019-07-23T22:01:32.065

目录构造路径:d:\aaa
目录名称:aaa
目录长度:4096字节
文件最后修改时间:2024-07-23T22:01:32.065
*/
package com.atguigu.file;

import java.io.File;

public class FileIs {
    public static void main(String[] args) {
        File f = new File("d:\\aaa\\bbb.java");
        File f2 = new File("d:\\aaa");
        // 判断是否存在
        System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists());
        System.out.println("d:\\aaa 是否存在:"+f2.exists());
        // 判断是文件还是目录
        System.out.println("d:\\aaa 文件?:"+f2.isFile());
        System.out.println("d:\\aaa 目录?:"+f2.isDirectory());
    }
}
/*
输出结果:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目录?:true
*/

2、创建删除文件和目录

序号方法描述
1public static File createTempFile(String prefix, String suffix) throws IOException在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。
2public static File createTempFile(String prefix, String suffix, File directory) throws IOException在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
3public boolean createNewFile()当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
4public boolean delete()删除由此File表示的文件或==空==目录。
5public boolean mkdir()创建由此File表示的目录。
6public boolean mkdirs()创建由此File表示的目录,包括任何必需但不存在的父目录。
7public boolean renameTo(File dest)重新命名此抽象路径名表示的文件或目录。但是此方法行为的许多方面都是与平台有关的:重命名操作无法将一个文件从一个文件系统移动到另一个文件系统。

方法演示,代码如下:

package com.atguigu.file;

import java.io.File;
import java.io.IOException;

public class FileCreateDelete {
    public static void main(String[] args) throws IOException {
        // 文件的创建
        File f = new File("aaa.txt");
        System.out.println("aaa.txt是否存在:"+f.exists()); 
        System.out.println("aaa.txt是否创建:"+f.createNewFile()); 
        System.out.println("aaa.txt是否存在:"+f.exists()); 

        // 目录的创建
        File f2= new File("newDir");
        System.out.println("newDir是否存在:"+f2.exists());
        System.out.println("newDir是否创建:"+f2.mkdir());
        System.out.println("newDir是否存在:"+f2.exists());

        // 创建一级目录
        File f3= new File("newDira\\newDirb");
        System.out.println("newDira\\newDirb创建:" + f3.mkdir());
        File f4= new File("newDir\\newDirb");
        System.out.println("newDir\\newDirb创建:" + f4.mkdir());
        // 创建多级目录
        File f5= new File("newDira\\newDirb");
        System.out.println("newDira\\newDirb创建:" + f5.mkdirs());

        // 文件的删除
        System.out.println("aaa.txt删除:" + f.delete());

        // 目录的删除
        System.out.println("newDir删除:" + f2.delete());
        System.out.println("newDir\\newDirb删除:" + f4.delete());
    }
}

/*
运行结果:
aaa.txt是否存在:false
aaa.txt是否创建:true
aaa.txt是否存在:true
newDir是否存在:false
newDir是否创建:true
newDir是否存在:true
newDira\newDirb创建:false
newDir\newDirb创建:true
newDira\newDirb创建:true
aaa.txt删除:true
newDir删除:false
newDir\newDirb删除:true
*/

3、文件或目录的上下级

序号方法描述
1public String getParent()返回此抽象路径名父目录的路径名字符串
2public File getParentFile()返回此抽象路径名父目录的抽象路径名
3public String[] list()返回一个String数组,表示该File目录中的所有子文件或目录。
4public File[] listFiles()返回一个File数组,表示该File目录中的所有的子文件或目录。
5public File[] listFiles(FileFilter filter)返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(File pathname)方法返回 true 时,该路径名才满足过滤器。
6public String[] list(FilenameFilter filter)返回返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FilenameFilter .accept(File dir, String name)方法返回 true 时,该路径名才满足过滤器。
7public File[] listFiles(FilenameFilter filter)返回返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FilenameFilter .accept(File dir, String name)方法返回 true 时,该路径名才满足过滤器。
package com.atguigu.file;

import org.junit.Test;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;

public class DirListFiles {
    @Test
    public void test01() {
        File dir = new File("d:/atguigu");
        String[] subs = dir.list();
        for (String sub : subs) {
            System.out.println(sub);
        }
    }

    @Test
    public void test02() {
        File dir = new File("d:/atguigu");
        listSubFiles(dir);
    }

    public void listSubFiles(File dir) {
        if (dir != null && dir.isDirectory()) {
            File[] listFiles = dir.listFiles();
            if (listFiles != null) {
                for (File sub : listFiles) {
                    listSubFiles(sub);//递归调用
                }
            }
        }
        System.out.println(dir);
    }

    @Test
    public void test03() {
        File dir = new File("D:/atguigu");
        listByFilenameFilter(dir);
    }

    public void listByFilenameFilter(File file) {
        if (file != null && file.isDirectory()) {
            File[] listFiles = file.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.endsWith(".java") || new File(dir,name).isDirectory();
                }
            });
            if (listFiles != null) {
                for (File sub : listFiles) {
                    if(sub != null && sub.isFile()){
                        System.out.println(sub);
                    }
                    listByFilenameFilter(sub);//递归调用
                }
            }
        }
    }

    @Test
    public void test04() {
        File dir = new File("D:/atguigu");
        listByFileFilter(dir);
    }

    public void listByFileFilter(File file) {
        if (file != null && file.isDirectory()) {
            File[] listFiles = file.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return pathname.getName().endsWith(".java") || pathname.isDirectory();
                }
            });
            if (listFiles != null) {
                for (File sub : listFiles) {
                    if(sub != null && sub.isFile()){
                        System.out.println(sub);
                    }
                    listByFileFilter(sub);//递归调用
                }
            }
        }
    }
}

4、各种路径问题

序号方法描述
1public String getPath()将此File转换为路径名字符串。
2public String getAbsolutePath()返回此File的绝对路径名字符串。
3String getCanonicalPath()返回此File对象所对应的规范路径名。

File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。

默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性“user.dir”指定,通常也就是运行Java虚拟机时所作的路径。

  • 构造路径:使用File构造器创建File对象时指定的路径名。它可以是绝对路径,也可以是相对路径。
  • 绝对路径:从盘符开始的路径,这是一个完整的路径。当构造路径是绝对路径时,那么getPath和getAbsolutePath结果一样。
  • 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。当构造路径是相对路径时,那么getAbsolutePath的路径 = user.dir的路径 + 构造路径
  • 规范路径:所谓规范路径名,即对路径中的“..”等进行解析后的路径名。当路径中不包含".."和"/开头"等形式的路径,那么规范路径和绝对路径一样,否则会将..等进行解析。路径中如果出现“..”表示上一级目录,路径名如果以“/”开头,表示从“根目录”下开始导航。
  • window的路径分隔符使用“\”,而Java程序中的“\”表示转义字符,所以在Windows中表示路径,需要用“\”。或者直接使用“/”也可以,Java程序支持将“/”当成平台无关的路径分隔符。或者直接使用File.separator常量值表示。
package com.atguigu.file;

import org.junit.Test;

import java.io.File;
import java.io.IOException;

public class FilePath {
    @Test
    public void test1() throws IOException{
        File f1 = new File("d:\\atguigu\\javase\\HelloIO.java"); //绝对路径
        System.out.println("文件/目录的名称:" + f1.getName());
        System.out.println("文件/目录的构造路径名:" + f1.getPath());
        System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath());
        System.out.println("文件/目录的规范路径名:" + f1.getCanonicalPath());
        System.out.println("文件/目录的父目录名:" + f1.getParent());
    }
    @Test
    public void test02()throws IOException{
        File f2 = new File("/HelloIO.java");//绝对路径,从根路径开始
        System.out.println("文件/目录的名称:" + f2.getName());
        System.out.println("文件/目录的构造路径名:" + f2.getPath());
        System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());
        System.out.println("文件/目录的规范路径名:" + f2.getCanonicalPath());
        System.out.println("文件/目录的父目录名:" + f2.getParent());
    }

    @Test
    public void test03() throws IOException {
        File f3 = new File("HelloIO.java");//相对路径
        System.out.println("user.dir =" + System.getProperty("user.dir"));
        System.out.println("文件/目录的名称:" + f3.getName());
        System.out.println("文件/目录的构造路径名:" + f3.getPath());
        System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath());
        System.out.println("文件/目录的规范路径名:" + f3.getCanonicalPath());
        System.out.println("文件/目录的父目录名:" + f3.getParent());
    }
    @Test
    public void test04() throws IOException{
        File f4 = new File("../../HelloIO.java");//相对路径
        System.out.println("user.dir =" + System.getProperty("user.dir"));
        System.out.println("文件/目录的名称:" + f4.getName());
        System.out.println("文件/目录的构造路径名:" + f4.getPath());
        System.out.println("文件/目录的绝对路径名:" + f4.getAbsolutePath());
        System.out.println("文件/目录的规范路径名:" + f4.getCanonicalPath());
        System.out.println("文件/目录的父目录名:" + f4.getParent());
    }

    public static void main(String[] args)throws IOException {
        File f5 = new File("HelloIO.java");//相对路径
        System.out.println("user.dir =" + System.getProperty("user.dir"));
        System.out.println("文件/目录的名称:" + f5.getName());
        System.out.println("文件/目录的构造路径名:" + f5.getPath());
        System.out.println("文件/目录的绝对路径名:" + f5.getAbsolutePath());
        System.out.println("文件/目录的规范路径名:" + f5.getCanonicalPath());
        System.out.println("文件/目录的父目录名:" + f5.getParent());
    }
}

IO流

数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input输出output ,即流向内存是输入流,流出内存的输出流。

Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。File对象不能直接读取和写入数据,如果要操作数据,需要IO流。

IO的分类

1、输入流和输出流

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

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

    • 以InputStream,Reader结尾
  • 输出流 :把数据从内存 中写出到其他设备上的流。

    • 以OutputStream、Writer结尾

2、字节流和字符流

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

  • 字节流 :以字节为单位,读写数据的流。

    • 以Stream结尾
  • 字符流 :以字符为单位,读写数据的流。

    • 以Reader和Writer结尾

3、节点流和处理流

根据IO流的角色不同分为:节点流处理流

(1)节点流:可以从或向一个特定的地方(节点)读写数据。常用的节点流:

  • 文 件IO流: FileInputStream、FileOutputStream、FileReader、FileWriter 。
  • 字符串IO流: StringReader、StringWriter。
  • 数 组IO流: ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter。

(2)处理流:是对一个已存在的流进行连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。处理流的特点是其对象的创建需要依赖于另一个IO流。常用处理流:

  • 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter---增加缓冲功能,避免频繁读写硬盘。
  • 转换流:InputStreamReader、OutputStreamReader---实现字节流和字符流之间的转换。
  • 数据流:DataInputStream、DataOutputStream -提供读写Java基础数据类型功能
  • 对象流:ObjectInputStream、ObjectOutputStream--提供直接读写Java对象功能
  • 打印流:PrintStream、PrintWriter--提供各种print、println方法输出各种类型的数据
  • 管 道IO流:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter--负责两个线程之间的数据交互

(3)装饰者设计模式

IO流的设计使用了装饰模式(Decorator Pattern)也称为包装模式(Wrapper Pattern)。装饰模式是使用一种对客户端透明的方式来动态地扩展对象的功能,它是通过继承扩展功能的替代方案之一。在现实生活中你也有很多装饰者的例子,例如:人需要各种各样的衣着,不管你穿着怎样,但是,对于你个人本质来说是不变的,充其量只是在外面加上了一些装饰,有,“遮羞的”、“保暖的”、“好看的”、“防雨的”....

四大顶级抽象父类们

输入流输出流
字符流字符输入流Reader字符输出流Writer
字节流字节输入流InputStream字节输出流OutputStream

1、字符输出流【Writer】

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

序号方法描述
1public void write(int c)写入单个字符。
2public void write(char[] cbuf)写入字符数组。
3public void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
4public void write(String str)写入字符串。
5public void write(String str, int off, int len)写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
6public void flush()刷新该流的缓冲。
7public void close()关闭此输出流并释放与此流相关联的任何系统资源。

2、字符输入流【Reader】

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

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

3、字节输出流【OutputStream】

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

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

4、字节输入流【InputStream】

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

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

文件IO流

FileWriter类

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

  • FileWriter(File file): 创建一个新的 FileWriter,给定要写入内容的文件的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要写入内容的文件的文件路径名。
  • FileWriter(String fileName, boolean append): 创建一个新的 FileWriter,可以往某个文件中追加内容。
  • FileWriter(String fileName, Charset charset): 创建一个新的 FileWriter,给定要写入内容的文件的文件路径名 和 文件编码。
  • FileWriter(String fileName, Charset charset, boolean append) :创建一个新的 FileWriter,给定要追加内容的文件的文件路径名 和 文件编码。

注意:

当你创建一个流对象时,必须传入一个文件路径,否则会报错。

对于FileWriter和FileOutputStream流来说,如果文件不存在,则会自动创建。如果文件已经存在,则会清空文件内容,写入新的内容,除非使用追加模式。

1、写出字符数据

package com.atguigu.fileio;

import org.junit.Test;

import java.io.FileWriter;
import java.io.IOException;

public class FWWrite {
    @Test
    public void test01()throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据
        fw.write(97); // 写出第1个字符
        fw.write('b'); // 写出第2个字符
        fw.write('C'); // 写出第3个字符
        fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。

          /*
        【注意】FileWriter与FileOutputStream不同。
           如果不关闭,数据只是保存到缓冲区,并未保存到文件。
        */
        // fw.close();
    }

    @Test
    public void test02()throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 字符串转换为字节数组
        char[] chars = "尚硅谷".toCharArray();

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

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

        // 关闭资源
        fw.close();
    }

    @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();
    }
}

2、续写

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

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

操作类似于FileOutputStream。

package com.atguigu.fileio;

import org.junit.Test;

import java.io.FileWriter;
import java.io.IOException;

public class FWWriteAppend {
    @Test
    public void test01()throws IOException {
        // 使用文件名称创建流对象,可以续写数据
        FileWriter fw = new FileWriter("fw.txt",true);
        // 写出字符串
        fw.write("尚硅谷真棒");
        // 关闭资源
        fw.close();
    }
}

3、换行

package com.atguigu.fileio;

import java.io.FileWriter;
import java.io.IOException;

public class FWWriteNewLine {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象,可以续写数据
        FileWriter fw = new FileWriter("fw.txt");
        // 写出字符串
        fw.write("尚");
        // 写出换行
        fw.write("\r\n");
        // 写出字符串
        fw.write("硅谷");
        // 关闭资源
        fw.close();
    }
}

4、关闭和刷新

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

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

代码使用演示:

package com.atguigu.fileio;

import java.io.FileWriter;
import java.io.IOException;

public class FWWriteFlush {
    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方法,释放系统资源。

5、编码处理

package com.atguigu.io;

import org.junit.Test;

import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;

public class TestFileWriterEncoding {

    @Test
    public void test1()throws IOException {
        //当前程序编码是UTF-8
        //写一段话到一个GBK的文件中
        FileWriter fw = new FileWriter("d:\\gbk.txt", Charset.forName("GBK"));
        fw.write("尚硅谷的原则是技术为王!");
        fw.close();
    }
    @Test
    public void test2()throws IOException {
        //当前程序编码是UTF-8
        //往一个GBK的文件中续写一段话
        FileWriter fw = new FileWriter("d:\\gbk.txt", Charset.forName("GBK"),true);
        fw.write("让天下没有难学的技术!");
        fw.close();
    }
}

FileReader类

Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。

小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。并且要求文件字符编码与程序字符编码一致,否则会乱码。

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

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

注意:

当你创建一个流对象时,必须传入一个文件路径。对于FileReader和FileInputStream 来说,如果该文件不存在,则报FileNotFoundException。如果传入的是一个目录,则会报IOException异常。

package com.atguigu.fileio;

import org.junit.Test;

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

public class FRRead {
    @Test
    public void test01() throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存数据
        int b;
        // 循环读取
        while ((b = fr.read())!=-1) {
            System.out.println((char)b);
        }
        // 关闭资源
        fr.close();
/*输出结果:
        尚
        硅
        谷*/
    }

    @Test
    public void test02()throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存有效字符个数
        int len;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf))!=-1) {
            System.out.println(new String(cbuf));
        }
        // 关闭资源
        fr.close();
        /*
        输出结果:
        尚硅
        谷硅
        最后错误数据硅,是因为最后一次流中只有一个字符“谷”,读取一个字符没有覆盖char[]数组cbuf的所有元素
         */
    }

    @Test
    public void test03() throws IOException {
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存有效字符个数
        int len;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf)) != -1) {
            System.out.println(new String(cbuf, 0, len));
        }
        // 关闭资源
        fr.close();
        /*
        输出结果:
        尚硅
        谷
         */
    }

}

FileOutputStream类

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。==支持各种类型的文件==。

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

当你创建一个流对象时,必须传入一个==文件==路径。如果传入的是一个目录,则会报IOException异常。

如果该文件不存在,会创建该文件。如果这个文件,可以覆盖这个文件或在文件后面续写内容。

package com.atguigu.fileio;

import org.junit.Test;

import java.io.FileOutputStream;
import java.io.IOException;

public class FOSWrite {
    @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 = "尚硅谷".getBytes();
        // 写出字节数组数据
        fos.write(b);
        // 关闭资源
        fos.close();
    }

    @Test
    public void test03()throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        // 字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
        fos.write(b,2,2);
        // 关闭资源
        fos.close();
    }
}
package com.atguigu.fileio;

import java.io.FileOutputStream;
import java.io.IOException;

public class FOSWriteAppend {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt",true);
        // 字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        fos.write(b);
        // 关闭资源
        fos.close();
    }
}
//这段程序如果多运行几次,每次都会在原来文件末尾追加abcde

FileInputStream类

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

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

当你创建一个流对象时,必须传入一个文件路径。如果文件不存在,会抛出FileNotFoundException 。如果传入的是一个目录,则会报IOException异常。

package com.atguigu.fileio;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;

public class FISRead {
    @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
         */
    }
}

复制文件

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

复制图片文件,代码使用演示:

package com.atguigu.fileio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {
    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();
    }
}

对象流

数据流与对象流概述

前面学习的IO流,在程序代码中,要么将数据直接按照字节处理,要么按照字符处理。那么,如果读写Java其他数据类型的数据,怎么办呢?

String name = “巫师”;
int age = 300;
char gender = ‘男’;
int energy = 5000;
double price = 75.5;
boolean relive = true;

Student stu = new Student("张三",23,89);

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

序号IO流描述
1DataOutputStream数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流(DataInputStream)将数据读入。
2DataInputStream数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
3ObjectOutputStream将 Java 基本数据类型和对象写入字节输出流中。稍后可以使用 ObjectInputStream 将数据读入。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中接收这些数据或重构对象。
4ObjectInputStream对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。

构造器及其API

数据流和对象流都需要包装其他的IO流来创建它们的对象:

序号构造器描述
1public DataOutputStream(out)指定一个OutputStream字节输出流对象创建DataOutputStream对象
2public DataInputStream(in)指定一个InputStream 字节输出流对象创建DataInputStream对象
3public ObjectOutputStream(OutputStream out)指定一个OutputStream 字节输出流对象创建ObjectOutputStream对象
4public ObjectInputStream(InputStream in)指定一个InputStream 字节输出流对象创建ObjectInputStream对象

因为DataOutputStream和DataInputStream只支持Java基本数据类型和字符串的读写,而不支持Java对象的对象。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以下面直接介绍对象流ObjectOutputStream和ObjectInputStream即可。

构造举例,代码如下:

FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);

ObjectOutputStream也从OutputStream父类中继承基本方法。另外,还扩展了很多方法:

序号ObjectOutputStreamObjectInpuStream描述
1public void write(int b)public int read()读写1个字节
2public void write(byte[] b)public int read(byte[] b)读写整个字节数组
3public void write(byte[] b, int off, int len)public int read(byte[] b,int off,int len)读写一个字节数组的一部分
4public void flush() 刷新输出流
5public void close()public void close()关闭
以下是扩展的方法
6public void writeBoolean(boolean val)public boolean readBoolean()读写一个boolean值
7public void writeByte(int val)public byte readByte()读写一个byte值
8public void writeShort(int val)public short readShort()读写一个short值
9public void writeChar(int val)public char readChar()读写一个char值
10public void writeInt(int val)public int readInt()读写一个int值
11public void writeLong(long val)public long readLong()读写一个long值
12public void writeFloat(float val)public float readFloat()读写一个float值
13public void writeDouble(double val)public double readDouble()读写一个double值
14public void writeUTF(String str)public String readUTF()读写一个String值
15public final void writeObject (Object obj)public final Object readObject ()读写一个对象
注意:读的顺序和方法与写的顺序和方法要一一对应。

读写各种基本数据类型的数据

示例代码:

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();
    }
}

对象的序列化与反序列化

1、序列化与反序列化概念

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

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

2、Serializable序列化接口

某个类的对象需要序列化输出时,该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

  • 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口
package com.atguigu.io.object;

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
package com.atguigu.io.object;

import org.junit.Test;

import java.io.*;

public class TestUserIO {
    @Test
    public void test1()throws IOException {
        User u = new User("chai","123456");
        FileOutputStream fos = new FileOutputStream("io/user.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(u);
        oos.close();
        fos.close();
    }

    @Test
    public void test2()throws Exception{
        FileInputStream fis = new FileInputStream("io/user.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object object = ois.readObject();
        System.out.println(object);
        ois.close();
        fis.close();
    }
}

3、不序列化字段:transient和static

对象序列化是指输出对象,即将对象当前的状态值输出。这也就意味着哪些不属于对象的状态值,或者哪些瞬态的、易变的状态值不需要序列化。

  • static修饰的静态变量的值不会序列化。因为静态变量的值不属于某个对象。
  • transient修改的成员变量值不会序列化。因为transient表示该属性是瞬态的、易变的。
package com.atguigu.io.object;

import java.io.Serializable;

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

    public Employee(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Employee{" +
                "company=" + company +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                '}';
    }
}
package com.atguigu.io.object;

import org.junit.Test;

import java.io.*;

public class TestEmployeeIO {
    @Test
    public void save() throws IOException {
        Employee.company  =" 尚硅谷";
        Employee e = new Employee("张三", "宏福苑", 23);
        // 创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io/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("io/employee.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        // 读取一个对象
        Employee e = (Employee) ois.readObject();
        // 释放资源
        ois.close();
        fis.close();

        System.out.println(e);
    }
}

4、反序列化失败问题

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

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

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

import java.io.Serializable;

public class Goods implements Serializable {
    private int id;
    private String name;
    private double price;

    public Goods(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
package com.atguigu.io.object;

import org.junit.Test;

import java.io.*;

public class TestGoodsIO {
    @Test
    public void test1()throws IOException {
        Goods g = new Goods(1,"鼠标",99.0);
        // 创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io/goods.dat"));
        // 写出对象
        oos.writeObject(g);
        // 释放资源
        oos.close();
    }

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

        System.out.println(e);
    }
}

之后修改了Goods类:

package com.atguigu.io.object;

import java.io.Serializable;

public class Goods implements Serializable {
    private int id;
    private String name;
    private double price;
    private double amount;//数量

    public Goods(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", amount=" + amount +
                '}';
    }
}

再次运行反序列化代码,出现异常:

java.io.InvalidClassException: com.atguigu.io.object.Goods; local class incompatible: stream classdesc serialVersionUID = -2991413533082785204, local class serialVersionUID = 6624462214759466750

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。如果没有声明serialVersionUID,则每次编译都会产生新的serialVersionUID序列化版本ID值,这样如果在序列化完成之后修改了类导致类重新编译,则原来的数据将无法反序列化。所以通常我们都会在实现Serializable接口时,声明一个serialVersionUID,并为其指定一个值。serialVersionUID必须是static和final修饰的long类型的数据,它的值由程序员随意指定即可。

如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。

package com.atguigu.io.object;

import java.io.Serializable;

public class Goods implements Serializable {
    private int id;
    private String name;
    private double price;
    private double amount;//数量
    private static final long serialVersionUID = -2991413533082785204L;

    public Goods(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", amount=" + amount +
                '}';
    }
}

5、序列化多个对象

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

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);
    }
}

重新认识System.in,System.out和Scanner

System类的三个IO流对象

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);

PrintStream类

我们每天都在用的System.out对象是PrintStream类型的。它也是IO流对象。

PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;另外,PrintStream 可以设置自动刷新。

丰富的构造器:

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

除了从OutputStream继承的方法之前,更是提供了丰富的print和println方法。

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();
    }
}

Scanner类

Scanner类不只是键盘输入。

  • public Scanner(File source):创建Scanner对象,从source文件读取内容
  • public Scanner(File source, String charsetName):创建Scanner对象,从source文件读取内容。文件编码是charsetName。
  • public Scanner(File source, Charset charset):创建Scanner对象,从source文件读取内容。文件编码是charset。
  • public Scanner(InputStream source):创建Scanner对象,从source输入流读取内容。
  • public Scanner(InputStream source, String charsetName):创建Scanner对象,从source输入流读取内容。流中文本编码是charsetName
  • public Scanner(InputStream source, Charset charset):创建Scanner对象,从source输入流读取内容。流中文本编码是charset

常用方法:

序号方法描述方法描述
1public boolean hasNextByte()当且仅当此扫描器的下一个标记是有效的字节值时才返回 truepublic byte nextByte()读取1个字节
2public boolean hasNextShort()当且仅当此扫描器的下一个标记是默认基数中的有效的 short 值时才返回 truepublic short nextShort()读取一个boolean值
3public boolean hasNextInt()当且仅当此扫描器的下一个标记是有效的 int 值时才返回 truepublic int nextInt()读取一个int值
4public boolean hasNextLong()当且仅当此扫描器的下一个标记是有效的 long 值时才返回 true将输入信息的下一个标记扫描为一个 long。读取一个long值
5public boolean hasNextFloat()当且仅当此扫描器的下一个标记是有效的 float 值时才返回 truepublic float nextFloat()读取一个float值
6public boolean hasNextDouble()当且仅当此扫描器的下一个标记是有效的 double 值时才返回 truepublic double nextDouble()读取一个double值
7public boolean hasNextBigInteger()当且仅当此扫描器的下一个标记是有效的 BigInteger 值时才返回 truepublic BigInteger nextBigInteger()读取一个BigInteger值
8public boolean hasNextBigDecimal()当且仅当此扫描器的下一个标记是有效的 BigDecimal 值时才返回 truepublic BigDecimal nextBigDecimal()读取一个BigDecimal值
9public boolean hasNext()如果此扫描器的输入中有另一个标记,则返回 true。在等待要扫描的输入时,此方法可能阻塞。public String next()读取一个字符串,遇到空格结束
10public boolean hasNextLine()当且仅当此扫描器有另一行输入时才返回 truepublic String nextLine()读取一行字符串,遇到换行符结束
package com.atguigu.systemio;

import org.junit.Test;

import java.io.*;
import java.util.Scanner;

public class TestScanner {

    @Test
    public void test01() throws IOException {
        Scanner input = new Scanner(System.in);
        PrintStream ps = new PrintStream("1.txt");
        while(true){
            System.out.print("请输入一个单词:");
            String str = input.nextLine();
            if("stop".equals(str)){
                break;
            }
            ps.println(str);
        }
        input.close();
        ps.close();
    }
    
    @Test
    public void test2() throws IOException {
        Scanner input = new Scanner(new FileInputStream("1.txt"));
        while(input.hasNextLine()){
            String str = input.nextLine();
            System.out.println(str);
        }
        input.close();
    }
}

JDK1.7新try..catch

IO流关闭和异常处理

package com.atguigu.io;

import org.junit.Test;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class TestIOClose {
    @Test
    public void test1() throws IOException {
        FileOutputStream fos = new FileOutputStream("d:\\data.io");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeInt(8);
        oos.writeDouble(3.14);
        oos.writeUTF("hello");
        fos.close();
        oos.close();//java.io.IOException: Stream Closed
        // 要求:先new的后关
    }

    @Test
    public void test2(){
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fos = new FileOutputStream("d:\\data.io");
            oos = new ObjectOutputStream(fos);
            oos.writeInt(8);
            oos.writeDouble(3.14);
            oos.writeUTF("hello");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

JDK1.7之后引入新try..catch

语法格式:

try(需要关闭的资源对象的声明){
    业务逻辑代码
}catch(异常类型 e){
    处理异常代码
}catch(异常类型 e){
    处理异常代码
}
....

它没有finally,也不需要程序员去关闭资源对象,无论是否发生异常,都会关闭资源对象。

需要指出的是,为了保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close方法。Closeable是AutoCloseable的子接口。Java7几乎把所有的“资源类”(包括文件IO的各种类、JDBC编程的Connection、Statement等接口…)进行了改写,改写后资源类都是实现了AutoCloseable或Closeable接口,并实现了close方法。

写到try()中的资源类的变量默认是final声明的,不能修改。

示例代码:

    @Test
    public void test3() {
        try(
        FileOutputStream fos = new FileOutputStream("d:\\data.io");
        ObjectOutputStream oos = new ObjectOutputStream(fos);) {
            oos.writeInt(8);
            oos.writeDouble(3.14);
            oos.writeUTF("hello");
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

© 著作权归作者所有

本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。

评论关闭