一、字符流
1、Reader和Writer
字符流和字节流唯一的不同是在一次性读取和写入都是两个字节,字节流读取和写入是一个字节,对于读取大的文本文件的时候一般用字符流,速度更快,效率更高。
之所以被称为字符流是因为刚开始的时候一个简体汉字字符的表示一般是采用两个字节存储。
2、InputStreamReader和OutputStreamWriter
这两个类也被成为转换流,在读取的时候可以把读取到的字节流转换成字符流,在写入的时候可以把字符流转换成字节流。在创建这两个对象的时候需要传入对应的InputStream或OutputStream对象。
3、FileReader和FileWriter
为了便于两个转换流的使用,API提供的相关子类的实现,创建相应的对象时,只需传入File对象或File的路径名称。
4、BufferedReader和BufferedWriter
自带缓冲区的字符流,可以指定缓冲区的大小。从字符输入流中读取文本或将文本写入到字符输出流中,缓冲各个字符,可以实现字符、数组和行的高效读取和写入。
BufferedReader类还提供了一个更高效的读取方法:
public String readLine()
该方法读取一个文本行。通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行。 返回值包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null ,
相应的BufferedWriter类提供的方法:
public void newLine()
写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 (‘\n’) 符。在写入完一行的数据后需要添加一个行分隔符。
例:用BufferedReader和BufferedWriter复制文件内容
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("test_copy.txt",true));//true表示追加到文件的末尾
String line;
while ((line = reader.readLine()) != null) {
writer.newLine();
writer.write(line);
writer.flush();
}
writer.close();
reader.close();
}
执行完后,test.txt文件中的内容就被复制到test_copy.txt中。
总结:
-字节流:
输入流 | 输出流 |
---|---|
InputStream | OutputStream |
FileInputStream | FileOutputStream |
BufferedInputStream | BufferedOutputStream |
-字符流:
输入流 | 输出流 |
---|---|
Reader | Writer |
InputStreamReader | OutputStreamWriter |
FileReader | FileWriter |
BufferedReader | BufferedWriter |
二、标准输入流、标准输出流(打印流)
标准的输入和输出流位于System类中。
public static final InputStream in
“标准”输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。
public static final PrintStream out
“标准”输出流。此流已打开并准备接受输出数据。通常,此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。
public static final PrintStream err
“标准”错误输出流。此流已打开并准备接受输出数据。
可以对标准的输入和输出流进行重定向,调用以下两个对应的方法可以对输入流和输出流重定向。
public static void setIn(InputStream in)
重新分配“标准”输入流。可以从文件中读取,而非读取键盘中的录入信息。
public static void setOut(PrintStream out)
重新分配“标准”输出流。 改变其写入到文件中,而非打印到控制台上。
例1:从文件中读取信息
public static void main(String[] args) throws FileNotFoundException {
System.setIn(new FileInputStream("test.txt"));//重定向标准输入流
Scanner sc = new Scanner(System.in);
String info = sc.next();
System.out.print(info);
}
例2:输出信息到文件中
public static void main(String[] args) throws IOException {
String info ="hello,printStream!!!";
PrintStream print = new PrintStream("review.txt");
// print.print(info);//直接操作PrintStream打印流对象,调用print方法
// print.close();
System.setOut(print);//重定向标准的输出流
System.out.print(info);
}
三、内存流ByteArrayInputStream/ByteArrayOutputStream
ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
采用上述两个流,可以实现对同一块内存的读写操作
例:使用ByteArrayOutputStream将数据写入到内存中,并通过toByteArray得到写入到内存中的数据,使用ByteArrayInputStream读取内存中的数据,并输出。
public static void main(String[] args) throws IOException {
String data = "hello,Android,i love you!";
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.out.println(out.size());// 0
out.write(data.getBytes());
System.out.println(out.size());// 22
System.out.println(out.toString());
byte[] bs = out.toByteArray();
System.out.println("************************");
ByteArrayInputStream input = new ByteArrayInputStream(bs);
int i;
while ((i = input.read()) != -1) {
System.out.print((char) i);
}
}
输出结果:
0
25
hello,Android,i love you!
************************
hello,Android,i love you!
四、对象流ObjectInputStream/ObjectOutputStream
ObjectOutputStream可以把Java对象转化为字节序列写入到文件中,这个过程称为序列化。前提是这个对象要实现Serializable接口。在Java对象中,除了瞬态(transient)和静态(static)的字段,都可以被序列化写入到文件中。
对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。
writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。
ObjectInputStream可以把字节序列从文件中反序列化成Java对象。
readObject 方法用于从流读取对象。应该使用 Java 的安全强制转换来获取所需的类型。在 Java 中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型。反序列化进程将忽略声明为瞬态或静态的字段。
例:实现将对象写入到文件中,再从文件中读取出来
Student对象:
public class Student implements Serializable{
private String username;
//transient透明的、暂时状态
private transient int age;
private double money;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String username, int age) {
super();
this.username = username;
this.age = age;
}
@Override
public String toString() {
return this.getUsername()+":"+this.getAge()+"";
}
}
读取和写入Student对象:
public static void main(String[] args) throws FileNotFoundException,IOException,ClassNotFoundException {
// write();
read();
}
private static void read() throws IOException,FileNotFoundException,ClassNotFoundException {
ObjectInputStream input = new ObjectInputStream(new FileInputStream("obj.txt"));
for(int i=0;i<3;i++){
Object s = input.readObject();
System.out.println(s);
}
input.close();
}
private static void write() throws IOException,FileNotFoundException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.txt"));
// 序列化--持久化
for(int i=0;i<3;i++){
Student s = new Student("teemo", 2);
out.writeObject(s);
}
out.close();
}
输出结果:
teemo:2
teemo:2
teemo:2
补充1:关于序列化ID的问题。
http://www.blogjava.net/invisibletank/archive/2007/11/15/160684.html
如果在写入了Student对象后,修改了Student对象的属性或增加或减少了一些属性(比如添加了salary属性),这个对象就改变了。在反序列化读取的时候会报异常信息:
java.io.InvalidClassException(非法的类),所以需要添加序列化ID:
private static final long serialVersionUID = 1L;
补充2:设置特定属性不序列化。
如设置age年龄为transient或static时,序列化Student对象时,不会把age的值写入到文件中,在读取的时候会自动忽略age的值。输出的结果如下:
teemo:0
teemo:0
teemo:0
补充3:同时写入多个对象,读取的时候需要和写入的顺序一致。
写入三个Student对象后,再写入一个日期Date对象
for(int i=0;i<3;i++){
Student s = new Student("teemo", 2);
out.writeObject(s);
}
out.writeObject(new Date());
读取的时候也要先读取三个Student对象,然后再读取对象赋给Date对象。
for(int i=0;i<3;i++){
Object s = input.readObject();
System.out.println(s);
}
Date date = input.readObject();
System.out.println(date);
输出结果:
teemo:2
teemo:2
teemo:2
Fri Mar 11 00:03:55 CST 2016
五、随机访问流RandomAccessFile
mode 参数指定用以打开文件的访问模式。允许的值及其含意为:
“r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
“rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
“rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
“rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。
例子:
public static void main(String[] args) throws IOException {
//write();
RandomAccessFile ac = new RandomAccessFile("ac.txt","rw");
System.out.println("pointer="+ac.getFilePointer());//0
System.out.println(ac.readInt());
System.out.println("pointer="+ac.getFilePointer());//4
System.out.println(ac.readChar());
System.out.println("pointer="+ac.getFilePointer());//6
System.out.println(ac.readUTF());
System.out.println("pointer="+ac.getFilePointer());//12
ac.seek(4);//跳过前面4个字节(int占四个字节)读取到英文字符
System.out.println(ac.readChar());
}
private static void write() throws FileNotFoundException, IOException {
RandomAccessFile ac = new RandomAccessFile("ac.txt","rw");
//整数,英文字符,中文等等信息
ac.writeInt(6);
ac.writeChar('a');
ac.writeUTF("放假");
ac.close();
}
输出结果:
pointer=0
6
pointer=4
a
pointer=6
放假
pointer=14
a
例:实现文件的断电续传的功能
public static void main(String[] args) throws IOException {
RandomAccessFile source = new RandomAccessFile("d:\\生活大爆炸.rmvb", "rw");
RandomAccessFile target = new RandomAccessFile("d:\\生活大爆炸_copy.rmvb","rw");
RandomAccessFile index = new RandomAccessFile("d:\\index_temp.txt","rw");
// 如果中间断了,下次再启动该程序的时候,需要从一个上次的一个点开始?什么点
if(index.length()==0){
target.setLength(source.length());//
source.seek(0);
target.seek(0);
}else{
long point = index.readLong();
System.out.println(point);
source.seek(point);
target.seek(point);
}
byte[] bs = new byte[1024];
int len = 0;
while ((len = source.read(bs)) != -1) {
target.write(bs, 0, len);
//记录写到哪了
index.seek(0);
index.writeLong(target.getFilePointer());
}
index.close();
source.close();
target.close();
}