目录
常用流对象
1.文件字节流
FileInputSream通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本文件等)。Java也提供了FileReader专门读取文本文件
FileOutputStream通过字节的方式写数据到文件中,适合所有类型的文件。Java也提供了FileWriter专门写入文本文件
1.1文件字节输入流
我的D盘下有一张jpg格式的图片,现在用字节输入流实现用程序读取这张图片的字节信息
1.2文件字节输出流
先用字节输入流,将文件读取到程序当中,然后将程序当中的数据基于字节流的方式保存到外部的文件当中
public class FileStreamDemo {
public static void main(String[] args) {
FileInputStream fis=null;
FileOutputStream fos=null;
try{
//创建文件字节输入流对象
fis=new FileInputStream("d:/sky.jpg");
//创建文件字节输出流对象
fos=new FileOutputStream("d:/aabb.jpg");
int temp=0;
while ((temp=fis.read())!=-1){
fos.write(temp);//读取指定输入流的路径的字节,读一个字节写一个字节,将字节通过输出流写到了指定的文件当中
}
//将数据从内存中写入到磁盘中
fos.flush();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
if (fis!=null){
fis.close();
}
if (fos!=null){
fos.close();
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
打开D盘,成功看到将 sky.jpg读取进程序,然后程序中的输出流又将指定的字节写入指定的文件(aabb.jpg)中
1.3通过缓冲区提高读写效率
1.3.1方法一
通过创建一个指定长度的字节数组作为缓冲区,以此来提高IO流的读写效率。该方式适用于读取较大图片时的缓冲区定义。注意:缓冲区的长度一定是2的整数幂。一般情况下1024长度较为合适
就好比现在有一袋两百斤的米需要拿回家,而你现在却是每次拿一粒米回家,这样的话效率实在太低。我们可以用一个包,每次运二十斤米回家,这样,转运的效率大大提高。
没有使用缓冲区的例子:
读一个写一个的效率太低了
那么就可以通过缓冲区提高读写效率
现在用的是字节输入流和字节输出流,所以可以定义一个字节数组,每次把读取的字节放进数组里面,读取的时候就一个数组一个数组的读取,效率大大提高。
使用了缓冲区的例子:
这里的write()方法有三个参数,第一个是要读取的数组,第二个是读取的起始位置的索引,第三个是要读取的数组长度。(这里在进行write时,肯定每次要把数组里读到的所有字节都要取出来,所以长度为每次读到的长度temp)
1.3.2方法二
通过创建一个字节数组作为缓冲区,数组长度是通过输入流对象的available()返回当前文件的预估长度来定义的。在读写文件时,是在一次读写操作中完成文件读写操作的。注意:如果文件过大,那么对内存的占用也是比较大的。所以大文件不建议使用该方法。
就好比把两百斤的米一次性运回家,不分批运。
与前面同样的例子,使用方法二进行改进后的地方用大括号括了起来
1.4字节缓冲流
BufferedInputStream字节输入缓冲流 BufferedOutputStream字节输出缓冲流
java缓冲流本身并不具有IO流的读取和写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来,因此缓冲流是一种处理流(包装流)。
当对文件或者其他数据进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。
因此,缓冲流还是很重要的,我们在IO操作时记得加上缓冲流来提高性能。
BufferedInputStream和BufferedOutputStream这两个流是缓存字节流,通过内部缓存数组来提高操作流的效率
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class FileStreamBuffered3Deomo {
public static void main(String[] args) {
FileInputStream fis=null;
FileOutputStream fos=null;
BufferedInputStream bis=null;//缓存输入字节流
BufferedOutputStream bos=null;//缓存输出字节流
try{
fis=new FileInputStream("d:/sky.jpg");
bis=new BufferedInputStream(fis);//也可以在bis的构造器里面构造fis
//这样也是可行的 bis=new BufferedInputSream(new FileInputStream("d:/sky.jpg"));
fos=new FileOutputStream("d:/ff.jpg");
bos=new BufferedOutputStream(fos);
//在缓冲区中,byte数组长度默认是8192,是2^13
//在缓冲流当中已经有byte数组了,所以我们不需要自己再定义缓冲字节数组了
//把管道定义好后就可以读取数据了
int temp=0;
while ((temp=bis.read())!=-1){
bos.write(temp);
}
bos.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
//注意:关闭流的顺序,后开的先关闭
if (bis!=null){
bis.close();
}
if (fis!=null){
fis.close();
}
if (bos!=null){
bos.close();
}
if (fos!=null){
fos.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
1.5定义文件拷贝工具类
每次拷贝文件时都要进行写一段代码,我们现在直接定义一个工具类,里面有拷贝文件的方法,用的时候直接用类名调用即可
2.文件字符流
前面介绍的文件字节流可以处理所有的文件,如果我们处理的是文本文件,也可以使用文件字符流,它以字符为单位进行操作。
2.1文件字符输入流( FileReader)
注意:read()方法,文件字符输入流调用,返回的是文本文件当中字符的Unicode值,而字节输入流调用read方法返回的就是字节转换为的整数,是ASCII码值
public class FileReaderDemo {
public static void main(String[] args) {
FileReader frd=null;
try {
//实例化文件字符输入流FileReader对象
frd=new FileReader("d:/a.txt");
int temp=0;
while ((temp=frd.read())!=-1){//返回的是int值,是文本当中字符的十进制的Unicode值
System.out.println((char)temp);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (frd!=null){
frd.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
2.2文件字符输出流(FileWriter)
下面对FileWriter的使用进行一个演示
FileWriter发现我们给定的路径中没有这个文件时,会自动帮我们把这个文件创出来
write()方法里面可以是字符串,此方法将这个字符串直接写进文件
\r\n是表示换行的意思
如果用多个filewriter操作同一个文件的话,如果没有给定是否追加的指定,那么默认的就是覆盖
此时可见,第一个FileWriter写入文件的内容已经被第二个FileWriter对象写入的内容覆盖掉
由于如果用多个filewriter操作同一个文件的话,如果没有给定是否追加的指定,那么默认的就是覆盖
给定追加指定的方式是在FileWriter创建的时候后面给一个boolean类型的参数true
如下,fw写入文件的内容没有被fw2覆盖掉,因为fw2在创建的时候追加了指定true(表示是在原来的基础上进行追加)
2.3使用字符流实现文本文件的拷贝处理
输入流和输出流先关闭谁都可以
import java.io.FileReader;
import java.io.FileWriter;
public class FileCopyTools2 {
public static void main(String[] args) {
FileReader fr=null;
FileWriter fw=null;
try{
fr=new FileReader("d:/wxx.txt");
fw=new FileWriter("d:/wxx2.txt");
char[] buffer=new char[1024];
int temp=0;
while ((temp=fr.read(buffer))!=-1){
fw.write(buffer,0,temp);
}
fw.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
fr.close();
fw.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
2.4通过缓冲区提高读写效率
与字节流不同的是,
- 字符流的缓冲区采用的是char类型的数组,(因为现在操作的对象是字符流)而字节流使用的是byte类型的数组
- 定义char类型的数组的时候只能自己定义一个具体的长度,不能像字节流一样通过输入流采用available方法来获取数组长度
其余的地方没有区别
2.5字符缓冲流
BufferedReader字符输入缓冲流 BufferedWriter字符输出缓冲流
- BufferedReader字符输入缓冲流
BufferedReader提供了更方便的按行读取的方法:readLine();此方法返回的是String类型的数据
在使用字符流读取文本文件时,我们可以使用该方法以行为单位进行读取。
public class BufferedReaderDemo {
public static void main(String[] args) {
FileReader fr=null;
BufferedReader br=null;
try{
fr=new FileReader("d:/wxx.txt");
br=new BufferedReader(fr);
String temp="";//定义String类型是因为readLine方法读取出来的是文本内容,为String类型
while ((temp=br.readLine())!=null){
System.out.println(temp);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if (fr!=null){
fr.close();
}
if (br!=null){
br.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
- BufferedWriter字符输出缓冲流
BufferedWriter是针对字符输出流的缓冲流对象,在字符输出缓冲流中可以使用newLine();方法实现换行处理
2.6通过字符缓冲流实现文本文件的拷贝
public class FileCopyTools3 {
public static void main(String[] args) {
CopyFile("d:/wxx.txt","d:/wxx3.txt");
}
/**
* 基于字符缓冲流实现文本文件的拷贝
*/
public static void CopyFile(String src,String des){
BufferedReader br=null;
BufferedWriter bw=null;
try{
br=new BufferedReader(new FileReader(src));
bw=new BufferedWriter(new FileWriter(des));
String temp="";
while ((temp=br.readLine())!=null){
bw.write(temp);
bw.newLine();
}
bw.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if (br!=null){
br.close();
}
if (bw!=null){
bw.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
2.7通过字符缓冲流为文件中的内容添加行号
public class LineNumberDemo {
public static void main(String[] args) {
BufferedReader br=null;
BufferedWriter bw=null;
try{
br=new BufferedReader(new FileReader("d:/wxx.txt"));
bw=new BufferedWriter(new FileWriter("d:/wxx4.txt"));
String temp="";
int num=1;
while ((temp=br.readLine())!=null){
bw.write(num+":"+temp);
bw.newLine();
num++;
}
bw.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try{
}catch (Exception e){
e.printStackTrace();
}
}
}
}
可见到,我们写出了一个叫wxx4.txt的文件,是通过wxx.txt文件读取出来的内容进行写入的
3.转换流
3.1通过转换流实现键盘输入控制台输出
InputStreamReader(把字节输入流转换为字符流) OutputStreamWriter用来实现将字节流转化成字符流。
比如以下场景:
- System.in(System类下有一个静态的变量in)是字节流对象,代表键盘的输入,如果我们想按行接收用户的输入时,就必须用到缓冲字符流BufferedReader特有的方法readLine(),但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象,这时候我们的转换流InputStreamReader就派上用场了。
这个时候我们可以看见输入的内容为a,而把这个内容输出却为97。
因为InputStream的read方法是基于字节的形式来获取用户输入的内容。
而Reader的readline方法可以把输入的内容基于字符的形式读取,这时候InputStreamReader就派上用场了。
BufferedReader br=null; br=new BufferedReader(new InputStreamReader(System.in)); //由于BufferedReader构造方法里面必须是Reader对象,但System.in是字节流对象,那么用InputStreamReader处理流,把字节流System.in转换为字符流,再把转化成的这个字符流Reader放到字符缓冲流构造器中即可 //(System类下有一个静态的变量in),System.in是字节流(InputStream)对象
- 而System.out也就是字节流对象,代表输出到显示器,按行读取用户的输入后,并且要将读取的一行字符直接显示到控制台,就需要用到字符流的write(String str)方法,所以我们要使用OutputStreamWriter将字节流转化为字符流。
处理方式和上面讲到的将System.in转换为Writer对象差不多
3.2通过字节流读取文本文件并添加行号
思路:先把字节流转换成字符流(通过“转换流”转换),再把字符流转换成字符缓冲流,然后才能读取一行(字符缓冲流具有readLine方法),才能添加行号。
import java.io.*;
public class LineNumberDemo2 {
public static void main(String[] args) {
BufferedReader br=null;
BufferedWriter bw=null;
try{
br=new BufferedReader(new InputStreamReader(new FileInputStream("d:/wxx.txt")));
bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d:/wxx5.txt")));
String temp="";
int i=1;
while ((temp=br.readLine())!=null){
bw.write(i+":"+temp);
bw.newLine();
i++;
}
bw.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if (br!=null){
br.close();
}
if (bw!=null){
bw.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
4.字符输出流
PrintWriter
在Java的IO流中专门提供了用于字符输出的流对象PrintWriter。该对象具有自动行刷新缓冲字符输出流,特点是可以按行写出字符串,并且可通过println()方法实现自动换行。
下面举一个例子:将D盘里面的wxx.txt文件拷贝到d:/wxx.txt中,并对每一行加上行号处理。
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class LineNumberDemo3 {
public static void main(String[] args) {
BufferedReader br=null;
PrintWriter pw=null;
try{
br=new BufferedReader(new InputStreamReader(new FileInputStream("d:/wxx.txt")));
pw=new PrintWriter("d:/wxx6.txt");//PrintWriter本身就是一个节点流
String temp="";
int i=1;
while ((temp=br.readLine())!=null){
pw.println(i+","+temp);
i++;
}
//PrintWriter这个字符输出流的作用就是用来字符输出的,本身就自带刷新作用,不用调用flush方法了
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if (br!=null){
br.close();
}
if (pw!=null){
pw.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
5.字节数组流
ByteArrayInputStream和ByteArrayOutputSream经常用在需要流和数组之间转化的情况
5.1字节数组输入流
说白了,FileInputStream是把文件当做数据源。ByteArrayInputStream则是把内存中的“字节数组对象”当作数据源。
在Java中,String的getBytes()方法是得到一个操作系统默认的编码格式的字节数组。这个表示在不同情况下,返回的东西不一样。
public class ByteArrayInputDemo {
public static void main(String[] args) {
byte[]arr="abcdefg".getBytes();
ByteArrayInputStream bis=null;
StringBuilder sb=new StringBuilder();
try{
//该构造方法的参数是一个字节数组,这个字节数组就是数据源
bis=new ByteArrayInputStream(arr);
int temp=0;
while ((temp=bis.read())!=-1){
sb.append((char)temp);//把读取到的字节转换为字符,并加到StringBuilder里面
}
System.out.println(sb.toString());
}finally {
try{
bis.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
5.2字节数组输出流
ByteArrayOutputStream流对象是将流中的数据写入到字节数组中
public class ByteArrayOutputDemo {
public static void main(String[] args) {
ByteArrayOutputStream bos=null;
try{
StringBuilder sb=new StringBuilder();
bos=new ByteArrayOutputStream();
bos.write('a');//把a写到字节数组中
bos.write('b');//把b写到字节数组中
bos.write('c');//把c写到字节数组中
byte[] arr=bos.toByteArray();//获得这个字节输出流的字节数组
for (int i=0;i<arr.length;i++){
sb.append((char)arr[i]);
}
System.out.println(sb);
}finally {
try{
if (bos!=null){
bos.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
6.数据流(处理流)
数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串类型。
DataInputStream和DataOutputStream提供了可以存取与机器无关的所有Java基础类型数据(如:int、double、String等)的方法。
6.1数据输出流
用数据输出流向文件里写出数据,文件到底是什么类型的都可以,用什么扩展名都可以,加txt可以,不加txt也可以。
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class DataOutputDemo {
public static void main(String[] args) {
DataOutputStream dos=null;
try{
dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("d:/data2")));
dos.writeChar('a');
dos.writeInt(10);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("我爱吃香菜");//写一个字符串
dos.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if (dos!=null){
dos.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
写出来的文件如下
出现了乱码现象,因为它是基于文件的字节流向文本文件输出的
6.2数据输入流
读取基于数据输出流向文件当中所写出的那些数据,通过数据输入流,把这些数据按照相应的类型读取到我们程序当中
//直接读取数据,注意:读取的顺序一定要与写入的顺序一致,否则不能正确读取数据
此代码是下面进行读取的代码要读取的文本文件的写入的过程
进行文件的内容读取:
在下面可看到,读取的顺序与写入的顺序是一致的
public class DataInputDemo { public static void main(String[] args) { DataInputStream dis=null; try{ dis=new DataInputStream(new BufferedInputStream(new FileInputStream("d:/data.txt"))); //直接读取数据,注意:读取的顺序一定要与写入的顺序一致,否则不能正确读取数据 System.out.println("char:"+dis.readChar()); System.out.println("int:"+dis.readInt()); System.out.println("double:"+dis.readDouble()); System.out.println("boolean:"+dis.readBoolean()); System.out.println("String"+dis.readUTF()); }catch (Exception e){ e.printStackTrace(); }finally { try{ if (dis!=null){ dis.close(); } }catch (Exception e){ e.printStackTrace(); } } } }
运行结果如下:
7.对象流
对象的本质是用来组织的存储数据的,对象本身也是数据。那么,能不能将对象存储到硬盘上的文件中呢?能不能将对象通过网络传输到另一个电脑呢?我们可以通过序列化和反序列化来实现这些需求。
7.1Java对象的序列化和反序列化
序列化和反序列化是什么?
当两个进程远程通信时,彼此可以发送各种类型的数据,无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串信息;我们也可以在网络上直接发送java对象。发送方需要把这个Java对象转换为字节序列(对象的序列化)才能在网络上传送;接收方则需要把字节序列再恢复为Java对象(对象的反序列化)才能正常读取。
对象序列化的作用有如下两种:
- 持久化:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
- 网络通信:在网络上传送对象的字节序列。比如:服务器之间的数据通信,对象传递。
7.2序列化涉及的类和接口
ObjecctOutputStream 代表对象输出流,它的writeObject(Object obj)方法可对参数指定的
obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjecctInputStream代表代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable接口的类的对象才能被序列化。Serializable接口是一个空接口,只起到标记作用。
7.3使用对象流操作基本数据类型
我们前边学到的数据流只能实现对基本数据类型和字符串类型的读写,并不能对Java对象进行读写操作(字符串除外),但是在对象流中除了能实现对基本数据类型进行读写操作以外,还可以对Java对象进行读写操作。
7.3.1适用对象流写出基本数据类型
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class ObjectOutputStreamBasicTypeDemo {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("d:/mytry.txt")));
oos.writeInt(10);
oos.writeDouble(Math.random());
oos.writeChar('a');
oos.writeBoolean(true);
oos.writeUTF("我爱吃香菜");
oos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
7.3.2适用对象流读取基本数据类型数据
读取的是上面写基本数据类型用到的文件
运行代码:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class ObjectInputStreamBasicTypeDemo {
public static void main(String[] args) {
ObjectInputStream ois=null;
try{
ois=new ObjectInputStream(new BufferedInputStream(new FileInputStream("d:/mytry.txt")));
//这个过程和使用数据流是一样的,必须要按照写入的顺序进行读取
System.out.println("int:"+ois.readInt());
System.out.println("double:"+ois.readDouble());
System.out.println("char:"+ois.readChar());
System.out.println("boolean:"+ois.readBoolean());
System.out.println("String:"+ois.readUTF());
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if (ois!=null){
ois.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果如下:
7.4使用对象流操作对象
7.4.1将对象序列化到文件
ObjectOutputStream可以将一个内存中的Java对象通过序列化的方式写入到磁盘中的文件中,被序列化的对象必须要实习Serializable序列化接口,否则就会抛出异常。