Java--输入/输出

InputStream与OutputStream

InputStream与OutputStream初探

InputStreamOutputStream可以将一切输入来源与输出目的地进行抽象化,就算不知道具体的输入来源和输出目的地,我们也可以进行数据的读取与写入,这就是这两个API的主要功能。

代码实例:

import java.io.*;

public class IO{
    public static void dump(InputStream src, OutputStream dest) throws IOException{       //抽象化数据输入来源与目的地
        try(InputStream input = src, OutputStream output = dest){        //尝试自动关闭资源
            byte[] data = new byte[1024];
            int length;

            while((length = input.read(data)) != -1){
                output.write(data, 0, length);
            }   
        }
    }
}

我们可以看到,在这个实例中,我们事先并不知道输入来源是什么,有可能是文档,有可能是数据库,还有可能是网络输入输出。但是就算我们不知道要读取的内容是什么,并且不知道要将数据以怎样的格式存储起来,我们还是将输入与输出直接抽象化,这样不管是什么样的数据我们都可以读取,存储。

如果我们要将某个文档读入并存入另一个文档之中,那么我们可以这样使用:

import java.io.*;

public class Copy {
    public static void main(String[] args) throws IOException{
        IO.dump(
                new FileInputStream(args[0]),          //文件输入流
                new FileOutputStream(args[1])          //文件输出流
        );
    }
}

这个程序可以由命令行自变量指定读取的文档来源与输出目的地:
这里写代码片QkFCMA==/dissolve/70/gravity/SouthEast” alt=”这里写图片描述” title=”” />

结果:
这里写图片描述

我们可以看到,已经将creattree中的内容写入到了1.txt中了。

我们在后面将会介绍到FileInputStream是InputSream的子类,用于衔接文档以读入数据;FileOutputStream是OutputStream的子类,用于衔接文档以写出数据。

我们还可以从HTTP服务器读取某个网页,并另存为文档,代码如下:

import java.io.*;
import java.net.URL;

public class Download {
    public static void main(String[] args) throws IOException{
        URL url = new URL(args[0]);

        InputStream src = url.openStream();         //数据来源
        OutputStream dest = new FileOutputStream(args[1]);     //数据目的地
        IO.dump(src, dest);
    }
}

对于这个代码不做过多的解释。

我们可以看到,无论来源或目的地的实体形式如何,只要想办法取得InputStream和OutputStream,那么接下来都是调用这两个的相关方法。


串流继承架构

前面说过,要了解一个API,活用一个API,我们必须了解他的继承架构,这样我们才会知道在什么情况下改用哪些API。

这里写图片描述

这里写图片描述

上面的图片来自百度,我们可以清晰的看到InputStream的继承架构,我们来详细看看每个子类都有什么含义。


标准输入/输出
      System.in与System.out分别是InputStream和PrintStream的实例,分别代表标准输入和标准输出,对于个人计算机而言,通常对应文本模式中的输入与输出。因为我们在文本模式下一般是取得整行用户输入,因此较少直接操作InputStream,而是用Scanner打包System.in。
     我们可以使用setIn的方法指定InputStream实例,重新指定标准输入来源,下面我们将标准输入来源指定为FileInputStream,可以读取指定文档并显示在文本模式之中。
import java.io.*;
import java.util.*;

public class StandardIn {
    public static void main(String[] args) throws IOException{
        System.setIn(new FileInputStream(args[0]));      //改变标准输入来源

        try(Scanner scanner = new Scanner(System.in)){   //已经将文档输入变为标准输入
            while(scanner.hasNextLine()){
                System.out.println(scanner.nextLine());
            }
        }
    }
}

标准输出也可以重新导至文档,在执行程序的时候用>就可以,和Linux下的重定向还有点相似。

可以使用System的setOut()方法指定PrintStream实例,将结果输出至指定的目的地。

import java.io.*;

public class StandardOut {
    public static void main(String[] args) throws IOException{
        try(PrintStream printStream = new PrintStream(        //指定输出流
                new FileOutputStream(args[0]))){

            System.setOut(printStream);                       //改变标准输出流
            System.out.println("HelloWorld");
        }
    }
}

除了前两个之外,还有一个System.err为Printstream实例,成为标准错误输出串流,它是用来立即显示错误信息的。例如在文本模式之下,System.out输出的信息可以使用>或>>重新导向至文档,但System.err的输出信息一定会显示在文本模式之中,无法进行重新导向,也可以使用System.setErr()指定Printstream,重新指定标准的错误输出串流。


FileInputStream与FileOutputStream

FileInputStream与FileOutputstream分别是InputStream和OutputStream的子类,可以指定文件名创建实例,一旦创建文档就开启,然后就可以进行数据的读写,这两个类在不使用的时候都要使用close()关闭文档。

FileInputStream主要操作了InputStream的read方法,FileOutputstream主要操作了OutputStream的write方法。

FileInputstream和FileOutputstream在进行文件的读写时,都是以字节为单位的,通常会使用高阶类进行打包,进行一些高阶操作,之后我们会介绍打包器类。


ByteArrayInputStream与ByteArrayOutputStream

ByteArrayInputStream与ByteArrayOutputStream可以指定byte数组创建实例,一旦创建就可以将byte数组当做数据源/目的地进行读写。

ByteArrayInputStream与ByteArrayOutputStream分别主要操作了InputStream和OutputStream的read方法和write方法。


串流处理装饰器(打包器)

InputStream和OutputStream提供最基本的串流操作,如果想要对输入/输出的数据进行加工处理,则可以使用打包器类也称装饰器,例如Scanner类就是打包器,它会操作打包的InputStream取得数据,并转换为你想要的数据类型。

PrintStream也是打包器类,他会自动转换为byte字节数组,利用打包的OutputStream进行输出。

常用的打包器类有具有缓冲区作用的BufferedInputStream和BufferedOutputStream,具备数据转换处理作用的DataInputStream和DataOutputStream,具备对象串行化能力的ObjectInputStream和ObjectOutputStream等。


BufferedInputStream与BufferedOutputStream

我们以文档存取为例,如果是FileInputStream和FileOutputStream实例,那么每次read()时都会要求读取硬盘,每次write时都会要求写入硬盘,这就会花费很多时间在硬盘的定位上,如果我们能够一次性读取足够多的数据至缓冲区,然后我们每次read都在缓冲区中取,那么我们就可以减少从硬盘读取的次数,对读取效率将会有帮助,如果我们可以在写入的时候都先将数据写入内存的缓冲区中,缓冲区满了在将缓冲区中的数据写入目的地,那么我们就可以减少对目的地的写入次数,对写入效率也会有一些帮助。我们可以使用默认或者自定义的缓冲区大小。具体实现如下:

import java.io.*;

public class BufferedIO {
    public static void dump(InputStream src, OutputStream dest) throws IOException{
        try(InputStream input = new BufferedInputStream(src);
        OutputStream output = new BufferedOutputStream(dest)){
            byte[] data = new byte[1024];
            int length;

            while((length = input.read(data)) != -1){
                output.write(data, 0, length);
            }
        }
    }
}

从上面的代码我们可以看到,创建BufferedInputStream和BufferedOutputStream实例必须将InputStream和OutputStream进行打包。


DataInputStream与DataOutputStream

这两个类提供读取写入Java基本数据类型的方法,像是读写int,double,boolean等方法。这些方法会自动的在指定的类型与字节间进行转换,不用你亲自做字节与类型的准换。来看一个实际的例子。下面的Member类可以调用save()存储Member实例本身的数据,文件名为Member的会员号码,调用Member.load()指定会员号码,则可以读取文档中的会员数据,封装为Member实例并返回。

import java.io.*;

public class Member {
    private String number;
    private String name;
    private int age;

    public Member(String number, String name, int age){         //构造函数
        this.number = number;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString(){
        return String.format("(%s %s %d)", number, name, age);
    }

    public void save() throws IOException{
        try(DataOutputStream output = new DataOutputStream(new FileOutputStream(number))){
            output.writeUTF(number);
            output.writeUTF(name);
            output.writeInt(age);
        }
    }

    public static Member load(String number) throws IOException{
        Member member;

        try(DataInputStream input = new DataInputStream(new FileInputStream(number))){
            member = new Member(input.readUTF(), input.readUTF(), input.readInt());
        }

        return member;
    }
}

下面是个使用Member类的例子:

import java.io.IOException;
import static java.lang.System.out;

public class MemberDemo {
    public static void main(String[] args) throws IOException{
        Member[] members = {
                new Member("B1234", "Justin", 90),
                new Member("B5678", "Monica", 95),
                new Member("B9876", "Irene", 88),
        };

        for(Member member : members){
            member.save();
        }

        out.println(Member.load("B1234"));
        out.println(Member.load("B5678"));
        out.println(Member.load("B9876"));
    }
}

ObjectInputStream与ObjectOutputStream

前面的实例是将Member的number,name,age数据进行存储,读回时也是先取得number,name,age数据再创建Member实例。实际上我们可以使用这两个类将内存中的对象整个存储下来,之后再还原为对象。

使用这两个类我们就可以使用readObject方法将数据读入为对象,而writeObject方法将对象写至目的地,可以被这两个方法处理的对象,必须操作java.io.Serializable接口,这个接口中没有任何方法,只是做标示用的,标示这个对象是可串行化的。

来看一下前一个范例的改写。

import java.io.*;

public class Member2 implements Serializable{          //操作Serializable
    private String number;
    private String name;
    private int age;

    public Member2(String number, String name, int age){
        this.number = number;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString(){
        return String.format("(%s %s %d)", number, name, age);
    }

    public void save() throws IOException{
        try(ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(number))){
            output.writeObject(this);
        }
    }

    public static Member2 load(String number) throws IOException, ClassNotFoundException{
        Member2 member;

        try(ObjectInputStream input = new ObjectInputStream(new FileInputStream(number))){
            member = (Member2) input.readObject();
        }

        return member;
    }
}

下面的代码用来测试上面的类是否正确:

import java.io.*;
import static java.lang.System.out;

public class Member2Demo {
    public static void main(String args[]) throws Exception{
        Member2[] member2s = {
                new Member2("B1234", "Justin", 90),
                new Member2("B5678", "Monica", 95),
                new Member2("B9876", "Irene", 88),
        };

        for(Member2 member2 : member2s){
            member2.save();
        }

        out.println(Member2.load("B1234"));
        out.println(Member2.load("B5678"));
        out.println(Member2.load("B9876"));
    }
}

字符处理类

我们之前介绍的,都是读入与写出字节数据,如果处理字符数据,InputStream与OutputStream就得对照编码表,在字符与字节之间进行转换,但是我们有专门的字符处理类,可以帮我们省去这项工作。

如果想从来源读取字符数据或将字符数据写至目的地,可以参考下面的代码:

import java.io.*;

public class CharUtil {
    public static void dump(Reader src, Writer dest) throws IOException{
        try(Reader input = src; Writer output = dest){
            char[] data = new char[1024];
            int length;

            while((length = input.read(data)) != -1){
                output.write(data, 0, length);
            }
        }
    }
}

Reader与Writer继承架构

这里写图片描述

这里写图片描述

从继承架构中我们也可以知道,FileReader是一种Reader子类,主要用于读取文档并将读到的数据转换为字符,StringWriter也是一种Writer,可以将字符数据写至StringWriter,最后使用toString方法将字符串取出。所以可以改写上面的代码:

import java.io.*;

public class CharUtilDemo {
    public static void main(String[] args) throws IOException{
        FileReader reader = new FileReader(args[0]);
        StringWriter writer = new StringWriter();

        CharUtil.dump(reader, writer);
        System.out.println(writer.toString());
    }
}

其他子类和之前所讲的基本上都是很类似的,在这里不再赘述。


字符处理装饰器(打包器)

Reader和Writer也有装饰器类,可以对他们的功能进行加强。


InputStreamReader与OutputStreamWriter

在指定InputStreamReader与OutputStreamWriter时,可以指定编码,如果没有指定编码,则以JVM启动时所获取的默认编码来做字符转换。下面将CharUtil的dump进行改写,使它可以指定编码。

import java.io.*;

public class CharUtil2 {
    public static void dump(Reader src, Writer dest) throws IOException{
        try(Reader input = src; Writer output = dest){
            char[] data = new char[1024];
            int length;

            while((length = input.read(data)) != -1){
                output.write(data, 0, length);
            }
        }
    }

    public static void dump(InputStream src, OutputStream dest, String charset) throws IOException{
        dump(
                new InputStreamReader(src, charset),
                new OutputStreamWriter(dest, charset)
        );
    }

    //默认编码
    public static void dump(InputStream src, OutputStream dest) throws IOException{
        dump(src, dest, System.getProperty("file.encoding"));
    }

}

BufferedReader与BufferedWriter

与之前的BufferedInputStream与BufferedOutputStream有着相同的功能,用法方面稍有不同。在这里不再赘述。


PrintWriter

与PrintStream极为相似,除了可以对OutputStream打包之外,还可以对Writer打包,提供输出方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值