缓冲字节流&缓冲字符流
Java的缓冲流本身并不具备对文件的读取写入功能,他的作用只是在文件流的基础上,再对文件流进行包装,即在文件流在包上一层缓冲流,这样会大大提高IO流对文件读取写入的效率。因此,缓冲流就是一种处理流。在读写数据时提供缓冲功能。应用程序、缓冲流和底层字节流之间的关系如图所示:
当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因此,缓冲流还是很重要的,我们在IO操作时记得加上缓冲流来提升性能。
BufferedInputStream
和BufferedOutputStream
为缓冲字节流,它们的构造方法中分别接收InputStream
和OutputStream
类型的参数作为对象通过内部缓存数组来提高操作流的效率。
//将图片复制到另外一个位置:
long start = System.currentTimeMillis(); //程序开始执行的计时
BufferedInputStream bi = null;
BufferedOutputStream bo = null;
try {
File file1 = new File("src\\IOstream\\img1.jpg");
File file2 = new File("src\\IOstream\\img3.jpg");
FileInputStream fi = new FileInputStream(file1);
FileOutputStream fo = new FileOutputStream(file2);
bi = new BufferedInputStream(fi); //即把文件流包进缓冲流里去。
bo = new BufferedOutputStream(fo); //即把文件流包进缓冲流里去。
byte[] bytes = new byte[10];
int leng;
while ((leng = bi.read(bytes)) != -1) {
bo.write(bytes,0,leng);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bi != null){
try {
bi.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bo != null){
try {
bo.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis(); //程序结束时结束的计时
System.out.println(end - start); //执行程序所需的时间
在缓冲流中,输出方式还有另外一种方法:
//readLine()的使用:
//方式1:IO流输出写入的基本方法
// char[] chars = new char[5];
// int leng;
// while ((leng = br.read(chars)) != -1) {
// bw.write(chars,0,leng);
//方式2:方法一:readLine()方法--->不换行,输出数据为一整行
// String str;
// while ((str = br.readLine()) != null){ //默认不换行
// bw.write(str);
// }
//方式2:方法二:readLine()方法--->换行,输出数据行列分明
String str;
while ((str = br.readLine()) != null){
bw.write(str);
bw.newLine(); //再后面追加一行
}
缓冲字符流有BufferedReader
和BufferedWriter
,其增加了 缓存机制,大大提升了对与文件的读写操作的效率。同时,提供了更方便的按行读取的方法:readLine(); 处理文本时,我们一般可以使用缓冲字符流。
-
readLine()
:输出一行数据,默认不换行。 -
readLine()
方法是BufferedReader
特有的方法,可以对文本文件进行更加方便的读取操作。 - 写入一行后要记得使用newLine()方法换行。
//缓冲字符流加上readLine()的使用:
BufferedReader br = null;
BufferedWriter bw = null;
try {
File file1 = new File("src\\IOstream\\text2.txt");
File file2 = new File("src\\IOstream\\text3.txt");
FileReader fr = new FileReader(file1);
FileWriter fw = new FileWriter(file2);
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
//方式1:IO流输出写入的基本方法
// char[] chars = new char[5];
// int leng;
// while ((leng = br.read(chars)) != -1) {
// bw.write(chars,0,leng);
//方式2:方法一:readLine()方法--->不换行,输出数据为一整行
// String str;
// while ((str = br.readLine()) != null){ //默认不换行
// bw.write(str);
// }
//方式2:方法二:readLine()方法--->换行,输出数据行列分明
String str;
while ((str = br.readLine()) != null){
bw.write(str);
bw.newLine(); //再后面追加一行
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
转换流
转换流有InputStreamReader
和OutputStreamWriter
这两个,都属于字符流;可以用来实现将字节流转换为字符流,转换流也属于处理流的一种。它的原理有点类似于: 解码-->程序-->编码 这个过程。
-
解码:InputStreamReader:将字节流输入的转换成字符流输入;
-
编码:OutputStreamWriter:将字符流输出的转换成字节流输出;
-
提供字节流与字符流之间的转换;
InputStreamReader isf = null;
try {
FileInputStream file = new FileInputStream("src\\IOstream\\text3.txt");
// InputStreamReader isf = new InputStreamReader(参数1,参数2); 参数2可以自定义字符流的格式
isf = new InputStreamReader(file,"GBK");
char[] chars = new char[10];
int leng;
while ((leng = isf.read(chars)) != -1){
String str = new String(chars,0,leng);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isf != null){
try {
isf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//对字节流文件转换为字符流,再对其转换为字节流进行写入。即 解码-->程序-->编码
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
FileInputStream file1 = new FileInputStream("src\\IOstream\\text3.txt");
FileOutputStream file2 = new FileOutputStream("src\\IOstream\\text2.txt");
isr = new InputStreamReader(file1,"UTF-8");
osw = new OutputStreamWriter(file2,"GBK");
char[] chars = new char[10];
int leng;
while ((leng = isr.read(chars)) != -1){
osw.write(chars,0,leng);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isr != null){
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (osw != null){
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
对象流
数据流只能实现对基本数据类型和字符串类型的读写,并不能读取对象(字符串除外),如果要对某个对象进行读写操作,我们需要学习一对新的处理流---对象流:
对象流有ObjectInputStream和ObjectOutputStream;可用于存储和读取基本数据类型数据或者对象的处理流。可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
其中,包含着序列化与反序列化的机制操作:
-
序列化:用ObjectOutputStream保存基本数据类型或对象机制,将内存中的java对象保存到磁盘或者通过网络传输出去。
-
反序列化:用ObjectInputStream读取基本数据类型或者对象机制,还原序列化的数据。
-
注意一点,不能序列化static和transient修饰的成员变量。
-
对象流不仅可以读写对象,还可以读写基本数据类型。
-
系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。
-
重点:序列化思想
//序列化:
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(
new FileOutputStream("src\\IOstream\\ObjectIO\\data.dat"));
oos.writeObject("这是一个序列化写入,反序列化读取的程序!");
oos.flush(); //刷新操作
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//反序列化:
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(
new FileInputStream("src\\IOstream\\ObjectIO\\data.dat"));
Object obj = ois.readObject();
String str = (String)obj;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果需要自定义类时,自定义类需要满足以下要求,才可以序列化:
1、需要实现接口:Serializable
2、当前类提供一个全局常量:serialVersionUID
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; //(后面的值随意填写)
3、当前的类需要实现Serializable 接口,还必须保证其内部所有属性都为可序列化的。
//序列化自定义类:
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(
"src\\IOstream\\ObjectIO\\person.dat"));
oos.writeObject(new Person("XXXX",11,new Account("xxxxx")));
oos.flush(); //记得刷新数据
oos.writeObject(new Person("LLLL",22,new Account("lllll")));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//反序列化自定义对象:
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(
new FileInputStream("src\\IOstream\\ObjectIO\\person.dat"));
//不能定义obj对象,然后再去给Person转换,只能直接Person接ois.readObject()
Person p = (Person)ois.readObject();
Person p1 = (Person)ois.readObject();
System.out.println(p);
System.out.println(p1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
其中需要注意的有---自定义类需要满足一些要求:
如果时自定义B类属性给另一个A类使用,也需要满足这些要求:
RandomAccessFile随机存取数据
之前所使用的数据流,读取写出数据都是从头开始,不能自定义插入的位置,而RandomAccessFile他就可以解决这个问题,因为它可以指定位置读,指定位置写的一个类,通常开发过程中,多用于多线程下载一个大文件:
1、直接继承于java.lang.object类,实现DataInput和DataOutput接口。
2、既可作为输入流也可作为输出流。
3、作为输出流。文件存在,写出数据到文件中,将会从文件数据的开头开始覆盖,有多少覆盖多少,其他的无变化。
4、可以通过相关操作,实现RandomAccessFile的插入数据操作。
5、RandomAccessFile(File,”rw”);后面可以设置读取方式,如下表:
后缀 | 说明 |
---|---|
"rw" |
可读可写,当文件不存在时会自动创建新文件 |
"rws" |
打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
"rwd" |
打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 |
"r" |
只读取文件数据 |
public void test2() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile(new File("src\\IOstream\\ObjectIO\\text.txt"),"rw");
// raf1.writeBytes("xyz") ;
// raf1.seek(3); //字符串为3的索引开始进行替换
raf1.seek((new File("src\\IOstream\\ObjectIO\\text.txt").length()));
//每次在字符串末尾加上xyz
raf1.write("xyz".getBytes()); //将字符中的数替换成xyz
raf1.close();
}
如果想要从指定位置插入数据也可以:
- 读取文件数据
- 定义字符串,可以用来存储数据
- 定义,写入字符串中保存
- 再次定位,插入数据
- 把保存好的数据插入文件中
- 结束
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(new File("src\\IOstream\\ObjectIO\\text.txt"),"rw");
raf.seek(3); //将指针指向角标为3的位置
byte[] bytes = new byte[5];
int leng;
//将角标为3的位置之后的所有数据保存到strbf中
StringBuilder strbf = new StringBuilder((int)new File("src\\IOstream\\ObjectIO\\text.txt").length());
while ((leng = raf.read(bytes)) != -1){
strbf.append(new String(bytes,0,leng));
}
//调回指针,写入数据
raf.seek(3);
raf.write("xyz".getBytes());
//将StringBuilder的数据再写入文件中:
raf.write(strbf.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//最后将会在角标为3的位置插入xyz,而前面与后面的其他数据不会发生改变。
最后,到这里Java的IO流学习也已经到了尾声了,如果有其他需要扩展的欢迎补充!!!