欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java I/O Streams

程序员文章站 2024-03-04 15:10:29
...

Byte Streams

程序使用字节流去输入和输出字节(8-bit),所有的字节流都继承自 InputStream 和 OutputStream。

java平台为我们定义了许多字节流,我们聚焦于文件字节流,这样我们可以更好的演示,其他的字节流使用起来没什么区别除了构造函数的不同。

我们一段案例来开始探索字节流吧。

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

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

        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("xanadu.txt");
            out = new FileOutputStream("outagain.txt");
            int c;

            while ((c = in.read()) != -1) {
                out.write(c);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

但是这个CopyBytes程序是低效率的,因为我们会话很多的时间重复这个轮训(从输入流中读取一个字节,从输出流中写入一个字节),下面我们展示一下这段代码的工作流程:

java I/O Streams

在这里要提醒一点的时我们要记得在我们不在需要一个流的话一定要关掉它,以保证不会浪费资源。

我们在finally中进行,这样保证及时出错我们也可以安全的关掉这个流。

CopyBytes看起来像一个正常的程序,但它实际上代表了一种您应该避免的低级别I / O。由于xanadu.txt包含字符数据,因此最好的方法是使用字符流。当然还有更为复杂的数据类型的流。字节流我们一般只用于最原始的I/O操作。

Character Streams

java平台使用Unicode 编码来存储字符。字符流I / O自动将此内部格式转换为本地字符集或从本地字符集转换。

字符流和字节流一样简单,我们没有必要把精力放在如何解析为本地字符集,如果确实需要全球化的话,我们可以参见全球化模块。

所有的字符流类都继承自Reader 和 Writer,当然我们还是以一个特定的输入输出为例,我们选择使用 file I/O: FileReader and FileWriter. 

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

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

        FileReader inputStream = null;
        FileWriter outputStream = null;

        try {
            inputStream = new FileReader("xanadu.txt");
            outputStream = new FileWriter("characteroutput.txt");

            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}
值得一次的是我们都是使用int类型的变量进行接受的,字节流和字符流有什么不同吗?当然了,在使用字符流时,int变量在最后16位保存一个字符值 ; 在字节流中,int变量在其最后8位保存一个字节值。

我们更可以将字符流看做是一个字节流的代理,字符流底层使用字节流进行I/O操作,但是加了从字符转字节的操作, InputStreamReader 和OutputStreamWriter是字节流和字符流之间转化的桥梁。

面向行的I/O

通常字符不是单个出现的,当时有一个共同处就是行,可能是"\r\n",也有可能是"\r",还有可能是"\n"操作系统不同其行结束符也不同。

然我们修改一下上面的案例转而去使用line-oriented I/O,为了实现这一点我们就得使用 BufferedReader 和 PrintWriter.这两个类了 ,我们将在后面对Buffered I/O 和Formatting进行深入讨论,但是现在我们只聚焦于他们所支持的行I/O 。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;

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

        BufferedReader inputStream = null;
        PrintWriter outputStream = null;

        try {
            inputStream = new BufferedReader(new FileReader("xanadu.txt"));
            outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));

            String l;
            while ((l = inputStream.readLine()) != null) {
                outputStream.println(l);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

Buffered Streams

到目前为止的几个案例都是使用的无缓冲的I/O,这就意味着我们每次进行读写请求都是直接通过OS的,这样是低效的,应为每次请求都是触发磁盘操作,网络,或者其他的一些操作。为了减少这些开销,java平台实现了 buffered I/O流,Buffered input streams 从内存读取,这本地输入API只会当buffer为空时被调用,buffered output streams通过buffer进行数据的写入,本地输出API只有当buffer满时才进行调用。

一个无缓冲的流可以转换为有缓冲的通过使用代理进行转化。

例如:

inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

这有四个类用来进行无缓冲的代理:

BufferedInputStream and BufferedOutputStream //create buffered byte streams 
BufferedReader and BufferedWriter //create buffered character streams

有些时候我们可能会要求在缓冲区满之前进行数据的写入,我们称这种操作为Flushing Buffered Streams。

有些缓冲输出类可以支持自动刷新,通过操作构造函数,但自动刷新被允许的话,某些事件将会触发刷新操作,例如一个允许刷新的PrintWriter对象将触发刷新,当调用println或format。

当然我们也可以进行手动调用flush()方法,他可以在任意输出流中调用,但是如果不是buffer stream的话将是无效的。

Scanning and Formatting

I/O编程有时候需要转化为人类喜欢的格式,java平台提供了两个类来帮助我们去完成该操作 Scanner和Formatting.

Scanning

Scanner类型的对象可用于将格式化的输入分解为tokens ,并根据其数据类型转换单个tokens 

默认情况下scanner使用空白符进行拆分,(空白符包括空格,制表符和行终止符)

下面是一个小的案例:

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

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

        Scanner s = null;

        try {
            s = new Scanner(new BufferedReader(new FileReader("xanadu.txt")));

            while (s.hasNext()) {
                System.out.println(s.next());
            }
        } finally {
            if (s != null) {
                s.close();
            }
        }
    }
}

这里需要提到的是我们在这里调用了close() ,尽管scanner不是流,它以表明您已完成其基础流。

当你想要使用不同的分割符的话,那么应该调用scanner的useDelimiter() 方法并传递正则表达式。

Formatting

流对象如果是实现了formatting的实例的话如:PrintWriter(一个字符流类),PrintStream(一个字节流类)

值得一提的是你应该使用PrintWriter进行格式化输出而不是PrintStream除了(System.out和System.err);

像所有的字节和字符流对象一样,PrintStream和PrintWriter的实例为简单的字节和字符输出实现了一套标准的写入方法。另外,PrintStream和PrintWriter都实现了将内部数据转换为格式化输出的同一套方法。提供了两个级别的格式:

(1)基本的( print and println);

(2)格式化的(format);

The print and println Methods

public class Root {
    public static void main(String[] args) {
        int i = 2;
        double r = Math.sqrt(i);
        
        System.out.print("The square root of ");
        System.out.print(i);
        System.out.print(" is ");
        System.out.print(r);
        System.out.println(".");

        i = 5;
        r = Math.sqrt(i);
        System.out.println("The square root of " + i + " is " + r + ".");
    }
}

The format Method

public class Root2 {
    public static void main(String[] args) {
        int i = 2;
        double r = Math.sqrt(i);
        
        System.out.format("The square root of %d is %f.%n", i, r);
    }
}

I/O from the Command Line

Data Streams

数据流支持字节I/O基本数据类型(boolean, char, byte, short, int, long, float, double 和 String)所有的实现都是依赖于 DataInput 或DataOutput接口,但是我们现在只讨论最广泛使用的实现类: DataInputStream and DataOutputStream.

	static final String dataFile = "invoicedata";

	static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
	//尽管价格我们不应该使用double应为他就像十进制无法表示三分之一一样无法表示0.1,但是我们为了测试基本数据类型的I/O所以此处才用的
	static final int[] units = { 12, 8, 13, 29, 50 };
	static final String[] descs = {
	    "Java T-shirt",
	    "Java Mug",
	    "Duke Juggling Dolls",
	    "Java Pin",
	    "Java Key Chain"
	};
	public static void main(String[] args) throws Exception{
		out();
		in();
	}
	/*
	 * 值得注意的是DataStreams 使用EOFException来判断是否读到了文件末尾
	 * 所有实现DataInput 的方法都是使用EOFException来判断结尾的而不是返回值
	 * 
	 */
	public static void in() throws FileNotFoundException, IOException {
		DataInputStream in = new DataInputStream(new
	            BufferedInputStream(new FileInputStream(dataFile)));

		double price;
		int unit;
		String desc;
		double total = 0.0;
		try {
		    while (true) {
		        price = in.readDouble();
		        unit = in.readInt();
		        desc = in.readUTF();
		        System.out.format("You ordered %d" + " units of %s at $%.2f%n",
		            unit, desc, price);
		        total += unit * price;
		    }
		} catch (EOFException e) {
			System.out.println("读到文件结尾了");
		}
	}
	
	public static void out() throws FileNotFoundException, IOException {
		DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
	              new FileOutputStream(dataFile)));
		for (int i = 0; i < prices.length; i ++) {
		    out.writeDouble(prices[i]);
		    out.writeInt(units[i]);
		    out.writeUTF(descs[i]);
		}
		out.close();
	}

Object Streams

就像data stream 支持基本数据类型的I/O操作,object Stream用来支持对象的I/O操作,但是可i/o的的对象是有限制的,这个对象必须支持serialization ,他们通常会实现Serializable接口;

对象流的类是 ObjectInputStream 和 ObjectOutputStream,这些类实现了ObjectInput 和 ObjectOutput接口(是DataInput和DataOutput的子接口),这也就意味着所有基本数据类型的I/O操作都在对象流中实现了,所以对象流中可以既包含基本数据类型,也可以包含引用数据类型。

import java.io.*;
import java.math.BigDecimal;
import java.util.Calendar;

public class ObjectStreams {
    static final String dataFile = "invoicedata";

    static final BigDecimal[] prices = { 
        new BigDecimal("19.99"), 
        new BigDecimal("9.99"),
        new BigDecimal("15.99"),
        new BigDecimal("3.99"),
        new BigDecimal("4.99") };
    static final int[] units = { 12, 8, 13, 29, 50 };
    static final String[] descs = { "Java T-shirt",
            "Java Mug",
            "Duke Juggling Dolls",
            "Java Pin",
            "Java Key Chain" };

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

 
        ObjectOutputStream out = null;
        try {
            out = new ObjectOutputStream(new
                    BufferedOutputStream(new FileOutputStream(dataFile)));

            out.writeObject(Calendar.getInstance());
            for (int i = 0; i < prices.length; i ++) {
                out.writeObject(prices[i]);
                out.writeInt(units[i]);
                out.writeUTF(descs[i]);
            }
        } finally {
            out.close();
        }

        ObjectInputStream in = null;
        try {
            in = new ObjectInputStream(new
                    BufferedInputStream(new FileInputStream(dataFile)));

            Calendar date = null;
            BigDecimal price;
            int unit;
            String desc;
            BigDecimal total = new BigDecimal(0);

            date = (Calendar) in.readObject();

            System.out.format ("On %tA, %<tB %<te, %<tY:%n", date);

            try {
                while (true) {
                    price = (BigDecimal) in.readObject();
                    unit = in.readInt();
                    desc = in.readUTF();
                    System.out.format("You ordered %d units of %s at $%.2f%n",
                            unit, desc, price);
                    total = total.add(price.multiply(new BigDecimal(unit)));
                }
            } catch (EOFException e) {}
            System.out.format("For a TOTAL of: $%.2f%n", total);
        } finally {
            in.close();
        }
    }
}

Output and Input of Complex(复杂)Objects

writeObject 和 readObject中的方法看起来都很简单,但是他们包含了 复杂对象的管理逻辑,就显然对 Calendar这类只包含基本数据类型的对象没有什么作用,他作用于对象中包含另一个对象的引用的这类对象。在这种情况下,writeObject遍历整个对象引用的网,并将该网中的所有对象写入流中。因此,writeObject的单个调用可以导致大量的对象被写入到流中。

下面画图说明一下,a对象包含(b引用(包含d和e)和c引用)

java I/O Streams

你可能会发生疑惑,当两个引用 引用同一个对象时,通过同一个流进行写入操作,当他们被读回时是否还是单个对象呢?答案是肯定的,一个流只能包含同一个对象的一份拷贝,尽管你进行两次写入,其实你真正写的仅是引用两次。

但是如果使用两个不同的流,他们将是复制操作,也就是说一个读取两个流的单个程序将看到两个不同的对象。

public static void main(String[] args) throws Exception{
		
		People people = new People("weijinhao",24);
		People people2 = people;
		ObjectOutput  objectOutputStream = new ObjectOutputStream(
				new BufferedOutputStream(
						new FileOutputStream("obj")));
		objectOutputStream.writeObject(people);
		objectOutputStream.writeObject(people2);
		objectOutputStream.close();
		//进行读取操做
		ObjectInput input = new ObjectInputStream(
				new BufferedInputStream(
						new FileInputStream("obj")));
		People x = (People)input.readObject(); //执行几次写,对应的就得执行几次读
		People z = (People)input.readObject();
		System.out.println("x: " + x + "z: " + z);
		x.setAge(100);;
		System.out.println("x: " + x + "z: " + z);
		input.close();
		
	}

打印如下:

x: People [name=weijinhao, age=24]z: People [name=weijinhao, age=24]
x: People [name=weijinhao, age=100]z: People [name=weijinhao, age=100]