I/O流的使用情况多种多样,首先它的数据源就可能是文件、控制台、服务器等,它的单位可能是按字节、按字符、按行等。为了涵盖所有的可能,java类库中创建了大量的类,如此多的类让我们在使用时感觉有点难以选择。

I/O流按数据单位可分为字节流(InputStream/OutputStream)和字符流(Reader/Writer),其它的类主要是从这四个抽象类中派生出来的。除此之外,还有一个独立于这两种流之外的类——RamdomAccessFile。

一、字节流(InputStream/OutputStream)

1.InputStream

每一种数据源都有相应的InputStream的子类:

(1)字节数组——ByteArrayInputStream:

它能将字节数组转化为输入流,下面为简单示例:

    public static void main(String[] args) {
byte[] strArr={11,22,45,65};
ByteArrayInputStream bais=new ByteArrayInputStream(strArr);
int b;
while((b=bais.read())!=-1){
System.out.println(b);
}
/*
output:
11
22
45
65
*/
}

(3)文件——FileInputStream:

将文件转换为输入流,下面为简单的文本输出操作示例:

    public static void main(String[] args) throws FileNotFoundException {
File file=new File("D:/Test/log.txt");
FileInputStream fis=new FileInputStream(file);
byte[] buffer=new byte[8*1024];
int len;
try {
while((len=fis.read(buffer))!=-1){
System.out.println(new String(buffer,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}
}

管道——PipedInputStream:

需和PipeOutputStream搭配使用,下面是一个简单的示例:

public class PipedStreamTest {
public static void main(String[] args) {
Sender sender = new Sender();
Receiver receiver = new Receiver(); PipedOutputStream outStream = sender.getOutStream();
PipedInputStream inStream = receiver.getInStream(); try {
inStream.connect(outStream); // 与outStream.connect(inStream);等效,二选其一
} catch (Exception e) {
e.printStackTrace();
} sender.start();
receiver.start();
}
} class Sender extends Thread { private PipedOutputStream outStream = new PipedOutputStream(); public PipedOutputStream getOutStream() {
return outStream;
} public void run() {
Scanner in = new Scanner(System.in);
while (true) {
String str = in.nextLine();
if (str.equals("exit")) {
break;
}
try {
System.out.println("send message: " + str);
outStream.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
try {
if (outStream != null)
outStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Sender close");
}
} class Receiver extends Thread {
private PipedInputStream inStream = new PipedInputStream(); public PipedInputStream getInStream() {
return inStream;
} public void run() {
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inStream.read(buffer)) != -1) {
System.out.println("receive message: " + new String(buffer, 0, len));
System.out.println("-------------------------");
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (inStream != null)
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Receiver closed");
System.out.println("end");
}
}

对于上面的例子运行后在控制台进行测试:

hello  //Console input
send message: hello
receive message: hello
-------------------------
world //Console input
send message: world
receive message: world
-------------------------
exit
Sender close
Receiver closed
end

其它的InputStream流集合——SequenceInputStream

将两个InputStream转化成一个InputStream:

        byte[] bytes1={12,34};
byte[] bytes2={56,78};
ByteArrayInputStream bais1=new ByteArrayInputStream(bytes1);
ByteArrayInputStream bais2=new ByteArrayInputStream(bytes2);
SequenceInputStream sis=new SequenceInputStream(bais1,bais2);
int b;
try {
while((b=sis.read())!=-1){
System.out.print(b+"; ");
}
} catch (IOException e) {
e.printStackTrace();
}
/*
output:
12; 34; 56; 78;
*/

SequenceInputStream可以将不同类型的InputStream转化为一个InputStream,当InputStream大于两个的时候,需要用Vector类:

    public static void main(String[] args) throws IOException {
InputStream is1=new ByteArrayInputStream(new byte[]{111,112,113});
InputStream is2=new FileInputStream("D:/log.txt");
InputStream is3=new URL("http://www.baidu.com").openStream();
Vector<InputStream> v=new Vector<>();
v.add(is1);
v.add(is2);
v.add(is3);
SequenceInputStream is=new SequenceInputStream(v.elements());
byte[] buffer=new byte[1024];
int len;
int count=0;
while((len=is.read(buffer))!=-1){
System.out.println(new String(buffer,0,len));
if(++count%100==0){
break;
}
}
is.close();
}

……

2.OutputStream

(1)ByteArrayOutputStream。在内存中创建缓冲区,将写入的数据保存在缓冲区中。

    public static void main(String[] args) throws FileNotFoundException {
ByteArrayOutputStream baos=new ByteArrayOutputStream();
baos.write(12);
baos.write(23);
baos.write(34);
byte[] bytes=baos.toByteArray();
System.out.println(Arrays.toString(bytes));
/*
output:
[12, 23, 34]
*/
}

(2)FileOutputStream。可将信息写入文件,可选择覆盖或在末尾添加。

    public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("D:/Test/log.txt",true);
fos.write("我是好人".getBytes());
fos.close();
}

(3)PipeOutputSream。需和PipeOutputStream搭配使用,上文中已经提到,这里就不再叙述。

……


上面所提及的几种类型都是InputStream/OutputStream的直接子类,是进行I/O流操作的几种基本类型,但是对于写入和读取的数据类型限制太大,比如如果我们如果想要直接写入float或long类型的数据,就无法完成。

要实现这个需求,需要对InputStream和OutputStream进行封装处理,这里就得提一下两个装饰器类:FilterInputStream和FiltOutputStream。

FilterInputStream和FiltOutputStream分别是InputStream和OutputStream的子类,它们增加了I/O流操作的灵活性,但同时也增加了代码的复杂性。

这两个类是I/O操作的基础类,通常我们不会直接使用,而是用功能更加具体完善的它们的子类。

1.FilterInputStream

FilterInputStream的常见子类有DataInputStream、BufferedInputStream。

(1)DataInputStream

与DataOutputStream搭配使用,主要特点是可以读写基本类型数据,不过需要读写对应。

    public static void main(String[] args) throws IOException {
String path="D:/test.txt";
DataOutputStream dos=new DataOutputStream(new FileOutputStream(path));
DataInputStream dis=new DataInputStream(new FileInputStream(path));
dos.writeInt(110);
dos.writeUTF("我是好人");
System.out.println(dis.readInt());
System.out.println(dis.readUTF());
dis.close();
dos.close();
}

(2)BufferedInputStream

使用了缓冲区,避免每次读取时都进行实际操作,可指定缓冲区大小,默认为8192byte。每次读取时,首先会从缓冲区获取,若缓冲区中读取完了则从数据源输入流中重新获取数据至缓冲区中。

下面为读取操作的源码:

    private int read1(byte[] b, int off, int len) throws IOException {
int avail = count - pos;
if (avail <= 0) {
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, do not bother to copy the
bytes into the local buffer. In this way buffered streams will
cascade harmlessly. */
if (len >= getBufIfOpen().length && markpos < 0) {
return getInIfOpen().read(b, off, len);
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}

值得注意的是当读取的长度大于缓冲区的长度,将直接调用数据源输入流的read方法。

缓冲是用来减少来自输入设备的单独读取操作数的数量的,因此缓冲有时并不必要,当例如数据块足够大的时候,不需要使用缓冲,如文件的复制就不需要缓冲。

2.FilterOutputStream

FilterOutputStream常见的子类有DataOutputStream、BufferedOutputStream。

(1)DataOutputStream

需和DataInputStream搭配使用,上文中已经提及,此处不再叙述。

(2)BufferedOutputStream

BufferedOutputStream在输出的时候会先将数据写入缓冲区,满足一定条件后再将缓冲区中的数据输出,所以当我们完成输出操作时,需调用flush方法将缓冲区中的数据输出。

下面为输出操作源码:

    public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}

当输出的长度大于等于缓冲长度时,先将缓冲区中的数据输出,在讲待输出数据直接输出,不经过缓冲区这个过程。

当输出的长度小于缓冲区长度但又大于缓冲区剩余长度时,会先将缓冲区的数据输出,再将待输出数据写入缓冲区。

当输出的长度小于缓冲区长度时,数据会写入缓冲区。

jdk1.8中FilterOutputStream的close方法源码如下:

    public void close() throws IOException {
try (OutputStream ostream = out) {
flush();
}
}

这是一种新的实现机制,在try语句结束后,会自动调用Closeable.close()方法。close方法中调用的flush方法,避免了我们忘记调用flush方法时的输出遗漏,虽然如此,但我们最好还是自己调用flush方法。


二、字符流(Reader/Writer)

InputStream和OutputStream仅支持8位字节流,不能很好地支持16位的Unicode字符,Reader和Writer支持Unicode,实现了国际化。

对于纯文本通常使用字符流,对于其它的例如视频、音频和图片等文件通常使用字节流。

1.InputStreamReader&OutputStreamWriter

有些时候,我们需要将字节流和字符流结合起来使用,因此需要适配器,InputStreamReader和OutputStreamWriter这两个类能够将字节流转化为字符流,同时可以设置字符编码。

OutPutStreamWriter使用的缓冲区,所以输入完成后需要调用flush方法。

2.FileReader&FileWriter

这两个类分别是InputStreamReader和OutputStreamWriter的子类,它们只是简单地增加了文件和文件名转化为流的构造方法。

3.BufferedReader&BufferedWriter

这两个类是文本读写中较为常见,其内部结构与BufferedInputStream&BufferedOutputStream类似。

比较特殊的是输出时的newLine方法可换行,读取时的readLine方法可实现一行一行的读取。

下面为这两个类使用的简单示例:

    public static void main(String[] args) throws IOException {
String filename = "D:/Test/log.txt";
String charset = "utf-8";
String[] msgArr={"I'm line 1","I'm line 2","I'm line 3","I'm line 4","I'm line 5"}; BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), charset));
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filename), charset));
for (String string : msgArr) {
writer.write(string);
writer.newLine();
}
writer.flush(); StringBuilder sb=new StringBuilder();
String line;
while((line=reader.readLine())!=null){
sb.append(line+"\n");
}
writer.close();
reader.close();
System.out.println(sb.toString());
/*
output:
I'm line 1
I'm line 2
I'm line 3
I'm line 4
I'm line 5
*/
}

4.PrintWriter

这个类最大的特点是有很多的构造方法,方便我们面对各种情况是的使用,它还增加了各种类型数据输出,包括long、float、double等。

它的一个构造方法如下:

    public PrintWriter(Writer out,
boolean autoFlush) {
super(out);
this.out = out;
this.autoFlush = autoFlush;
lineSeparator = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}

当autoFlush为true的时候,每当调用newLine方法,将自动清空缓冲区,不过newLine是私有方法,需通过println方法调用。

……


三、RamdomAccessFile

RamdomAccessFile支持对随机访问文件的读取和写入,常用于多线程下载。

下面来看看RamdomAccessFile几个常用方法:

1. void setLength(long newLength)

此方法可设置文件大小,当文件原来的大小大于设置的大小,将会把文件裁剪为设置大小。

2. int length()

返回文件的大小。

3. long getFilePointer()

返回指针的位置。

4. void seek(long pos)

移动指针至pos位置。

下面为一个简单的示例:

import java.io.IOException;
import java.io.RandomAccessFile; public class Test { public static final String CHARSET = "utf-8";
public static final String[] MSG_ARR = { "1床前明月光,", "12疑是地上霜,", "123举头望明月,", "1234低头思故乡。" };
public static final int[] POS_ARR = new int[5];
public static final String FILENAME = "D:/log.txt"; public static void main(String[] args) throws IOException, InterruptedException {
POS_ARR[0] = 0;
int count = 0;
for (int i = 0; i < 4; i++) {
count += MSG_ARR[i].getBytes(CHARSET).length;
POS_ARR[i + 1] = count;
}
RandomAccessFile raf = new RandomAccessFile(FILENAME, "rw");
raf.setLength(POS_ARR[4]);
for (int i = 0; i < 4; i++) {
RafThread rt = new RafThread(FILENAME, POS_ARR[i], MSG_ARR[i]);
rt.start();
} Thread.sleep(100); String line;
while ((line = raf.readLine()) != null) {
System.out.println(new String(line.getBytes("ISO-8859-1"), CHARSET));
}
raf.close();
/*
outtput:
1床前明月光,12疑是地上霜,123举头望明月,1234低头思故乡。
*/
} } class RafThread extends Thread { private String filename;
private int po;
private String msg; public RafThread(String filename, int po, String msg) {
this.filename = filename;
this.po = po;
this.msg = msg;
} @Override
public void run() {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(filename, "rw");
raf.seek(po);
raf.write(msg.getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }
}

四、新I/O

jdk1.4引入了新的I/Ol类库java.nio,而旧的I/O也用新I/O重新实现过,所以就算不显示使用,相对旧I/O效率也有了提升。

由于没有什么使用经验,留待以后再说……

java之I/O流的更多相关文章

  1. java 字节流和字符流的区别 转载

    转载自:http://blog.csdn.net/cynhafa/article/details/6882061 java 字节流和字符流的区别 字节流与和字符流的使用非常相似,两者除了操作代码上的不 ...

  2. java 21 - 12 IO流的打印流

    打印流 字节流打印流 PrintStream 字符打印流 PrintWriter打印流的特点: A:只有写数据的,没有读取数据.只能操作目的地,不能操作数据源.(只能写入数据到文件中,而不能从文件中提 ...

  3. Java中的IO流系统详解(转载)

    摘要: Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java ...

  4. java 字节流和字符流的区别

    转载自:http://blog.csdn.net/cynhafa/article/details/6882061 java 字节流和字符流的区别 字节流与和字符流的使用非常相似,两者除了操作代码上的不 ...

  5. Java中的IO流系统详解

    Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java 内用 U ...

  6. java开发之IO流

    一直对IO流记不清楚,从别的地方转过来. 看下图: 流的概念和作用 学习Java IO,不得不提到的就是JavaIO流. 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两 ...

  7. java中的IO流

    Java中的IO流 在之前的时候我已经接触过C#中的IO流,也就是说集中数据固化的方式之一,那么我们今天来说一下java中的IO流. 首先,我们学习IO流就是要对文件或目录进行一系列的操作,那么怎样操 ...

  8. Java IO 文件与流基础

    Java IO 文件与流基础 @author ixenos 摘要:创建文件.文件过滤.流分类.流结构.常见流.文件流.字节数组流(缓冲区) 如何创建一个文件 #当我们调用File类的构造器时,仅仅是在 ...

  9. JAVA通过I/O流复制文件

    JAVA通过I/O流复制文件 本文是对字节流操作,可以多音频视频文件进行操作,亲测有效. 个人感觉这个东西就是靠记的, 没什么好解释的,,,, import java.io.File; import ...

  10. java中的缓冲流BufferedWriter和BufferedReader

    java中的缓冲流有BufferedWriter和BufferedReader 在java api 手册中这样说缓冲流: 从字符输入流中读取文本,缓冲各个字符,从而实现字符.数组和行的高效读取.可以指 ...

随机推荐

  1. NIO三大组件之Buffer

    什么是Buffer Buffer(这里并不是特指Buffer类)是一个存储数据的容器,与数组类似(其实底层依旧是用数组的结构来存储数据),但不同的是,Buffer对象提供了一组更有效的方法去进行写入和 ...

  2. 在Python中创建M x N的数组

    在Python中创建M x N的数组 一般有三种方法: 列表乘法 dp = [[0] * n] * m for 循环 dp= [[0 for _ in range(n)] for _ in range ...

  3. PTA 报数

    6-3 报数 (20 分)   报数游戏是这样的:有n个人围成一圈,按顺序从1到n编好号.从第一个人开始报数,报到m(<)的人退出圈子:下一个人从1开始报数,报到m的人退出圈子.如此下去,直到留 ...

  4. UML和设计模式原则总结

    UML总结: uml就是统一建模语言,包括语义概念 标记符号和指南 具有静态 动态 环境上的和组织性的部分 .它不是编程语言.uml预览它涉及的主要领域有结构性(静态视图,用例视图,构件图,实现视图, ...

  5. Elasticsearch 分页查询

    目录 前言 from + size search after scroll api 总结 参考资料 前言 我们在实际工作中,有很多分页的需求,商品分页.订单分页等,在MySQL中我们可以使用limit ...

  6. Shell prompt(PS1) 与 Carriage Return(CR) 的关系?-- Shell十三问<第二问>

    Shell prompt(PS1) 与 Carriage Return(CR) 的关系?-- Shell十三问<第二问> 当你成功登录进一个文字界面之后,大部份情形下,你会在荧幕上看到一个 ...

  7. 将Java编译为本地代码

    将Java编译为本地代码 通常Java程序的执行流程为:将Java代码编译为Byte Code(字节码),然后JVM执行引擎执行编译好的Byte Code.这是一种中间语言的特性,它的好处就是可以做到 ...

  8. Hystrix熔断原理

    Netflix的开源组件Hystrix的流程: 图中流程的说明: 将远程服务调用逻辑封装进一个HystrixCommand. 对于每次服务调用可以使用同步或异步机制,对应执行execute()或que ...

  9. Go+gRPC-Gateway(V2) 微服务实战,小程序登录鉴权服务(四):客户端强类型约束,自动生成 API TS 类型定义

    系列 云原生 API 网关,gRPC-Gateway V2 初探 Go + gRPC-Gateway(V2) 构建微服务实战系列,小程序登录鉴权服务:第一篇 Go + gRPC-Gateway(V2) ...

  10. xman_2019_format(非栈上格式化字符串仅一次利用的爆破)

    xman_2019_format(非栈上格式化字符串仅一次利用的爆破) 首先检查一下程序的保护机制 然后用IDA分析一下 存在后门 首先malloc了一片堆空间,读入数据 把刚刚读入的数据当作格式化字 ...