Java的IO流

本文总阅读量

//有时间再排版吧,累觉不爱 3/18

一、Java的流:

  • 各种各样的流对应各种各样的管道,它们有些可以套在一起使用。

二、分类:

  • 输入流,输出流——-文件的流向不同

输入流:进入程序(写的代码)

输出流:从程序(写的代码)出去

  • 字节流,字符流——-处理的数据单位不同

  • 节点流,处理流——-功能不同

节点流:(管道)直接怼到文件(水桶)上的流。

处理流:包在别的(管道)上面的(管道)。

凡是以stream结尾的都是字节流

三、节点流

1、FileInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.*;
public class TestFileInputStream {
public static void main(String[] args) {
int b = 0;
FileInputStream in = null;
try {
in = new FileInputStream("d:\\share\\java\\io\\TestFileInputStream.java");
} catch (FileNotFoundException e) {
System.out.println("找不到指定文件");
System.exit(-1);
}

try {
long num = 0;
while((b=in.read())!=-1){
//b!=-1相当于这个文件还没有读到结尾
System.out.print((char)b);
num++;
}
in.close();
System.out.println();
System.out.println("共读取了 "+num+" 个字节");
} catch (IOException e1) {
System.out.println("文件读取错误"); System.exit(-1);
}
}
}

这个程序执行结束后,读出的文件,如果有中文,则会显示乱码。因为它是一个字节字节地读,而中文占用的不止一个字节。

2、FileOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;
public class TestFileOutputStream {
public static void main(String[] args) {
int b = 0;
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("e:/javatest/testfile.java");
out = new FileOutputStream("e:/javatest/8/HWJ.java");
while((b=in.read())!=-1){//in这个管道在从文件里面读数据
System.out.print((char)b);
out.write(b);//out这个管道在向文件里面“灌”数据
}
in.close();
out.close();
} catch (FileNotFoundException e2) {
System.out.println("找不到指定文件"); System.exit(-1);
} catch (IOException e1) {
System.out.println("文件复制错误"); System.exit(-1);
}
System.out.println("文件已复制");
}
}

FileOutputStream(path,append):如果第二个参数写为true,则可以向文件后面追加。

3、FileReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;
public class TestFileReader {
public static void main(String[] args) {
FileReader fr = null;
int c = 0;
try {
fr = new FileReader("e:\\javatest\\8\\io\\TestFileReader.java");
int ln = 0;
while ((c = fr.read()) != -1) {
System.out.print((char)c);
}
fr.close();
} catch (FileNotFoundException e) {
System.out.println("找不到指定文件");
} catch (IOException e) {
System.out.println("文件读取错误");
}

}
}

这个程序执行结束,如果文件里面有中文,则会完美的将中文读出。因为这是以字符的形式读出。

4、FileWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;
public class TestFileWriter{
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("e:\\javatest\\8\\heijudy.txt");
fw.write("我的导师叫那英~");
fw.close();
} catch (IOException e1) {
e1.printStackTrace();
System.out.println("文件写入错误");
System.exit(-1);
}
System.out.println("文件写入完成");

}
}

这个程序如果目标目录下没有这个文件,则会自动创建一个文件。然后把字符串给写进去。
FileWriter(path,append):如果第二个参数写为true,则可以向文件后面追加。

四、处理流

1、缓冲流(带小桶的流)

(1)、BufferedInputStream、BufferedOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.*;

public class TestBufferStream1 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream(
"e:\\javatest\\8\\io\\test.java");
BufferedInputStream bis = new BufferedInputStream(fis);
//BufferedInputStream bis = new BufferedInputStream(fis,5);
int c = 0;
System.out.println(bis.read());
System.out.println(bis.read());
bis.mark(2);
for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
System.out.print((char) c + " ");
}
System.out.println();
bis.reset();
for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
System.out.print((char) c + " ");
}
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

BufferedInputStream.mark(int readlimit)
意思是:

标记当前位置,在后面读取readlimit个字节之前有效。

但实际中并不是这样!!!

实际中是这样的:

  • 当BufferedInputStream调用了mark(不管这里是几)后,后面读取的字节数如果超过了流的Size,则这个标记失效。代码中设置的Size=5的BufferedInputStream证实了这个。

(2)、BufferedWriter、BufferedReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.*;

public class TestBufferStream2 {
public static void main(String[] args) {
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(
"e:/javatest/8/我的家在东北.txt"));
BufferedReader br = new BufferedReader(new FileReader(
"e:/javatest/8/我的家在东北.txt"));
String s = null;
for (int i = 1; i <= 100; i++) {
s = String.valueOf(Math.random());
bw.write(s);
bw.newLine();//写一个换行符
}
bw.flush();
while ((s = br.readLine()) != null) {
System.out.println(s);
}
bw.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

  • BufferedWriter.newLine()//写一个换行符

  • BufferedWriter.write(String s,int off,int len)//写一个字符串s,从off写len个字符。off和len可以缺省,那么就写整个s。也有写字符数组的方法,类似。

  • 有一个很好用的方法:
    BufferedReader.readLine()//读一行内容
    它可以直接读出一行内容,即便是为了这个方法,也值得在其他流上套一个BufferedReader

2、转换流

转换流概念图片

(1)、OutputStreamWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.*;
public class TestTransForm1 {
public static void main(String[] args) {
try {
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("e:\\javatest\\8\\io\\char.txt"));
osw.write("mircosoft ibm sun apple hp");
// osw.getEncoding()拿到这个字符编码
System.out.println(osw.getEncoding());
osw.close();
osw = new OutputStreamWriter(new FileOutputStream(
"e:\\javatest\\8\\io\\char.txt", true), "GBK"); // latin-1

osw.write(" mircosoft ibm sun apple hp");
// osw.write(" mircosoft ibm sun apple hp", 3, 2);
System.out.println(osw.getEncoding());
osw.close();

InputStreamReader isr = new InputStreamReader(new FileInputStream(
"e:\\javatest\\8\\io\\char.txt"));
BufferedReader br = new BufferedReader(isr);
String s;
s = br.readLine();
System.out.println(s);
br.close();

} catch (FileNotFoundException e2) {
System.out.println("找不到指定文件");
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
}

System.out.println("FINISH");
}
}

图片:大管道套小管道
OutputStreamWriter.write(String str,int off,int len):可以写一个字符串从off开始len个字符。
off和len可以缺省,那么默认写全部str。
代码中的: osw.write(“ mircosoft ibm sun apple hp”, 3, 2);证实了这个。

(2)、InputStreamReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;
public class TestTransForm2 {
public static void main(String args[]) {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = null;
try {
s = br.readLine();
while (s != null) {
if (s.equalsIgnoreCase("exit"))
//如果s的内容是"exit",进来
break;
System.out.println(s.toUpperCase());
//toUpperCase()把小写转换成大写
s = br.readLine();
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

//System.in是个阻塞式的流,如果你没有输入,它就傻傻的等待~
图片:三个管道,读字节>读字符>读一行字符串

3、数据流

图片:概念

(1)、DataInputStream、DataOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.*;

public class TestDataStream {
public static void main(String[] args) {
/**
* ByteArrayOutputStream 的创建完成了2个操作:
* 一、在内存中分配了一个字节数组(即:缓冲区)
* 二、一根流管道怼在了这个字节数组上
*/

ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 在baos管道上再套接一层管道dos
DataOutputStream dos = new DataOutputStream(baos);
try {
/**
* 向这个流dos里面写了一个double,一个boolean
*
* 实际上是先把这两个数据写到了刚才在内存中分配
* 的字节数组(缓冲区)中暂时保存起来
*/

dos.writeDouble(Math.random());
dos.writeBoolean(true);
/**
* 创建一个字节数组输入流,将输出流的字节数组(缓冲区)
* 的内容以字节数组的形式返回
*/

ByteArrayInputStream bais = new ByteArrayInputStream(
baos.toByteArray());
// 可从此流中读取的剩余字节数
System.out.println(bais.available());
// 套在一个流上
DataInputStream dis = new DataInputStream(bais);
// 读取一个double,一个boolean
/**
* 正确的read的顺序应该和write的顺序相同。
* 如果相反,则会产生错误。
* “先写先读”-----先进先出-----队列
*/

System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
/**
* 关闭后,字符数组消失,管道都消失
*/

dos.close();
dis.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

4、print流

图片:概念

(1)、PrintStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.*;

public class TestPrintStream1 {
public static void main(String[] args) {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(
"e:\\javatest\\8\\io\\char.txt", true);
//在文件输出流外再套了一层输出流
ps = new PrintStream(fos);
} catch (IOException e) {
e.printStackTrace();
}
if (ps != null) {
//设置system的输出流为ps
System.setOut(ps);
}

System.out.println("\n中国好声音第四季----周杰伦歌迷见面会~~~~~~~~~~才怪~");
ps.close();
}
}

图片:管道套管道图,把往控制台的输出流设置为自己创建的流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;

public class TestPrintStream2 {
public static void main(String[] args) {
String filename = args[0];
if (filename != null) {
list(filename, System.out);
}
}

public static void list(String f, PrintStream fs) {
try {
BufferedReader br = new BufferedReader(new FileReader(f));
String s = null;
while ((s = br.readLine()) != null) {
fs.println(s);
}
br.close();
} catch (IOException e) {
fs.println("无法读取文件");
}
}
}

读取一个参数(文件名),然后输出到控制台

(2)、PrintWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.*;
import java.io.*;

public class TestPrintStream3 {
public static void main(String[] args) {
String s = null;
/**
* 一根管道怼到了标准输入上(键盘上)
*/

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
/**
* 后面带true的参数,用于写日志
*/

FileWriter fw = new FileWriter("e:\\javatest\\8\\io\\char.txt",
true); // Log4J
/**
* 又一根管道PrintWriter怼到了FileWriter流上
*/

PrintWriter log = new PrintWriter(fw);
/**
* br.readLine():此处是从标准输入流中读取一行内容
*/

while ((s = br.readLine()) != null) {
if (s.equalsIgnoreCase("exit"))
break;
System.out.println(s.toUpperCase());
/**向指向文件的流里面写数据
*
*/

log.println("-----");
log.println(s.toUpperCase());
log.flush();
}
//当写完后,在后面加上日期
log.println("===" + new Date() + "===");
/**
* flush()刷新此输出流并强制写出所有缓冲的输出字节
*/

log.flush();
log.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

5、Object流

直接将Object写入或写出

* 序列化:把一个Object直接转换为字节流写到硬盘(或者网络)上去。

Java序列化:

类通过实现 java.io.Serializable(标记性的接口) 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。
序列化接口没有方法或字段,仅用于标识可序列化的语义。
类实现Serializable接口,做了标记之后。实质上是告诉编译器,“我可以被序列化,come on~序列化我吧~~”。

关键字transient:
修饰变量,在序列化的时候不予考虑(即:往硬盘上写的时候,直接忽视它,当它是透明的)

transient使用小结(原文地址:http://blog.csdn.net/lanxuezaipiao/article/details/16358677)
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

(1)、ObjectInputStream、ObjectOutputStream

优点:
比DataInputStream、DataOutputStream流更方便,ObjectInputStream、ObjectOutputStream可以直接读写一个Object对象。而不用一个数据一个数据地读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.*;

public class TestObjectIO {
public static void main(String args[]) throws Exception {
T t = new T();
t.k = 8;
/**
* 输出流
* 一个管道套一个管道怼在了文件上
*/

FileOutputStream fos = new FileOutputStream("e:\\javatest\\8\\char.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
/**
* ObjectOutputStream.writeObject(Object o)//直接写一个对象进去
*/

oos.writeObject(t);
oos.flush();
oos.close();
/**
* 输入流
* 一个管道套一个管道怼在了文件上
*/

FileInputStream fis = new FileInputStream("e:\\javatest\\8\\char.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
T tReaded = (T) ois.readObject();
System.out.println(tReaded.i + " " + tReaded.j + " " + tReaded.d + " "
+ tReaded.k);
}
}

class T implements Serializable {
int i = 10;
int j = 9;
double d = 2.3;
transient int k = 15;
}

此例中,变量k被transient修饰,所以不能被序列化。输出tReaded.k的结果为0。

java.io.Externalizable:
实现了Serializable接口,对象序列化的过程自动进行,由JVM帮你控制。这TM自己控制不了它怎么序列化的,总感觉不太爽啊(¬_¬),Java还很人性的提供了Externalizable接口(估计是顾及到了一些程序员的感受(๑•ᴗ•๑))。

其实,
public interface Externalizable extends Serializable
它是Serializable的子接口~^_^~
程序员可以实现里面的readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,
就可以自己控制序列化的过程。