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

本文围绕Java IO流体系展开,介绍了基于InputStream和OutputStream的字节流,以及Reader和Writer的字符流。阐述了IO类库总体设计,包括流的流向、操作类型等。还讲解了File类的常用方法,以及装饰器模式在IO流中的应用。此外,涉及对象序列化、解压缩zip包和IO流典型用法等内容。

Java IO概述

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

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

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

a992fcc16d1aa9144f025fc5ffa25ca4.png在这里插入图片描述

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

ab8d249c7b9670f0aaf95f213ccafe9e.png在这里插入图片描述

其实除了面向字节流的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流的时候会有下面类似代码:

new FileInputStream(new File("a.txt"));

new BufferedInputStream(inputStream);

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

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

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

InputStream流体系

9281e8009d7bbb77289be46f9dcaeddd.png在这里插入图片描述

OutputStream流体系

fb326c5cb947111592d4e3abffa32053.png在这里插入图片描述

Reader体系

4209a3f57479784bbe7f6a1bf9215b55.png在这里插入图片描述

Writer体系

6b42270614ef7aa912852cfdb30af666.png在这里插入图片描述

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

4bf25db62555d1c8b0f428627f4da725.png在这里插入图片描述

File其实是个工具类

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

String   getAbsolutePath()   // 获取绝对路径

long   getFreeSpace()       // 返回分区中未分配的字节数。

getName()         // 返回文件或文件夹的名称。

getParent()         // 返回父目录的路径名字符串;如果没有指定父目录,则返回 null。

getParentFile()      // 返回父目录File对象

getPath()         // 返回路径名字符串。

long   getTotalSpace()      // 返回此文件分区大小。

long   getUsableSpace()    //返回占用字节数。

int   hashCode()             //文件哈希码。

long   lastModified()       // 返回文件最后一次被修改的时间。

long   length()          // 获取长度,字节数。

boolean canRead()  //判断是否可读

boolean canWrite()  //判断是否可写

boolean isHidden()  //判断是否隐藏

// 成员函数

static File[]    listRoots()    // 列出可用的文件系统根。

boolean    renameTo(File dest)    // 重命名

boolean    setExecutable(boolean executable)    // 设置执行权限。

boolean    setExecutable(boolean executable, boolean ownerOnly)    // 设置其他所有用户的执行权限。

boolean    setLastModified(long time)       // 设置最后一次修改时间。

boolean    setReadable(boolean readable)    // 设置读权限。

boolean    setReadable(boolean readable, boolean ownerOnly)    // 设置其他所有用户的读权限。

boolean    setWritable(boolean writable)    // 设置写权限。

boolean    setWritable(boolean writable, boolean ownerOnly)    // 设置所有用户的写权限。

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

下面是一些示例:

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

public class Directory{

public static File[] getLocalFiles(File dir, final String regex){

return dir.listFiles(new FilenameFilter() {

private Pattern pattern = Pattern.compile(regex);

public boolean accept(File dir, String name){

return pattern.matcher(new File(name).getName()).matches();

// 重载方法

public static File[] getLocalFiles(String path, final String regex){

return getLocalFiles(new File(path),regex);

public static void main(String[] args){

"d:";

".*\\.txt");

for(File file : files){

输出结果:

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

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

InputStream和OutputStream

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

public abstract class InputStream  extends Object  implements Closeable

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

//从数据中读入一个字节,并返回该字节,遇到流的结尾时返回-1

abstract int read()

//读入一个字节数组,并返回实际读入的字节数,最多读入b.length个字节,遇到流结尾时返回-1

int read(byte[] b)

// 读入一个字节数组,返回实际读入的字节数或者在碰到结尾时返回-1.

//b:代表数据读入的数组, off:代表第一个读入的字节应该被放置的位置在b中的偏移量,len:读入字节的最大数量

int read(byte[],int off,int len)

// 返回当前可以读入的字节数量,如果是从网络连接中读入,这个方法要慎用,

int available()

//在输入流中跳过n个字节,返回实际跳过的字节数

long skip(long n)

//标记输入流中当前的位置

void mark(int readlimit)

//判断流是否支持打标记,支持返回true

boolean markSupported()

// 返回最后一个标记,随后对read的调用将重新读入这些字节。

void reset()

//关闭输入流,这个很重要,流使用完一定要关闭

void close()

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

功能

ByteArrayInputStream

将字节数组作为InputStream

StringBufferInputStream

将String转成InputStream

FileInputStream

从文件中读取内容

PipedInputStream

产生用于写入相关PipedOutputStream的数据。实现管道化

SequenceInputStream

将两个或多个InputStream对象转换成单一的InputStream

FilterInputStream

抽象类,主要是作为“装饰器”的接口类,实现其他的功能流

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

public abstract class OutputStream implements Closeable, Flushable{}

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

// 写出一个字节的数据

abstract void write(int n)

// 写出字节到数据b

void write(byte[] b)

// 写出字节到数组b,off:代表第一个写出字节在b中的偏移量,len:写出字节的最大数量

void write(byte[] b, int off, int len)

//冲刷输出流,也就是将所有缓冲的数据发送到目的地

void flush()

// 关闭输出流

void close()

同样地,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的基类。Reader与InputStream类似,不同点在于,Reader基于字符而非基于字节。

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

Reader和Writer的基础功能类,可以对比InputStream、OutputStream来学习。

面向字节

面向字符

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。

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

d8a241a73083c7963264ccdf4a193390.png在这里插入图片描述

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

BufferedReader

BufferedWriter

PrintWriter

LineNumberReader

PushbackReader

System类中的I/O流

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

"hello world")

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

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

先说System.in,看下源码

public final static InputStream in

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

while ((s = reader.readLine()) != null && s.length() != 0){

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

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)进行重定向。比如可以将控制台的输出重定向到文件中。

new FileOutputStream("d:/system.out.txt");

new PrintStream(output);

压缩(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类型:

public CheckedInputStream(InputStream in, Checksum cksum){

super(in);

this.cksum = cksum;

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

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

将多个文件压缩成zip包

public class ZipFileUtils{

public static void compressFiles(File[] files, String zipPath) throws IOException{

// 定义文件输出流,表明是要压缩成zip文件的

new FileOutputStream(zipPath);

// 给输出流增加校验功能

new CheckedOutputStream(f,new Adler32());

// 定义zip格式的输出流,这里要明白一直在使用装饰器模式在给流添加功能

// ZipOutputStream 也是从FilterOutputStream 继承下来的

new ZipOutputStream(checkedOs);

// 增加缓冲功能,提高性能

new BufferedOutputStream(zipOut);

//对于压缩输出流我们可以设置个注释

"zip test");

// 下面就是从Files[] 数组中读入一批文件,然后写入zip包的过程

for (File file : files){

// 建立读取文件的缓冲流,同样是装饰器模式使用BufferedReader

// 包装了FileReader

new BufferedReader(new FileReader(file));

// 一个文件对象在zip流中用一个ZipEntry表示,使用putNextEntry添加到zip流中

new ZipEntry(file.getName()));

int c;

while ((c = bfReadr.read()) != -1){

// 注意这里要关闭

public static void main(String[] args) throws IOException{

"d:";

"d:/test.zip";

".*\\.txt");

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

解压缩zip包到目标文件夹

if(!destPath.endsWith(File.separator)){

if(!file.exists()){

while ((zipEntry = zipIn.getNextEntry()) != null){

"解压中" + zipEntry);

while ((size = buffIn.read(buffer, 0, buffer.length)) != -1) {

"校验和:" + checkedIns.getChecksum().getValue());

"d:";

"d:/test.zip";

".*\\.txt");

"F:/ziptest");

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

new ZipFile("test.zip");

while (e.hasMoreElements()){

"file:" + zipEntry);

对象序列化

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

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

d0c940fbb3e493895407c6b777e9308c.png在这里插入图片描述

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

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

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

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

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

public interface Serializable{

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

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

toString() {

return "User{" +

"name='" + name + '\'' +" + age + '\'' +二营长",18);f:/user.out"));f:/user.out"));

程序运行结果:

'二营长', age='18'}

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

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

private transient String password;

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

I/O流的典型使用方式

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

缓冲输入文件

public class BufferedInutFile{

public static String readFile(String fileName) throws IOException{

new BufferedReader(new FileReader(fileName));

// 这里读取的内容存在了StringBuilder,当然也可以做其他处理

new StringBuilder();

while ((s = bf.readLine()) != null){

"\n");

return sb.toString();

public static void main(String[] args) throws IOException{

"d:/1.txt"));

格式化内存输入

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

public class FormattedMemoryInput{

public static void main(String[] args) throws IOException{

try {

new DataInputStream(

new ByteArrayInputStream(BufferedInutFile.readFile("f:/FormattedMemoryInput.java").getBytes()));

while (true){

char) dataIns.readByte());

catch (EOFException e) {

"End of stream");

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

package com.herp.pattern.strategy;

import java.io.ByteArrayInputStream;

import java.io.DataInputStream;

import java.io.IOException;

public class FormattedMemoryInput{

public static void main(String[] args) throws IOException{

new DataInputStream(

new ByteArrayInputStream(BufferedInutFile.readFile("FormattedMemoryInput.java").getBytes()));

while (true){

char) dataIns.readByte());

基本的文件输出

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

public class BasicFileOutput{

static String file = "BasicFileOutput.out";

public static void main(String[] args) throws IOException{

new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput.java")));

new PrintWriter(new BufferedWriter(new FileWriter(file)));

int lineCount = 1;

while ((s = in.readLine()) != null){

": " + s);

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

1: package com.herp.pattern.strategy;

2:

3: import java.io.*;

4:

5: public class BasicFileOutput{

6:     static String file = "BasicFileOutput.out";

7:

8:     public static void main(String[] args) throws IOException{

9:         BufferedReader in = new BufferedReader(new StringReader(BufferedInutFile.readFile("f:/BasicFileOutput")));

10:         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));

11:

12:         int lineCount = 1;

13:         String s;

14:         while ((s = in.readLine()) != null){

15:             out.println(lineCount ++ + ": " + s);

16:         }

17:         out.close();

18:         in.close();

19:     }

20: }

数据的存储和恢复

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

public class StoringAndRecoveringData{

public static void main(String[] args) throws IOException{

new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));

3.1415926);

"我是二营长");

125);

"点赞加关注");

new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));

输出结果:

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

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

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

C语言-光伏MPPT算法:电导增量法扰动观察法+自动全局搜索Plecs最大功率跟踪算法仿真内容概要:本文档主要介绍了一种基于C语言实现的光伏最大功率点跟踪(MPPT)算法,结合电导增量法与扰动观察法,并引入自动全局搜索策略,利用Plecs仿真工具对算法进行建模与仿真验证。文档重点阐述了两种经典MPPT算法的原理、优缺点及其在不同光照和温度条件下的动态响应特性,同时提出一种改进的复合控制策略以提升系统在复杂环境下的跟踪精度与稳定性。通过仿真结果对比分析,验证了所提方法在快速性和准确性方面的优势,适用于光伏发电系统的高效能量转换控制。; 适合人群:具备一定C语言编程基础和电力电子知识背景,从事光伏系统开发、嵌入式控制或新能源技术研发的工程师及高校研究人员;工作年限1-3年的初级至中级研发人员尤为适合。; 使用场景及目标:①掌握电导增量法与扰动观察法在实际光伏系统中的实现机制与切换逻辑;②学习如何在Plecs中搭建MPPT控制系统仿真模型;③实现自动全局搜索以避免传统算法陷入局部峰值问题,提升复杂工况下的最大功率追踪效率;④为光伏逆变器或太阳能充电控制器的算法开发提供技术参考与实现范例。; 阅读建议:建议读者结合文中提供的C语言算法逻辑与Plecs仿真模型同步学习,重点关注算法判断条件、步长调节策略及仿真参数设置。在理解基本原理的基础上,可通过修改光照强度、温度变化曲线等外部扰动因素,进一步测试算法鲁棒性,并尝试将其移植到实际嵌入式平台进行实验验证。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值