文章目录
什么是IO?
java中I/O的操作主要是靠java.io
包下面的类和接口来实现的,进入输入,输出操作.输入也可以叫做读取数据,输出也可以叫做写入数据.
IO分类
根据数据的流向分为:输入流和输出流
- 输入流:把数据从其他设备上读取到内存当中的流.
- 输出流:把数据从内存当中写入到其他设备上的流.
根据数据的类型分为:字节流和字符流- 字节流:以字节为单位,读取数据的流
- 字符流:以字符为单位,读写数据的流
IO流的顶级父类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
字节流
一切皆为字节
一切文件数据(文本文档,图片,视频等)在存储时,都是以二进制数字的形式保存的,都是一个一个的字节, 那么数据在进行传输的时候也是如此.所以字节流可以传输任意文件的数据.在操作流的时候,我们要明确,无论使用了什么样的流对象,底层传输的始终为流对象.
字节输出流[OutputStream]
java. io .outputstream
此抽象类是表示输出字节流的所有类的超类,将指定的字节信息写入到目的地。它定义了字
节输出流的基本共性的api方法
- public void close():关闭此输出流并释放与此流相关的其他任何系统资源。
- public void flush():刷新并强制任何缓冲的输出字节信息被写入.
- public void write(byte[] b):将b.length字节从指定的字节数组写入到此输出流中。
- public void write(byte[ b,int fint len):从指定的字节数组写入len个字节,从偏移量off开始输出到此输出流中
- public abstract void write(int b):将制定的字节输出到此输出流中
备注:close写法,当完成流的操作时,必须调用此方法,释放系统资源.
FileOutputStream类
java.io.FileOutputStream
类是文件字节输出流,用于将数据写入到文件中。
构造方法
- FileOutputStream(File file) :创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
- FileOutputStream(String name) :创建文件输出流以指定的的文件名称写入文件中。
参数:
File file:目的地是一个文件
String name: 目 的地是一个文件的路径
构造方法的作用:
- 创建个FileOutputStream类对 象
- 会根据构造方法中传递的文件/文件路径(路径上文件不存在),创建个空的文件。
- 会把FileOutputStream对象指向创建好的文件。
字节输出流的使用步骤:
- 创建个FileOutputStream类对 象,构造方法中 传递写入数据的目的地。
- 调用File0utputStream对象中的方法write,把数据写入到文件中
- 释放资源。
当你创建一个流对象时,必须先传递一个文件路径,该路径下,如果没有这个文件,会创建该文件,如果有这个文件,会清空这个文件当中的数据。
示例代码:
// 1.创建一个FileOutputStream类对象,构造方法中传递写入数据的目的地。
FileOutputStream fos = new FileOutputStream("day28_IO\\a.txt");
// 2.调用FileOutputStream对象中的方法write,把数据写入到文件中
fos.write(97);
// 3.释放资源。
fos.close();
原理解析:
数据的追加续写
如何在保留目标文件中的数据,还能继续添加新的数据到目标文件中?
- public FileOutputStream(File file,boolean append):创建文件输出流以写入由指定的File对象表示的文件中.
- public FileOutputStream(String name,boolean append):创建文件输出流以指定的名称写入文件中.
这两个构造方法,参数中都需要传入一个boolean类型的值, true
表示的追加数据, false
表示的清空原有数据。在这样的情况下创建输出流对象,就可以指定是否需要在文件的末尾追加。
代码演示:
public class FileOutputStreamDemo03 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("day12.15_IO\\c.txt", true);
fos.write("你好!".getBytes());
fos.close();
}
}
写入换行
Windows系统里,换行符号是\r\n
。把以指定是否需要追加续写换行。
Linux系统里,换行符号是/n
mac系统换行符号是/r
Unix系统里,每行结尾只有换行,即\n
回车符\r和换行符\n
- 回车符:回到一行的开头
- 换行符:下一行(newLine)
系统中的换行: - Windows系统中,每行结尾是
回车+换行
.即\r\n
. - Unix系统中,每行的结尾只有
换行
,即/n - Max系统中,每行的结尾是
回车
,即\r
字节输入流[InputStream]
java.ioInputStream
此抽象类是表示字节输入流的所有类的超类.可以读取字节信息到内存中,他定义了字节输入流的基本共性的api方法:
- public void close(): 关闭此输入流并释放 与此流相关的其他的任何系统资源。
- public abstract int read():从输入流中读取数据的下一个字节。
- public int read(byte[] b):从输入流中读取一些字节数,并且将他们存储到字节数组b中
*备注:close方法,当完成流的相关操作后,需要调用此方法关闭输入流,释放资源.
FileInputStream类
java.io.FileInputStream
类是文件输入流,从文件中读取字节.
构造方法
- FileInputStream(File file):通过打开与实际文件的连接来创建一 个FileInputStream, 该文件由文件系统中的File对象file命名。
- FileInputStream(Sring name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名.
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException。
代码示例:
public static void main(String[] args) throws IOException {
/*
// 1. 创建一个FileInputStream类对象,构造方法中绑定要读取的文件
FileInputStream fis = new FileInputStream("day28_IO\\c.txt");
// 2. 使用FileInputStream类对象中的方法read(),读取文件中的数据
int len = 0 ;// 表示记录读取到的字节
while ( (len = fis.read()) != -1) {
System.out.print((char)len+"");// Hello
}
// 3. 释放资源,关闭流对象。
fis.close();
*/
可以使用字节数组来读取数据:read(byte[] b):从输入流中读取多个字节,并且将其存储到缓冲区数组b当中。当读取到文件的末尾时,返回一个`-1`。
// 1. 创建一个FileInputStream类对象,构造方法中绑定要读取的文件
FileInputStream fis = new FileInputStream("day12.15_IO\\c.txt");
// 2. 使用FileInputStream类对象中的方法read(),读取文件中的数据
int len = 0;// 表示记录读取到的字节
byte[] bytes = new byte[2];
while ((len = fis.read(bytes))!= -1){
System.out.println(new String(bytes,0,len));
}
// 3. 释放资源,关闭流对象。
fis.close();
}
备注:使用数组读取,每次可以读取多个字节,减少了系统间的10操作次数,从而提高了读取的效率,建议使用。
练习:通过字节流实现图片复制 FileInputStream和FileOutputStream
实现从桌面的G1.jpeg图片复制到E\documents\day28_ 10目录下。
原理:从已知的文件中读取字节,再把读取到的字节写入到另一个文件中.
public static void main(String[] args) throws IOException {
// 构建开始时间
long start = System.currentTimeMillis();
// 1.创建一个字节输入流对象,构造方法中绑定需要读取的图片路径
FileInputStream fis = new FileInputStream("C:\\Users\\admin\\Desktop\\3.gif");
// 2.创建一个字节输出流对象,构造方法中绑定需要写入的文件
FileOutputStream fos = new FileOutputStream("E:\\documents\\day28_IO\\3.gif");
byte[] bytes = new byte[1024];
int len = 0;//记录读取到的有效字节个数
while ((len = fis.read(bytes)) != -1) {
//4.使用字节输出流对象中的write方法,把读取到字节写入到指定的文件中
fos.write(bytes,0,len);
}
// 5.释放资源。(先开的后关,后开的先关)
fos.close();
fis.close();
// 构建结束的时间
long end = System.currentTimeMillis();
System.out.println("复制文件耗费的时间为:" + (end - start) + "ms");
}
字符流
当使用字节流读取文件文件的时候,可能会引发一点小问题。 如果你遇到了中文字符时,可能不会显示完整的字符。那就是因为一个中文字符可能占用多个字节存储。所以Java提供了 些字符流类,以字符为单位读写数据,专门用于处理文本文档文件.
字符输入流[Reader]
字符输入流[Reader]
java. io. Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存当中,它定义了字符输
入流的基本共性的api方法:
- public void I ise():关闭此输入流并且释放与此流相关的其他系统资源。
- public int read():从输入流中读取一个字符.
- public int read(char[] chuf):从输入流一次读取多个字符,并将它们存储到字符数组chuf当中.
FileReader类
java.io.FileReader
类主要是用于读取字符文件的便捷类。构造方法使用时默认的编码字符集和默认的字节缓冲区。
备注:
1. 字符编码:字节与字符的对应规则。Windows系统中的中文编码默认是GBK编码表,idea中采用UTF-8
2. 字节缓冲区:一个字节数组,用来临时存储字节数据。
构造方法
- FileReader(File file):创建一个新的FileReader对象,指定需要读取的file对象.
- FileReader(String filename):创建一个新的FileReader对象,指定需要读取的文件名称。
当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream。
代码示例:
// 1.创建对象,构造方法中绑定需要读取的文件数据源
FileReader fr = new FileReader("day28_IO\\c.txt");
// 2.使用该对象中的方法read()来读取文件数据
// int read() 读取单个字符并返回
/*int len = fr.read();
System.out.println((char)len);// 72 H 张*/
int len = 0;// 记录读取的字符
while ((len = fr.read()) != -1) {
System.out.print((char)len);
}
使用字符数组读取数据:read(char[] chuf),每次读取chuf的长度个字符到数组当中,返回读取到有效字符的个数。
当它读取到末尾时,返回-1
。
代码示例:
// 1.创建对象,构造方法中绑定需要读取的文件数据源
FileReader fr = new FileReader("day28_IO\\c.txt");
char[] chuf = new char[1024];
int len = 0;// 记录的是每次读取的有效的字符个数
//2. 使用该对象中的方法read()来读取文件数据
while ((len = fr.read(chuf)) != -1) {
// 字符数组转换成字符串
System.out.println(new String(chuf,0,len));
}
// 3.释放资源,关闭此输入流。
fr.close();
字符输出流[Writer]
java. io.Nriter
抽象类是表示用于输)字符流的所有类的超类。将指定的字符信息写入到目的地中。它定义了字符输出流的基本共性的api方法:
- void write(int c):写入单个字符
- void write(char[] chuf):写入字符数组
- abstract void write(charD chuf,int ff,int len):写入char数组的一部分, 从char数组的起始索引值of开始,len个写入字符个数.
- void write(String str):写入字符串.
- void write(String str,int off,int len):写入字符串的一 部分,从字符串的起始索引of开始,写入en个字符个数。
- void flush():刷新该流的缓冲
- void close():关闭此流,但是需要先刷新它
FileWriter类
java.io.FileWriter
类是用于写入字符到文件中.构造方法使用系统默认的字符编码和默认的字符缓冲区.
构造方法
- FileWriter(File file):创建一个新的FileWriter,指定写入的file对象.
- FileWriter(String filename):创建一个新的FileWriter,指定需要写入的文件名称
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
代码示例:
public static void main(String[] args) throws IOException {
// 1.创建一个FileWriter对象,构造方法中绑定需要写入数据的目的地。
FileWriter fw = new FileWriter("day28_IO\\e.txt");
// 2.使用fileWriter对象中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程)
// void write(int c) 写入单个字符
fw.write(122);// K z
// 3.使用FileWriter对象的中的方法flush,把内存缓冲区中的数据刷新到文件中。
//fw.flush();
// 4.释放资源(会先把内存缓冲区中的数据刷新到文件中)
fw.close();
}
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写入字符到文件中。但是关闭流对象,是无法继续写入数据到文件中。如果既想写入数据到文件中,又想继续使用流对象,那么就需要flush方法。
- flush:刷新缓冲区,流对象可以继续使用
- close:先刷新缓冲区,然后会通知系统释放资源,关闭流对象,流对象不可以再使用。
代码演示:
public static void main(String[] args) throws IOException {
// 1.创建对象
FileWriter fw = new FileWriter("day28_IO\\e.txt");
// 2.调用write方法
fw.write(97);// a
// 3. 刷新
fw.flush();
// 刷新之后流可以继续使用
fw.write(98);
// 4. 关闭流对象
fw.close();
// close方法之后,流已经关闭了,已经从内存当中消失了,流不能再使用了,如果使用抛出Stream closed
fw.write(99);
}
写入字符数组:write(char[] chuf)
和write(char[] chuf,int off,int len)
,每次可以写入一个字符数组的数据,用法类似于FileOutputStream
代码示例:
//1. new
FileWriter fw = new FileWriter("day28_IO\\f.txt");
//2. write
//2.1 char[]
char[] chars = new char[]{'a','b','c','d','e','f'};
fw.write(chars);// abcdef
//abstract void write(char[] chuf,int off,in len):写入字符数组的一部分,从off起始索引开始,写入len个字符个数
fw.write(chars, 3, 3);// def
//void write(String str):写入一个字符串
fw.write("你好,Java");
//void write(String str,int off,int len):写入字符串的一部分,从off起始索引开始,写入len个字符个数
fw.write("你好,Java",3,4);// Java
//3. flush
//fw.flush();
//4. 关闭
fw.close();
续写和换行:操作类似于FileOutputStream
public static void main(String[] args) throws IOException {
// 1. new
FileWriter fw = new FileWriter("day28_IO\\g.txt", true);
//2. write
char[] chars = new char[50];
//fw.write();
for (int i = 0; i < 50; i++) {
chars[i] = (char) (20000+i);
fw.write(chars[i] + "\r\n");//换行性质
}
//3. 刷新
fw.flush();
//4. 关闭
fw.close();
}
JDK7新特性
可以使用try-with-resource语句,该语句确保了每个资源在语句结束时关闭,所谓的资源(resource)是 指在程序完成后,必须要关闭的对象。
格式:
try(创建流对象语句,如果多个,请使用”;"隔开) {
//可能产生异常的代码
} catch (异常类型 异常变量名) {
//异常处理逻辑
}
属性集[Properties]
java.util.Properties
类继承于Hashtable.用来表示一个持久的属性集,它使用键值结构存储数据,每个键及其对应的值都是一个字符串
构造方法
- public Properties():创建一个空的属性集列表.
共性的api方法
- public Object setProperty(String key,String value): 保存一对属性
- public String getProperty(String key):使用此属性列表中的指定的键搜索对应的值。
- public Set stringPropertyNames():获取所有键的名称并封装到Set集合中。
public static void main(String[] args) {
// 创建属性集对象
Properties properties = new Properties();
// 添加键值对元素
properties.setProperty("name", "abc.txt");
properties.setProperty("size", "12000");
properties.setProperty("destination","D:\\abc.txt");
properties.put("data","小孙");
System.out.println(properties);// {destination=D:\abc.txt, name=abc.txt, size=12000}
// 通过键来获取值
String data = properties.getProperty("data");
System.out.println(data); // 小孙
String size = properties.getProperty("size");
System.out.println(size);// 12000
// 遍历该属性集
// public Set<String> stringPropertyNames():获取所有键的名称并封装到Set集合中。
Set<String> keys = properties.stringPropertyNames();
// 遍历keys
for (String key : keys) {
// 通过key获取value
String value = properties.getProperty(key);
System.out.println(key + "=" + value);
}
}
与流相关的方法
- public void load(InputStream input):从字节输入流中读取键值对
参数中使用了字节输入流,通过流对象,可以关联到某个文件上,这样既可以加载文件中的数据。文件中的数据的格式:key=value
例如:
data=小孙
size=12000
name=abc.txt
代码演示:
public static void show01() throws IOException {
// 0.构建一个流对象
FileReader fr = new FileReader("day29_IO\\abc.txt");
// 1.创建Properties集合
final Properties properties = new Properties();
// 2.使用Properties集合中的方法load读取保存在输入流中的数据
properties.load(fr);
// 3.遍历Properties集合
final Set<String> set = properties.stringPropertyNames();
for (String key : set) {
// 通过key获取value值
final String value = properties.getProperty(key);
System.out.println(key + "=" + value);
}
/*
name=abc.txt
size=12000
data=小孙
目的地=D:\abc.txt
*/
}
- public void store(OutputStream out,String comments):把集合当中数据写入字节输出流中
可以使用Properties集合当中的方法store,把集合当中的临时数据,持久化写入到硬盘文件中保存。
代码示例:
public static void show02() throws IOException {
// 1. 创建Properties集合对象,添加数据
final Properties properties = new Properties();
properties.setProperty("四大名著1","红楼梦");
properties.setProperty("四大名著2","西游记");
properties.setProperty("四大名著3", "水浒传");
properties.setProperty("四大名著4", "三国演义");
// 2. 创建字节输出流/字符输出流对象,构造方法中绑定需要写入数据的目的地
final FileWriter fw = new FileWriter("day29_IO\\abcd.txt", true);
// 3. 使用Properties集合中的方法store,把集合当中的临时数据,持久化写入到硬盘当中存储
properties.store(fw, "si da ming zhu");
// 4.释放资源。
fw.close();
}
缓冲流[Buffered]
缓冲流我们可以理解为对原来的使用数组方式进行数据传输的一种增强
按照类型分为:
- 字符缓冲流: BufferedReader, BufferedWriter
- 字节缓冲流: BufferedInputStream, BufferedOutputStream
缓冲流的基本原理,是在创建流对象的时候,会创建一个内置的默认大小的缓冲区数组, 通过缓冲区读写数据,减少系统lO操作的次数,减少开销,提高程序的读写的效率。
字节缓冲流
构造方法
- public BufferedInputStream(InputStream input):创建一个 新的缓冲输入流
- public BufferedOutputStream( tputStream output):创建一个新的缓冲输出流
代码示例:
字符缓冲流
构造方法
- public BufferedWriter(Writer out):创建一个新的字符缓冲输出流
- public BufferedReader(Reader in):创建一个新的字符缓冲输入流
特有方法
- BufferedReader: public String readLine(): 读取正行的文本信息.
- BufferedWriter:public void newLine():写入一行的行分隔符 ,由系统属性定义换行符号。
字符缓冲流代码演示:
public static void main(String[] args) throws IOException {
//1. 创建一个字符缓冲输入流对象,构造方法中传递一个字符输入流
final BufferedReader br = new BufferedReader(new FileReader("day29_IO\\abc.txt"));
//2. 使用字符缓冲输入流对象中的read/readLine,读取文本信息
/* String str = br.readLine();
System.out.println(str);// data=小孙*/
// 循环的结束条件 readLine()返回值是null
String str = null;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
// 3.释放资源。
br.close();
}
字符缓冲输出流代码演示:
public static void main(String[] args) throws IOException {
//1.创建一个字符缓冲输出流对象,构造方法中传递一个字符输出流
final BufferedWriter bw = new BufferedWriter(new FileWriter("day29_IO\\two.txt"));
//2.调用字符缓冲输出流对象中的write,把数据写入到内存缓冲区中。
bw.write("我今天学习了PS");
bw.newLine();
bw.write("3d软件mmd");
bw.newLine();
bw.write("c4d");
//3.调用字符缓冲输出流对象中的flush方法,把内存缓冲区中的数据刷新到文件中。
bw.flush();
//4.释放资源。
bw.close();
}
练习:文件复制
代码演示:
// 使用缓冲流完成文件复制
public static void show02() throws IOException {
// 获取开始的时间
long start = System.currentTimeMillis();
// 1.构建一个字节缓冲输入流
final BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\admin\\Desktop\\3.gif"));
// 2.构建一个字节缓冲输出流
final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\documents\\day29_IO\\3.gif"));
// 3.使用字节缓冲输入流对象中的方法read(byte[] b),读取文件
byte[] bytes = new byte[1024];
// 确定while循环结束的条件 read() == -1
int len = 0;// 记录读取到的有效字节个数
while ((len = bis.read(bytes)) != -1) {
// 4. 把读取到的字节内容再次写入到目的地文件中,调用write
bos.write(bytes, 0, len);
}
// 5.释放资源
bos.close();
bis.close();
// 获取结束的时间
long end = System.currentTimeMillis();
System.out.println("文件复制耗费的时间为:" + (end - start) + "ms");// 文件复制耗费的时间为:91ms
}
转换流[字节流<—>字符流]
字符编码:
按照某种规则,将字符存储到计算机中,称为编码;反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。有进行编码和解码过程中,我们必须采用是同一种规则,才能数据正常,否则,会导致乱码现象
- 字符编码:就是一套自然语言中的字符与二进制数之间的对应规则。
字符集:
- 字符集:是一个系统可支持的所有字符的集合,包括各国文字,标点符号,图形符号,数字等,也叫编码表。
计算机中要准确的存储和识别各种文字的字符符号,需要进行字符编码,-套字符集至少有一个套字符编码
常见的字符编码么有ASCII字符集、GBK字符集、Unicode字符集。
ASCII字符集:
- ASCI是基于拉 丁字母的一套编码系统, 用于显示现代英语。
- 基本的ASCII字符集,使用7位(bit)表示个字符, 共128个字符。ASCIl的扩展字符集使用8位(it)表示个字符,共256个字符
ISO-8859-1字符集:
- 拉丁码表,别名–Lantin-1,用于显示欧洲使用的语言,包括荷兰,丹麦,德语,意大利语,西班牙语等。
- ISO-8859-1使用单字节编码,兼容ASCII编码。
GB系列字符集:
- GB2312: 称为简体中文码表,里面大概含有7000多个简体汉字,此外数学符号,罗马希腊的字母、日本的假名都编进去了,连在ASCII里的原来就与的数字、标点字母都统统重新用的两个字节编写进去了。
- GBK:最常用的中文编码。是在原来GB23121码表基础上进行扩展。使用双字节编码。共收录了21000多个汉字,完全兼容GB2312,标准,同时支持繁体汉字以及日韩汉字等。
- GB18030:最新的中文码表,共收录了7万多个汉字,采用多字节编码,每个字可以由1个字节,2个字节或者4个字节组成,支持国内少数名族的文字,同时支持繁体字以及日韩汉字等。
Unicode字符集:
- Unicode编码系统为表达任意语言的任意字符而设计的,是业界的一种标准,也成为统一 编码,标准万国码
表。 - 它产多使用4个字节的数字来表示每个字母、符号、或者文字,有三种常见的编码方案: UTF-8, UTF-16,UT-32。
- UTF-8编码表,用来表示Unicode标准中的任意字符,编码规则:
1.128个US ASCII字符,使用的是一个字节编码
2.拉丁字的字母,需要两个字节编码
3.大部分常见的汉字,使用的是三个字节编码
4.其他极少数的辅助字符,采用的四个字节编码。
编码会引发的问题
由于编码规则不一致,导致引发乱码现象。
那么如何读取GBK编码的文件呢?
InputStreamReader类
转换流java.io.InputStreamReader
,是Reader的子类,它是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,或者可以使用平台默认的字符集。
构造方法
- public InputStreamReader(InputStream in):创一个使用默认的字符集的字符流。
- public InputStreamReader(InputStream in,String charsetName):创建一个指定字符集的字符流。
代码演示:
// 读取一个使用UTF-8编码的文件
public static void show02() throws IOException{
//1. 创建InputStreamReader对象,构造方法中传递字节输入流和和指定的编码表名称
InputStreamReader isr = new InputStreamReader(new FileInputStream("day29_IO\\one.txt"), "GBK");
//2. 使用InputStreamReader对象中的方法read读取文件中的信息
int len = 0;
while ((len = isr.read()) != -1) {
System.out.print((char)len+" ");// H e l l o W o r l d _ J a v a
}
//3. 释放资源。
isr.close();
}
OutputStreamWriter类
转换流java.io.OutputStreamWriter
是Writer的子类, 它是字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以手动E也可以使用平台默认的字符集。
构造方法
- public OutputStreamWriter(OutputStream out):创建一个使用平台默认的字符集的字符流。
- public OutputStreamWriter(OutputStream out,String charsetName):创建个指定的字符集的字符流。
练习:转换文件的编码
将GBK编码的文本文件,转换UTF-8编码的文件
分析:
- 指定GBK编码的转换流,读取文本文件.InputStreamReader
- 使用UTF-8编码的转换流,写入到文本文件中.OutputWriter
代码示例:
public static void main(String[] args) throws IOException {
//1.1 使用指定GBK编码的转换流,读取文本文件。InputStreamReader
final InputStreamReader isr = new InputStreamReader(new FileInputStream("day30_IO\\GBK.txt"), "GBK"); //1.2 使用UTF-8编码的转换流,写入到文本文件中。OutputStreamWriter 此时不写默认就是UTF-8
final OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day30_IO\\UTF-8.txt"));
//2. 使用转换流读取数据源文件
//2.1 定义一个指定长度的字符数组
char[] chars = new char[1024];
//2.2 定义一个变量,记录读取到的有效字符个数
int len = 0;
//2.3 循环读取
while ((len = isr.read(chars)) != -1) {
// 读取出来的数据要写入到目的地文件中
osw.write(chars, 0, len);
}
//3. 释放资源
osw.close();
isr.close();
}
序列化流
Java提供了一种对象序列化的机制,用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写入到文件中后,就相当于在文件中保存了一个对象信息。
反之,该字节序列还可以文件读取出来,重构对象,对它进行反序列化.对象的数据,对象的类型和存储的数据信息,都可以用来在内存中创建对象.
ObjectOutputStream类
java.io.ObjectOutputStream
类,将Java对象的原始数据类型写入到文件中,实现对象的持久化存储。
构造方法
- public ObjectOutputStream(OutputStream out):创建一个指定的OutputStream的ObjectOutputStream类对象
特有的独有方法:
- void writeObject(Object obj):将指定的对象写入到ObjectOutputStream类对象中。
序列化操作
- 一个对象想要能够序列化和反序列化,必须满足两个条件:
- 该类必须实现java.io.Serializable
接口,Serializable接口,是一个标记型接口,如果该类没有实现Serializable接口,将会抛出NotSerializableException。
- 该类的所有属性必须是可以实现序列化或者反序列化。如果有一个属性不想让它参与序列化,则该属性必须标明是瞬态的,瞬时的,这个关键字是transient
。
public class Student implements Serializable {
private String name;
private transient Integer age;// 不让age属性参与序列化
}
ObjectInputStream类
java.io.ObjectInputStream
类是反序列化流,将之前使用ObjectOutputStream序列化流的原始数据恢复为对象。
特有的方法:
- public final Object readObject():从反序列化流中读取一个对象。
对于JVM来说,能够进行反序列的对象 ,前提条件是必须能够找到class文件的类,如果找不到该类的class文件,则会抛出一个ClassNotFoundException异常。
另外,当JVM序列化对象时,能够找到class文件,但是class文件在序列化对象时,发生了修改,那么反序列化操做会抛出一个InvalidClassException异常。原因如下:
- 该类的序列化版本号与从流中读取出来描述该类的版本号不一致。
- 该类包含了未知数据类型。
- 该类没有可访问的无参构造方法。
Serializable接口给需要序列化的类,提供了一个序列化版本号,serialVersionUID 该版本号的目的就是在于验证序列化的对象和对应的类是否是版本一致的。
代码演示:
// 序列化操作类
public class Demo01ObjectOutputStream {
public static void main(String[] args) throws IOException {
//1.创建ObjectOutputStream流对象,构造方法中传递指定的字节输出流。
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day30_IO\\student.txt"));
//2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中。
//2.1 先创建一个对象
Student s = new Student("小孙", 30);
s.score = 60;
//2.2写对象到文件中
oos.writeObject(s);
//3.释放资源。
oos.close();
}
}
// 反序列化类操作
public class Demo02ObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1. 创建一个ObjectInputStream流对象,构造方法中传递一个字节输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day30_IO\\student.txt"));
// 2. 使用ObjectInputStream流对象中的方法readObject,读取保存在文件中的对象数据
Object obj = ois.readObject();
// 3.释放资源。
ois.close();
// 4. 查看对象的数据
System.out.println(obj);// Student{name='小孙', age=30}
if ( obj instanceof Student) {
Student student = (Student)obj;
System.out.println(student.getAge() + "--" + student.getName());
} else {
System.out.println("转换失败");
}
}
}
// 需要被序列化的类
import java.io.Serializable;
public class Student implements Serializable {
// 可以选择手动自定义一个序列化版本号
private static final long serialVersionUID = 1L;
//private static String name;
private String name;
private Integer age;
private transient String address = "郑州市";
transient int score;// 0
}
原理分析:
练习:存储一堆对象,实现序列化和反序列化动作.
@SuppressWarnings("unchecked")
public class ObjectOutputStreamTest {
public static void main(String[] args) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day12.17_IO\\stu's.txt"));
ArrayList<Student> stus = new ArrayList<>();
stus.add(new Student("刘崇",22,"南阳"));
stus.add(new Student("任苗苗",18,"商丘"));
stus.add(new Student("阿艳",12,"北京"));
oos.writeObject(stus);
//oos.flush();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day12.17_IO\\stu's.txt"));
Object o = ois.readObject();
if(o instanceof ArrayList){
ArrayList<Student> list = (ArrayList<Student>)o;
//遍历集合
list.forEach(System.out::println);
}
oos.close();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Student implements Serializable {
//手动自定义一个序列化版本号
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private String address;
public Student() {
}
public Student(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
打印流
java.io.PrintStream
类能够很方便打印各种数据类型的值.
构造方法
- public PrintStream(String filename):使用指定的文件名创建一个新的打印流对象.
改变打印流的方向
正常System.out就是PrintStream类型的,数据的流动的位置在控制台中.改变数据的流动位置.通过System.setOut(PrintStream print)来改变流向。
PrintStream out = System.out;
out.print(123);// 在控制台中
// 构造方法创建一个打印流对象
PrintStream printStream = new PrintStream("day30_IO\\print.txt");
// 改变打印流的方向为"day30_IO\\print.txt"路径
System.setOut(printStream);
System.out.println("我已经改变了输出数据的位置");
System.out.println("我想在控制台中输出数据");
System.out.println("啦啦啦啦啦");
函数式接口
概念
函数式接口在Java指的是:有且仅有一个抽象方法的接口就称为函数式接口.
函数式接口,适用于函数式编程的,在Java当中的函数式变成体现在Lambda表达式,所以函数式接口就是用来服务Lambda表达式.只有确保接口当中有且仅有一个抽象方法,Java中的Lambda才能顺利进行推导.
备注:“语法糖”
是指使用更加便利方便,但是原理不变的代码语法。就比如遍历集合时使用for-each语法, 其实底层使用的是迭代器,这便是"语法糖"。
格式:
只有确保接口当中有且仅有一个抽象方法即可:
修饰符interface InterfaceName{
//只能定义一个抽象方法
public abstract 返回值类型方法名称(参数列表);
//还可以定义其他的非抽象方法
}
示例:
public interface Funct ionInterfaceOne {
public abstract void show01( );
public default void show02(){
}
//void show03;有且仅有一个抽象方法,才称为函数式接口
@Functionallnterface注解
与@Override注解作用类似,Java 8中专为函数式接口引入的一个新注解@FunctionalInterface ,该注解主要定义在接口上。一旦在接口上使用注解,编译期将会强制检查该接口是不是函数式接口,该接口中是不是有且仅有一个抽象方法
@FunctionalInterface
public interface FunctionInterfaceOne {
//定义一个抽象的方法
void method();
//void show();
default void showe2(){
}
}
自定义函数式接口的用途
对于自定义的函数式接口,一般用于方法的参数和返回值上.
函数式编程
能够兼顾Java的面向对象特性基础上,通过Lambda式与方法引用,为开发者打开函数式编程的大门.
Lambda的延迟加载
有些场景的代码执行后,结果不一定会被使用到,从而造成性能的浪费.而Lambda表达式是延迟执行的,正好可以解决此问题,提高性能.
常用的函数式接口
JDK提供了大量常用的函数式接口,丰富ambda表达式的使用场景。他们主要在java.util. function
包中被提
供。
Supplier接口
java.util. function.Supplier
接口,该接口有且仅有一个无参的方法: T get()。用来获取一个泛型参数指定类型的对象数据。由于该接口是一个函数式接口, 所以我们可以使用ambda表达式来操作它。
Supplier<T>接口称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get()方法就会生产什么类型的数据.
练习:
定义一个
Stream流(跟IO流不同)
在Java 1.8中,由于Lambda表达式这种函数式编程JDK引入了一个全新的概念"Stream流"。用于解决已有集合类库的一些弊端的。
给定你些集合的数据
public