day21
-
内存输出流
-
ByteArrayOutputStream : 内存输出流
案例 : 使用字节输入流读取了带有中文纯文本文件,一边读还将读到的结果打印出来,出现问题,将中文的两个字节拆分开,于是出现了乱码
解决方法 : 字符流读取文件 ; 内存输出流 -
内存输出流的作用 :
如果一个流在进行字节内容读取时,没有读完,需要一个字节数组容器,将没读完的先放到一个容器中,等到都读完,一起查看结果, 于是内存输出流就是这样一个字节数组容器 -
内存输出流实质: 就是一个大小可变的字节数组容器,专门用于承装IO流读取到的字节数据,之后将所有的字节数据显示出来.内存输出流与文件没有交互
-
内存输出流的实现
- 创建一个内存输出流的对象,需要构造方法,空参数构造即可
- ByteArrayOutputStream 也具有写的功能,存储到数组中
- toByteArray() : 将内存输出流的字节数组内容获取到,相当于查看了文件内容
- toString() : 将字节数组使用平台默认的编码表,转换成字符串,返回值类型String
代码
package com.zgjy.otherStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class ByteArrayOutputStreamDemo {
public static void main(String[] args) throws IOException{
// 1. 使用字节输入流读取带有中文纯文本
FileInputStream fis = new FileInputStream("chinese.txt");
// 3. 创建一个内存输出流的容器
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 2. 按照一个字节一个字节的读取中文内容
int len;
while((len = fis.read()) != -1) {
// 4. 向容器中添加字节
baos.write(len);
}
//5. 所有内容读取完毕,查看一下容器中的数据内容
// 先获取到容器
byte[] b = baos.toByteArray();
System.out.println(new String(b));
// 7. toString(): 将内存输出流中的字节数组内容转化成字符串
System.out.println(baos.toString());
// 6. 关闭资源
fis.close();
}
}
- 对象操作流(掌握)
对象操作流的功能 : 将内存中的对象以及对象中的数据写入到文件中 ; 还能将文件中的对象数据信息获取到程序中;
使用原因 :
目前为止,在代码中所有的数据,都不能做持久化存储,例如 : 数据库,会将代码中发生的一些有用数据,存储下来, 那么对象的操作流,可以暂时做数据的持久化的存储,将对象的数据写入文件中,文件存储在磁盘上,只要文件不删除,这些对象中的数据就能长久存在
对象输出流 : 内存中的对象以及对象中的数据写入到文件中,ObjectOutputStream
对象输入流 : 能将文件中的对象数据信息获取到程序中,ObjectInputStream
2.1 对象输出流ObjectOutputStream
-
ObjectOutputStream : 是OutputStream的子类,主要关注将对象写入文件中,
将对象写入到文件中的过程,称为对象的序列化过程 -
ObjectOutputStream 构造方法
ObjectOutputStream (OutputStream out) : 将一个普通的字节输出流,包装成一个对象的输出流,依靠OutputStream 将对象数据写入到文件中. -
写入对象的方法:
writeObject(Object obj): 将obj对象写入到指定的文件中 -
将自定义的Person类对象,写入到文件中,报错,java.io.NotSerializableException ,没有序列化异常
发生异常原因: 因为将Person对象写入到文件的过程就叫做序列化过程,因此要求对象所属的类型必须是可以序列化的类型,需要Person实现一个接口,java.io.Serializable -
java.io.Serializable 接口 : 表示是一个序列化的接口
类通过实现 java.io.Serializable 接口以启用序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化
- Serializable接口中定义没有任何方法,于是将没有任何方法的接口Serializable称为一个标志性接口
现在超市卖猪肉,一般猪肉上面都有一个戳,表示检疫合格,看到戳猪肉放心购买,这个戳就是一个标志,类比理解Serializable
- 序列化的文件,文件中的对象内容,人类看不懂,主要关注可以通过对象的输入流,将对象的内容获取到
序列化类型Person定义代码
package com.zgjy.objectStream;
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int 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;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
// TODO Auto-generated constructor stub
}
}
序列化过程代码:
package com.zgjy.objectStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjecOutputStreamDemo {
// 向文件中写入Person对象
public static void main(String[] args) throws IOException{
// 1.创建一个ObjectOutputStream对象
FileOutputStream fos = new FileOutputStream(“person.txt”);
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 2. 向person.txt写入对象
//java.io.NotSerializableException: com.zgjy.objectStream.Person
Person p = new Person(“二麻子”, 20);
oos.writeObject§;
oos.close();
}
}
2.2 对象输入流ObjectInputStream
ObjectInputStream : 来自于java.io包.是InputStream的子类
功能:
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化
反序列化 : 将对象从文件中读取出来的过程就是反序列化
- ObjectInputStream 构造方法:
ObjectInputStream(InputStream in) : 将一个普通的字节输入流封装成一个对象的输入流,可以读取文件中的对象数据,通过InputStream读 - 读取对象的方法:
readObject() : 将文件中的对象读取出来,返回值Object类型,方法每次只能读取一个对象
代码
package com.zgjy.objectStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException{
// 1. 确定从哪里读取序列化的文件,person.txt
FileInputStream fis = new FileInputStream(“person.txt”);
ObjectInputStream ois = new ObjectInputStream(fis);
// 2. 通过readObject方法,从person.txt文件中读取对象
// 方法每次从文件中读取出一个对象
Object obj = ois.readObject();
// 3. 看一下获取到的对象内容
// 将Object类型的对象转换成Person对象,多态的向下转型
Person p = (Person)obj;
System.out.println(p.getName() + p.getAge());
ois.close();
}
}
- 对象输入流读取文件的异常
java.io.EOFException
EOFException : End Of File Exception 文件到末尾的异常
发生异常的原因是 : 文件中已经没有对象了,还在继续使用readobject()获取对象,但是目前并不知道文件中的对象个数有多少,代码需要优化,参见2.3
代码
package com.zgjy.objectStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException{
// 1. 确定从哪里读取序列化的文件,person.txt
FileInputStream fis = new FileInputStream(“person.txt”);
ObjectInputStream ois = new ObjectInputStream(fis);
// 2. 通过readObject方法,从person.txt文件中读取对象
// 方法每次从文件中读取出一个对象
Object obj = ois.readObject();
Object obj1 = ois.readObject();
Object obj2 = ois.readObject();
// java.io.EOFException
// EOFException : End Of File Exception 文件到末尾的异常
// obj3发生异常的原因是 : 文件中已经没有对象了,还在继续获取对象,抛异常
Object obj3 = ois.readObject();
// 3. 看一下获取到的对象内容
// 将Object类型的对象转换成Person对象,多态的向下转型
Person p = (Person)obj;
System.out.println(p.getName() + p.getAge());
Person p1 = (Person)obj1;
System.out.println(p1.getName() + p1.getAge());
Person p2 = (Person)obj2;
System.out.println(p2.getName() + p2.getAge());
Person p3 = (Person)obj3;
System.out.println(p3.getName() + p3.getAge());
ois.close();
}
}
2.3优化序列化的过程
如果向文件中存储对象,可以向文件中写入多个对象,但是读取文件中的对象时,不知道文件中对象个数是多少,于是读取可能有问题,多读就会报出EOFException
优化思路:
创建多个对象,将多个对象放置到一个ArrayList容器中, 然后将容器对象写入到文件中
,后期再进行对象读取时,只需要读一次,读到了ArrayList容器,遍历容器中的内容,将所有对象数据获取到
代码
package com.zgjy.objectStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
public class ObjectYouHua {
public static void main(String[] args) throws Exception{
writeObject();
readObject();
}
// 向文件中写入容器对象,容器中橙装了很多其他对象内容
public static void writeObject() throws IOException{
// 1. 创建一个ObjectOutputStream
FileOutputStream fos = new FileOutputStream("personArr.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 2.创建一个容器
ArrayList<Person> list = new ArrayList<>();
// 3. 向容器中添加Person对象数据
list.add(new Person("张三", 15));
list.add(new Person("李四", 16));
list.add(new Person("海岛", 17));
list.add(new Person("QQ", 18));
//4. 向文件中写入容器对象
oos.writeObject(list);
// 5. 关闭资源
oos.close();
}
// 将文件中的容器对象读出来
public static void readObject() throws IOException, ClassNotFoundException{
// 1.创建一个ObjectInputStream
FileInputStream fis = new FileInputStream("personArr.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
// 2. 从personArr.txt读取出容器对象
Object obj= ois.readObject();
// 3. 将obj转换 成ArrayList类型,多台向下转型
ArrayList<Person> list = (ArrayList<Person>)obj;
// 4. 遍历集合,获取到集合中的每一个Person对象内容
for(Person p : list) {
System.out.println(p.getName() + "---"+p.getAge());
}
ois.close();
}
}
2.4序列号ID冲突问题
-
目前.已经把Person对象写入到文件中,随着需求的推进,客户要求,在Person中添加一个成员变量,性别,使用sex表示
问题: 当将Person对象写入到文件中(序列化),修改了Person类的内容,再将序列化的内容进行反序列化(讲对象从文件中读取出来),报错,InvalidClassException
InvalidClassException 发生原因 : 该类的序列版本号与从流中读取的类描述的版本号不匹配 -
序列版本号 : 每个类的版本都是有一个版本号的标识
举例 : 1. 创建一个Person---->name,age 版本号1
2.修改Person类,系统西东生成一个新的版本号2
如上,版本号1和版本号2不一致,导致了Person的反序列化失败
- 如何解决: 在Person类中,定义一个固定的不能被修改的序列号,如下代码
/**
* serialVersionUID Person类的版本号,也称为序列号
* 使用一个long类型的数值表示
*/
private static final long serialVersionUID = 1L;
- 随机访问流
RandomAccessFile : 叫做随机访问流,不属于IO流体系,只是
一个普通的类文件,父类直接是Object,功能比IO流还强大
为什么使用随机访问流
- 可以对文件进行读和写
- 可以在文件的任意位置进行读和写
-
RandomAccessFile的构造方法
RandomAccessFile(File f ,String mode) : 参数f确定一个文件,随机访问流就对f所表示的文件进行操作; 参数mode ,表示可以对文件做的操作是什么
mode : r 只读 rw 读写
RandomAccessFile(String path ,String mode) :参数path确定一个文件,随机访问流就对path所表示的文件进行操作; 参数mode ,表示可以对文件做的操作是什么 -
随机访问流的读写功能参见API
-
seek(long pos): RandomAccessFile类可以将要读取的文件,看做是一个字节的数组,定义了一个指针,指针可以定位在数组中的任意位置,可以使用seek方法,定位指针的位置,在该位置发生下一个读取或写入操作
代码
package com.zgjy.otherStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
// 1. 创建一个随机访问流对象,对文件进行操作
RandomAccessFile raf = new RandomAccessFile("random.txt", "rw");
// 2. 先读一下文件内容
int i = raf.read();
System.out.println((char)i);
// seek方法将文件中的指针设置在3的位置上,就从3的位置开始接下来的读操作
raf.seek(3);
int ii = raf.read();
System.out.println((char)ii);
// seek方法将文件中的指针设置在4的位置上,就从4的位置开始接下来的写操作
raf.seek(4);
raf.write(101);//e
raf.close();
}
}
- 打印流
字节打印流 : PrintStream
字符打印流 : PrintWriter
打印流本身也是具有文件的读写功能,但是不关注.关注打印流进行数据的输出打印
PrintStream和PrintWriter类中有很多的print和println方法,这些方法都是方法的重载
主要是将任意的数据类型转换成字符串,然后将字符串输出到指定的设备上
4.1 PrintStream字节打印流
- System.out : 系统的标准输出流,结果就是PrintStream,默认将数组打印在控制台上
- PrintStream中有方法,叫做println和print.能将任意数据类型转化成称字符串进行输出
- PrintStream单独进行对象创建,可以将数据打印到指定的文件中
代码
package com.zgjy.otherStream;
import java.io.IOException;
import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
// 1. 将System.out返回的字节打印流,默认关联输出在控制台上
System.out.println(true);
int[] arr = {1,2,3};
char[] ch = {'1','2','3'};
System.out.println(arr);// [I@15db9742
/*
* PrintStream中的println方法,重载
* 但是在数组中,只有char类型的数组,println在定义char类型数组打印结果时
* 不是打印地址值,而是将char数组中的内容进行输出
*
* */
System.out.println(ch);// 123
// 2. 自己创建一个字节的打印流,将数据打印到指定文件中
PrintStream ps = new PrintStream("print.txt");
ps.print(false);
ps.print('国');
ps.println('家');
ps.println("12345");
ps.close();
}
}
4.2 PrintWriter字符打印流
PrintWriter 类,是Writer字符输出流的子类,具有针对于字符的写的功能,主要关注的中的print和println方法,与PrintStream中的打印方法功能基本一致
PrintWriter作为Writer的子类,具有数组的缓冲1024大小缓冲,写数据是,先存储在数组中,然后再向文件中打印同步
PrintWriter中有方法可以实现自动的刷出,直接将数据同步到文件中(println,printf,format)
- PrintWriter的构造方法
可以进行自动刷出功能的构造方法:
- PrintWriter(OutputStream out , boolean autoFlush) : 将一个字节的输出流包装成一个打印流,可以使用打印的方法; autoFlush这个布尔类型参数,设置为true,那么方法的功能就可以自动将数据同步到文件中
- PrintWriter(Writer out , boolean autoFlush):将一个字符的输出流包装成一个打印流,可以使用打印的方法; autoFlush这个布尔类型参数,设置为true,那么方法的功能就可以自动将数据同步到文件中
代码
package com.zgjy.otherStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public class PrintWriterDemo {
public static void main(String[] args) throws IOException{
// 1. 创建一个PrintWriter对象
FileOutputStream fos = new FileOutputStream(“writer.txt”);
PrintWriter pw = new PrintWriter(fos, true);
// 2. 使用字符的输出流向文件writer.txt输出数据
pw.print(true);
pw.write("你好".toCharArray());
// println中的方法能将数组缓冲中的数据都同步到文件中
pw.println(123);
pw.close();
}
}
-
标准输入输出流
-
标准输入流: System.in ,结果是InputStream类型,字节输入流的抽象父类
默认关联 : 键盘输入数据. InputStream 将通过键盘输入的数据读取到程序中
Scanner sc = new Scanner(System.in); -
标准输出流 : System.out ,结果PrintStream,字节打印流,主要功能就是进行数据的输出
默认输出 : 到控制台上
System.out.println(); -
Properties(掌握)
Properties类来自于java.util包,是Hashtable的子类,Hashtable<K,V>的键值对映射关系集合
Properties表示一个持久性的属性集,也是具有两个泛型,也是一个映射关系集合
Properties类的定义上没有看到key和value的泛型,Properties的key和value都是固定的,都是String类型
Properties的功能:
就是可以读取配置文件中的内容,将配置文件中的键值对关系直接的读到Properties集合中
- 配置文件 : 文件中写的内容是一些键值对,一般用于表示一些固定的数据内容,例如 : 数据库的配置文件
举例配置文件: 后缀名一般都是使用properties
配置文件内容 :
userName=root
userPassword=123456
6.1 Properties类的特有方法
- setProperty(String key,String value) : 将key和value键值对映射关系添加到properties类中
- getProperty(String key): 通过key的值获取到对应的value值,返回值类型String类型
- stringPropertyNames(): 将properties集合中的所有key获取到,放置到一个Set集合中,Set
代码
package com.zgjy.pro;
import java.util.Properties;
import java.util.Set;
public class PropertiesMethod {
public static void main(String[] args) {
// 1. 创建一个properties对象
Properties p = new Properties();
// 2. 向集合中添加String类型的键值对关系
// setProperty类比map集合中的put方法
p.setProperty(“1”, “a”);
p.setProperty(“2”, “b”);
p.setProperty(“3”, “c”);
//3.遍历集合中的每一个key和value的元素
// stringPropertyNames(): 将properties集合中的所有key获取到,放置到一个Set集合中,Set
// stringPropertyNames 类比map中keySet方法
Set set = p.stringPropertyNames();
// 4. 遍历Set集合
for(String key : set) {
// 5. getProperty(String key): 通过key的值获取到对应的value值,返回值类型String类型
String value = p.getProperty(key);
System.out.println(key+"—"+value);
}
// p.put(key, value);方法可以使用的
// p.keySet();方法可以使用的
}
}
6.2 Properties类读取配置文件内容
- 创建一个配置文件,主要是以properties为后缀名
file.properties
- load(InputStream in) : 使用in这个字节的输入流读取文件file.properties,load方法将流中读到的文件内容,放置到集合的键值对关系上
- load(Reader r) : 使用r这个字符的输入流读取文件file.properties,load方法将流中读到的文件内容,放置到集合的键值对关系上
代码
package com.zgjy.pro;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
public class ReadFile {
// 使用Properties读取配置文件内容
public static void main(String[] args) throws IOException{
// 1. 创建一个properties对象
Properties p = new Properties();
// 2. 创建一个字符的输入流,绑定读取哪个配置文件
FileReader fr = new FileReader(“file.properties”);
// 3. load方法将配置文件中的键值对已经放置到集合中
p.load(fr);
// 4. 遍历集合,查看文件内容
Set set= p.stringPropertyNames();
for(String key : set) {
String value = p.getProperty(key);
System.out.println(key+"----"+value);
}
}
}
6.3 Properties类修改配置文件内容
file.properties
name=zhangsan
age=20
修改配置文件:
name=lisi
age=25
修改后的配置文件结果如下图:
- store(OutputStream out,String s) : store方法能将Properties集合中的键值对的关系同步到文件中,同步其实就是向文件中写入内容,写的动作只能IO流进行,因此可以利用out所表示的字节的输出流向文件中覆盖内容; s表示修改说明
- store(Writer w,String s): store方法能将Properties集合中的键值对的关系同步到文件中,同步其实就是向文件中写入内容,写的动作只能IO流进行,因此可以利用out所表示的字节的输出流向文件中覆盖内容; s表示修改说明
代码
package com.zgjy.pro;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class WriteFile {
public static void main(String[] args) throws IOException {
// 1. 创建一个properties对象
Properties p = new Properties();
// 2. 设置集合中的键值对元素
p.setProperty(“name”, “lisi”);
p.setProperty(“age”, “25”);
// 3. store(Writer w ,String s)
p.store(new FileWriter(“file.properties”), “change”);
}
}
- IO流异常处理标准格式(掌握)
IO流写作规范: - 使用时先导包,java.io
- 使用时,需要抛出异常,需要处理
- 流资源使用完毕,一定记得关闭资源
IO流异常的标准处理方式:
- 流资源的创建,可能或发生文件找不到异常,因此放在try代码块中
- 流资源进行文件的读写时,可能会发生异常,因此放在try代码块中
- 流资源必须要关闭,因此放在finally中进行处理
- 因为流资源的创建和流的关闭在不同的代码块中,因为变量的作用范围需要提升,将资源的声明放置在整个异常处理之上,设置默认值为null
- 因为关闭的流资源有多个,而每一个流资源在关闭时,都有可能发生异常,因此,多个流需要分别进行try和catch的处理
- 如果创建资源就发生异常,那么资源创建失败,资源对应的值就为null,null值关闭流的时候,会抛出空指针异常,所以关闭流资源之前,先判断流资源不为null,再关流
标准的流资源关闭代码
package com.zgjy.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
}
// 使用字节流进行文件复制
public static void copy1() {
// 将流资源变量的定义放在try...catch...finally之外,为了提高变量的作用范围
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 1. 创建一个字节的输入流,读取文件
fis = new FileInputStream("chinese.txt");
// 2. 创建一个字节的输出流,绑定一个数据目的
fos = new FileOutputStream("chineseCopy.txt");
// 3. 文件复制,边读边写
int len;
while((len = fis.read()) != -1) {
fos.write(len);
}
}catch(FileNotFoundException ex) {
ex.printStackTrace();
}catch(IOException ex) {
ex.printStackTrace();
}finally {
// 流资源因为一定要关闭,因此放在finally中
// 有因为有两个流资源关闭,因此两个资源分开关闭,才能保证两个流资源都关闭掉
try {
if(fis!= null) {
fis.close();
}
}catch(IOException ex) {
ex.printStackTrace();
}finally {
try {
if(fos!= null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// IO流的优化的处理方式
// JDK1.7版本以及以上
}
标准的IO流异常处理比较繁琐,从JDK1.7版本开始,优化了IO流的异常处理:
语法格式:
try (
// 小括号中写流的创建语句,写在try小括号中的流资源的创建,不需要关流
// 不需要close(),try在流使用结束之后,自动关闭资源
){
// 可能会发生问题的代码
}catch(可能会发生的异常类型 异常名字) {
// 异常的处理方式
}
代码
public static void copy2() {
public static void main(String[] args) {
try(
FileInputStream fis = new FileInputStream(“chinese.txt”);
FileOutputStream fos = new FileOutputStream(“chineseCopy.txt”);
){
// 可以回发生问题的代码
// 3. 文件复制,边读边写
int len;
while((len = fis.read()) != -1) {
fos.write(len);
}
}catch(IOException ex) {
ex.printStackTrace();
}
}
}