Java Io原理及应用

java

概述

IO是编程中无法回避的问题,它往往会成为程序运行的性能瓶颈(JAVA在1.4后引入了NIO提高了IO性能),JAVA的IO流分类很丰富,IO包下含有大量的类和接口,从分类上看,主要有以下分类:

  1. 基于字节操作的 I/O 接口:InputStream 和 OutputStream
  2. 基于字符操作的 I/O 接口:Writer 和 Reader
  3. 基于磁盘操作的 I/O 接口:File
  4. 基于网络操作的 I/O 接口:Socket

在看具体的接口前,首先介绍一个设计模式:Decorator Pattern 装饰者模式,因为整个 JAVA IO 的核心就是采用了Decorator(装饰)模式。

使用JAVA IO的时候经常会看到以下写法:

InputStream in=new BufferedInputStream(new FileInputStream("filename"));

这几个类的关系如下:

public class BufferedInputStream extends FilterInputStream
public class FilterInputStream extends InputStream
public class FileInputStream extends InputStream

我模仿这几个类的关系写了一个装饰关系模型:

/**

* 程序员

* 具有写代码功能的抽象被装饰器。

* 对应Inputstream

*/

interface Coder {

void writeCode();

}

/**

* java程序员

* 具有写JAVA代码的具体被装饰器

* 对应FileInputStream

*/

class JavaCoder implements Coder {

@Override

public void writeCode() {

System.out.println("write java code!");

}

}

/**

* 有梦想的程序员(本身和JAVA程序员没有直接关系)

* 具备装饰器的抽象意义

* 对应FilterInputStream

*/

class HavaDreamCoder implements Coder {

Coder coder;

protected HavaDreamCoder(Coder coder) {

this.coder = coder;

}

@Override

public void writeCode() {

coder.writeCode();

}

}

/**

* 高级的JAVA程序员

* 具有具体功能 (设计代码) 的装饰器

* 对应BufferedInputStream

*/

class SeniorJavaCoder extends HavaDreamCoder {

SeniorJavaCoder(Coder coder) {

super(coder);

}

@Override

public void writeCode() {

design();

coder.writeCode();

}

public void design() {

System.out.println("design code!");

}

//额外方法--高级程序员会读优秀的源码

public void readCode() {

System.out.println("read nice code!");

}

}

使用方法如下:

 SeniorJavaCoder coder = new SeniorJavaCoder(new JavaCoder());
 coder.writeCode();

打印结果:

design code!
write java code!

高级JAVA程序员可以解决大部分问题,但是有一天我们发现一个非常复杂的难题,高级JAVA程序员已经解决不了了,但是我们又不忍心解雇高级JAVA程序员(因为她是一个PLMM),所以我们需要招聘一个大牛JAVA程序员(比如DataInputStream),这个大牛JAVA程序员比高级JAVA程序员写代码的方法又更进步了,而且他会写C/C++等代码..于是再次装饰。

基于字节的 I/O 操作接口

InputStream 相关类层次结构


OutputStream 相关类层次结构



基于字符的 I/O 操作接口

Reader 相关类层次结构

Writer 相关类层次结构

数据持久化或网络传输都是以字节进行的,所以必须要有字符到字节或字节到字符的转化。字符到字节需要转化,其中读的转化过程如下图所示:

字节与字符的转化接口

读入:

按字符方式读入文件我们可以这样写:

 FileReader  f=new FileReader("C:\\log.txt");

我们看看FileReader源码:

   public FileReader(String fileName) throws FileNotFoundException {

super(new FileInputStream(fileName));

}

从它的构造函数可以看出读文件实际上是读取文件流,只是在它的的父类:

  public InputStreamReader(InputStream in) {

super(in);

try {

sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object

} catch (UnsupportedEncodingException e) {

// The default encoding should always be available

throw new Error(e);

}

}

通过StreamDecoder将字节转化为字符。

写出:

 实例测试

 缓冲提高IO性能测试,并解释了一个使用上的误区:是不是用了带Buffer的类一定会比不带Buffer的快,看下面代码:

import java.io.*;

/**

* Created with IntelliJ IDEA.

* User: wangq

* Date: 13-3-28

* Time: 下午1:03

* To change this template use File | Settings | File Templates.

*/

public class TestIo {

/**

* 带缓冲的复制文件

*

* @param sourceFile

* @param targetFile

* @throws IOException

*/

public static void copyFileWithBuffer(File sourceFile, File targetFile) throws IOException {

BufferedInputStream inBuff = null;

BufferedOutputStream outBuff = null;

try {

// 新建文件输入流并对它进行缓冲

inBuff = new BufferedInputStream(new FileInputStream(sourceFile));

// 新建文件输出流并对它进行缓冲

outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));

// 缓冲数组

byte[] b = new byte[1024 * 5];

int len;

while ((len = inBuff.read(b)) != -1) {

outBuff.write(b, 0, len);

}

// 刷新此缓冲的输出流

outBuff.flush();

} finally {

// 关闭流

if (inBuff != null)

inBuff.close();

if (outBuff != null)

outBuff.close();

}

}

/**

* 无缓冲的文件复制

*

* @param sourceFile

* @param targetFile

* @throws IOException

*/

public static void copyFileWithOutBuffer(File sourceFile, File targetFile) throws IOException {

FileInputStream fin = null;

FileOutputStream fout = null;

try {

fin = new FileInputStream(sourceFile);

fout = new FileOutputStream(targetFile);

byte[] buff = new byte[1024 * 5];

int len;

while ((len = fin.read(buff)) != -1) {

fout.write(buff, 0, len);

}

} finally {

// 关闭流

if (fin != null)

fin.close();

if (fout != null)

fout.close();

}

}

public static void main(String[] args) {

File oldfile1 = new File("E:\\影视\\电影\\Lost in Thailand1.rmvb");//546mb

File oldfile2 = new File("E:\\影视\\电影\\Lost in Thailand2.rmvb");//546mb

File newFileNoBuff = new File("D:\\a.rmvb");

File newFileWithBuff = new File("D:\\b.rmvb");

long begtime = System.currentTimeMillis();

try {

copyFileWithOutBuffer(oldfile1, newFileNoBuff);

System.out.println("无Buffer耗时:" + (System.currentTimeMillis() - begtime));

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

begtime = System.currentTimeMillis();

try {

copyFileWithBuffer(oldfile2, newFileWithBuff);

System.out.println("含Buffer耗时:" + (System.currentTimeMillis() - begtime));

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}

}

测试结果:

无Buffer耗时:17523
含Buffer耗时:26176

结果为什么会这样?其实这两个方法都使用了缓冲技术, FileInputStream并不是一个字节一个字节的读,而是自己做缓冲。大块文件被从磁盘读取,然后每次访问一个字节或字符。缓冲是一个基本而重要的加速I/O 的技术。尽管FileInputStream自己做缓冲速度是最快的,但是这却不是一个值得推荐的实现,或许BufferedInputStream的实现才更加适合应用。(上述例子中并没有给出FileInputStream直接read()的例子,这种方式肯定是最慢的)

既然使用缓冲可以减少读写次数加速IO,那缓冲区是不是越大越好?是不是可以这么写

  int len= (int) new File("filename").length();
  byte[] buff=new byte[len];

这么写虽然把文件读取降到最小,但是如果文件过大的话,这么写会直接耗尽内存。

还有一点需要注意的是,BufferedInputStream默认缓冲区大小是:8192,使用时可以不显示的声明一个byte数组,这样也能降低代码的复杂度,类似下面的写法:

 BufferedInputStream inBuff = null;

BufferedOutputStream outBuff = null;

try {

// 新建文件输入流并对它进行缓冲

inBuff = new BufferedInputStream(new FileInputStream(sourceFile));

// 新建文件输出流并对它进行缓冲

outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));

// 缓冲数组

int data;

while ((data = inBuff.read()) != -1) {

outBuff.write(data);

}

// 刷新此缓冲的输出流

outBuff.flush();

} finally {

// 关闭流

if (inBuff != null)

inBuff.close();

if (outBuff != null)

outBuff.close();

}

大数据处理方式,接下来我们从内存中制造100万数据,使用串行化和非串行化方式将这些数据存储到文件,并读回到内存。

import java.io.*;

import java.util.ArrayList;

import java.util.List;

/**

* Created with IntelliJ IDEA.

* User: wangq

* Date: 13-3-28

* Time: 下午3:32

* To change this template use File | Settings | File Templates.

*/

public class TestIo2 {

/**

* 串行化写入数据结

*

* @param list

*/

public static void writeObject(List<Integer> list) {

ObjectOutputStream out = null;

try {

out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("D:\\obj")));

out.writeObject(list);

} catch (FileNotFoundException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} finally {

if (out != null)

try {

out.close();

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}

}

/**串行化读取数据

*

*/

public static void readObj() {

ObjectInputStream in = null;

try {

in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("D:\\obj")));

List<Integer> list = (List<Integer>) in.readObject();

for (int i = 0; i < 10; i++) {

System.out.println("list2=" + list.get(i));

}

System.out.println("list2.size=" + list.size());

} catch (FileNotFoundException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} catch (ClassNotFoundException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} finally {

if (in != null)

try {

in.close();

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}

}

/**非串行化写入数据

*

* @param list

*/

public static void write(List<Integer> list) {

BufferedOutputStream out = null;

try {

out = new BufferedOutputStream(new FileOutputStream("D:\\num"));

for (Integer data : list) {

out.write(intToBytes(data.intValue()));

}

} catch (FileNotFoundException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} finally {

if (out != null)

try {

out.close();

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}

}

/**非串行化读入数据

*

*/

public static void read() {

BufferedInputStream in = null;

try {

in = new BufferedInputStream(new FileInputStream("D:\\num"));

List<Integer> list = new ArrayList<Integer>();

byte[] buff = new byte[4];

while ((in.read(buff)) != -1) {

list.add(byteToInt2(buff));

}

for (int i = 0; i < 10; i++) {

System.out.println("list1=" + list.get(i));

}

System.out.println("list1.size=" + list.size());

} catch (FileNotFoundException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} finally {

if (in != null)

try {

in.close();

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}

}

public static byte[] intToBytes(int n) {

byte[] b = new byte[4];

for (int i = 0; i < 4; i++) {

b[i] = (byte) (n >> (24 - i * 8));

}

return b;

}

public static int byteToInt2(byte[] b) {

int mask = 0xff;

int temp = 0;

int n = 0;

for (int i = 0; i < 4; i++) {

n <<= 8;

temp = b[i] & mask;

n |= temp;

}

return n;

}

public static void main(String[] args) {

List<Integer> list = new ArrayList<Integer>();

//测试数据为100万

for (int i = 0; i < 1000000; i++) {

list.add(i);

}

long beg = System.currentTimeMillis();

write(list);

read();

System.out.println("非串行化耗时:" + (System.currentTimeMillis() - beg));

beg = System.currentTimeMillis();

writeObject(list);

readObj();

System.out.println("串行化耗时:" + (System.currentTimeMillis() - beg));

}

}

测试结果:


list1=0
list1=1
list1=2
list1=3
list1=4
list1=5
list1=6
list1=7
list1=8
list1=9
list1.size=1000000
非串行化耗时:89
list2=0
list2=1
list2=2
list2=3
list2=4
list2=5
list2=6
list2=7
list2=8
list2=9
list2.size=1000000
串行化耗时:466

Process finished with exit code 0
注意我们使用缓冲提高I/O操作的速度。

在具体的使用中是不是串行化数据,还需要根据自己方案来进行权衡。

 字符流使用,理解了字节流,再来操作字符流就很简单了:

import java.io.*;

import java.util.ArrayList;

import java.util.List;

/**

* Created with IntelliJ IDEA.

* User: wangq

* Date: 13-3-29

* Time: 上午10:37

* To change this template use File | Settings | File Templates.

*/

public class TestIoReaderWriter {

/**

* 写入字符串到文件,没个字符串占用一行

*

* @param strList

*/

public static void writeStr(List<String> strList) {

BufferedWriter writer = null;

try {

writer = new BufferedWriter(new FileWriter("D:\\test.txt")); //注意FileWriter有个构造函数可以附加内容

for (String data : strList) {

writer.write(data);

writer.newLine(); //写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 ('\n') 符。

}

writer.flush();

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} finally {

if (writer != null)

try {

writer.close();

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}

}

/**

* 一次读取一行字符串

*/

public static void readStr() {

BufferedReader reader = null;

List<String> list = new ArrayList<String>();

try {

reader = new BufferedReader(new FileReader("D:\\test.txt"));

String data = null;

while ((data = reader.readLine()) != null) { //读取一个文本行。通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。

list.add(data);

}

System.out.println(list.size());

} catch (FileNotFoundException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

} finally {

if (reader != null)

try {

reader.close();

} catch (IOException e) {

e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.

}

}

}

public static void main(String[] args) {

List<String> list = new ArrayList<String>();

for (int i = 0; i < 10000; i++) {

list.add(String.valueOf(i));

}

long beg = System.currentTimeMillis();

writeStr(list);

readStr();

System.out.println("耗时:" + (System.currentTimeMillis() - beg));

}

}

其他IO类常用特殊功能介绍

ByteArrayOutputStream:此类实现了一个输出流,其中的数据被写入一个 byte 数组。

public byte[] toByteArray()
可以将输出流中的内容复制到byte数组中返回。比如将一个文件转成byte数组,就可以使用该功能。

FilePermission:此类表示对文件和目录的访问。FilePermission 由路径名和对该路径名有效的操作集合组成。

public String getActions()
返回文件的操作权限,此方法总是以下列顺序返回存在的操作:read、write、execute、delete。例如,如果此 FilePermission 对象允许写和读操作,则调用 getActions 将返回字符串 "read,write"。

LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int)getLineNumber(),它们可分别用于设置和获取当前行号。

PipedInputStream  PipedOutputStream:管道流,这两个类的实例对象必须要通过connect方法连接,这两个类主要用来完成线程之间的通信,一个线程的PipedInputStream对象能够从另外一个线程的PipedOutputStream对象中读取数据。这两个类的使用场景应该类似于生产者--消费者模式,并且需要多线程环境。

import java.io.PipedInputStream;

import java.io.PipedOutputStream;

public class TestPiped {

public static void main(String[] args) {

Sender s = new Sender();

Receiver r = new Receiver();

PipedOutputStream out = s.getOut();

PipedInputStream in = r.getIn();

try {

in.connect(out);

s.start();

r.start();

} catch (Exception e) {

e.printStackTrace();

}

}

}

class Sender extends Thread {

PipedOutputStream out = new PipedOutputStream();

public PipedOutputStream getOut() {

return out;

}

public void run() {

String str = "Hello,receiver ! I`m sender\n";

try {

out.write(str.getBytes());

out.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

//receiver.java

class Receiver extends Thread {

PipedInputStream in = new PipedInputStream();

public PipedInputStream getIn() {

return in;

}

public void run() {

byte[] buf = new byte[1024];

try {

int len = in.read(buf);

System.out.println("the following is from sender:\n" + new String(buf, 0, len));

in.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

以上是 Java Io原理及应用 的全部内容, 来源链接: utcz.com/z/391877.html

回到顶部