面试必备:详解Java I/O流,掌握这些就可以说精通了?

@TOC

Java IO概述

IO就是输入/输出。Java IO类库基于抽象基础类InputStream和OutputStream构建了一套I/O体系,主要解决从数据源读入数据和将数据写入到目的地问题。我们把数据源和目的地可以理解为IO流的两端。当然,通常情况下,这两端可能是文件或者网络连接。

我们用下面的图描述下,加深理解:

从一种数据源中通过InputStream流对象读入数据到程序内存中

在这里插入图片描述

当然我们把上面的图再反向流程,就是OutputStream的示意了。

在这里插入图片描述

其实除了面向字节流的InputStream/OutputStream体系外,Java IO类库还提供了面向字符流的Reader/Writer体系。Reader/Writer继承结构主要是为了国际化,因为它能更好地处理16位的Unicode字符。

在学习是这两套IO流处理体系可以对比参照着学习,因为有好多相似之处。

要理解总体设计

刚开始写IO代码,总被各种IO流类搞得晕头转向。这么多IO相关的类,各种方法,啥时候能记住。

其实只要我们掌握了IO类库的总体设计思路,理解了它的层次脉络之后,就很清晰。知道啥时候用哪些流对象去组合想要的功能就好了,API的话,可以查手册的嘛。

首先从流的流向上可以分为输入流InputStream或Reader,输出流OutputStream或Writer。任何从InputStream或Reader派生而来的类都有read()基本方法,读取单个字节或字节数组;任何从OutputSteam或Writer派生的类都含有write()的基本方法,用于写单个字节或字节数组。

从操作字节还是操作字符的角度,有面向字节流的类,基本都以XxxStream结尾,面向字符流的类都以XxxReader或XxxWriter结尾。当然这两种类型的流是可以转化的,有两个转化流的类,这个后面会说到。

一般在使用IO流的时候会有下面类似代码:

1 FileInputStream inputStream = new FileInputStream(new File("a.txt"));2 BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

这里其实是一种装饰器模式的使用,IO流体系中使用了装饰器模式包装了各种功能流类。不了解装饰器模式的看下这篇【详解设计模式】-装饰者模式

在Java IO流体系中FilterInputStream/FilterOutStreamFilterReader/FilterWriter就是装饰器模式的接口类,从该类向下包装了一些功能流类。有DataInputStream、BufferedInputStream、LineNumberInputStream、PushbackInputStream等,当然还有输出的功能流类;面向字符的功能流类等。

下面几张图描述了整个IO流的继承体系结构

InputStream流体系

在这里插入图片描述

OutputStream流体系

在这里插入图片描述

Reader体系

在这里插入图片描述

Writer体系

在这里插入图片描述

最后再附加一张表加深印象:

在这里插入图片描述

File其实是个工具类

File类其实不止是代表一个文件,它也能代表一个目录下的一组文件(代表一个文件路径)。下面我们盘点一下File类中最常用到的一些方法

 1File.delete() 删除文件或文件夹目录。 2File.createNewFile() 创建一个新的空文件。 3File.mkdir() 创建一个新的空文件夹。 4File.list() 获取指定目录下的文件和文件夹名称。 5File.listFiles() 获取指定目录下的文件和文件夹对象。 6File.exists() 文件或者文件夹是否存在 7 8String   getAbsolutePath()   // 获取绝对路径 9long   getFreeSpace()       // 返回分区中未分配的字节数。10String   getName()         // 返回文件或文件夹的名称。11String   getParent()         // 返回父目录的路径名字符串;如果没有指定父目录,则返回 null。12File   getParentFile()      // 返回父目录File对象13String   getPath()         // 返回路径名字符串。14long   getTotalSpace()      // 返回此文件分区大小。15long   getUsableSpace()    //返回占用字节数。16int   hashCode()             //文件哈希码。17long   lastModified()       // 返回文件最后一次被修改的时间。18long   length()          // 获取长度,字节数。19boolean canRead()  //判断是否可读20boolean canWrite()  //判断是否可写21boolean isHidden()  //判断是否隐藏222324// 成员函数25static File[]    listRoots()    // 列出可用的文件系统根。26boolean    renameTo(File dest)    // 重命名27boolean    setExecutable(boolean executable)    // 设置执行权限。28boolean    setExecutable(boolean executable, boolean ownerOnly)    // 设置其他所有用户的执行权限。29boolean    setLastModified(long time)       // 设置最后一次修改时间。30boolean    setReadable(boolean readable)    // 设置读权限。31boolean    setReadable(boolean readable, boolean ownerOnly)    // 设置其他所有用户的读权限。32boolean    setWritable(boolean writable)    // 设置写权限。33boolean    setWritable(boolean writable, boolean ownerOnly)    // 设置所有用户的写权限。34

需要注意的是,不同系统对文件路径的分割符表是不一样的,比如Windows中是“\”,Linux是“/”。而File类给我们提供了抽象的表示File.separator,屏蔽了系统层的差异。因此平时在代码中不要使用诸如“\”这种代表路径,可能造成Linux平台下代码执行错误。

下面是一些示例:

根据传入的规则,遍历得到目录中所有的文件构成的File对象数组

 1public class Directory { 2    public static File[] getLocalFiles(File dir, final String regex){ 3        return dir.listFiles(new FilenameFilter() { 4            private Pattern pattern = Pattern.compile(regex); 5            public boolean accept(File dir, String name) { 6                return pattern.matcher(new File(name).getName()).matches(); 7            } 8        }); 9    }1011    // 重载方法12    public static File[] getLocalFiles(String path, final String regex){13        return getLocalFiles(new File(path),regex);14    }1516    public static void main(String[] args) {17        String dir = "d:";18        File[] files = Directory.getLocalFiles(dir,".*\\.txt");19        for(File file : files){20            System.out.println(file.getAbsolutePath());21        }22    }23}

输出结果:

1d:\\1.txt2d:\\新建文本文档.txt

上面的代码中dir.listFiles(FilenameFilter ) 是策略模式的一种实现,而且使用了匿名内部类的方式。

【详解设计模式】-策略模式

Java内部类超详细总结(含代码示例)

上面的例子是《Java 编程思想》中的示例,这本书中的每个代码示例都很经典,Bruce Eckel大神把面向对象的思想应用的炉火纯青,非常值得细品。

InputStream和OutputStream

InputStream是输入流,前面已经说到,它是从数据源对象将数据读入程序内容时,使用的流对象。通过看InputStream的源码知道,它是一个抽象类,

1public abstract class InputStream  extends Object  implements Closeable2

提供了一些基础的输入流方法:

 1//从数据中读入一个字节,并返回该字节,遇到流的结尾时返回-1 2abstract int read()  3 4//读入一个字节数组,并返回实际读入的字节数,最多读入b.length个字节,遇到流结尾时返回-1 5int read(byte[] b) 6 7// 读入一个字节数组,返回实际读入的字节数或者在碰到结尾时返回-1. 8//b:代表数据读入的数组, off:代表第一个读入的字节应该被放置的位置在b中的偏移量,len:读入字节的最大数量 9int read(byte[],int off,int len)1011// 返回当前可以读入的字节数量,如果是从网络连接中读入,这个方法要慎用,12int available() 1314//在输入流中跳过n个字节,返回实际跳过的字节数15long skip(long n)1617//标记输入流中当前的位置18void mark(int readlimit) 1920//判断流是否支持打标记,支持返回true21boolean markSupported() 2223// 返回最后一个标记,随后对read的调用将重新读入这些字节。24void reset() 2526//关闭输入流,这个很重要,流使用完一定要关闭27void close()28

直接从InputStream继承的流,可以发现,基本上对应了每种数据源类型。

功能
ByteArrayInputStream 将字节数组作为InputStream
StringBufferInputStream 将String转成InputStream
FileInputStream 从文件中读取内容
PipedInputStream 产生用于写入相关PipedOutputStream的数据。实现管道化
SequenceInputStream 将两个或多个InputStream对象转换成单一的InputStream
FilterInputStream 抽象类,主要是作为“装饰器”的接口类,实现其他的功能流

OutputStream是输出流的抽象,它是将程序内存中的数据写入到目的地(也就是接收数据的一端)。看下类的签名:

1public abstract class OutputStream implements Closeable, Flushable {}

提供了基础方法相比输入流来说简单多了,主要就是write写方法(几种重载的方法)、flush冲刷和close关闭。

 1// 写出一个字节的数据 2abstract void write(int n) 3 4// 写出字节到数据b 5void write(byte[] b) 6 7// 写出字节到数组b,off:代表第一个写出字节在b中的偏移量,len:写出字节的最大数量 8void write(byte[] b, int off, int len) 910//冲刷输出流,也就是将所有缓冲的数据发送到目的地11void flush()1213// 关闭输出流14void close()15

同样地,OutputStream也提供了一些基础流的实现,这些实现也可以和特定的目的地(接收端)对应起来,比如输出到字节数组或者是输出到文件/管道等。

功能
ByteArrayOutputStream 在内存中创建一个缓冲区,所有送往“流”的数据都要放在此缓冲区
FileOutputStream 将数据写入文件
PipedOutputStream 和PipedInputStream配合使用。实现管道化
FilterOutputStream 抽象类,主要是作为“装饰器”的接口类,实现其他的功能流

使用装饰器包装有用的流

Java IO 流体系使用了装饰器模式来给哪些基础的输入/输出流添加额外的功能。这写额外的功能可能是:可以将流缓冲起来提高性能、是流能够读写基本数据类型等。

这些通过装饰器模式添加功能的流类型都是从FilterInputStream和FilterOutputStream抽象类扩展而来的。可以再返回文章最开始说到IO流体系的层次时,那几种图加深下印象。

FilterInputStream类型

功能
DataInputStream 和DataOutputStream搭配使用,使得流可以读取int char long等基本数据类型
BufferedInputStream 使用缓冲区,主要是提高性能
LineNumberInputStream 跟踪输入流中的行号,可以使用getLineNumber、setLineNumber(int)
PushbackInputStream 使得流能弹出“一个字节的缓冲区”,可以将读到的最后一个字符回退

FilterOutStream类型

功能
DataOutputStream 和DataInputStream搭配使用,使得流可以写入int char long等基本数据类型
PrintStream 用于产生格式化的输出
BufferedOutputStream 使用缓冲区,可以调用flush()清空缓冲区

大多数情况下,其实我们在使用流的时候都是输入流和输出流搭配使用的。目的就是为了转移和存储数据,单独的read()对我们而言有啥用呢,读出来一个字节能干啥?对吧。因此要理解流的使用就是搭配起来或者使用功能流组合起来去转移或者存储数据。

Reader和Writer

Reader是Java IO中所有Reader的基类。ReaderInputStream类似,不同点在于,Reader基于字符而非基于字节。

Writer是Java IO中所有Writer的基类。与ReaderInputStream的关系类似,Writer基于字符而非基于字节,Writer用于写入文本,OutputStream用于写入字节。

ReaderWriter的基础功能类,可以对比InputStreamOutputStream来学习。

面向字节 面向字符
InputStream Reader
OutputStream Writer
FileInputStream FileReader
FileOutputStream FileWriter
ByteArrayInputStream CharArrayReader
ByteArrayOutputStream CharArrayWriter
PipedInputStream PipedReader
PipedOutputStream PipedWriter
StringBufferInputStream(已弃用) StringReader
无对应类 StringWriter

有两个“适配器” 流类型,它们可以将字节流转化成字节流。这就是InputStreamReader 可以将InputStream转成为Reader,OutputStreamWriter可以将OutputStream转成为Writer。

适配器类,字节流转字符流

在这里插入图片描述

当然也有类似字节流的装饰器实现方式,给字符流添加额外的功能或这说是行为。这些功能字符流类主要有:

  • BufferedReader
  • BufferedWriter
  • PrintWriter
  • LineNumberReader
  • PushbackReader

System类中的I/O流

想想你的第一个Java程序是啥?我没猜错的话,应该是 hello world。

1System.out.println("hello world")

简单到令人发指,今天就说说标准的输入/输出流。

在标准IO模型中,Java提供了System.in、System.out和System.error。

先说System.in,看下源码

1public final static InputStream in

是一个静态域,未被包装过的InputStream。通常我们会使用BufferedReader进行包装然后一行一行地读取输入,这里就要用到前面说的适配器流InputStreamReader

1public class SystemInReader {2    public static void main(String[] args) throws IOException {3        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));4        String s;5        while ((s = reader.readLine()) != null && s.length() != 0){6            System.out.println(s);7        }8    }9}

该程序等待会一直等待我们输入,输入啥,后面会接着输出。输入空字符串可以结束。

112131234123

System.out是一个PrintStream流。System.out一般会把你写到其中的数据输出到控制台上。System.out通常仅用在类似命令行工具的控制台程序上。System.out也经常用于打印程序的调试信息(尽管它可能并不是获取程序调试信息的最佳方式)。

System.err是一个PrintStream流。System.err与System.out的运行方式类似,但它更多的是用于打印错误文本。

可以将这些系统流重定向

尽管System.in, System.out, System.err这3个流是java.lang.System类中的静态成员,并且已经预先在JVM启动的时候初始化完成,你依然可以更改它们。

可以使用setIn(InputStream)、setOut(PrintStream)、setErr(PrintStream)进行重定向。比如可以将控制台的输出重定向到文件中。

1OutputStream output = new FileOutputStream("d:/system.out.txt");2PrintStream printOut = new PrintStream(output);3System.setOut(printOut);

压缩(ZIP文档)

Java IO类库是支持读写压缩格式的数据流的。我们可以把一个或一批文件压缩成一个zip文档。这些压缩相关的流类是按字节处理的。先看下设计压缩解压缩的相关流类。

压缩类 功能
CheckedInputStream getCheckSum()可以为任何InputStream产生校验和(不仅是解压缩)
CheckedOutputStream getCheckSum()可以为任何OutputStream产生校验和(不仅是压缩)
DeflaterOutputStream 压缩类的基类
ZipOutputStream 继承自DeflaterOutputStream,将数据压缩成Zip文件格式
GZIPOutputStream 继承自DeflaterOutputStream,将数据压缩成GZIP文件格式
InflaterInputStream 解压缩类的基类
ZipInputStream 继承自InflaterInputStream,解压缩Zip文件格式的数据
GZIPInputStream 继承自InflaterInputStream,解压缩GZIP文件格式的数据

表格中CheckedInputStream 和 CheckedOutputStream 一般会和Zip压缩解压过程配合使用,主要是为了保证我们压缩和解压过程数据包的正确性,得到的是中间没有被篡改过的数据。

我们以CheckedInputStream 为例,它的构造器需要传入一个Checksum类型:

1    public CheckedInputStream(InputStream in, Checksum cksum) {2        super(in);3        this.cksum = cksum;4    }

而Checksum 是一个接口,可以看到这里又用到了策略模式,具体的校验算法是可以选择的。Java类库给我提供了两种校验和算法:Adler32 和 CRC32,性能方面可能Adler32 会更好一些,不过CRC32可能更准确。各有优劣吧。

好了,接下来看下压缩/解压缩流的具体使用。

将多个文件压缩成zip包

 1public class ZipFileUtils { 2    public static void compressFiles(File[] files, String zipPath) throws IOException { 3 4        // 定义文件输出流,表明是要压缩成zip文件的 5        FileOutputStream f = new FileOutputStream(zipPath); 6 7        // 给输出流增加校验功能 8        CheckedOutputStream checkedOs = new CheckedOutputStream(f,new Adler32()); 910        // 定义zip格式的输出流,这里要明白一直在使用装饰器模式在给流添加功能11        // ZipOutputStream 也是从FilterOutputStream 继承下来的12        ZipOutputStream zipOut = new ZipOutputStream(checkedOs);1314        // 增加缓冲功能,提高性能15        BufferedOutputStream buffOut = new BufferedOutputStream(zipOut);1617        //对于压缩输出流我们可以设置个注释18        zipOut.setComment("zip test");1920        // 下面就是从Files[] 数组中读入一批文件,然后写入zip包的过程21        for (File file : files){2223            // 建立读取文件的缓冲流,同样是装饰器模式使用BufferedReader24            // 包装了FileReader25            BufferedReader bfReadr = new BufferedReader(new FileReader(file));2627            // 一个文件对象在zip流中用一个ZipEntry表示,使用putNextEntry添加到zip流中28            zipOut.putNextEntry(new ZipEntry(file.getName()));2930            int c;31            while ((c = bfReadr.read()) != -1){32                buffOut.write(c);33            }3435            // 注意这里要关闭36            bfReadr.close();37            buffOut.flush();38        }39        buffOut.close();40    }4142    public static void main(String[] args) throws IOException {43        String dir = "d:";44        String zipPath = "d:/test.zip";45        File[] files = Directory.getLocalFiles(dir,".*\\.txt");46        ZipFileUtils.compressFiles(files, zipPath);47    }48}

在main函数中我们使用了本文中 File其实是个工具类 章节里的Directory工具类。

解压缩zip包到目标文件夹

 1    public static void unConpressZip(String zipPath, String destPath) throws IOException { 2        if(!destPath.endsWith(File.separator)){ 3            destPath = destPath + File.separator; 4            File file = new File(destPath); 5            if(!file.exists()){ 6                file.mkdirs(); 7            } 8        } 9        // 新建文件输入流类,10        FileInputStream fis = new FileInputStream(zipPath);1112        // 给输入流增加检验功能13        CheckedInputStream checkedIns = new CheckedInputStream(fis,new Adler32());1415        // 新建zip输出流,因为读取的zip格式的文件嘛16        ZipInputStream zipIn = new ZipInputStream(checkedIns);1718        // 增加缓冲流功能,提高性能19        BufferedInputStream buffIn = new BufferedInputStream(zipIn);2021        // 从zip输入流中读入每个ZipEntry对象22        ZipEntry zipEntry;23        while ((zipEntry = zipIn.getNextEntry()) != null){24            System.out.println("解压中" + zipEntry);2526            // 将解压的文件写入到目标文件夹下27            int size;28            byte[] buffer = new byte[1024];29            FileOutputStream fos = new FileOutputStream(destPath + zipEntry.getName());30            BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length);31            while ((size = buffIn.read(buffer, 0, buffer.length)) != -1) {32                bos.write(buffer, 0, size);33            }34            bos.flush();35            bos.close();36        }37        buffIn.close();3839        // 输出校验和40        System.out.println("校验和:" + checkedIns.getChecksum().getValue());41    }4243    // 在main函数中直接调用44    public static void main(String[] args) throws IOException {45        String dir = "d:";46        String zipPath = "d:/test.zip";47//        File[] files = Directory.getLocalFiles(dir,".*\\.txt");48//        ZipFileUtils.compressFiles(files, zipPath);4950        ZipFileUtils.unConpressZip(zipPath,"F:/ziptest");51    }

这里解压zip包还有一种更加简便的方法,使用ZipFile对象。该对象的entries()方法直接返回ZipEntry类型的枚举。看下代码片段:

1        ZipFile zipFile = new ZipFile("test.zip");2        Enumeration e = zipFile.entries();3        while (e.hasMoreElements()){4            ZipEntry zipEntry = (ZipEntry) e.nextElement();5            System.out.println("file:" + zipEntry);6        }

对象序列化

什么是序列化和反序列化呢?

序列化就是将对象转成字节序列的过程,反序列化就是将字节序列重组成对象的过程。

在这里插入图片描述

为什么要有对象序列化机制

程序中的对象,其实是存在有内存中,当我们JVM关闭时,无论如何它都不会继续存在了。那有没有一种机制能让对象具有“持久性”呢?序列化机制提供了一种方法,你可以将对象序列化的字节流输入到文件保存在磁盘上。

序列化机制的另外一种意义便是我们可以通过网络传输对象了,Java中的 远程方法调用(RMI),底层就需要序列化机制的保证。

在Java中怎么实现序列化和反序列化

首先要序列化的对象必须实现一个Serializable接口(这是一个标识接口,不包括任何方法)

1public interface Serializable {2}

其次需要是用两个对象流类:ObjectInputStream 和ObjectOutputStream主要使用ObjectInputStream对象的readObject方法读入对象、ObjectOutputStream的writeObject方法写入对象到流中

下面我们通过序列化机制将一个简单的pojo对象写入到文件,并再次读入到程序内存。

 1public class User implements Serializable { 2    private String name; 3    private int age; 4 5    public User(String name, int age) { 6        this.name = name; 7        this.age = age; 8    } 910    @Override11    public String toString() {12        return "User{" +13                "name=‘" + name + ‘\‘‘ +14                ", age=‘" + age + ‘\‘‘ +15                ‘}‘;16    }1718    public static void main(String[] args) throws IOException, ClassNotFoundException {19        User user = new User("二营长",18);20        ObjectOutputStream objectOps = new ObjectOutputStream(new FileOutputStream("f:/user.out"));21        objectOps.writeObject(user);22        objectOps.close();2324        // 再从文件中取出对象25        ObjectInputStream objectIns = new ObjectInputStream(new FileInputStream("f:/user.out"));2627        // 这里要做一次强转28        User user1 = (User) objectIns.readObject();29        System.out.println(user1);30        objectIns.close();31    }32}33

程序运行结果:

1User{name=‘二营长‘, age=‘18‘}

不想序列化的数据使用transient(瞬时)关键字屏蔽

如果我们上面的user对象有一个password字段,属于敏感信息,这种是不能走序列化的方式的,但是实现了Serializable 接口的对象会自动序列化所有的数据域,怎么办呢?在password字段上加上关键字transient就好了。

1 private transient String password;

序列化机制就简单介绍到这里吧。这是Java原生的序列化,现在市面上有好多序列化协议可以选择,比如Json、FastJson、Thrift、Hessian 、protobuf等

I/O流的典型使用方式

IO流种类繁多,可以通过不同的方式组合I/O流类,但平时我们常用的也就几种组合。下盘通过示例的方式盘点几种I/O流的典型用法。

缓冲输入文件

 1public class BufferedInutFile { 2    public static String readFile(String fileName) throws IOException { 3        BufferedReader bf = new BufferedReader(new FileReader(fileName)); 4        String s; 5 6        // 这里读取的内容存在了StringBuilder,当然也可以做其他处理 7        StringBuilder sb = new StringBuilder(); 8        while ((s = bf.readLine()) != null){ 9            sb.append(s + "\n");10        }11        bf.close();12        return sb.toString();13    }1415    public static void main(String[] args) throws IOException {16        System.out.println(BufferedInutFile.readFile("d:/1.txt"));17    }18}

格式化内存输入

要读取格式化的数据,可以使用DataInputStream。

 1public class FormattedMemoryInput { 2    public static void main(String[] args) throws IOException { 3        try { 4            DataInputStream dataIns = new DataInputStream( 5                    new ByteArrayInputStream(BufferedInutFile.readFile("f:/FormattedMemoryInput.java").getBytes())); 6            while (true){ 7                System.out.print((char) dataIns.readByte()); 8            } 9        } catch (EOFException e) {10            System.err.println("End of stream");11        }12    }13}

上面程序会在控制台输出当前类本身的所有代码,并且会抛出一个EOFException异常。抛出异常的原因是已经到留的结尾了还在读数据。这里可以使用available()做判断还有多少可以的字符。

 1package com.herp.pattern.strategy; 2 3import java.io.ByteArrayInputStream; 4import java.io.DataInputStream; 5import java.io.IOException; 6 7public class FormattedMemoryInput { 8    public static void main(String[] args) throws IOException { 9        DataInputStream dataIns = new DataInputStream(10                new ByteArrayInputStream(BufferedInutFile.readFile("FormattedMemoryInput.java").getBytes()));11        while (true){12            System.out.println((char) dataIns.readByte());13        }14    }15}

基本的文件输出

FileWriter对象可以向文件写入数据。首先创建一个FileWriter和指定的文件关联,然后使用BufferedWriter将其包装提供缓冲功能,为了提供格式化机制,它又被装饰成为PrintWriter

 1public class BasicFileOutput { 2    static String file = "BasicFileOutput.out"; 3 4    public static void main(String[] args) throws IOException { 5        BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput.java"))); 6        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file))); 7 8        int lineCount = 1; 9        String s;10        while ((s = in.readLine()) != null){11            out.println(lineCount ++ + ": " + s);12        }13        out.close();14        in.close();15    }16}

下面是我们写出的BasicFileOutput.out文件,可以看到我们通过代码字节加上了行号

 11: package com.herp.pattern.strategy; 22:  33: import java.io.*; 44:  55: public class BasicFileOutput { 66:     static String file = "BasicFileOutput.out"; 77:  88:     public static void main(String[] args) throws IOException { 99:         BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput")));1010:         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));1111: 1212:         int lineCount = 1;1313:         String s;1414:         while ((s = in.readLine()) != null){1515:             out.println(lineCount ++ + ": " + s);1616:         }1717:         out.close();1818:         in.close();1919:     }2020: }

数据的存储和恢复

为了输出可供另一个“流”恢复的数据,我们需要使用DataOutputStream写入数据,然后使用DataInputStream恢复数据。当然这些流可以是任何形式(这里的形式其实就是我们前面说过的流的两端的类型),比如文件。

 1public class StoringAndRecoveringData { 2    public static void main(String[] args) throws IOException { 3        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt"))); 4        out.writeDouble(3.1415926); 5        out.writeUTF("我是二营长"); 6        out.writeInt(125); 7        out.writeUTF("点赞加关注"); 8        out.close(); 910        DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));11        System.out.println(in.readDouble());12        System.out.println(in.readUTF());13        System.out.println(in.readInt());14        System.out.println(in.readUTF());15        in.close();16    }17}

输出结果:

13.14159262我是二营长31254点赞加关注

需要注意的是我们使用writeUTF()和readUTF()来写入和读取字符串。

好了。关于Java I/O流体系就总结这么多吧。


我是二营长,一个转行的程序员,菜鸡一枚,热衷于码砖。

原文地址:https://www.cnblogs.com/happyone/p/12663145.html

时间: 2024-10-14 03:28:51

面试必备:详解Java I/O流,掌握这些就可以说精通了?的相关文章

Protocol Buffer技术详解(Java实例)

Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发团队中目前主要使用的开发语言就是C++.Java和Python,其中Python主要用于编写各种工具程序.然而为了保证该篇Blog的完整性和独立性,我仍然会将上一篇Blog中已经出现的内容再一次赘述,同时对于Java中特有的部分也会着重介绍.          一.生成目标语言代码.      下面

详解Java解析XML的四种方法

(1)DOM解析 DOM是html和xml的应用程序接口(API),以层次结构(类似于树型)来组织节点和信息片段,映射XML文档的结构,允许获取 和操作文档的任意部分,是W3C的官方标准 [优点] ①允许应用程序对数据和结构做出更改. ②访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据. [缺点] ①通常需要加载整个XML文档来构造层次结构,消耗资源大. [解析详解] ①构建Document对象: DocumentBuilderFactory dbf = DocumentBu

Java学习之道:详解Java解析XML的四种方法

XML现在已经成为一种通用的数据交换格式,它的平台无关性,语言无关性,系统无关性,给数据集成与交互带来了极大的方便.对于XML本身的语法知识与技术细节,需要阅读相关的技术文献,这里面包括的内容有DOM(Document Object Model),DTD(Document Type Definition),SAX(Simple API for XML),XSD(Xml Schema Definition),XSLT(Extensible Stylesheet Language Transform

【Java】详解Java解析XML的四种方法

XML现在已经成为一种通用的数据交换格式,平台的无关性使得很多场合都需要用到XML.本文将详细介绍用Java解析XML的四种方法. AD: XML现在已经成为一种通用的数据交换格式,它的平台无关性,语言无关性,系统无关性,给数据集成与交互带来了极大的方便.对于XML本身的语法知识与技术细节,需要阅读相关的技术文献,这里面包括的内容有DOM(Document Object Model),DTD(Document Type Definition),SAX(Simple API for XML),XS

详解java动态代理

生活中的代理: 比如一个明星成名了以后,是需要有一个代理的,因为太多人想找他签名,应付不来,那么这个时候代理的作用是拦截你对真正明星的访问,他可以拦截下来收点费用,再叫真正的明星过来为你签名. 程序中的代理: 1,要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理.日志.计算方法的运行时间.事务管理.等等,你准备如何做? 2,编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码 下图显示了在程序中代理的调用原理(

详解Java中代码块和继承

本文发表于个人GitHub主页,原文请移步详解Java中代码块和继承 阅读. 概念 1.代码块 局部代码块 用于限定变量生命周期,及早释放,提高内存利用率 静态代码块 对类的数据进行初始化,仅仅只执行一次. 构造代码块 把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造代码块. 2.继承 继承是已有的类中派生出新的类,新的类能够吸收已有类的数据属性和行为,并能扩展新的功能. 代码块的执行顺序 public class Test {    public String name

详解java垃圾回收机制(转)及finalize方法(转)

详细介绍Java垃圾回收机制 垃圾收集GC(Garbage Collection)是Java语言的核心技术之一,之前我们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机制并未改变.垃圾收集的目的在于清除不再使用的对象.GC通过确定对象是否被活动对象引用来确定是否收集该对象.GC首先要判断该对象是否是时候可以收集.两种常用的方法是引用计数和对象引用遍历. 引用计数收集器 引用计数是垃圾收集器中的早期策略.在这种方法中,堆中每个对象(不是

【转帖】windows命令行中java和javac、javap使用详解(java编译命令)

windows命令行中java和javac.javap使用详解(java编译命令) 更新时间:2014年03月23日 11:53:15   作者:    我要评论 http://www.jb51.net/article/48380.htm 学习一下java 最近重新复习了一下java基础,这里便讲讲对于一个类文件如何编译.运行.反编译的.也让自己加深一下印象 如题,首先我们在桌面,开始->运行->键入cmd 回车,进入windows命令行.进入如图所示的画面: 可知,当前默认目录为C盘User

POI使用详解 java 复杂excel导出

Apache POI使用详解 1.POI结构与常用类 (1)POI介绍 Apache POI是Apache软件基金会的开源项目,POI提供API给Java程序对Microsoft Office格式档案读和写的功能. .NET的开发人员则可以利用NPOI (POI for .NET) 来存取 Microsoft Office文档的功能. (2)POI结构说明 包名称说明 HSSF提供读写Microsoft Excel XLS格式档案的功能. XSSF提供读写Microsoft Excel OOXM