Java Io原理及应用
概述
IO是编程中无法回避的问题,它往往会成为程序运行的性能瓶颈(JAVA在1.4后引入了NIO提高了IO性能),JAVA的IO流分类很丰富,IO包下含有大量的类和接口,从分类上看,主要有以下分类:
- 基于字节操作的 I/O 接口:InputStream 和 OutputStream
- 基于字符操作的 I/O 接口:Writer 和 Reader
- 基于磁盘操作的 I/O 接口:File
- 基于网络操作的 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