文章目录
特殊操作流
标准输入输出流
System类中有两个静态的成员变量:
public static final InputStream in:标准输入流,通常该流对应于键盘输入、由主机环境或用户 指定的另一个输入源
static修饰:表示它可以通过类名直接访问;
final修饰:表示它是常量;
InputStream修饰:表示它的类型是InputStream类型(字节输入流);
in 表示常量名;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
——自己实现。
Scanner sc = new Scanner(System.in);
——java提供的实现键盘录入的类。
InputStream is = System.in; ——该方式采用的是:多态的形式赋值,因为InputStream是标准输入流的基类,等号右侧一定是 它的子类
public static final PrintStream out:标准输出流,通常该流对应于显示输出、由主机环境或用户 指定的另一个输出目标
static修饰:表示它可以通过类名直接访问;
final修饰:表示它是常量;
PrintStream修饰:表示它的类型是PrintStream类型;
out表示常量名。
PrintStream 继承自 OutputStream ,说明它也是字节输出流。
PrintStream 可以方便地打印各种数据值
PrintStream ps = System.out; 输出语句的本质:是一个标准的输出流
PrintStream类有的方法,System.out都可以使用。
案例:
System.out
// 39-SystemOutDemo
public class SystemOutDemo {
public static void main(String[] args) {
PrintStream ps = System.out;
ps.print("friends");
// System.out 本质是一个字节输出流
// 也就是说PrintStream类中有print方法。
}
}
System.in
// 39-SysTemInDemo
public class SysTemInDemo {
public static void main(String[] args) throws IOException {
// public static final InputStream in :标准输入流
//InputStream is = System.in;
// 该方式采用的是:多态的形式赋值,因为InputStream是标准输入流的基类,等号右侧一定是 它的子类
/* int by;
while((by = is.read()) != -1){
System.out.print((char)by);
}*/
// 这里读取的数据来自于:键盘输入。
// 使用字节流读取中文汉字存在问题:中 会识别成 ä¸
// 如何把字节流转换为字符流?用转换流
// InputStreamReader isr = new InputStreamReader(is);
// InputStreamReader 的构造方法 可以支持 InputStream参数传入
// 使用字符流能够实现一次读取一行数据。
// 该方法是字符缓冲流的特有方法
//BufferedReader br = new BufferedReader(isr);
// BufferedReader 可以将 字符流 包装成 字符缓冲流
// 上述三行内容 可以直接 使用一行替代。
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 包装过程:字节流--字符流--字符缓冲输入流 底层的执行还是字节流。
System.out.println("请输入一个字符串:");
String line = br.readLine();
System.out.println("你输入的字符串是:" + line);
System.out.println("请输入一个整数:");
int i = Integer.parseInt(br.readLine());
System.out.println("你输入的整数是:" + i);
// 自己实现键盘录入数据太麻烦了,java提供了Scanner类实现。
}
}
代码分析:
InputStream is = System.in;
*该方式采用的是:多态的形式赋值,因为InputStream是标准输入流的基类,等号右侧一定是 它的子类
InputStreamReader isr = new InputStreamReader(字节流);
*实现字节流 =》 字符流
BufferedReader br = new BufferedReader(isr);
*实现字符流 =》 字符缓冲流
整理一下就是:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
*包装过程:字节流--字符流--字符缓冲输入流 底层的执行还是字节流。
字节打印流PrintStream 和 字符打印流 PrintWriter
打印流
分类:字节打印流 PrintStream;字符打印流 PrintWriter
打印流的特点:①只负责输出数据,不负责读取数据;②有自己的特有方法;
PrintStream
- PrintStream(String fileName):使用指定的文件名创建新的打印流
- write方法:使用继承父类(OutputStream类)的方法写数据,查看的时候会转码;(转码的含义是:使用PrintStream写入的内容是数字,打开文件显示是字符,比如写入是97 打开文件显示的是a)
- print println :使用自己特有的方法写数据,查看的数据原样输出;
写数据有三种方式:
方法名 | 说明 |
---|---|
write(int b) | 写一个字节 |
write(byte[] b) | 写一个字节数组 |
write(byte[] buf,int off,int len) | 写一个字节数组的一部分 |
案例:
// 39-PrintStreamDemo
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
PrintStream ps = new PrintStream(".\\ps.txt");
// 字节输出流特有的方法
ps.write(97); // 写入文件的是 字符
// 使用特有方法写数据
ps.print(97);//写入文件的是 97
ps.print(98);
// 释放资源
ps.close();
}
}
PrintWriter
- 在java.io 包下,使用需要导包。
- 它继承自Writer,说明它是一个字符输出流。
- 它继承自Writer有五种方法写数据。可以写一个字符数组、一个字符数组的一部分、一个字符、一个字符串、一个字符串的一部分。
构造方法:
方法名 | 说明 |
---|---|
PrintWriter(String fileName) | 使用指定的文件名创建一个新的PrintWriter,不会自动执行刷新操作 也就是需要写flush方法 |
PrintWriter(Writer out,boolean autoFlush) | 创建一个新的PrintWriter out:字符输出流 autoFlush:一个布尔值,如果为真,则printIn、printf 或 format 方法将刷新输出缓冲区 也就是不需要使用flush方法 |
案例:
// 39-PrintWriterDemo
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
// PrintWriter pw = new PrintWriter(".\\ps.txt");
/* pw.write("pretty");
// 只写write 不能写入文件中,因为它是字符打印流,它是字符流它的数据是不能直接到文件的。
pw.write("\r\n");
pw.flush();
pw.write("sunshine");
pw.write("\r\n");
pw.flush();
// 不写入换行符 写入文件的内容会在一行显示。*/
// 使用println方法,实现字符的写入 也需要写flush刷新。
/* pw.println("pretty");
pw.flush();*/
PrintWriter pw1 = new PrintWriter(new FileWriter(".\\pw.txt"),true);
pw1.println("sunshine");
}
}
案例:复制Java文件(打印流改进版)
需求:把模块目录下的xxx.java复制到模块目录下的yyy.java
// 39-Demo
public class Demo {
public static void main(String[] args) throws IOException {
/* // 数据源创建字符输入流对象
BufferedReader br = new BufferedReader(new FileReader(".\\Demo5.java"));
// 数据目的地创建字符输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter(".\\copy.java"));
// 读写数据,赋值文件
String line;
// readLine 是BufferedReader特有方法,读一行文字
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
// 释放资源
br.close();
bw.close();*/
// 数据源创建字符输入流对象
BufferedReader br = new BufferedReader(new FileReader(".\\Demo5.java"));
// 根据目的地创建字符打印输出流对象
PrintWriter pw = new PrintWriter(new FileWriter(".\\copy.java",true));
// 读写数据,复制文件
String line;
while((line = br.readLine()) != null){
pw.println(line);
}
// 释放资源
br.close();
pw.close();
}
}
分析
使用BufferedWriter将字符流写入文件,需要使用执行flush刷新方法;
使用PrintWriter将字符流写入文件,使用构造方法PrintWriter(Writer writer, boolean aotuflush),则不需要执行flush刷新方法。
对象序列化流
对象序列化
对象序列化
对象序列化 = 将对象状态转化为字节流。
原因:
①实现对象持久化存储 = 将对象保存在磁盘中 ② 进行网络传输 = 创建在网络中的传输对象
作用:
将对象保存到磁盘中,或者在网络中传输对象
这种机制就是 使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据 和 对象中存储的属性 等信息。
字节序列写到文件之后,相当于文件中持久保存了一个对象的信息。
对象反序列化
对象反序列化
该字节序列还可以从文件中读取字节序列,重构对象,对它进行反序列化。
要实现序列化和反序列化就要使用对象序列化流和对象反序列化流:
对象序列化流:ObjectOutputStream
对象反序列化流:ObjectInputStream
ObjectOutputStream
- ObjectOutputStream 将java对象的原始数据类型和图形写入OutputStream,可以使用ObjectInputStream读取对象,可以通过使用流的文件夹实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
- 构造方法:
ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的objectOutputStream - 序列化对象的方法:
void writeObject(Object obj):将指定的对象写入ObjectOutputStream - 注意:
①一个对象要想被序列化,该对象所属的类必须实现Serializable接口。
②Serializable是一个标记接口(空接口),实现该接口,不需要重写任何方法。
代码执行过程知识:
1 执行之后 报错 NotSerializableException :抛出一个实例需要一个Serializable接口,序列化运行时或实例的类可能会抛出此异常。
2 Serializable 接口,类的序列化由实现java.io.Serializable接口的类启用,不实现此接口的类将不会使任何状态序列化或反序列化。
3 Serializable 接口中没有方法需要重写。
对象序列化代码执行过程
1 被实现的类要继承Serializable接口
public class 类名 implements Serializable{}
2 创建一个写入指定输出流OutputStream的对象序列化流ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("文件路径"));
3 创建一个对象
类型 obj = new 类型();
4 使用对象序列化流的writeObject方法写入输出流OutputStream
oos.writeObject(obj);
5 释放资源
oos.close();
代码:
// 39-ObjectOutputStreamDemo
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建对象序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(".\\oos.txt"));
// 创建对象
Student s = new Student("汪苏泷",30);
// 使用序列化对象的方法: void writeObject
oos.writeObject(s);
// 这种方式写入的数据,一部分可以认识,一部分不可以认识。
// 释放资源
oos.close();
// 执行之后 报错 NotSerializableException :抛出一个实例需要一个Serializable接口,序列化运行时或实例的类可能会抛出此异常。
// Serializable 接口,类的序列化由实现java.io.Serializable接口的类启用,不实现此接口的类将不会使任何状态序列化或反序列化。
// Serializable 接口中没有方法需要重写。
}
}
ObjectInputStream
- ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象。
- 构造方法:
ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream - 反序列化对象的方法:
Object readObject():从ObjectInputStream读取一个对象
使用此方法需要抛出一个 异常 ClassNotFoundException
对象反序列化执行流程
1 创建实现serializable接口的实现类
public class 类名x implements serializable{}
2 创建对象反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("文件路径"));
3 使用ObjectInputStream对象的readObject()方法读取对象
Object o = ois.readObject();
4 强制转换对象的数据类型
类名x obj = (类名x)o;
5 释放对象反序列化流
ois.close();
代码:
// 39-
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建反序列化流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(".\\oos.txt"));
// 使用方法实现读取一个对象
Object obj = ois.readObject();
// 是一个Student对象 向下转型
Student s = (Student)obj;
System.out.println(s.getName() + ", " + s.getAge());
// 释放资源
ois.close();
}
}
对象序列化流的三个问题
问题1
描述:用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题?
问题产生操作:通过在Student类中重写toString方法,使用快捷键Alt + insert 自动生成。
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
报错说明:
java.io.InvalidClassException:
1 注释:当序列化运行时检测到类中的以下问题之一时抛出:
1 类的串行版本与从流中读取的类描述符的类型不匹配;
2 该类包含未知的数据类型;
3 该类没有可访问的无参数构造函数;
问题分析:类中没有未知的数据类型、类中有可访问的无参构造方法,所以报错的原因就是类的串行版本与从流中读取的类描述符的类型不匹配。
itiheima39.test1.Student;
local class incompatible:
stream classdesc serialVersionUID = -6397297634351572786,
local class serialVersionUID = 6598352291789188323
抛出异常的原因是:serialVersionUID 类的串行版本 与 流中读取的类描述符版本 不一致
序列化运行时与《每个可序列化的类》关联一个版本号,称为serialVersionUID,
它在反序列化过程中使用,以验证序列化对象的发送者和接受者是否加载了与序列化兼容的对象的类。
如果接受者已经为具有与对应发件人类别不同的serialVersionUID的对象加载了一个类,则反序列化将导致一个InvalidClassException。
没有添加内容之前的Student类,有一个serialVersionUID;当修改了Student类后,会产生一个新的serialVersionUID。两者不一样时,就会出现问题。
问题2
描述:出现问题1的情况,如何解决?
解决:一个可序列化的类必须声明一个显式的serialVersionUID值。
一个可序列化的类可以通过声明一个名为“serialVersionUID”的字段来显式地声明它自己的serialVersionUID,该字段必须是static final 和 long 类型:
private static final long serialVersionUID = 42L;
这样声明的好处:
1 不显式声明,它是根据java(TM)对象序列化规范中所描述的类的各个方面计算该类的默认serialVersionUID值;
2 默认的serialVersionUID计算 对类细节非常敏感,因编译器的不同而不同,为了保证不同java编译器之间实现一致的serialVersionUID值,所以需要显式声明serialVersionUID值。
问题3
描述:如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
解决:成员变量使用transient 关键词修饰,则反序列化 该变量将不被显示。
代码:
// 39-test1
public class Demo {
public static void main(String[] args) throws IOException,ClassNotFoundException{
// write();
read();
// 问题1:修改了对象所属类之后,会报错。
}
// 对象序列化流
public static void write() throws IOException {
// 创建对象序列化流 对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(".\\oos.txt"));
// 使用writeObject方法写数据
Student s = new Student("许嵩",30);
oos.writeObject(s);
// 释放资源
oos.close();
}
// 对象反序列化流
public static void read() throws IOException, ClassNotFoundException {
// 创建对象反序列化流 对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(".\\oos.txt"));
// 使用readObject 方法 读数据
Object obj = ois.readObject();
// 向下转换
Student s = (Student)obj;
// 输出内容
System.out.println(s.getName() + ", " + s.getAge());
// 释放资源
ois.close();
}
}
Properties
概述
- public class properties extends Hashtable< Object,Object>
- 是一个Map体系的集合类,表示一组持久的属性。
- Properties可以保存到流中 或 从流中加载 。
- 属性列表中 每个键及其对应的值都是一个字符串。
- 继承于HashTable,可以使用HashTable的put方法,但是不建议使用;因为put方法允许调用者插入键或值不是String的类型;
- 作用是:用于读取java配置文件,其配置文件常为.properties文件,是以键值对的形式进行参数配置的。
案例:
// 39-
public class ProtertieDemo {
public static void main(String[] args) {
// Properties作为Map集合的使用
Properties prop = new Properties();
// 存储元素
prop.put("001", "汪苏泷");
prop.put("002","许嵩");
prop.put("003", "林俊杰");
// 遍历集合
Set<Object> objs = prop.keySet();
// 由于没有指定集合类型,所以默认是object类型
for(Object key:objs){
Object value = prop.get(key);
System.out.println(key + ", " + value);
}
// 输出结果
// 001, 汪苏泷
// 002, 许嵩
// 003, 林俊杰
}
}
Properties常用的方法
Properties可以从保存到流中 也可以从流中加载。
方法名 | 说明 |
---|---|
Properties() | 创建一个没有默认值的空属性列表 |
Object setProperty(String key,String value) | 设置集合的键和值,都是String类型,底层调用Hashtable方法 put |
String getProperty(String key) | 使用此属性列表中指定的键key 搜索 属性值value |
Set< String > stringPropertyNames() | 从该属性列表中返回一个不可修改的键集(key),其中键及其对应的值是字符串 |
void load(InputStream instream) | 从输入字节流读取属性列表 (键值对) |
void load(Reader reader) | 从输入字符流读取属性列表(键值对) |
void list(PrintStream out) | 将此属性列表打印到指定的字节输出流 使用Properties类在控制台打印输出JVM参数 properties对象.list(System.out) System.out 返回值是 PrintStream |
void list(PrintWriter out) | 将此属性列表打印到指定的字符输出流 |
案例:
// 39-
public class PropertiesDemo2 {
public static void main(String[] args) {
// 创建Properties集合对象
Properties prop = new Properties();
// 添加集合对象 setPropert
prop.setProperty("001", "汪苏泷");
/*
setProperty(String key, String value) {
return put(key, value);
}
// 视频中的
Object put(Object key,Object value){
return map.put(key,value);
}
// 上述这种设计,让原本put接收Object类型的方法,变成只接收String类型
// 自己和视频中的put方法内容不一样,可能是因为jdk版本不一样的问题,自己是jdk1.8,视频中是jdk1.9
*/
prop.setProperty("002", "许嵩");
prop.setProperty("003", "林俊杰");
// 使用此属性列表中指定的键搜索属性。String getProperty(String key)
System.out.println(prop.getProperty("002"));
// 输出许嵩
// Set<String> stringPropertyNames() 从属性列表中返回一个不可修改的键集
Set<String> names = prop.stringPropertyNames();
for(String key:names){
System.out.println(key);
// 输出 001 002 003
}
}
}
Properties 和 IO流 相结合的方法
方法名 | 说明 |
---|---|
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out,String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适应于使用load(InputStream)方法的格式写入输出流字符 |
void store(Writer writer,String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流 |
案例:字符流
// 39-
public class PropertiesDemo3 {
public static void main(String[] args) throws IOException{
// 把集合中的数据保存到文件
// myStore();
// 把文件中的数据加载到集合
myLoad();
}
private static void myLoad() throws IOException{
// 字符输入流对象
FileReader fr = new FileReader(".\\fw.txt");
// 定义集合
Properties prop = new Properties();
prop.load(fr);
fr.close();
Set<String> keys = prop.stringPropertyNames();
for(String key : keys){
String value = prop.getProperty(key);
System.out.println(key + ", " + value);
}
}
// 把集合中的数据保存到文件
private static void myStore() throws IOException {
// 定义集合
Properties prop = new Properties();
// 集合添加元素
prop.setProperty("001","sunshine");
prop.setProperty("002", "pretty");
prop.setProperty("003", "information");
FileWriter fw = new FileWriter(".\\fw.txt");
prop.store(fw, null);
fw.close();
}
}
案例:游戏次数
需求:
请写程序实现猜数字小游戏只能试玩3次,如果还想玩,提示:游戏试玩已结束,想玩请充值(www.itcast.cn)
代码:
// 39-test2
// 游戏类
public class GuessNumber {
private GuessNumber(){
}
public static void start(){
// 设置随机数 要猜的数字
Random r = new Random();
// 设置的随机数是0-100的,包含0 不包含100 要想实现1-100 所以要+1
int number = r.nextInt(100) + 1;
while(true){
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要猜的数字:");
int x = sc.nextInt();
if(x > number){
System.out.println("猜测数字" + x + "大了");
}else if(x < number){
System.out.println("猜测数字" + x + "小了");
}else{
System.out.println("恭喜猜中!");
break;
}
}
}
}
// 测试类
public class test {
// 需求:请写程序实现猜数字小游戏只能试玩3次,如果还想玩,提示:游戏试玩已结束,想玩请充值(www.itcast.cn)
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader(".\\game.txt");
Properties prop = new Properties();
prop.load(fr);
fr.close();
Set<String> keys = prop.stringPropertyNames();
for(String key: keys){
// 关于代码中的key 是game.txt文件写好的内容,该文件写的内容是count=0,所以这里的key表示的是count
String value = prop.getProperty(key);
int val = Integer.parseInt(value);
if(val >= 3){
System.out.println("游戏试玩已结束,想玩请充值(www.itcast.cn)");
}else{
GuessNumber.start();
val++;
prop.setProperty(key, String.valueOf(val));
FileWriter fw = new FileWriter(".\\game.txt");
prop.store(fw, null);
fw.close();
}
}
}
}
IO流总结
& IO概述
java的IO流,用来处理设备间数据传输的问题;
主要可以分为输入流、输出流以及字节流、字符流两个大类。
- 字节流:
包括字节输入流和字节输出流;他们的父类是InputStream类和OutputStream类,且两者都是抽象类;
抽象类不能直接实例化对象,需要通过其子类实例化;
- 常用的子类有FileInputStream、FileOutputStream;
前者表示从文件中获取输入数据,后者表示将数据写入文件; - 输入字节缓冲流BufferedInputStream和输出字节缓冲流BufferedOutputStream;
前者可以实现向输入字符流写数据、后者实现向输出字节流写数据。 - 为什么会存在缓冲流:减少每次使用字节流时对底层系统的调用。使用FileInputStream时,会执行三个过程,1调用底层系统创建文件;2创建字节输入流对象;3使得字节输入流对象指向创建好的文件;
- 字符流:
包括字符输入流和输出流;他们的父类是Reader和Writer,且两者都是抽象类;
- 常用的子类有InputStreamReader和OutputStreamWriter;
前者表示将字节输入流转换成字符输入流;后者表示将字节输出流转换成字符输出流; - 常用的子类FileReader和FileWriter;
前者表示从文件中获取输入字符流;后者表示将输出字符流写入文件; - 常用的字符缓冲流有BufferedReader和BufferedWriter;
前者表示向输入字符流写数据;后者表示向输出字符流写数据; - 为什么会存在字符缓冲流?字符缓冲流有特殊的功能,比如读一行数据readLine和写一行数据newLine
字符流/字节流定义
字符缓冲流(字符流(字节流(String)))
= BufferedReader/BufferedWriter(InputStreamReader/OutputStreamWriter(FileInputStream/FileOutputStream(String)))
字符缓冲流(字符流(String))
= BufferedReader/BufferedWriter(FileReader/FileWriter(String))
字符流(字节流(String))
= InputStreamReader/OutputStreamWriter(FileInputStream/FileOutputStream(String))
字符流(String)
= FileReader/FileWriter(String)
字节缓冲流(字节流(String))
= BufferedInputStream/BufferedOutputStream(FileInputStream/FileOutputStream(String))
字节流(String)
= FileInputStream/FileOutputStream(String)