输入流和输出流
一. 数据流的基本概念
1. 数据流的概念
-
数据流是指一组有顺序,起点,终点的字节集合
-
Java中把不同类型的输入,输出源抽象为流,其中输入或输出的数据称为数据流
-
数据流分为输入数据流和输出数据流
-
IO包中提供了表示数据流的四大家族
- Input Stream
- Output Stream
- Reader
- Writer
- IO包中加入了专门用于字符流处理的类,以Reader 和 Writer 为基础派生的类
- 为了使对象的状态可以永久保存下来
- 提供了以字节流为基础的永久化保存状态的机制
- 通过实现 ObjectInput 和 ObjectOutput 接口来完成
2. 输入数据流
-
输入数据流是只能读不能写的数据流,用于向计算机内输入信息使用
- 所有的输入数据流都是从抽象类 InputStream 继承而来
- 并且实现了其中所有的方法
-
输入数据流中主要数据操作方法如下:
-
int read(); // 从输入流中读取一个字节的二进制数据,抽象方法,需要子类加以继承 int read(byte[] b); // 将多个字节读到数组中 int read(byte[] c,int off,int len); // 从输入流中读取长度为len的数据,从数组b中下标为off的位置开始放入读入的数据,读完返回读取的字节数
- 以上3个方法中,当输入流读取结束,会得到 -1 ,标志数据流结束
- 在实际中,为了提高效率,读取数据时以系统允许的最大数据块长度为单位读取(BufferedInputStream)
-
void close(); // 关闭数据流
- Java有垃圾自动回收机制,当一个流对象不再使用时,系统可以自动关闭
- 为了提高程序的安全性和可读性,建议显式关闭流对象
-
int available(); // 返回目前可以从数据流中读取的字节数(实际操作所读的字节数可能大于该返回值) long skip(long l); // 跳过数据流中指定数量的字节不读,返回表示跳过的字节数
-
如果需要反方向读取,则需要 使用 回推操作
-
boolean markSupported(); // 测试数据流是否支持回推操作,支持 mark 和 reset 方法返回true void mark(intmarkarea); // 标记数据流的当前位置,并划出一个缓冲区,其大小至少为指定参数大小 void reset(); // 将输入流重新定位到对此流最后调用mark方法时的位置
-
-
3. 输出数据流
-
输出数据流是 只能写不能读的流,用于从计算机中输出数据
-
输出数据流大多是从 抽象类 OutputStream 继承而来的
-
输出数据流主要提供的方法有如下
-
void write(); // 将字节i 写入到数据流中,该方法是抽象方法,需要在其输出流子类实现才能使用 void write(byte[] b); // 将数组b[] 中的全部 b.length 个字节写入数据流 void write(byte[] c,int off,int len); // 将数组b[] 中从下标 off开始的len个字节 写入数据流,元素 b[off]是最后写入的一个字节,b[off + len -1]是最后写入的
- 与操作输入数据流一样,以系统允许的最大数据块长度为单位进行写操作
-
void close(); // 关闭输出数据流
-
void flush(); // 刷新此输出流并强制写出使用缓冲的输出字节
-
-
目前通用存储介质中,内存访问的速度 是最快的
- 为加快数据传输速度,提高输出效率
- 有时 输出数据流 会在提交数据之前把所要输出的数据先暂时保存在 内存缓冲区中
- 任何成批进行输出,每次以特定数据长度为单位进行传输
- 一般末尾会有一部分数据由于数量不够一个批次,存留在缓冲区
- 此时调用 flush( ) 可以将这部分数据强制提交
二. 基本字节数据流类
1. 文件数据流
-
InputStream 和 OutputStream 两个都是抽象类
-
抽象类是无法实例化的,所以用到的都是他们两个的子类
-
文件数据流包括 FileInputStream 和 FileOutputStream
- 两个类都是进行文件的IO处理,其数据源或终点都应该是 文件
-
不支持 mark 和 reset 回推操作
-
文件数据流示例
-
FileInputStream fis = new FileInputStream("myFile");
- 文件 myFile 是该数据流的 数据源
- 同样可以使用 FIleOutpuStream 向文件中输出字节
-
-
FileInputStream
- 进行IO操作时, 可能有数据源不存在的情况,则会抛出一个 FileNotFoundException
- 是非运行时异常,需要加以捕获或声明
-
FileOutputStream
- 如果指定数据源不存在,则系统会自动创建一个新文件
- 如果存在,新写入的内容会覆盖原有的数据
- 在读写或者生成文件时发生错误,会产生 IO异常(异常捕获记得由小到大,由具体到IO)
2. 过滤器数据流
- 一个过滤器数据流在创建时与一个已经存在的数据流相连,他提供的是一个对元素输入数据流内容进行了特定处理的数据
a. 缓冲区数据流
-
缓冲区数据有 BufferedInputStream 和 BufferedOuputStream
- 它们在数据流上在加 了一个缓冲区
- 当读写数据时,数据以块为单位先进入缓冲区(块的大小可以设置),其后的读写操作则作用于缓冲区
- 采用这个办法可以降低不同硬件设备之间的差异,提高IO操作的效率
- 提供对 mark rest 回推操作 和 skip 等方法的支持
-
在创建该类的实例对象时,可以使用两种方法
-
使用默认缓冲区的大小
-
FileInputStream fis = new FileInputStream("myFile"); InputStream is = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("myFile"); OutputStream outputStream = new BufferedOutputStream(fos);
-
-
自行设置缓冲区的大小
-
FileInputStream fis = new FileInputStream("myFile"); InputStream is = new BufferedInputStream(fis,1024); FileOutputStream fos = new FileOutputStream("myFile"); OutputStream outputStream = new BufferedOutputStream(fos,1024);
-
-
一般在关闭一个缓冲区输出流之前,都应该先 flush 强制输出剩余数据
-
确保缓冲区中的所有数据写入输出流
-
b.数据操作流
-
DataInputStream 和 DataOutputStream
-
它们允许通过数据流来读写Java基本类型,boolean,float等
-
假设 is 和 os 是已经创建好的输入/输出数据流对象,创建格式如下
-
DataInputStream dis = new DataInputStream(is); DataOutputStream dos = new DataOutputStream(os);
-
-
-
这两个类之所以可以对基本类型操作,是因为有了如下方法
-
DataInputStream 数据输入流
-
int readInt(); byte readBuffer[]; long readLong(); float readFloat(); String readUTF(); ......
-
-
DataOutputStream 数据输出流
-
void writeByte(int v); void writeShort(int v); void writeChar(int v); void writeInt(int v); void writeUTF(String str); .......
-
-
-
这两个方法都是成对出现的,由于字符编码的问题,应该避免使用这些方法,稍后的Reader 和 Writer 重载了这两个方法
- 对字符串进行操作时,应该使用 Reader 和 Writer 两个系列类中方法
3. 对象流
-
Java提供把对象写入文件数据流或从文件数据流中读出的功能,由 ObjectInputStream 和 ObjectOutputStream 两个类实现的,称为 对象流
-
写对象数据流
-
将Java.util.Date 对象写入文件中
-
Date date = new Date(); // 一个对象date try { FileOutputStream f = new FileOutputStream("date.ser"); ObjectOutputStream s = new ObjectOutputStream(f); // 输出文件 s.writeObject(date); // 将对象写入文件 s.close(); // 关闭文件 } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); }
-
-
-
读对象数据流
-
需要注意 方法readObject 把数据流以 Object 类型返回
-
返回内容应该在转换为正确的类名之后再执行该类的方法
-
public static void main(String[] args) throws IOException { Date d = null; FileInputStream f = new FileInputStream("date.ser"); ObjectInputStream s = new ObjectInputStream(f); d = (Date) s.readObject(); s.close(); System.out.println(d); }
-
-
4. 序列化
a. 序列化的概念
-
对象的持久性
- 记录自己的状态以便于将来得到复原能力
- 对象通过数值来描述自己的状态,记录对象也就是记录下这些数值
-
把对象转换为字节序列的过程称为对象的序列化
- 序列化的主要任务是写出对象实例变量的数值
- 一种用来处理对象流的机制
- 解决在对对象流进行读写操作时所引发的问题
-
把字节序列恢复为对象的过程称为反序列化
-
如果变量是另一个对象的引用,则引用的对象也需要序列化,这个过程是递归的
-
Serializable
- 该接口是JDK1.1新增的
- 接口中没有定义任何方法,只是作为一个标记指明实现该接口的类可以进行序列化
- 只有实现该接口的类才能够被序列化
- 实现了这个接口的,JVM会为其生成一个序列化版本号,后期根据这个版本号判断是不是同一个对象
-
序列化实例
-
public class Stu implements Serializable { int id; String name; int age; String department; public Stu() { } public Stu(int id, String name, int age, String department) { this.id = id; this.name = name; this.age = age; this.department = department; } }
- 要序列化一个对象,必须与特定的对象输出/输入流联系起来
- 通过对象输出流将对象状态保存起来,或者将该对象保存到文件中(writeObject()方法)
- 之后通过对象输入流将对象状态恢复(readObject()方法)
-
-
对象的存储实例
-
public class Objectser implements Serializable { public static void main(String[] args) { Stu stu = new Stu(999909, "张三", 19, "CSD"); try { FileOutputStream fo = new FileOutputStream("data.ser"); ObjectOutputStream so = new ObjectOutputStream(fo); so.writeObject(stu); so.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
-
data.ser文件
-
�� sr com.lcy.util.Stu�״��*� I ageI idL departmentt Ljava/lang/String;L nameq ~ xp A�t CSDt 张三
-
-
-
-
对象的恢复实例
-
public class ObjectRecov implements Serializable { public static void main(String[] args) { Stu stu = null; try { FileInputStream fi = new FileInputStream("data.ser"); ObjectInputStream si = new ObjectInputStream(fi); stu = (Stu) si.readObject(); si.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println("ID:"+stu.id+"name:"+stu.name+"age"+stu.age+"dept:"+stu.department); } } 控制台: ID:999909name:张三age19dept:CSD
-
b. 对象结构表
-
序列化只能保存对象的非静态成员变量
- 不能保存任何成员方法和静态成员变量
- 并且保存的只是变量的值,对于变量的任何修饰符都不能保存
- 访问权限对数据域的序列化没影响
-
transient 关键字
- 有一些对象类不具有可持久性,它会经常变化,如Thread对象或流对象
- 对于这样的成员变量必须用 transient 关键字标明,被标明的都不会被保存
-
序列化可能涉及将对象存放到磁盘或在网络上发生数据,产生安全问题
- 对于一些需要保密的数据,不应保存在永久介质中
- 需要在这些变量上加上 transient 关键字
-
如果一个持久化对象中包含一个指向不可持久化元素的引用会导致操作失败
-
public class Objectser implements Serializable { public static void main(String[] args) { Peo peo = new Peo(); Stu stu = new Stu(999909, "张三", 19, "CSD",peo); try { FileOutputStream fo = new FileOutputStream("data.ser"); ObjectOutputStream so = new ObjectOutputStream(fo); so.writeObject(stu); so.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } 控制台: java.io.NotSerializableException: com.lcy.util.Peo
-
Peo类 并没有实现序列化接口,也没有 在Stu类中引入Peo时加 transient 关键字
-
加上即不会影响序列化操作
-
三. 基本字符流
1. Reader 和 Writer
- 专门处理字符流处理的类
- 同 Reader 和 Writer 也是抽象类,只提供了用于处理字符流处理的接口
- 方法与 InputStream 和 OutputStream 类似,只是将其中的参数换成了字符或者字符数组
2. 读者 和 写者
-
通过读者 和 写者,Reader 和 Write 实现 对不同平台之间数据流中的数据进行转换
-
Java使用的 Unicode是一种大字符集,用两个字节(16bit)来表示一个字符,其他语言使用的ASCII字符集,一个字节(8bit)表示一个字符,此时字节与字符就不相同了
-
其中实现这转换的关键 它们两个的子类
-
InputStreamReader 和 OutputStreamWriter两个类
-
用来在字节流和字符流之间作为中介
-
-
3. 缓冲区读者 和 写者
-
如其他IO操作一样,如果格式转换以较大的数据块单位进行,那么会提高效率
-
于是提供了 BufferedReader 和 BufferedWriter
-
其构造方法与BufferedInputStream它们类似
-
另外还提供了整行字符的处理方法
-
public String readLine() // 从输入流中读取一行字符,行结束标志为 \n \r public String newLine() // 向输出流中写入一个行结束标准
-
-
-
将BufferedReader 和 BufferedWriter 正确连接到 InputStreamReader 和 OutputStreamWriter‘的末尾
- 注意要在 BufferedWriter 中 使用 flush( )方法,强制清空缓冲区中的剩余内容
四. 文件的处理
1. File类
-
File类提供了获取文件基本信息以及操作文件的一些工具
-
对一个文件进行IO操作之前,必须先得到这个文件的基本信息(绝对路径,能不能被读取/写入,文件长度)
-
创建一个新的 File 对象有以下3种构造方法
-
File myFile; myFile = new File("my");
- 当程序中只用到一个文件时,使用这种
-
File myFile = new File("/","my");
- 如果使用了同一目录下的几个文件,这种和以下那种更方便
-
File file = new File("/"); File myFile = new File(file,"my");
-
-
-
与文件名相关的方法
-
String getName(); // 获取文件名 String getParent(); // 获取文件父目录名称 String getPath(); // 获取文件路径 boolean renameTo(File dest); // 更改文件名 String getAbsolutePath(); // 获取文件绝对路径
-
-
文件测定方法
-
boolean canWrite(); // 文件对象是否可写 boolean canReade(); // 文件对象是否可读 boolean isFile(); // 文件对象是否为文件 boolean isDirectory(); // 文件对象是否为目录 boolean exists(); // 文件对象是否存在
-
-
常用文件信息和方法
-
long lastModified(); // 获取文件最后修改时间 long length(); // 获取文件长度 boolean delete(); // 删除文件对象指向的文件
-
-
目录工具
-
boolean mkdir(); // 创建新目录 boolean mkdirs(); // 创建新目录 String[] list(); // 列出符合模式的文件名
- mkdir 和 mkdirs 的区别在于 后者可以一次生成多个层次的子目录
-
2. 随机访问文件
-
RandomAccessFile 类
- 在读写文件时有时候需要,到一个位置读一条记录。另一个位置读一条,任何读另外一条,每次都在文件的不同位置进行读取
- 于是提供了 RandomAccessFile类处理这种类型的输入/输出
-
创建一个随机访问文件有以下两种方法
-
使用文件名
-
temp = new RandomAccessFile(String name, String mode);
- 参数 mode 决定是以 只读方式(“r”),还是读写方式(“rw”)访问文件
-
-
使用文件对象
-
temp = new RandomAccessFile(File file, String mode);
- 参数 mode 与上述一致
-
-
-
RandomAccessFile 读写信息的方法与 数据输入/输出对象的方法相同,可以访问 DataInpuStream 和 DataOutputStream类中的所有 read 和 write 方法