重点
1.字节数组输出流
1.1概述
ByteArrayOutputStream 字节数组输出流
ByteArrayOutputStream 不需要关联文件
此类实现了一个输出流,其中的数据被写入一个 byte 数组 缓冲区。
缓冲区会随着数据的不断写入而自动增长。
可使用 toByteArray() 和 toString() 获取数据。
1.2ByteArrayOutputStream的使用
//1.创建字节数组输出流对象
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//2.输入流
FileInputStream fis = new FileInputStream("a.txt");
//3.写入输出流
int b = 0;
while((b = fis.read()) != -1){
baos.write(b);
}
//自动把字节数组 转成 字符串
System.out.println(baos.toString());
System.out.println(baos.toString("UTF-8"));//指定编码格式的字符串
//获取文件数据
byte[] bytes = baos.toByteArray();
System.out.println(new String(bytes));
System.out.println(new String(bytes, "UTF-8"));//指定编码格式的字符串
//4.关流
fis.close();
2.对象操作流
2.1ObjectOutputStream
这个类是将一个对象写入文件
如果使用这个类写入对象,这个对象需要序列化
序列化就是让这个对象实现一个Serializable接口
如果没实现Serializable接口,会抛异常NotSerializableException
对象写入文件时,乱码没有关系,取出来正确就行了
2.2ObjectInputStream
这个类是从文件中读取对象
3.序列化相关概念
归档(序列化) :将对象存在一个文件的过程
解归档(反序列化):把一个文件解析出对象
4.Serializable接口的ID讲解
1.要归档或者序列化的对象必须实现Serializable接口才能被序列化
2.Serializable 中有个id,但ID不是一定要加的
3.SerialVersionUid,简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
4.如果在新版本中这个值修改了,新版本就不兼容旧版本,反序列化时会抛出InvalidClassException异常。
5.如果修改较小,比如仅仅是增加了一个属性,我们希望向下兼容,老版本的数据都能保留,那就不用修改;
6.如果我们删除了一个属性,或者更改了类的继承关系,必然不兼容旧数据,这时就应该手动更新版本号,即SerialVersionUid。
7.一般不会添加ID,就算添加了ID,版本号最好不要修改
public class Demo01 {
public static void main(String[] args) throws IOException, ClassNotFoundException{
// TODO Auto-generated method stub
//save();
//取学生
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.data"));
Student stu = (Student) ois.readObject();
System.out.println(stu);
}
public static void save() throws IOException, FileNotFoundException {
//1.创建学生对象
Student stu = new Student("霍建华");
//2.把学生存入文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.data"));
/**
* 存的学生的版本号是1
*/
oos.writeObject(stu);
//3.关流
oos.close();
}
}
class Student implements Serializable{
/**
* 序列化版本ID
*/
private static final long serialVersionUID = 1L;
private String name;
private String hometown;
public Student(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "Student [name=" + name + "]";
}
}
5.打印流
5.1PrintStream 打印字节流
System.out就是一个PrintStream, 其默认向控制台输出信息
该流可以很方便的将对象的toString()结果输出, 并且自动加上换行
5.2PrintWriter 打印字符流
这个流是向文件打印信息,也就是将打印的内容写入文件
//1.打印的字节流【打印到控制台】
PrintStream ps = System.out;
ps.println("你好,元宵节快乐");//打印字符串
ps.println(19.6);
//2.打印的字符流
/**
* 1.PrintWriter调用打印方法,控制台是没有内容,它是把内容写到文件中
* 2.如果打印了内容,没有调用flush或者close,内容在文件中也不存在
*/
PrintWriter pw = new PrintWriter("test.txt");
pw.println("吃汤圆了...");
pw.print("超市一包汤圆10几块钱");
pw.print(13.01);
//pw.flush();
pw.close();
6.标准输入输出流
System.in是InputStream, 标准输入流, 默认可以从键盘输入读取字节数据
System.out是PrintStream, 标准输出流, 默认可以向Console中输出字符和字节数据
7.随机访问流
RandomAccessFile类不属于流,是Object类的子类。
但它融合了InputStream和OutputStream的功能。
支持对随机访问文件的读取和写入。
/**
* 构造方法两个参数:
* name:文件名称、路径
* mode:模式 ,r=read 只读、 w=write
*/
//1.创建一个随机访问流对象,以读写的方式打开文件
RandomAccessFile raf = new RandomAccessFile("a.txt", "rw");
//2.读字符
/**
* 使用RandomAccessFile的readChar/readLine方法读文件有乱码问题
*/
/* System.out.println(raf.readChar());
System.out.println(raf.readChar());
System.out.println(raf.readChar());
System.out.println(raf.readLine());*/
//使用字节数组来读比较好
/* byte[] buf = new byte[1024];
int len;
while((len = raf.read(buf)) != -1){
System.out.println(new String(buf,0,len));
}*/
//3.写数据
//raf.writeChars("abc");
//raf.writeBytes("abc");
raf.seek(4);//指定位置
raf.write(97);
raf.write(98);
raf.write(99);
}
8.数据输入输出流
DataInputStream, DataOutputStream可以按照基本数据类型大小读写数据
例如按Long大小写出一个数字, 写出时该数据占8字节.
读取的时候也可以按照Long类型读取, 一次读取8个字节.
long a = 997;
long b = 998;
long c = 999;
//使用FileOutputStream没法写入long类型数据
FileOutputStream fos = new FileOutputStream("a.txt");
//byte -128~127 0~255
/* fos.write(997);只会写一个字节,不会写8个字节
fos.write(998);
fos.write(999);*/
//fos.wr
DataOutputStream dos = new DataOutputStream(fos);
dos.writeLong(a);//写8个字节
dos.writeLong(b);
dos.writeLong(c);
dos.close();
//读3个long数据
DataInputStream dis = new DataInputStream(new FileInputStream("a.txt"));
System.out.println(dis.readLong());//读8个字节
System.out.println(dis.readLong());//读8个字节
System.out.println(dis.readLong());//读8个字节
9.Properties类【掌握-经常用】
Properties:属性,与Map的使用有点类似
Properties 类表示了一个持久的属性集。
Properties 可保存在流中或从流中加载,这个类可以读写文件。
属性列表中每个键及其对应值都是一个字符串。
存中文时,会转成Unicode编译存储
public class Demo01 {
public static void main(String[] args) throws IOException, IOException {
//Properties的概述和作为Map集合的使用【掌握-经常用】
/**
* Properties:属性
1.Properties 类表示了一个持久的属性集。
2.Properties 可保存在流中或从流中加载。
3.属性列表中每个键及其对应值都是一个字符串。
*/
//遍历properties所有属性key和值value
//1.创建属性对象
Properties p = new Properties();
//2.关联文件
p.load(new FileInputStream("info.properties"));
//3.遍历一
Set<Object> keys = p.keySet();
for(Object key : keys){
System.out.println(key + "=" + p.get(key));
}
//System.out.println(keys);
//4.遍历二
System.out.println("=====================");
//p.entrySet();
for(Entry<Object, Object> entry :p.entrySet()){
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
public static void test2() throws IOException, FileNotFoundException {
//使用Properties读取数据
//1.创建属性对象
Properties p = new Properties();
//2.关联文件
p.load(new FileInputStream("info.properties"));
//3.通过key读数据
String name = p.getProperty("name");
String city = p.getProperty("city");
String hometown = p.getProperty("hometown");
System.out.println(name);
System.out.println(city);
System.out.println(hometown);
}
/**
* 使用Properties来存储数据
*/
public static void test1() throws IOException, FileNotFoundException {
//1.创建属性对象
Properties p = new Properties();
//2.存数据
p.setProperty("name", "gyf");
p.setProperty("city", "广州");
p.setProperty("hometown", "梅州");
//3.关联文件
/**
* 当Properties把key和value存入文件,把中文转成unicode编码
*/
p.store(new FileOutputStream("info.properties"), null);
}
}
练习题
1.1直接把对象存在文件中
public static void test1() throws FileNotFoundException, IOException {
//案例:将对象直接存入文件
//1.创建女朋友对象
GirlFriend gf1 = new GirlFriend("林志玲", 1.78);
GirlFriend gf2 = new GirlFriend("林心如", 1.68);
//2.创建对象输出流
FileOutputStream fos = new FileOutputStream("gf.data");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//3.往文件存入对象
oos.writeObject(gf1);
oos.writeObject(gf2);
//4.关流
oos.close();
}
}
class GirlFriend implements Serializable{
String name;
double height;
public GirlFriend(String name, double height) {
super();
this.name = name;
this.height = height;
}
@Override
public String toString() {
return "GirlFriend [name=" + name + ", height=" + height + "]";
}
}
1.2从文件中直接取出对象
//案例:从文件中取出女朋友对象
//1.创建一个对象输入流
FileInputStream fis = new FileInputStream("gf.data");
ObjectInputStream ois = new ObjectInputStream(fis);
//2.读数据
GirlFriend gf1 = (GirlFriend) ois.readObject();
System.out.println(gf1);
GirlFriend gf2 = (GirlFriend) ois.readObject();
System.out.println(gf2);
/**
* 如果没有数据可读了,强制读的时候报错了EOFException,
*/
System.out.println(ois.readObject());
2.对象操作流优化
把女朋友对象存在List中,再把List写入文件【序列化/归档】
public class Demo01 {
public static void main(String[] args) throws IOException, IOException {
//对象操作流优化-思路,存多个对象时,把对象存在list里面
//1.创建集合
List<GirlFriend> list = new ArrayList<GirlFriend>();
//2.添加女朋友
list.add(new GirlFriend("林志玲", 1.78));
list.add(new GirlFriend("林心如", 1.68));
list.add(new GirlFriend("林嘉欣", 1.58));
//3.把list存入文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.data"));
oos.writeObject(list);
//4.关流
oos.close();
}
}
class GirlFriend implements Serializable{
String name;
double height;
public GirlFriend(String name, double height) {
super();
this.name = name;
this.height = height;
}
@Override
public String toString() {
return "GirlFriend [name=" + name + ", height=" + height + "]";
}
}
从list.data 读取所有的女朋友对象【反序列化、解归档】
public class Demo02 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
//1.创建对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.data"));
//2.读取List对象
Object obj = ois.readObject();
//把obj转list
@SuppressWarnings("unchecked")
List<GirlFriend> list = (List<GirlFriend>)obj;
System.out.println(obj.getClass());
//遍历
for(GirlFriend gf : list){
System.out.println(gf);
}
ois.close();
}
}
面试题
1.找Bug
public class Demo01 {
public static void main(String[] args) throws IOException {
/* 面试题:找bug
定义一个文件输入流,调用read(byte[] b)方法,
将a.txt文件中的内容打印出来(byte数组大小限制为5)*/
//1.文件输入流
FileInputStream fis = new FileInputStream("a.txt");
//2.字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//3.字节数组【缓冲区思想,杯子思想】
byte[] arr = new byte[5];
int len;
while((len = fis.read(arr)) != -1) {
//通过字节缓冲数组写入
baos.write(arr, 0, len);
//把字节转成字符串
/**中文乱码问题*/
System.out.println(new String(arr,0,len));
}
//内部会调用toString方法,把字节数组转成字符串
System.out.println(baos);
fis.close();
}
}
- UTF-8存储的中文字符占3个字节,用5个字节数组当“杯子”,会出现中文乱码。
总结
IO流学了几天了,学了很多流的使用方法。这里做一个归纳:
public static void main(String[] args) {
/**
* 一、IO流-输入输出流
* I:input
* O:out
* IO流 操作文件,读取文件内容,往文件写内容
*
* 二、字节流
*
* >InputStream
* -FileInputStream 文件输入流,读的单位是字节
* -BufferedInputStream 缓冲输入流,内部有个byte[]字节数组
* -SequenceInputStream 序列流,把多个字节流整合成一个流
* -ObjectInputStream 对象输入流,直接从文件中读取一个对象,这个对象要实现serilazable接口
* -Sytem.in 标准输入流-指键盘
* -DataInputStream 数据输入流,按基本数据类型的大小(long) 读取文件
*
* >OutputStream
* -FileOutputStream 文件输出流,写的单位是字节
* -BufferedOutputStream 缓冲输出流,内部有个byte[]字节数组
* -ByteArrayOutputStream 字节数组输出流,把数据读取到内存中,这个类不需要关联文件
* -ObjectOutputStream 对象输出流,直接把一个对象存入文件,
* -PrintStream 打印流,把内容打印到控制台
* -System.out 标准输出流-指控制台
* -DataOutputStream 数据输出流,按基本数据类型的大小(long) 写入文件
*
* 三、字符流
* >Reader
* -FileReader 文件读取流,读取的单位是字符
* -BufferedReader 缓冲读取流,内部有个char[] 字符数组
* -InputStreamReader 指定字符编码读取文件
*
* >Writer
* -FileWriter 文件写入流,写入的单位是字符
* -BufferedWriter,缓冲写入流,内部有个char[] 字符数组
* -OutputStreamWriter 指定字符编码写入文件
* -PrintWriter 打印流,把内容打印一个文件
*
*
* RandomAccessFile 随机访问流,特点:读和写都在一个类中
*
* Properties 相当于Map一样使用,这个类把数据存在一个后缀名为.properties文件
*/
}
附加学了序列化/反序列化,以及Properties(内部继承的是Hashtable,可以当作Map来使用),这么多的流,掌握常用的字节流FileInputStream/FileOutputStream/BufferInputStream/BufferOutputStream;常用的字符流FileReader/FileWriter/BufferedReader /BufferedWriter.另外还需掌握流的读写方法,杯字思想(缓冲思想)、字节数组(缓冲区)。其他的流到时候可以查API文档,了解即可。