Day09+Day10
一、IO
IO分别代表Input输入,和Output输出。数据的传输需要借助流,可以理解为数据的流动。
- 根据流动的方向不同,分为输入流(由设备到内存)和输出流(由内存到设备)。
- 根据传输的数据类型不同,分为字节流和字符流。
流的顶级父类:
顶级父类 | 输入流 | 输出流 |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
二、 字节流
数据的传输,无论体现出来是哪种形式,最底层一定是字节的传输。
字节输出流:
2.1OutputStream
java.io.OutputStream
是所有字节输出流的父类,是一个抽象类,它定义了字节输出流的基本方法。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。 (必须要)public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public abstract void write(int b)
:将指定的字节输出流。
2.2 FileOutputStream
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
构造方法有两种形式:
//当前路径下以字符串形式创建
FileOutputStream fout = new FileOutputStream("a.txt");
//以文件作为参数构造
File f= new File("a.txt");
FileOutputStream fout2 = new FileOutputStream(f);
写出数据write(int n)
、写出数组write(byte[] n)
、写出指定长度数组write (byte[] n , int off ,int lenth)
、写出换行write("\r\n")
public static void main(String[] args) throws IOException {
//写出数据,默认追加为false,如果要追加创建的时候应为...("a.txt",true);
FileOutputStream file = new FileOutputStream("a.txt");
file.write(97);
byte[] s = "abcde".getBytes();
//写出全部数组
file.write(s);
//写出换行
file.write("\r\n".getBytes());
//写出数组中从0位置开始,三个长度的字节数据
file.write(s, 0, 3);
file.close();
}
-
虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
-
流操作完毕后,必须释放系统资源,调用close方法,千万记得。
2.3 FileInputStream
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
构造方法:
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
int len;
//读单个字节
while((len=fis.read())!=-1) {
System.out.println((char)len);
}
fis.close();
//读字节数组,需要获取每次读到的长度,如果直接输出数组b,例如数组长度是5,这一次只读到3个字节,那么有两个字节是上一次的,会导致错误的输出,用len可以保证读了多少输出多少
FileInputStream fis2 = new FileInputStream("a.txt");
byte [] b = new byte[5];
while((len=fis2.read(b))!=-1) {
System.out.println(new String(b,0,len));
}
fis.close();
}
中间需要再次打开的时候,是因为之前单个打印的时候已经全部读完了,需要重头再来。
2.4 读取图片练习
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("copy.jpg");
FileOutputStream fos = new FileOutputStream("test.jpg");
int len;
//采用数组的方式读写,数组长度越长,IO次数就越少,效率越高
byte [] b = new byte[10];
//读取数组
while((len=fis.read(b))!=-1) {
//写出数组,用len保证数组的准确性
fos.write(b,0,len);
}
//读写完之后一定要关闭,流的关闭原则:先开后关,后开先关,类似于离开一栋房子,先关上房间里面的门,最后再关大门
fos.close();
fis.close();
}
三、字符流
3.1 输入流Reader
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
3.2 FileReader
java.io.FileReader
类可以直接读取字符文件,构造时,使用系统默认的字符编码(Windows是GBK)和默认字节缓冲区(一个字节数组,用来临时储存数据)。
构造与读取字符:
public static void main(String[] args) throws IOException {
//采用路径直接构造
FileReader f = new FileReader("a.txt");
int len;
char[] cubf = new char[10];
//读取单个字符,提升为int类型
while((len=f.read())!=-1) {
System.out.println((char)len);
}
f.close();
//读取字符数组
FileReader f2 = new FileReader("a.txt");
while((len=f2.read(cubf))!=-1) {
System.out.println(new String(cubf,0,len));
}
}
3.3 FileWriter
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
写入方法
void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。- 构造方法:直接用地址构造,或以文件构造
public FileWriter(File file)
public FileWriter("a.txt")
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("aa.txt");
//写字符
fw.write('A');
//写字符串
fw.write("大会上年度涉农打算把");
String s = "不然赛排毒素大数据库";
//写出换行
fw.write("\r\n");
//写出字符串中指定位置+指定长度内容
fw.write(s, 1, 3);
char[] cubf = "一个字节数组".toCharArray();
//写出字符数组
fw.write(cubf);
//写出字符数组中指定位置+指定长度内容
fw.write(cubf, 2, 2);
fw.close();
}
四、 缓冲流
4.1 简介
缓冲流是对输入输出流的"增强"。其基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
4.2 字节缓冲流
构造方法:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("copy.jpg"));//以基本输入流作为参数构造
BufferedInputStream bos = new BufferedInputStream(new FileInputStream("buffer.txt"));
读取效率对比
public static void main(String[] args) throws IOException, ClassNotFoundException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("copy.jpg"));
// BufferedInputStream bos = new BufferedInputStream(new FileInputStream("buffer.txt"));
int len=0;
byte [] b = new byte[10];
FileInputStream filein = new FileInputStream("copy.jpg");
long time = System.currentTimeMillis();
while((len=filein.read(b))!=-1) {}
time =System.currentTimeMillis()-time;
System.out.println("读取时间:"+time);
time = System.currentTimeMillis();
while((len=bis.read(b))!=-1) {}
time =System.currentTimeMillis()-time;
System.out.println("读取时间:"+time);
filein.close();
bis.close();
}
输出结果:192
3
4.4字符缓冲流
-
构造方法
BufferedReader br = new BufferedReader(new FileReader("a.txt")); //以基本字符流的作为构造参数,实际上是对基本流的“增强” BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt");
-
特有方法
- 读取一行
br.readLine();
- 写出系统对应的行分隔符
bw.newLine();
- 读取一行
4.5 文本读取排序分析
按照文本前的序号进行升序排序:
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建缓冲字符输入与输出流
BufferedReader br = new BufferedReader(new FileReader("test.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("test2.txt"));
//创建字符串集合
ArrayList<String> list = new ArrayList<String>();
String line =null;
//逐行读入,添加至集合list中
while((line=br.readLine())!=null) {
list.add(line);
}
//根据字符串的第一个字,也就是序号升序排列,采用了Lambda表达式
Collections.sort(list, (o1,o2) -> o1.charAt(0)-o2.charAt(0));
//将排序后的list写入新的txt文件中
for(String s : list) {
bw.write(s);
bw.newLine();
}
bw.close();
br.close();
}
五、转换流
5.1 转换流的作用
字符在计算机中都是以二进制数据存储的,但是我们不能直接看的明白二进制数据,需要对二进制数据进行“翻译”。
由二进制数据 -> 我们能够识别的字符,这一过程称为解码
字符 -> 二进制数据 这一过程称为编码
字符编码Character Encoding
: 就是一套自然语言的字符与二进制数之间的对应规则。对于不同的编码,有不同的规则,编码对应的规则称为字符集。
- 编码和解码必须采用相同的字符集,如果一个文本是以ACSII码进行编码,却用GBK字符集进行解码,则会读到乱码
5.2 InputStreamReader&&OutputStreamWriter
构造方法:以文件输入流作为构造参数,还可以选择对应的解码字符集,默认为UTF-8编码。其余读写功能与FileInputStream一致。
public static void main(String[] args) throws IOException, ClassNotFoundException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("a.txt"),"GBK");
InputStreamReader isr3 = new InputStreamReader(new FileInputStream("a.txt"),"UTF-8");
int len;
while((len=isr.read())!=-1) {
System.out.print((char)len);
}
System.out.println();
while((len=isr2.read())!=-1) {
System.out.print((char)len);
}
System.out.println();
while((len=isr3.read())!=-1) {
System.out.print((char)len);
}
isr.close();
isr2.close();
isr3.close();
}
输出:dasniondiaosniof大设计师大教室的kxzcln2nkldsanl
dasniondiaosniof大设计师大教室的kxzcln2nkldsanl
dasniondiaosniof????????????kxzcln2nkldsanl
输出转换流的构造与输入流一致,其区别是一个指定读取的解码字符集,一个指定写入的编码字符集。
5.3文本的读入与输出案例
以出师表为例,用GBK码读出,用UTF-8码编入。
public static void main(String[] args) throws IOException, ClassNotFoundException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"),"GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"),"UTF-8");
int len;
char b[] = new char[10];
while((len=isr.read(b))!=-1) {
osw.write(b,0,len);
}
isr.close();
osw.close();
}
查看转换后的文本大小,发现UTF-8码的文件稍大一些
五、序列化流
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
序列化流有两个必须的条件:
- 类要实现
java.io.Serializable
接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
- 该类的所有属性必须是可序列化的,不需要序列化的属性要加
transient
修饰符 - 构造方法:
public ObjectOutputStream(OutputStream out)
public ObjectIntputStream(InputStream in)
5.1 序列化与反序列化
public class Day10 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化、反序列化流的构造
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
//创建类的一个对象e
Employee e = new Employee("ddd","aaa",7);
//序列化对象e
oos.writeObject(e);
oos.close();
//反序列化对象e,因为读取对象后变为Object类,需要向下造型
Employee object = (Employee)ois.readObject();
object.addressCheck();
}
}
class Employee implements java.io.Serializable{
public String name;
public String address;
public int eid;
//瞬态成员变量,不会被序列化
public transient int member;
//增加一个版本号,验证序列化的对象和对应类是否版本匹配
private static final long serialVersionUID = 1L;
public Employee(String name, String address, int eid) {
super();
this.name = name;
this.address = address;
this.eid = eid;
}
public void addressCheck() {
System.out.println("Employee [name=" + name + ", address=" + address + ", eid=" + eid+ "]");
}
}
-
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个
ClassNotFoundException
异常。 -
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个
InvalidClassException
异常。这是由于:-
该类的序列版本号与从流中读取的类描述符的版本号不匹配
-
包含未知的数据类型
-
没有可以访问的无参构造
-
5.1 练习
- 将存有多个自定义对象的集合序列化操作,保存到
list.txt
文件中。 - 反序列化
list.txt
,并遍历集合,打印对象信息。
//Employee类同上,不再给出
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
ArrayList<Employee> list = new ArrayList<Employee>();
//创建Employee类对象
Employee e = new Employee("first","diyi",1);
Employee e2 = new Employee("second","dier",2);
Employee e3 = new Employee("third","disan",2);
//将对象添加进集合
Collections.addAll(list, e,e2,e3);
//将集合序列化
oos.writeObject(list);
oos.close();
//反序列化集合并输出
ArrayList<Employee> list2 = (ArrayList<Employee>)ois.readObject();
for(Employee ee: list2) {
ee.addressCheck();
}
}
六、打印流
平时我们在控制台打印输出,是调用print
方法和println
方法完成的,这两个方法都来自于java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
6.1PrintSystem类
- 构造方法:
public PrintStream(String filename)
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建打印流的对象,会覆盖文件
PrintStream ps = new PrintStream("a.txt");
//将System.out.println的目标从控制台改变为ps对象关联的文件
System.setOut(ps);
System.out.println("6666667打算的撒。。**77");
}
特点:可以打印各种类型的数据
七、IO的异常处理
7.1 JDK之前的处理
采用try-catch-finally的形式,保证流一定关闭。
public static void main(String[] args) throws IOException {
FileWriter fw = null;
try{fw = new FileWriter("aa.txt");
fw.write('A');}
catch(IOException e) {
System.out.println("抓住了");
}
finally{
if(fw != null){
fw.close();}
}
}
}
7.2 JDK7的处理
JDK7优化后的try-with-resource
语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
public static void main(String[] args) {
try(FileWriter fw = new FileWriter("aa.txt");
FileWriter fw2 = new FileWriter("bb.txt");
){
fw.write('A');
fw2.write('Q'); }
catch(IOException e) {
System.out.println("抓住了");
}
}//即把创造流对象的语句放在try()中,如果有多个,用分号隔开
7.3 JDK9的处理
JDK9中try-with-resource
的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close,我们来了解一下格式。
public static void main(String[] args) {
FileWriter fw = new FileWriter("aa.txt");
FileWriter fw2 = new FileWriter("bb.txt");
try(fw; fw2 ){
fw.write('A');
fw2.write('Q'); }
catch(IOException e) {
System.out.println("抓住了");
}
}//即只要把创建好的流对象放在try()中即可
八、 属性集
java.util.Properties
继承于Hashtable
,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。
基本方法:
-
public Properties()
:创建一个空的属性列表。 -
public Object setProperty(String key, String value)
: 保存一对属性。 -
public String getProperty(String key)
:使用此属性列表中指定的键搜索属性值。 -
public Set<String> stringPropertyNames()
:所有键的名称的集合。
public static void main(String[] args) throws {
Properties p = new Properties();
p.setProperty("一个键", "一个值");
System.out.println(p.getProperty("一个键"));//输出:一个值
System.out.println(p);
Set<String> s = p.stringPropertyNames();
}
比较特别的是它可以与流结合使用:
public load(InputStream in)
加载一个输入流的信息,流对应一个文本,可以使属性集加载文本中的数据,前提是文本中数据的格式必须是键值对的形式,可以使用空格、等号、冒号等符号分隔。
public static void main(String[] args) throws IOException {
Properties p = new Properties();
p.load(new FileInputStream("KV.txt"));
Set<String> s= p.stringPropertyNames();
for(String ss : s) {
System.out.println(ss+p.getProperty(ss));
}
}
输出:
???filename-KV.txt
date-2020.6.3
time-1531