IO流
IO流的分类
按流的方向分 | |
输入流 | 读取文件 |
输出流 | 写入文件 |
按数据的类型划分 | |||
字节流 | 字节输入流 | InputStream | 读取字节 |
字节输出流 | OutputStream | 写入字节 | |
字符流 | 字符输入流 | Reader | 读取字符 |
字符输出流 | Writer | 写入字符 |
开发步骤:
- 创建字节输出流对象
- 写数据
- 关闭资源
InputStream、OutputStream、Reader、Writer都是抽象类无法实例化
当针对文件进行操作的时候使用他们的子类FileOutputStream、FileInputStream、FileReader、FileWriter。
字节流
FileOutputStream
字节文件输出流,用于将字节数据写入到文件中。
构造方法
FileOutputStream(String name)
参数中指定一个文件的路径,该文件如果不存在会创建该文件,每次执行都会覆盖之前写入的内容。
FileOutputStream(String name, boolean append)
append指定为true的时候为向文件的末尾追加数据。
常用方法
close()
关闭流资源,关闭后就不能对流进行操作了。每次使用io流之后都要进行关闭
write(int b)
一次写一个字节
write(byte[] b)
一次写一个字节数组。
- 输入字符串的时候字符串通过getBytes方法转化成字节数组再通过该方法写入到文件中。
- 可以输入换行符来将输入的内容换行。\r\n
write(byte[] b, int off,int len)
一次写一部分字节数组
异常处理
try…catch…finally…的方式来处理异常。
- 一般将输入输出流对象在try之外声明,在try内new;
- 在catch中抛出异常;
- 在finally中释放资源。一般释放前需要进行非空判断。
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
FileOutputStream fos = null;
// 声明字节输入流对象
try {
fos = new FileOutputStream("b.txt");
// 创建字节输入流对象
fos.write("hello".getBytes());
// 将hello字符串转化成字节数组写入
} catch (IOException e) {
e.printStackTrace();
// 抛出异常
} finally {
if (fos != null) {// 非空判断
try {
fos.close();// 释放资源
} catch (IOException e) {
e.printStackTrace();
// close方法也需要抛出异常
}
}
}
}
}
此时在当前项目下就会创建b.txt文件并写入hello
FileInputStream
字节文件输入流,用于程序读取文件中的数据。
开发步骤
- 创建字节文件输入流对象
- 读数据
- 释放资源
常用方法
int read()
一次读取一个字节,如果读取不到返回-1
int read(byte[] b)
一次读取一个字节数组 (读取实际的字节数),返回值为读取到的字节的长度,如果读取不到返回-1。
import java.io.FileInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream fis=new FileInputStream("b.txt");
int by=0;
while ((by=fis.read())!=-1) {
//read读取的是ASCII码表转化的字节,所以输出时候需要强制转化
System.out.print((char)by);
//因为中文的编码一个中文是两个字节所以该方法无法正常解析中文
}
fis.close();
}
}
/*
hello0
hello1
hello2
hello3
hello4
hello5
????
*/
复制文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 将b.txt复制到a.txt
// 文件读取流对象
FileInputStream fis = new FileInputStream("b.txt");
// 文件写入流对象
FileOutputStream fos = new FileOutputStream("a.txt");
// 读取的同时写入
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
// 分别关闭输入和输出流
fis.close();
fos.close();
}
}
因为复制操作没有进行强制转化所以中文没有出现乱码的情况
一次读取一个字节数组
比起一次读取一个字节来复制文件要快的多,相当于构建了一个缓冲区。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 创建文件字节输入流对象
FileInputStream fis = new FileInputStream("a.txt");
// 创建文件字节输出流对象
FileOutputStream fos = new FileOutputStream("b.txt");
// 创建一个字节数组,初始化长度,最好为1024的倍数
byte[] arr = new byte[1024];
// 定义长度变量
int len = 0;
// 一次读取一个字节数组
while ((len = fis.read(arr)) != -1) {
fos.write(arr, 0, len);
}
fis.close();
fos.close();
}
}
GBK 编码中一个中文为两个字节,多为负数如:
import java.util.Arrays;
public class Test {
public static void main(String[] args){
String s = "你好";
// 转化成字节数组遍历
System.out.println(Arrays.toString(s.getBytes()));
// [-60, -29, -70, -61]
String s2 = "abcd";
System.out.println(Arrays.toString(s2.getBytes()));
// [97, 98, 99, 100]
}
}
BufferedInputStream
字节缓冲输入流,套在某个InputStream外面提供一个缓冲区,可以提高里面的InputStream的性能,无法独立使用。
构造方法
BufferedInputStream(InputStream in)
以默认缓冲区大小构造缓冲输入流对象,参数中需要传入一个InputStream 对象。
BufferedInputStream(InputStream in,int size)
指定缓冲区大小构造缓冲输入流对象,一般情况下默认的已经足够使用了。
常用方法
int read()
和FileInputStream中的read方法相比,BufferedInputStream中的该方法一次会尽可能多的读取数据以减少io的访问次数。
BufferedInputStream中自带8192字节的缓冲区,读取文件中先将缓冲区读满,然后从缓冲区读取数据,这样一来每次读取都是从内存中读取,比FileInputStream每次从硬盘中读取速度要快得多。
int read(byte[] b,int off,int len)
如果 size <= 0,会出现IllegalArgumentException异常。
BufferedOutputStream
字节缓冲输出流
构造方法
BufferedOutputStream(OutputStream out)
用默认的缓冲区大小,来构造一个字节缓冲输出流对象。通常默认大小就够用了。
BufferedOutputStream(OutputStream out,int size)
指定缓冲区大小构造缓冲输出流对象,如果 size <= 0会发生IllegalArgumentException异常。
常用方法
write(int by)
write(byte[] b, int off, int len)
同样相比FileOutputStream中的方法更高效。
flush()
刷新缓冲区的流,强制将当前缓冲区中的数据写入文件中,并清空缓冲区。因为BufferedOutputStream需要先写满缓冲区然后再向文件中写数据,如果数据已经写完但是缓冲区没满系统就会一直等待缓冲区写满,此时就可以使用flush方法强制清空缓冲区并写入文件。
在BufferedOutputStream的close方法中已经包含了flush方法一般只需关闭流就可以了,不需要手动刷新。
字节缓冲流实现复制文件
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
// 字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
// 字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));
// 一次读取一个字节数组
byte[] arr = new byte[1024];
int len = 0;
while ((len = bis.read(arr)) != -1) {
bos.write(arr, 0, len);
}
// 关闭流
bis.close();
bos.close();
}
}
此时实现将a.txt中的内容复制到b.txt中。
字符流
为解决中文乱码的问题而产生的流操作,一次操作一个字符。
编码和解码
编码和解码的格式必须一致不然就会乱码
编码
字符串转为字节数组
getBytes()
使用默认编码格式(GBK)
getBytes(Charset charset)
指定一种编码格式
解码
字节数组转为字符串,String的构造方法
String(byte[] bytes)
使用平台默认编码集(gbk)
String(byte[] bytes,Charset charset)
用指定的编码格式来解码
FileReader
字符读取流,一般仅用于纯文本文件
常用方法
int read(char[] chs)
读取一个字符数组
int read()
读取单个字符
FileWriter
字符写入流,一般仅用于纯文本文件
常用方法
write(int c)//写单个字符
write(char[] cbuf)//写字符数组
write(char[] cbuf, int off, int len)//写字符数组的一部分
write(String str)//写字符串
write(String str,int off, int len)//写字符串的一部分
字符流的write方法可以直接写入字符串,字节流不可以
flush()
刷新流,将缓冲区中的内容写入文件并清空缓冲区。
close();
关闭前必须刷新。区别于BufferedOutputStream的close方法,BufferedOutputStream的close方法中自带了flush方法
将项目下a.txt中的内容复制到b.txt中
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//字符读取流
FileReader fr=new FileReader("a.txt");
//字符写入流
FileWriter fw=new FileWriter("b.txt");
char[]arr=new char[1024];
int len=0;
while ((len=fr.read(arr))!=-1) {
fw.write(arr, 0, len);
}
//释放资源
fr.close();
fw.close();
}
}
InputStreamReader
字符转换输入流,套在字节输入流的外面,一次读取一个或多个字节然后通过编码转化成字符。可以指定编码格式。FileReader是他的子类
InputStreamReader(InputStream in)
构造一个字符转换输入流使用默认编码
InputStreamReader(InputStream in,Charset cs)
构造一个字符转换输入流,指定编码
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Test {
public static void main(String[] args) throws IOException {
InputStreamReader isr=new InputStreamReader(new FileInputStream("a.txt"),"gbk");
char[]arr=new char[1024];
int len=0;
while((len=isr.read(arr))!=-1) {
System.out.print(new String(arr, 0, len));//你好
isr.close();
}
}
}
OutputStreamWriter
字符转换输出流,套在字节输出流的外面,可以指定编码格式。FileWriter是他的子类。
OutputStreamWriter(OutputStream out)
使用默认的编码格式构造一个字符转换输出流对象
OutputStreamWriter(OutputStream out, Charset cs)
使用指定编码格式构造一个字符转换输出流对象
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class Test {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("b.txt"), "gbk");
osw.write("你好");
osw.close();
}
}
效果:
BufferedReader
字符缓冲读取流,自带缓冲区,套在字符流的外面提高字符流的效率,也可以指定缓冲区大小,不过通常默认大小已经足够大了。
常用方法
readLine()
BufferedReader特有方法,一次读取一行,但是读不到换行符,只能读可见的部分。
BufferedWriter
字符缓冲写入流,自带缓冲区,套在字符流的外面提高字符流的效率
常用方法
newLine()
BufferedWriter特有的方法,写入一个行的分隔符号相当于 \r\n
flush()
关闭流之前要先刷新,如果通过循环写入行的话每行都要刷新。
用字符缓冲流复制文件
将a.txt的内容复制到b.txt
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
String line = null;
while ((line = br.readLine()) != null) {// 一次复制一行
bw.write(line);
bw.newLine();// 换行
bw.flush();// 每行都刷新
}
bw.close();
br.close();
}
}
综合运用实例
从一个包含人名的文件中随机获取一个人
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
public class Test {
public static void main(String[] args) throws IOException {
//使用BufferedReader读取文件
BufferedReader br=new BufferedReader(new FileReader("b.txt"));
//存入集合中
ArrayList<String> list=new ArrayList<String>(5);
//一次读一行
String line=null;
while((line=br.readLine())!=null) {
//添加到集合
list.add(line);
}
br.close();
//创建随机对象
Random r=new Random();
//通过脚标获取需要的人
String name=list.get(r.nextInt(list.size()));
//输出选中的人
System.out.println(name);
}
}
常用的一些流
数据流
DataInputStream
数据输入流,可以从文件内读取基本类型,需要套在一个输入流的外面使用。
DataInputStream(InputStream in)
DataOutputStream
数据输出流,直接向文件内写入基本类型,需要套在一个输出流外面使用,写入的数据大部分看不懂,需要用DataInputStream读取。
DataOutputStream(OutputStream out)
实例:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
DataOutputStream dos=new DataOutputStream(new FileOutputStream("b.txt"));
dos.writeInt(10);
dos.writeShort(100);
dos.writeByte(120);
dos.writeDouble(13.34);
dos.writeFloat(12.56F);
dos.writeBoolean(true);
dos.writeChar('a');
dos.close();//释放资源
//此时b.txt中内容dx@*�z酖瓵H趺,无法阅读,需要通过DataInputStream
DataInputStream dis=new DataInputStream(new FileInputStream("b.txt"));
System.out.println(dis.readInt());//10
System.out.println(dis.readShort());//100
System.out.println(dis.readByte());//120
System.out.println(dis.readDouble());//13.34
System.out.println(dis.readFloat());//12.56
System.out.println(dis.readBoolean());//true
System.out.println(dis.readChar());//a
dis.close();
}
}
内存操作流
适用于临时存储数据,存在内存中。
byteArrayInputStream
内存操作输入流
ByteArrayInputStream(byte[] buf)
将字符数组转化成内存操作输入流对象
byteArrayOutputStream
内存操作输出流
ByteArrayOutputStream()
创建一个新的字节数组输出流。 缓冲区容量最初为32字节,也支持指定容量。
方法
write()
只能写入字节或字节数组,写入到内存中。
close()
内存流不需要执行该方法
toByteArray()
将存入内存中的数组转化成字节数组返回
实例:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int i = 0; i < 5; i++) {
baos.write(("hello" + i).getBytes());
baos.write("\r\n".getBytes());
}
// 不需要释放资源
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
int b = 0;
while ((b = bais.read()) != -1) {
System.out.print((char) b);
}
}
}
/*
hello0
hello1
hello2
hello3
hello4
*/
打印流
PrintWriter
字符打印流,属于输出流,只能写数据,不能读。
构造方法
PrintWriter(File file)
PrintWriter(String fileName)
如果一个类中的构造方法里面有File对象或者String类型数据说明这个类可以对文本文件直接操作
例如:
FileInputStream
FileOutputStream
FileWriter
FileReader
PrintWriter
都是可以直接操作文本文件的。
PrintWriter(OutputStream out, boolean autoFlush)
PrintWriter(Writer out, boolean autoFlush)
第二个参数为true时表示对象可以自动刷新。
方法
print(XXX)
println(XXX)
向文件中打印,参数可以是任意类型
write(xxx)
向文件中写入,参数可以是一个字符,字符串,字符数组。
实例1:
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class Test {
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter(new FileWriter("b.txt"), true);//启动自动刷新
pw.println("java");
pw.println("hello");
pw.println("word");
pw.close();
}
}
向b.txt中打印内容:
实例2:
将a.txt中的内容复制到b.txt用PrintWriter改进之前的方法
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class Test {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("b.txt"), true);// 自动刷新
String line = null;// 一次读一行
while ((line = br.readLine()) != null) {
pw.println(line);// 打印一行并换行
}
// 释放资源
br.close();
pw.close();
}
}
printStream
字节打印流,System.out返回的就是这个流对象
标准的输入输出流
InputStream in <- System.in
PrintStream out <- Syste.out
标准的输入流
System.in
返回值是:InputStream 输入流对象
除了Scanner可以实现键盘录入外还可以通过BufferedReader
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Test {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
System.out.println("输入内容:");
String line = br.readLine();
System.out.println("输入的内容是:"+line);
}
}
/*
输入内容:
hello
输入的内容是:hello
*/
包装者设计模式:
BufferedReader里面包装字符转换输入流InputStreamReader,InputStreamReader再包装System.in。
标准的输出流
Syste.out
返回值是PrintStream 输出流对象
PrintStream
方法
println(XXX)
println()
print(XXX)
在控制台打印输出
实例:
使用BufferedWriter 去包装System.out
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class Test {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("hello");
bw.newLine();
bw.write("word");
bw.newLine();
bw.write("java");
bw.newLine();
bw.flush();
bw.close();
}
}
/*
hello
word
java
*/
合并流
SequenceInputStream
构造方法
SequenceInputStream(InputStream s1, InputStream s2)
将两个流合并为一个流进行操作。
实例:
将a.txt和b.txt合并写入到c.txt中
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
public class Test {
public static void main(String[] args) throws IOException {
FileInputStream f1 = new FileInputStream("a.txt");
FileInputStream f2 = new FileInputStream("b.txt");
// 创建合并流
SequenceInputStream sis = new SequenceInputStream(f1, f2);
// 写入c.txt 使用字节缓冲输入流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("c.txt"));
byte[] by = new byte[1024];
int len = 0;
while ((len = sis.read(by)) != -1) {
bos.write(by, 0, len);
bos.flush();// 如果是写图片文件时候要刷新
}
sis.close();
bos.close();
}
}
最后创建的c.txt实际是a.txt写完紧接着写b.txt
SequenceInputStream(Enumeration<? extends InputStream> e)
另一种构造方法,利用Vector集合中elements()方法返回Enumeration接口对象,来提供多个流合并的操作。
实例2:
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
public class Test {
public static void main(String[] args) throws IOException {
// 将a.txt\b.txt\c.txt合并到d.txt
FileInputStream f1 = new FileInputStream("a.txt");
FileInputStream f2 = new FileInputStream("b.txt");
FileInputStream f3 = new FileInputStream("c.txt");
// 定义Vector集合
Vector<FileInputStream> v = new Vector<FileInputStream>();
// 添加到集合中
v.add(f1);
v.add(f2);
v.add(f3);
// 特有方法
Enumeration<FileInputStream> enu = v.elements();
// 创建合并流对象
SequenceInputStream sis = new SequenceInputStream(enu);
// 字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("d.txt"));
byte[] by = new byte[1024];
int len = 0;
while ((len = sis.read(by)) != -1) {
bos.write(by, 0, len);
bos.flush();
}
sis.close();
bos.close();
}
}
#效果:
序列化对象流
序列化ObjectOutputStream
将对象按照流的方式存储到文本文件中或者再网络中传输,对象转化为流数据。
Serializable接口
需要被序列化的自定义类需要实现该接口,该接口只用作标记,接口中没有构造方法、方法和字段。
- 实现该接口的类被称作标记类,在序列化中会产生序列化版本id。
- 实现该接口后可以点击类名称上的黄色警告线添加一个固定id值,否则每次修改该类都会变动id值。
- id值与流中的id不匹配,例如,通过ObjectOutputStream将对象写入文本之后又修改了类然后再用ObjectInputStream读取就会发生InvalidClassException异常。
transient关键字
自定义类中被transient关键字修饰的属性不参与序列化
方法
writeObject(Object obj)
将自定义类的对象序列化写入ObjectOutputStream流
反序列化ObjectInputStream
将文本文件中的流对象或者网络传输中的流对象还原成对象,流数据转化为对象。
方法
readObject()
从ObjectInputStream中读取一个对象
实例:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Student implements Serializable{
//序列化的类必须实现Serializable接口,只是用作标记,Serializable中没有方法,没有字段
private static final long serialVersionUID = -3251042750794115146L;
//产生一个随机固定的id,点类上的黄色警告线就会生成
//自定义类
private String name;
transient int age;//被transient修饰的属性不参与序列化
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
public class Test {
public static void main(String[] args) throws Exception {
//序列化ObjectOutputStream,对象转流
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("a.txt"));
Student s=new Student("张三", 21);
oos.writeObject(s);
//此时a.txt中内容看不懂,乱码只能用 ObjectInputStream反序列化去读取
oos.close();
//反序列化ObjectInputStream
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("a.txt"));
Object obj = ois.readObject();
System.out.println(obj);//Student [name=张三, age=0]
//年龄被transient修饰不参与序列化
ois.close();
}
}
属性集合类
Properties
继承自Hashtable的双列集合,集合中的每一对键值对的类型都为String, 可保存在流中或从流中加载。
构造方法
Properties()
创建一个没有默认值的空属性列表。 Properties集合没有泛型结构,因为所有参数都是String类型的。
方法
虽然继承自Hashtable可以使用Hashtable中的put方法添加元素,但是因为put方法允许添加不属于String类型的数据,所以应该使用该类特有的方法来添加元素。
setProperty(String key, String value)
添加键值对,并且强制要求都为String类型
stringPropertyNames()
获取所有的键的集合
getProperty(String key)
用指定的键寻找对应的值
store(Writer writer,String comments)
将属性集合类添加到流中,writer是一个输出流对象,comments是对集合中的键值对的说明。
load(Reader reader)
从文本文件中加载键值对到属性集合类中,reader是一个输入流对象,文本文件中的键值对是以 键=值的格式存储的。
实例:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
public class Test {
public static void main(String[] args) throws IOException {
Properties p=new Properties();
//添加元素
p.setProperty("张三", "21");
p.setProperty("李四", "22");
p.setProperty("王五", "20");
//保存到流中
p.store(new FileWriter("a.txt"), "name,age");
//读取a.txt中的键值对
Properties p2=new Properties();
p2.load(new FileReader("a.txt"));
//遍历p2
Set<String> kset = p2.stringPropertyNames();
for (String k : kset) {
String v = p2.getProperty(k);
System.out.println("姓名:"+k+"\t年龄:"+v);
}
}
}
a.txt中的内容