I/O 流

1. I/O流介绍 

1.1 什么是I/O流

        I/O指的是Input/Output,IO流:输入输出流。 统称为数据流(IO Stream)

        在Java程序中,对于数据的输入 / 输出操作以流的方式进行;流是从起源到接收的有序数据。JDK提供了各种各样的流类,用以获取不同种类的数据;

1.2 I/O流分类

按流向分:

        ◦ 输入流:程序可以从中读取数据的流

        ◦ 输出流:程序能向其中写入数据的流

按数据传输单位分:

        ◦ 字节流:以字节为单位传输数据的流

        ◦ 字符流:以字符为单位传输数据的流

按功能分:

        ◦ 节点流:用于直接操作目标设备的流

        ◦ 处理流:是对一个已存在的流的连接和封装,通过对数据的处理为程序提供更强大、灵活的读写功能。

1.3 I/O 流的基类 

分类字节输出流字节输入流字符输出流字符输入流
抽象基类OutputStreamInputStreamWriterReader
访问文件FileOutputStreamFileInputStreamFileWriterFileReader
访问数组ByteArrayOutputStreamByteArrayInputStreamCharArrayWriterCharArrayReader
访问管道PipedOutputStreamPipedInputStreamPipedWriterPipedReader
访问字符串StringWriterStringReader
缓冲流BufferedOutputStreamBufferedInputStreamBufferedWriterBufferedReader
转换流OutputStreamWriterInputStreamReader
对象流ObjectOutputStreamObjectInputStream
抽象基类FilterOutputStreamFilterInputStreamFilterWriterFilterReader
打印流PrintStreamPrintWriter
推回输入流PushbackInputStreamPushbackReader
特殊流DataOutputStreamDataInputStream

 1.3.1 InputStream字节输入流

        继承自InputStream的流都是用于向程序中输入数据的,且数据的单位为字节(8位),下图中深色为节点流,浅色为处理流

InputStream基本方法:

1、public abstract int read() throwsIOException

        从输入流中读取数据的下一个字节, 返回读到的字节值.若遇到流的末尾,返回-1

2、public int read(byte[] b) throwsIOException

        从输入流中读取 b.length 个字节的数据并存储到缓冲区数组b中.返回的是实际读到的字节总数

3、public int read(byte[] b, int off, int len)throws IOException

        读取 len 个字节的数据,并从数组b的off位置开始写入到这个数组中

4、public void close() throws IOException

        关闭此输入流并释放与此流关联的所有系统资源

5、public int available() throws IOException

        返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数

6、public skip(long n) throws IOException

        跳过和丢弃此输入流中数据的 n 个字节,返回实现路过的字节数。

1.3.2 OutputStream字节输出流

        继承自OutputStream的流是程序用于向外输出数据的,且数据的单位为字节(8位),下图中深色为节点流,浅色为处理流  

OutputStream主要方法:

1、public abstract void write(int b) throwsIOException

        将指定的字节写入此输出流

2、public void write(byte[] b) throwsIOException

        将 b.length 个字节从指定的 byte 数组写入此输出流

3、public void write(byte[] b, int off, intlen) throws IOException

        将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流

4、public void flush() throws IOException

        刷新此输出流并强制写出所有缓冲的输出字节

5、pulbic void close() throws IOException

        关闭此输出流并释放与此流有关的所有系统资源

1.3.3 Reader字符输入流  

        继承自Reader的流都是用于向程序中输入数据的,且数据的单位为字符(16位),下图中深色为节点流,浅色为处理流  

1、public int read() throws IOException

        读取单个字符,返回作为整数读取的字符,如果已到达流的末尾返回-1

2、public int read(char[] cbuf) throwsIOException

        将字符读入数组,返回读取的字符数

3、public abstract int read(char[] cbuf,int off, int len) throws IOException

        读取 len 个字符的数据,并从数组cbuf的off位置开始写入到这个数组中

4、public abstract void close() throwsIOException

        关闭该流并释放与之关联的所有资源

5、public long skip(long n) throwsIOException

        跳过n个字符。

 1.3.4  Writer字符输出流

        继承Writer的流都是向程序中输出数据,且数据的单位为字符(16 bit);下图中深色为节点流,浅色为处理流

1、public void write(int c) throwsIOException

        写入单个字符

2、public void write(char[] cbuf) throws IOException

        写入字符数组

3、public abstract voidwrite(char[] cbuf, int off, int len) throws IOException

        写入字符数组的某一部分

4、public void write(String str) throwsIOException

        写入字符串

5、public void write(String str,int off, int len) throws IOException

        写字符串的某一部分

6、public abstract void close() throwsIOException

        关闭此流,但要先刷新它

7、public abstract void flush() throwsIOException

        刷新该流的缓冲,将缓冲的数据全写到目的地

 1.4 节点流

节点流为可以从一个特定的数据源(节点)读写数据(如:文件,内存)

类型字符流字节流
File(文件)FileReader;FileWriterFileInputStream;FileOutputStream
Memory ArrayCharArrayReader;CharArrayWriterByteArrayInputStream;ByteArrayOutputStream
Memory StringStringReader;StringWriter
PipePipedReader;PipedWriterPipedInputStream;PipedOutPutStream

1.4.1 FileInputStream

FileInputStream 类创建一个能从文件读取字节的InputStream类,它的两个常用的构造方法如下:

FileInputStream(String filepath)

FileInputStream(FilefileObj)

这两个构造方法都能引发FileNotFoundException异常。这里,filepath是文件的绝对路径,也可是相对路径。fileObj是描述该文件的File对象。

下面的例子创建了两个使用同样磁盘文件且各含一个上面所描述的构造方法的FileInputStreams类:

InputStreamf0 = new FileInputStream("c:\test.txt");

Filef = new File("c:\test.txt");

InputStreamf1 = new FileInputStream(f);

尽管第一个构造方法可能更常用到,而第二个构造方法则允许在把文件赋给输入流之前用File方法更进一步检查文件。当一个FileInputStream被创建时,它可以被公开读取

1.4.2 FileOutputStream

FileOutputStream创建了一个可以向文件写入字节的类OutputStream,它常用的构造方法如下:

FileOutputStream(StringfilePath)

FileOutputStream(FilefileObj)

FileOutputStream(StringfilePath, boolean append)

它们可以引发IOException或SecurityException异常。这里filePath是文件的绝对路径,也可以是相对路径。fileObj是描述该文件的File对象。如果append为true,则表示追加。FileOutputStream 的创建不依赖于文件是否存在。在创建对象时,FileOutputStream会在打开输出文件之前就创建它。这种情况下如果试图打开一个只读文件,会引发一个IOException异常

1.4.3 FileReader

FileReader类创建了一个可以读取文件内容的Reader类。它最常用的构造方法显示如下:

FileReader(StringfilePath)

FileReader(FilefileObj)

每一个都能引发一个FileNotFoundException异常。

1.4.4 FileWriter

FileWriter 创建一个可以写文件的Writer类。它最常用的构造方法如下:

FileWriter(StringfilePath)

FileWriter(StringfilePath, boolean append)

FileWriter(FilefileObj)

它们可以引发IOException或SecurityException异常。这里,filePath是文件的绝对路径,fileObj是描述该文件的File对象。如果append为true,输出是附加到文件尾的。FileWriter类的创建不依赖于文件存在与否。在创建文件之前,FileWriter将在创建对象时打开它来作为输出。如果试图打开一个只读文件,将引发一个IOException异常。

 1.5 文件复制

实现文件复制(使用字节流):

package cn.sz.gl.test05;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class Test {

	public static void copyFileByStream(File source, File target) {
		// 判断源文件是否存在,如果不存在,就退出程序
		if (!source.exists()) {
			System.out.println("源文件不存在,程序退出");
			System.exit(0);
		}
		// target.getParentFile()表示用来获取目标对象的父目录对象
		// 如果目标文件路径不存在,就创建
		if (!target.getParentFile().exists()) {
			target.getParentFile().mkdirs();
		}

		InputStream is = null;
		OutputStream os = null;

		try {
			// 准备输入流
			is = new FileInputStream(source);
			// 准备输出流
			os = new FileOutputStream(target);
			// 准备一个数组,用来存放读写的数据
			byte[] b = new byte[1024];
			int len = 0;
			// read(b)实现读取操作,数据存入b数组,返回读取长度给len,当所有内容都读取完毕,len=-1
			while ((len = is.read(b)) != -1) {
				// 实现写的操作
				os.write(b, 0, len);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (is != null) {
					is.close();
				}
				if (os != null) {
					os.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public static void main(String[] args) {
		File source = new File("D:\\java\\Java高级\\myfile\\a.txt");
		File target = new File("D:\\java\\Java高级\\myfile\\b.txt");
		copyFileByStream(source, target);
	}
}

实现文件复制(通过字符流)

package cn.sz.gl.test05;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

public class Test {

	public static void copyFileByWriter(File source, File target) {
		// 判断源文件是否存在
		if (!source.exists()) {
			System.out.println("源文件不存在,程序退出");
			System.exit(0);
		}

		// 判断目标文件路径是否存在,不存在就创建
		if (!(target.getParentFile().exists())) {
			target.getParentFile().mkdirs();
		}

		Reader reader = null;
		Writer writer = null;
		try {
			reader = new FileReader(source);
			writer = new FileWriter(target);
			char c[] = new char[1024];
			int len = 0;
			while ((len = reader.read(c)) != -1) {
				writer.write(c, 0, len);
			}
			writer.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				// 多个资源需要关闭时,原则上应该是先开的后关
				if (writer != null) {
					writer.close();
				}
				if (reader != null) {
					reader.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		File source = new File(
				"D:\\java\\Java高级\\myfile\\a.txt");
		File target = new File(
				"D:\\java\\Java高级\\myfile\\b.txt");
		copyFileByWriter(source, target);
	}
}

 

2.缓冲流(处理流)

缓冲流是建立在相应的节点流之上,对读写的数据提供了缓冲的功能提高了读写的效率,还增加了一些新的方法。

JDK提供四种缓冲流:

BufferedInputStream 可以对任何的InputStream流进行包装

BufferedOutputStream 可以对任何的OutputStream流进行包装

BufferedReader 可以对任何的Reader流进行包装

BufferedWriter 可以对任何的Writer流进行包装

注意:

对于缓冲输出流,写出的数据会先缓存在内存缓冲区中,关闭此流前要用flush()方法将缓存区的数据立刻写出。

关闭过滤流时,会自动关闭缓冲流包装的所有底层流。

2.1 字节缓冲流

字节缓冲分输入缓冲和输出缓冲。

1、 缓冲字节输入流:BufferedInputStream。BufferedInputStream为另一个输入流添加一些功能,即缓冲输入以及支持markreset 方法的能力。

2、 缓冲字节输出流:BufferedOutputStream。输出字节时,先把要输出的字节输出到缓冲区,当手动调用flush()方法或者缓冲区满或者流关闭时才会把数据输出到结点流。

3、字节缓冲输入流的缓冲区默认大写为8k字节。也可以指定缓冲区大小

2.2 字符缓冲流

字符缓冲分输入缓冲和输出缓冲。

1、 BufferedReader 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 mark和 reset方法。新增了readLine()方法用于一次读取一行字符串(以‘\r’或‘\n’认为一行结束)。

2、 BufferedWriter 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

3、字符缓冲输入流的缓冲区默认大写为8k个字符。也可以指定缓冲区大小。

2.3. 转换流

前面已经讲过,Java支持字节流和字符流,但有时需要字节流和字符流之间的转换。

InputStreamReader 和OutputStreamWriter,这两个类是将字节流转换为字符流的类,InputStreamReader 可以将一个InputStream转换为Reader,OutputStreamWriter可以将一个OutputStream转换为Writer。

InputStreamReader有两个主要的构造函数:

InputStreamReader(InputStream in)

// 用默认字符集创建一个InputStreamReader对象

InputStreamReader(InputStream in,String CharsetName)

// 接受已指定字符集名的字符串,并用该字符集创建对象

OutputStreamWriter也有对应的两个主要的构造函数:

OutputStreamWriter(OutputStream in)

// 用默认字符集创建一个OutputStreamWriter对象

OutputStreamWriter(OutputStream in,String CharsetNarme)

// 接受已指定字符集名的字符串,并用该字符集创建OutputStreamWriter对象

为了达到最高的效率,避免频繁地进行字符与字节间的相互转换,最好不要直接使用这两个类来进行读写,应尽量使用BufferedWriter类包装OutputStreamWriter类,用BufferedReader类包装InputStreamReader类。例如:

BufferedWriterout = new BufferedWriter(new OutputStreamWriter(System.out));

BufferedReaderin = new BufferedReader(new InputStreamReader(System.in));

接着从一个实际的应用中来了解InputStreamReader的作用,用一种简单的方式读取键盘上输入的一行字符:

BufferedReader in = newBufferedReader(new InputStreamReader(System.in));

String strLine =in.readLine();

可见,构建BufferedReader对象时,必须传递一个Reader类型的对象作为参数,而键盘对应的System.in是一个InputStream 类型的对象,所以这里需要用到一个InputStreamReader的转换类,将System.in转换成字符流之后,放入到字符流缓冲区之中,之后从缓冲区中每次读入一行数据

例如:

package cn.sz.gl.test05;
 ​
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 ​
 public class Test {
 ​
     public static void main(String args[]) {
         BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
         String str = null;
         while (true) {
             System.out.print("请输入数字:");
             try {
                 str = buf.readLine();
             } catch (IOException e) {
                 e.printStackTrace();
             }
             int i = -1;
             try {
                 i = Integer.parseInt(str);
                 i++;
                 System.out.println("输入的数字修改后为:" + i);
                 break;
             } catch (Exception e) {
                 System.out.println("输入的内容不正确,请重新输入!");
             }
         }
     }
 }

3. 打印流

3.1 打印流构造方法

在整个IO包中,打印流是输出信息最方便的类,主要包括字节打印流(PrintStream)和字符打印流(PrintWriter)。打印流提供了非常方便的打印功能,可以打印任何的数据类型。如:小数、整数、字符串等。

PrintStream和PrintWriter都属于输出流,分别针对输出字节和字符。

PrintStream和PrintWriter提供了重载的print()、println()方法用于多种数据类型的输出。

PrintStream和PrintWriter不会抛出异常,用户通过检测错误状态获取错误信息。

PrintStream和PrintWriter有自动flush 功能。

·PrintStream类有下面几个构造方法:

PrintStream(OutputStream out)

PrintStream(OutputStream out, boolean auotflush)

PrintStream(OutputStream out, boolean auotflush, String encoding)

·PrintWriter类有下面几个构造方法:

PrintWriter(OutputStream out)

PrintWriter(OutputStream out, boolean autoflush)

PrintWriter(Writer out)

PrintWriter(Writerout, boolean autoflush)

其中autoflush控制在Java中遇到换行符(\n)时是否自动清空缓冲区,encoding是指定编码方式。

3.2 打印流常用方法

PrintWriter即使遇到换行符(\n)也不会自动清空缓冲区,只在设置了autoflush模式下使用了println方法后才自动清空缓冲区。PrintWriter相对PrintStream最有利的一个地方就是println方法的行为,在Windows的文本换行是"\r\n",而Linux下的文本换行是"\n",如果希望程序能够生成平台相关的文本换行,而不是在各种平台下都用"\n"作为文本换行,那么就应该使用PrintWriter的println方法时,PrintWriter的println方法能根据不同的操作系统而生成相应的换行符。

控制台打印:

 package cn.sz.gl.test05;
 ​
 import java.io.PrintWriter;
 ​
 public class Test {
 ​
     public static void main(String args[]){
         // 通过System.out为PrintWriter实例化
         PrintWriter out = new PrintWriter(System.out);
         // 向屏幕上输出
         out. println("Hello World!");
         out.close();    //如果此句不写,则没有内容,跟PrintStream有区别
         }
 }
在文件中打印:

 package cn.sz.gl.test05;
 ​
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
 ​
 public class Test {
 ​
     public static void main(String args[]) {
         PrintWriter out = null;
         File f = new File("c:\\temp.txt");
         try {
             // 由FileWriter实例化,则向文件中输出
             out = new PrintWriter(new FileWriter(f));
         } catch (IOException e) {
             e.printStackTrace();
         }
         out.print("Hello World!" + "\r\n");
         out.close();
     }
 }

4. 对象流

4.1 对象的序列化

Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。通过将对象序列化,可以方便的实现对象的传输及保存。

Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等,而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,实现进程间的对象传送,就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。

4.2 对象输入流和输出流

在Java中提供了ObjectlnputStream与ObjectOutputStream这两个类用于序列化对象的操作。使用对象输出流输出序列化对象的步骤,有时也称为序列化。使用对象输入流读入对象的过程,有时也称为反序列化。

这两个类是用于存储和读取对象的输入输出流类,不难想象,只要把对象中的所有成员变量都存储起来,就等于保存了这个对象,之后从保存的对象之中再将对象读取进来就可以继续使用此对象。ObjectInputStream与ObjectOutputStream类,可以帮开发者完成保存和读取对象成员变量取值的过程,但要求读写或存储的对象必须实现了java.io.Serializable接口,但Serializable接口中没有定义任何方法,仅仅被用作一种标记,以被编译器作特殊处理。

如下范例所示:

 import java.io.*;
 public class Person implements Serializable{
     private String name;
     private int age;
     public Person(String name,int age){
         this.name = name;
         this.age = age;
     }
     public String toString(){
         return " 姓名:"+this.name+",年龄:"+this.age;
     }
 }

第2行所中,类Person实现了Serializable接口,所以此类的对象可序列化。下面的范例使用ObjectOutputStream与ObjectInputStream将Person类的对象保存在文件之中

package cn.sz.gl.test05;
 ​
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.OutputStream;
 ​
 public class Test {
 ​
     // 以下方法为序列化对象方法,将对象保存在文件之中
     public static void serialize(File f) throws Exception{
         OutputStream outputFile = new FileOutputStream(f);
         ObjectOutputStream cout = new ObjectOutputStream(outputFile)
         cout.writeObject(new Person("张三",25));
         cout.close();
     }
 ​
     // 以下方法为反序列化对象方法,从文件中读取已经保存的对象
     public static void deserialize(File f) throws Exception {
         InputStream inputFile = new FileInputStream(f);
         ObjectInputStream cin = new ObjectInputStream(inputFile);
         Person p = (Person) cin.readObject();
         System.out.println(p);
     }
 ​
     public static void main(String args[]) {
         File f = new File("SerializedPerson");
         serialize(f);
         deserialize(f);
     }
 }
4.2.1 serialVersionUID 常量

在对象进行序列化或反序列化操作的时候,要考虑 JDK 版本的问题。如果序列化的 JDK 版本和反序列化的 JDK 版本不统一,则可能造成异常。因此在序列化操作中引入了一个serialVersionUID 的常量来验证版本的一致性。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体(类)的serialVersionUID 进行比较。如果相同就认为是一致的,可以进行反序列化,否则就会出现反序列化版本不一致的异常

Idea 配置自动生成序列号:

在需要生成序列化id类上: alt + enter

4.2.2 transient关键字

如果不希望类中的属性被序列化,可以在声明属性之前加上transient关键字。如下所示,下面的代码修改自前面所用到的Person.java程序,在声明属性时,前面多加了一个transient关键字

private transient String name;

private transient int age;

注意:

序列化细节:

1) 被序列化的类的内部的所有属性,必须是可序列化的

2) static,transient修饰的属性,不可以被序列化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值