1 IO概述
1.1 什么是IO?
input : 输入 读
output: 输出 写
1.2 为什么有IO?
在操作系统中,一切需要永久保存的数据都以文件的形式 存储。需要长久保存的文件数据,存储在外部设备。
但是需要读入内存才能显示这些数据。同时,内存的大小有限,因此常常需要在内存和外设之间交换数据,即I/O
1.3 Java中如何实现IO功能
Java流模型
1.4 IO的分类
按流向分(以内存为参照物)
- 输出流: 内存—> 磁盘 (写入)
- 输入流: 磁盘—> 内存 (读取)
按照数据类型分
- 字节流: 逻辑单位是字节,(1B = 8bit 0000 0000)
- 字符流: 逻辑单位是字符(理解为一种文化符号,abc , “你”, “の”)
1.5 四个抽象基类
字节输出流: OutputStream
字节输入流: InputStream
字符输出流: Writer
字符输入流: Reader
由这4个抽象基类派生的子类都是以其父类名作为后缀的
如:InputStream的子类FileInputStream。
如:Reader的子类FileReader
1.6 什么时候用什么流?
一般来讲, 纯文本文件用字符流 .txt .java .cpp
其他情况用字节流(字节流是万能的) .word .ppt .mp4 .mp3 .jpg . png .exe
2 字节流
2.1 字节输出流
2.1.1 抽象基类OutputStream
此抽象类是表示输出字节流的所有类的超类
继承关系
成员方法
void | close() 关闭此输出流并释放与此流有关的所有系统资源。 |
---|---|
void | flush() 刷新此输出流并强制写出所有缓冲的输出字节。 |
void | write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。 |
void | write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 |
abstract void | write(int b) 将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 |
2.1.2 具体子类
2.1.2.1 FileOutputStream文件字节输出流
用于将数据写入 File
继承关系
构造方法
FileOutputStream(File file) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 |
---|
FileOutputStream(File file, boolean append) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 |
FileOutputStream(String fileName) 创建一个向具有指定名称的文件中写入数据的输出文件流。 |
FileOutputStream(String name, boolean append) 创建一个向具有指定 name 的文件中写入数据的输出文件流 append - 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处 |
成员方法
void | write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。 |
---|---|
void | write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 |
abstract void | write(int b) 将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 |
/**
* @package: _01code._io._01bytestream.out
* @description:
* @author: Yunyang
* @date: 2023/11/1 10:10
* @version:1.0
**/
/*
* 写数据的步骤
1.创建输出流对象
2.write方法写数据
3.释放资源 close
*/
public class Demo {
public static void main(String[] args) throws IOException {
// 1.创建输出流对象
// File file = new File("a.txt");
// FileOutputStream out = new FileOutputStream(file);
// FileOutputStream out = new FileOutputStream(new File("a.txt"));
FileOutputStream out = new FileOutputStream("a.txt");
// 2.write方法写数据
// write(int b)写单个字节
// writeSingle(out);
// write(byte[] b) 写字符数组
String s = "abcdef";
//String --->byte[]
byte[] bytes = s.getBytes();
// writeMulti(out, bytes);
// write(byte [] b , int off , int len) 写字节数组的部分
out.write(bytes,1,2);
// 3.释放资源 close
out.close();
}
private static void writeMulti(FileOutputStream out, byte[] bytes) throws IOException {
out.write(bytes);
}
private static void writeSingle(FileOutputStream out) throws IOException {
out.write(97);
}
}
2.1.2.2 注意事项
-
当我们创建一个输出流对象的时候,发生了什么?
- jvm向操作系统中看这个文件是否存在
- 如果文件不存在, 帮我们创建
- 文件已经存在, 覆盖重新开始写
-
如何实现文件追加功能?
- 借助于带append参数的构造方法
/*
如何实现文件追加功能?
- 借助于带append参数的构造方法
*/
public class Demo3 {
public static void main(String[] args) throws IOException {
// 创建输出流对象
FileOutputStream out = new FileOutputStream("a.txt",true);
// write
out.write(97);
out.write("aaa".getBytes());
//close
out.close();
}
}
- 如何实现换行功能
/**
* @package: _01code._io._01bytestream.out
* @description:实现换行功能
* @author: Yunyang
* @date: 2023/11/1 13:35
* @version:1.0
**/
public class Demo4 {
public static void main(String[] args) throws IOException {
// 创建输出流对象
FileOutputStream out = new FileOutputStream("a.txt");
//write
out.write("abc".getBytes());
// 向文件中写入换行符\r\n
out.write("\r\n".getBytes());
out.write(98);
out.write("\r".getBytes());
out.write(99);
out.write("\n".getBytes());
out.write(100);
// 系统换行符:System.lineSeparator()
out.write(System.lineSeparator().getBytes());
//close
out.close();
}
}
-
如何异常处理
- try-catch-finally
/**
* @package: _01code._io._01bytestream.out
* @description:进行异常处理
* @author: Yunyang
* @date: 2023/11/1 13:47
* @version:1.0
**/
/*
进行异常处理
1.传统的try-catch-finally
注意2点
a.输出流/输入流声明写在try外面
b.close之前 做null的校验
2.try-with-resources
*/
public class Demo5 {
public static void main(String[] args) {
// 创建输出流对象
FileOutputStream out = null;
try {
//放的是可能出现异常的代码
//do sth
// XXXX
// 。。。
out = new FileOutputStream("a.txt");
//write
out.write("abc".getBytes());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//close 进行资源释放
try {
//进行null判断
if(out != null){
out.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
- 验证自动执行
/**
* @package: _01code._io._01bytestream.out
* @description:
* @author: Yunyang
* @date: 2023/11/1 18:37
* @version:1.0
**/
/*
* 验证close方法自动执行
* */
public class Demo7 {
public static void main(String[] args) {
try(A a = new A()){
//主动调用func 不调用close
a.func();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class A implements AutoCloseable{
public void func(){
System.out.println("func方法执行了");
}
@Override
public void close() throws Exception {
System.out.println("close 方法执行了");
}
}
-
为什么要close?
- jvm使用了不属于jvm的资源, 不能通过GC回收, 只能通过close显式的释放资源。
2.1.2.3 BufferedOutputStream缓冲字节输出流
该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
继承关系
构造方法
BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。默认缓冲区大小是8KB |
---|
BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。 指定缓冲区size大小 |
成员方法
3个write方法
write(int b)
write(byte[] b)
write(byte[] b, int off, int len)
/**
* @package: _01code._01io._01bytestream._01buffer
* @description:
* @author: Yunyang
* @date: 2023/11/2 10:15
* @version:1.0
**/
/*
带缓冲区的输出流
1.创建输出流对象
2.write
3.flush
4.close
*/
public class Demo {
public static void main(String[] args) throws IOException {
// 创建缓冲输出流 分开写
// FileOutputStream fileOutputStream = new FileOutputStream("a.txt");
// BufferedOutputStream outputStream = new BufferedOutputStream(fileOutputStream);
// 创建缓冲输出流 合并写
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("a.txt"));
// 3个write方法
// write(int b) 写单个字节
out.write(97);
// 批量写
// write(byte[] b)
out.write(System.lineSeparator().getBytes());//换行
out.write("abcd".getBytes());
// write(byte[] b, int off, int len)
out.write("abcdef".getBytes(), 1, 4);
//flush
out.flush();
//close
// close方法会调用flush方法
out.close();
}
}
注意:
-
使用所有带缓冲区的输出流, 记得flush操作
-
执行close方法会自动执行flush
-
缓冲区满了会自动刷新
/**
* @package: _01code._01io._01bytestream._01buffer
* @description:
* @author: Yunyang
* @date: 2023/11/2 10:24
* @version:1.0
**/
public class Demo2 {
public static void main(String[] args) throws IOException {
//指定缓冲区大小
BufferedOutputStream out
= new BufferedOutputStream(new FileOutputStream("a.txt"), 4);
//write
//大于缓冲区的大小会自动刷新
out.write("abcdef".getBytes());
// flush
// close
}
}
2.2 字节输入流
2.2.1 抽象基类InputStream
此抽象类是表示字节输入流的所有类的超类。
继承关系
成员方法
abstract int | read() 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。返回值代表了读取到的字节值 readData |
---|---|
int | read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。返回值表示读取的字节的个数 readCount |
int | read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。 |
2.2.2 具体子类
2.2.2.1 FileInputStream文件字节输入流
FileInputStream
从文件系统中的某个文件中获得输入字节
继承关系
构造方法
FileInputStream(File file) 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。 |
---|
FileInputStream(String fileName) 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。 |
成员方法
abstract int | read() 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。返回值代表了读取到的字节值 readData |
---|---|
int | read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。返回值表示读取的字节的个数 readCount |
int | read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。 |
/**
* @package: _01code._io._01bytestream._02in
* @description:读取数据
* @author: Yunyang
* @date: 2023/11/1 18:59
* @version:1.0
**/
/*
* 读取数据的步骤
* 1.创建输入流对象
* 2.read方法读取数据
* 3.close
* */
public class Demo {
public static void main(String[] args) throws IOException {
// 1.创建输入流对象
FileInputStream in = new FileInputStream("a.txt");
// 2.read方法读取数据
//read() 读取单个字节 返回值 表示读取到的字节值的值 readData
// readSingle(in);
// read(byte[] b) 批量读取,读取数据填充到数组中,返回值表示读了多少个字节 readCount
byte[] bytes = new byte[1024];
// readMulti(in, bytes);
//read(byte[] b,int off, int len)
int readCount = in.read(bytes, 2, 4);
System.out.println(readCount);
// 3.close
in.close();
}
private static void readMulti(FileInputStream in, byte[] bytes) throws IOException {
int readCount = in.read(bytes);
System.out.println(readCount);
// byte[]--->String
String s = new String(bytes);
System.out.println(s);
int readCount2 = in.read(bytes);
System.out.println(readCount2);
}
private static void readSingle(FileInputStream in) throws IOException {
int readData1 = in.read();
System.out.println(((char) readData1));
int readData2 = in.read();
System.out.println(((char) readData2));
int readData3 = in.read();
System.out.println(((char) readData3));
int readData4 = in.read();
System.out.println(((char) readData4));
int readData5 = in.read();
System.out.println(readData5);
}
}
/**
* @package: _01code._io._01bytestream._02in
* @description:
* @author: Yunyang
* @date: 2023/11/1 19:36
* @version:1.0
**/
public class Demo2 {
public static void main(String[] args) throws IOException {
// 创建输入流对象
FileInputStream in = new FileInputStream("a.txt");
//read
byte[] bytes = new byte[4];
int readCount = in.read(bytes);
System.out.println("readCount = " + readCount);
System.out.println(new String(bytes));//abcd
int readCount2 = in.read(bytes);
System.out.println("readCount2 = " + readCount2);
System.out.println(new String(bytes));//efcd
System.out.println(new String(bytes, 0, readCount2));//ef
// close
in.close();
}
}
循环读取
/**
* @package: _01code._io._01bytestream._02in
* @description:
* @author: Yunyang
* @date: 2023/11/1 19:51
* @version:1.0
**/
// **循环读取**
public class Demo3 {
public static void main(String[] args) throws IOException {
// 创建输入流对象
FileInputStream in = new FileInputStream("a.txt");
// read
//方式一 不推荐使用
// readWhile1(in);
//推荐的方式
//单字节读取
//表示读取到的字节值
// int readData;
// while ((readData = in.read()) != -1){
// System.out.print(((char) readData));
// }
//批量读取
readWhile3(in);
// close
in.close();
}
private static void readWhile3(FileInputStream in) throws IOException {
byte[] bytes = new byte[1024];
int readCount;
// == -1 就结束循环 意味文件里读取完了
while ((readCount = in.read(bytes)) != -1) {
System.out.println(new String(bytes, 0,readCount));
}
}
private static void readWhile1(FileInputStream in) throws IOException {
while (true) {
int readData = in.read();
if (readData == -1){
break;
}
System.out.print(((char) readData));
}
}
}
2.2.2.2 BufferedInputStream缓冲字节输入流
继承关系
构造方法
BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。默认缓冲区8KB |
---|
BufferedInputStream(InputStream in, int size) 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 使用指定的缓冲区 |
成员方法
3个read
read() 读取单个字节
read(byte [] b) 读取数据填充到数组
read(byte [] b, int off , int len) 读取数据填充到数组
/**
* @package: _01code._01io._01bytestream._01buffer
* @description:使用缓冲字节输入流读取数据
* @author: Yunyang
* @date: 2023/11/2 10:28
* @version:1.0
**/
public class Demo3 {
public static void main(String[] args) throws IOException {
//创建输入流对象
BufferedInputStream in
= new BufferedInputStream(new FileInputStream("a.txt"));
//read 单个读取
int readData = in.read();
System.out.println("((char) readData) = " + ((char) readData));
// 创建byte数组 长度为1024 批量读取
byte[] bytes = new byte[1024];
// readCount表示读取到的字节个数
int readCount = in.read(bytes);
System.out.println("readCount = " + readCount);
System.out.println(new String(bytes));
// close
in.close();
}
}
2.3 文件复制功能
思路:
- 读取源文件, 把数据读取到内存里
- 把内存的数据写到新文件
主要逻辑
/**
* @package: _01code._io._01bytestream._03copy
* @description:
* @author: Yunyang
* @date: 2023/11/1 20:05
* @version:1.0
**/
/*
文件复制
1.文本文件
单字节:292ms
批量:1ms
2.图片文件
单字节:9006ms
批量:11ms
3.视频文件
单字节:19640ms
批量:25ms
*/
public class Demo {
public static void main(String[] args) throws IOException {
// //创建输入流文件 1.文本文件
// FileInputStream in = new FileInputStream("D:\\eula.1028.txt");
// // 创建输出流文件
// FileOutputStream out = new FileOutputStream("copy_eula.1028.txt");
// //创建输入流文件 2.图片文件
// FileInputStream in = new FileInputStream("D:\\JAVA.png");
// // 创建输出流文件 3.视频文件
// FileOutputStream out = new FileOutputStream("copy_JAVA.png");
//创建输入流文件
FileInputStream in = new FileInputStream("D:\\aa.ev4a");
// 创建输出流文件
FileOutputStream out = new FileOutputStream("copy_aa.ev4a");
//边读边写
long start = System.currentTimeMillis();
// 单字节复制
// copySingle(in, out);
// 批量复制
copyMulti(in, out);
// 循环结束 复制完成
long end = System.currentTimeMillis();
System.out.println(end - start);
//close
in.close();
out.close();
}
private static void copyMulti(FileInputStream in, FileOutputStream out) throws IOException {
byte[] bytes = new byte[1024];
int readCount;
while((readCount = in.read(bytes)) != -1){
// write(byte[] b)
// 读了多个个字节 写多少个
out.write(bytes,0,readCount);
}
}
private static void copySingle(FileInputStream in, FileOutputStream out) throws IOException {
int readData;
while ((readData = in.read()) != -1){
//write
out.write(readData);
}
}
}
使用字节流复制
文本文件: 正常复制
图片文件: 正常复制
视频文件: 正常复制
使用字符流复制
文本文件: 正常复制
图片文件: 复制出错
视频文件: 复制出错
单字节复制还是字节数组方式效率高?
- 字节数组批量的方式效率高
为什么?
- 批量的方式会减少跟操作系统的交互
举例:
我在JD 买了5个快递
单字节: 快递小哥1次送一个
字节数组方式: 东哥说了 大家都是兄弟 配车 快递装车里
3 字节流
3.1 为什么有字符流?
- 使用字节流读取英文数字
- 没有问题
- 使用字节流读取中文
- 可能有问题
3.2 一个字符是如何存在计算机中的
基于某个编码表. 每个字符对应这一个整数值(编码值), 计算机存的就是这个整数值
字符 | 编码值(整数值) |
---|---|
a | 97 —> 二进制 01100001 |
你 | 20320 —>二进制 |
3.3 编码表
-
ASCII:美国标准信息交换码。
用一个字节的7位可以表示。0000 0000 - 0111 1111 -
ISO8859-1:拉丁码表。欧洲码表
用一个字节的8位表示。 0000 0000 - 1111 1111 -
GB2312:中国的中文编码表。
-
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
-
GB18030:GBK的取代版本
-
BIG-5码 :通行于台湾、香港地区的一个繁体字编码方案,俗称“大五码”。
-
Unicode:国际标准码,融合了多种文字。
-
UTF-8:可变长度来表示一个字符。
UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:
它将Unicode编码为00000000-0000007F的字符,用单个字节来表示 0111 1111 = 7F
它将Unicode编码为00000080-000007FF的字符用两个字节表示
它将Unicode编码为00000800-0000FFFF的字符用3字节表示 1字节 0xxxxxxx 2字节 110xxxxx
10xxxxxx 3字节 1110xxxx 10xxxxxx 10xxxxxx
-
utf-16:jvm使用的编码表,用2个字节来编解码
-
char : 2 字节
工作中常用的
- ASCII: 1个字节的低7位
- ISO8859-1 : 1个字节
- GBK : 2个字节表示一个中文字符
- UTF-8 : 3个字节表示一个中文字符
3.4 编解码
编码
- 把一字符串数据转为二进制数据存到计算机的过程(把人看懂的东西-----> 计算机看懂的东西)
解码
- 编码的逆过程(把计算机看懂的东西 ---- > 人看懂的东西)
默认的编码表
- idea: UTF-8
- Win: GBK (ANSI 默认的)
3.5 Java中的编解码
/**
* @package: _01code._01io._02charstream._02encode
* @description:
* @author: Yunyang
* @date: 2023/11/2 11:49
* @version:1.0
**/
/*
Java中的编解码
*/
public class Demo2 {
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "你好";
// 编码
//使用平台的默认字符集将此String编码为byte序列
//并将结果存储到一个新的byte数组中
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes));
// [-28, -67, -96, -27, -91, -67]
//使用指定的字符集编码
//getBytes(String charsetName)
//使用指定的字符集将String编码为byte序列,并将结果存储到一个新的byte数组中
byte[] gbks = s.getBytes("GBK");
System.out.println(Arrays.toString(gbks));
// [-60, -29, -70, -61]
// 解码
// 使用String构造器
// 通过使用平台的默认字符集解码指定的byte数组,构造一个新的String
String s1 = new String(bytes);
System.out.println(s1);
// 指定字符集 进行解码
// String(byte[] b, String charsetName)
String s2 = new String(gbks, "GBK");
System.out.println(s2);
}
}
乱码问题
- 产生的原因: 编码解码不一致
- 解决: 使其一致
3.6 字符流的本质
3.7 字符输出流
3.7.1 抽象基类Writer
写入字符流的抽象类
继承关系
成员方法
void | write(char[] cbuf) 写入字符数组。 |
---|---|
abstract void | write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 |
void | write(int c) 写入单个字符。 |
void | write(String str) 写入字符串。 |
void | write(String str, int off, int len) 写入字符串的某一部分 |
3.7.2 具体子类
3.7.2.1 OutputStreamWriter转换流
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset
将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
继承关系
构造方法
OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter。 |
---|
OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter。 |
成员方法
5个write 3+2
3个跟字符相关的
- write(int c) 写入单个字符
- write(char[] c) 批量写入
- write(char[] c, int off ,int len)
2个跟字符串相关的 - write(String s)
- write(String s,int off ,int len)
/**
* @package: _01code._01io._02charstream._03transfer
* @description:
* @author: Yunyang
* @date: 2023/11/2 14:43
* @version:1.0
**/
/*
* 使用转换流来写数据
* */
public class Demo {
public static void main(String[] args) throws IOException {
// 创建输出流对象
OutputStreamWriter out =
new OutputStreamWriter(new FileOutputStream("a.txt"));
// OutputStreamWriter out =
// new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream("a.txt")));
// write
// 3个跟字符相关的
// write(int c) 写入单个字符
out.write(97);
// 换行
System.out.println(System.lineSeparator());
// write(char[] c) 批量写入
String s = "今天下雨了";
//String--->char[] toCharArray()方法将字符串转换成字符数组
char[] chars = s.toCharArray();
out.write(chars);
out.write(System.lineSeparator());
// write(char[] c, int off ,int len)
out.write(chars, 2, 3);
out.write(System.lineSeparator());
//2个跟字符串相关
// write(String s)
out.write("外面下雨了,可我没带伞");
out.write(System.lineSeparator());
// write(String s,int off ,int len)
out.write("外面下雨了,可我没带伞", 2, 9);
// flush
out.flush();
// close
out.close();
}
}
3.7.2.2 FileWriter简化流
用来写入字符文件的便捷类
继承关系构造方法
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象。 |
---|
FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象。 |
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象。 |
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。 |
成员方法
5个write
3个跟字符相关的
2个跟字符串相关的
/**
* @package: _01code._01io._02charstream._05simple
* @description:
* @author: Yunyang
* @date: 2023/11/4 16:03
* @version:1.0
**/
/*
使用简化流写入数据
*/
public class Demo {
public static void main(String[] args)throws IOException {
// 创建输出流对象
FileWriter fileWriter = new FileWriter("a.txt");
// write
fileWriter.write("晚上吃什么");
// flush
fileWriter.flush();
// close
fileWriter.close();
}
}
3.7.2.3 BufferedWrite缓冲流
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了
继承关系
构造方法
BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。默认缓冲区大小是16KB |
---|
BufferedWriter(Writer out, int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。缓冲区大小size |
成员方法
5 + 1
5个常规的write
1个独有的方法
newLine() 写入一个行分隔符。
/**
* @package: _01code._01io._02charstream._06buffer
* @description:
* @author: Yunyang
* @date: 2023/11/4 16:18
* @version:1.0
**/
/*
使用缓冲流写数据
*/
public class Demo {
public static void main(String[] args) throws IOException {
// 创建输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
// write
bw.write("我想喝可乐");
// 自己独有的方法 newLine 换行
bw.newLine();
bw.write("喝可口可乐");
// flush
bw.flush();
// close
bw.close();
}
}
3.8 字符输入流
3.8.1 抽象基类Reader
用于读取字符流的抽象类
成员方法
int | read() 读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff),如果已到达流的末尾,则返回 -1 用readData 读取到的字符值 |
---|---|
int | read(char[] cbuf) 将字符读入数组。 读取的字符数,如果已到达流的末尾,则返回 -1 用readCount表示 读取到的字符的个数 |
abstract int | read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。 |
3.8.2 具体子类
3.8.2.1 InputStreamReader转换流
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset
读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
构造方法
InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。 |
---|
InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader。 |
成员方法 |
3个read方法
read()
read(char[] c)
read(char[] c ,int off, int len)
/**
* @package: _01code._01io._02charstream._03transfer
* @description:
* @author: Yunyang
* @date: 2023/11/2 14:54
* @version:1.0
**/
/*
使用转换流读取数据
*/
public class Demo2 {
public static void main(String[] args) throws IOException {
// 创建输入流对象
InputStreamReader in
= new InputStreamReader(new FileInputStream("a.txt"));
// read
// read()读取单个字符 readData
int readData = in.read();
System.out.println(((char) readData));
// read(char[] c) 读取数据填充到数组 readCount
char[] chars = new char[1024];
int readCount = in.read(chars);
String s = new String(chars,0,readCount);
System.out.println(s);
// read(char[] c,int off, int len) 读取数据填充到数组 readCount
in.read(chars,0,3);
// close
in.close();
}
}
3.8.2.2 FileReader简化流
继承关系
构造方法
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader。 |
---|
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader。 |
成员方法
3个read
/**
* @package: _01code._01io._02charstream._05simple
* @description:
* @author: Yunyang
* @date: 2023/11/4 16:06
* @version:1.0
**/
/*
使用简化流去读取数据
*/
public class Demo2 {
public static void main(String[] args) throws IOException {
// 创建输入流对象
FileReader fileReader = new FileReader("a.txt");
// read
char[] chars = new char[1024];
int readCount = fileReader.read(chars);
System.out.println(new String(chars, 0, readCount));
// close
fileReader.close();
}
}
转换流VS简化流
- 使用角度, 简化流简单, 转换流麻烦
- 继承关系, 转换流是简化流的父类
- 核心区别, 简化流不能指定字符集(使用默认的字符集), 转化流可以指定的字符集
3.8.2.3 BufferedReader缓冲流
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了
继承关系
构造方法
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。 |
---|
BufferedReader(Reader in, int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流。 |
成员方法
3+1
3个常规read
1个独有的方法
String | readLine() 读取一个文本行。如果已到达流末尾,则返回 null |
---|
/**
* @package: _01code._01io._02charstream._06buffer
* @description:
* @author: Yunyang
* @date: 2023/11/2 15:29
* @version:1.0
**/
/*
*使用缓冲流读取数据*/
public class Demo2 {
public static void main(String[] args) throws IOException {
// 创建输入流对象
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// read
// char[] chars = new char[1024];
// int readCount = br.read(chars);
// System.out.println(new String(chars, 0, readCount));
// 独有的方法readLine 读取一行数据
// readSingleLine(br);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
//close
br.close();
}
private static void readSingleLine(BufferedReader br) throws IOException {
String s1 = br.readLine();
String s2 = br.readLine();
String s3 = br.readLine();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
4 其它流
4.1 数据流
需求: 用字节流向文件中写个整数1000 小数3.14
- 做不到
4.1.1 DataOutputStream数据输出流
数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
构造方法
DataOutputStream(OutputStream out) 创建一个新的数据输出流,将数据写入指定基础输出流。
成员方法
每种数据类型都有1个对应的write方法
举例
int ----> writeInt(int a)
double ----> writeDouble(double d)
······
4.1.2 DataInputStream数据输入流
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型
构造方法
DataInputStream(InputStream in) 使用指定的底层 InputStream 创建一个 DataInputStream。
成员方法
每种数据类型都有1个对应的read方法
举例
int ----> readInt()
double ----> readDouble()
…
/**
* @package: _01code._01io._03otherstream._01data
* @description:
* @author: Yunyang
* @date: 2023/11/2 16:05
* @version:1.0
**/
public class Demo2 {
public static void main(String[] args) throws IOException {
writeData();
readData();
}
private static void readData() throws IOException {
// 创建输入流对象
DataInputStream in = new DataInputStream(new FileInputStream("a.txt"));
//readInt()
int i = in.readInt();
System.out.println("i = " + i);
// readDouble()
double v = in.readDouble();
System.out.println("v = " + v);
// close
in.close();
}
private static void writeData() throws IOException {
// 创建输出流对象
DataOutputStream out = new DataOutputStream(new FileOutputStream("a.txt"));
// writeInt(int a)
out.writeInt(1000);
// writeDouble(double d)
out.writeDouble(3.14);
// close
out.close();
}
}
/**
* @package: _01code._01io._03otherstream._01data
* @description:
* @author: Yunyang
* @date: 2023/11/4 16:55
* @version:1.0
**/
public class Demo3 {
public static void main(String[] args) throws IOException {
writeData();
DataInputStream in = new DataInputStream(new FileInputStream("b.txt"));
int a = in.readByte();
System.out.println("a = " + a);
short b = in.readShort();
System.out.println("b = " + b);
int c = in.readInt();
System.out.println("c = " + c);
long d = in.readLong();
System.out.println("d = " + d);
float e = in.readFloat();
System.out.println("e = " + e);
double f = in.readDouble();
System.out.println("f = " + f);
char ch = in.readChar();
System.out.println("ch = " + ch);
boolean flag = in.readBoolean();
System.out.println("flag = " + flag);
}
private static void writeData() throws IOException {
// 创建数据输出流对象
DataOutputStream out = new DataOutputStream(new FileOutputStream("b.txt"));
// write
out.writeByte(1);
out.writeShort(20);
out.writeInt(300);
out.writeLong(10000);
out.writeFloat(3.14f);
out.writeDouble(12.35);
out.writeChar('a');
out.writeBoolean(true);
// close
out.close();
}
}
注意
- 写的顺序是什么, 读取的时候要按照相同的顺序
4.2 打印流
核心思想: 把不同的数据类型—>String
案例:
定义一个类Printer
定义成员变量OutputStream
定义5个方法
写int的方法 void printInt(int a)
写int并且换行的方法 void printIntLn(int a)
写double的方法 void printDouble(double a)
写double并且换行的方法 void printDoubleLn(double a)
写一个close方法 void close()
/**
* @package: _01code._01io._03otherstream._02print
* @description:
* @author: Yunyang
* @date: 2023/11/2 16:19
* @version:1.0
**/
/*
核心思想: 把不同的数据类型--->String
案例:
定义一个类Printer
定义成员变量OutputStream
定义5个方法
写int的方法 void printInt(int a)
写int并且换行的方法 void printIntLn(int a)
写double的方法 void printDouble(double a)
写double并且换行的方法 void printDoubleLn(double a)
写一个close方法 void close()*/
public class Demo {
public static void main(String[] args) throws IOException {
Printer printer = new Printer(new FileOutputStream("a.txt"));
// 写入1000
printer.printIntLn(1000);
// 写入3.14
printer.printDouble(3.14);
// close
printer.close();
}
}
class Printer {
// 定义成员变量OutputStream
private OutputStream out;
public Printer(OutputStream out) {
this.out = out;
}
// 写int的方法 void printInt(int a)
public void printInt(int a) throws IOException {
String s = String.valueOf(a);
out.write(s.getBytes());
}
// 写int并且换行的方法 void printIntLn(int a)
public void printIntLn(int a) throws IOException {
//int --->String "1000"
String s = String.valueOf(a);
out.write(s.getBytes());
out.write(System.lineSeparator().getBytes());
}
// 写double的方法 void printDouble(double a)
public void printDouble(double a) throws IOException {
//double ---->"3.14"
String s = String.valueOf(a);
out.write(s.getBytes());
}
// 写double并且换行的方法 void printDoubleLn(double a)
public void printDoubleLn(double a) throws IOException {
//douelb---->String "3.14"
String s = String.valueOf(a);
out.write(s.getBytes());
out.write(System.lineSeparator().getBytes());
}
// 写一个close方法 void close()
public void close() throws IOException {
out.close();
}
}
4.2.1 PrintStream字节打印流
PrintStream
为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
继承关系
构造方法
PrintStream(File file) 创建具有指定文件且不带自动行刷新的新打印流。 |
---|
PrintStream(OutputStream out) 创建新的打印流。 |
PrintStream(OutputStream out, boolean autoFlush) 创建新的打印流。 |
PrintStream(String fileName) 创建具有指定文件名称且不带自动行刷新的新打印流。 |
成员方法
每个数据类型都有一个相对应的print方法
举例:
int —> print(int a)
double —> print(double d)
…
4.2.2 PrintWriter字符打印流
向文本输出流打印对象的格式化表示形式
继承关系
构造方法
PrintWriter(File file) 使用指定文件创建不具有自动行刷新的新 PrintWriter。 |
---|
PrintWriter(OutputStream out) 根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。 |
PrintWriter(OutputStream out, boolean autoFlush) 通过现有的 OutputStream 创建新的 PrintWriter。 |
PrintWriter(String fileName) 创建具有指定文件名称且不带自动行刷新的新 PrintWriter。 |
PrintWriter(Writer out) 创建不带自动行刷新的新 PrintWriter。 |
PrintWriter(Writer out, boolean autoFlush) 创建新 PrintWriter。 |
成员方法
每个数据类型都有一个相对应的print方法
举例:
int —> print(int a)
double —> print(double d)
…
/**
* @package: _01code._01io._03otherstream._02print
* @description:
* @author: Yunyang
* @date: 2023/11/2 16:30
* @version:1.0
**/
/*
* 使用打印流写数据*/
public class Demo2 {
public static void main(String[] args) throws IOException {
// 创建输出流对象
// PrintStream printStream = new PrintStream("a.txt");
//
// printStream.print(1000);
//
// printStream.print(3.14);
//
// printStream.close();
//想要使用自动刷新,必须是println(),printf(),format()
PrintWriter printWriter =
new PrintWriter(new FileWriter("a.txt"), true);
printWriter.print(true);
printWriter.print(3.1415);
// printWriter.println(false);
printWriter.printf("%.2f",2.736);
// printWriter.flush();
//
// printWriter.close();
}
}
打印流特点
-
只能操作目的地,不能操作数据来源。
- 没有与之相对应的输入流
-
可以操作任意类型的数据。
- 把任意类型的数据—>String (String.valueOf(不同类型的数据))
-
如果启动了自动刷新,能够自动刷新。
- autoFlush如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区
- autoFlush如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区
-
可以操作文件的流
- 构造方法里可以传File对象或者String fileName
4.3 标准输入输出流
标准输入流
- System.in
- 默认输入设备是键盘
- 本质: InputStream 普通的字节输入流
标准输出流
- System.out
- 默认输出设备 显示器
- 本质: PrintStream 字节打印流
/**
* @package: _01code._01io._03otherstream._03standard
* @description:
* @author: Yunyang
* @date: 2023/11/2 16:44
* @version:1.0
**/
/*
标准输出流:
system.out
本质:PrintStream 字节打印流
默认输出设备:显式器
标准输入流:
System.in
本质:InputStream 普通字节输入流
默认输入设备:键盘
*/
public class Demo {
public static void main(String[] args) throws IOException {
// 标准输出流
PrintStream out = System.out;
out.println(1);
out.println(3.1415);
out.println(true);
out.println(new Object());
// 标准输入流
InputStream in = System.in;
// read() 是一个阻塞方法
System.out.println("read before");
int readData = in.read();
System.out.println("read after");
System.out.println(((char) readData));
}
}
练习:利用System.in 完成Scanner的nextLine()的功能。
想到BufferedReader 有个readLine
/**
* @package: _01code._01io._03otherstream._03standard
* @description:
* @author: Yunyang
* @date: 2023/11/2 17:14
* @version:1.0
**/
/*
* 练习:利用System.in 完成Scanner的nextLine()的功能。
想到BufferedReader 有个readLine
*/
public class Demo2 {
public static void main(String[] args) throws IOException {
// 借助于转换流
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(System.in));
// System.out.println("read before");
// String s = bufferedReader.readLine();
// System.out.println("read after");
// System.out.println(s);
// 能多次输入
String line;
// 通过一个双方约定的标记 来结束输入
//假设输入“gun”结束
while ((line = bufferedReader.readLine()) != null) {
if ("gun".equals(line)) {
break;
}
System.out.println(line);
}
bufferedReader.close();
}
}
4.3 对象流(序列化与反序列化流)
什么是序列化与反序列化?
- 序列化: 把对象数据转为二进制数据, 存到文件的过程
- 反序列:(序列化的逆过程) 把二进制数据还原回对象数据的过程
为什么需要序列化?
把对象持久化保存
Student s = new Student("zs",20);
4.3.1 ObjectOutputStream序列化流
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
只能将支持 java.io.Serializable 接口的对象写入流中 Serializable接口是一个空接口, 起到标记的作用
writeObject 方法用于将对象写入流中
继承关系
构造方法
ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。
成员方法
void | writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。 |
---|
4.3.2 ObjectInputStream反序列化流
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化
继承关系
构造方法
ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream。
成员方法
Object | readObject() 从 ObjectInputStream 读取对象。 |
---|
/**
* @package: _01code._01io._04Object
* @description:
* @author: Yunyang
* @date: 2023/11/2 17:29
* @version:1.0
**/
/*
* 使用序列化与反序列化流*/
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化操作
serialize();
// 反序列化操作
unSerizalize();
}
private static void unSerizalize() throws IOException, ClassNotFoundException {
// 创建反序列化对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("b.txt"));
// readObject()读取对象
Object o = in.readObject();
System.out.println(o);
// close
in.close();
}
private static void serialize() throws IOException {
// 创建序列化对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("b.txt"));
// 创建一个要保存的对象
Student student = new Student("zs", 20, 99);
// writeObject(Object obj)
out.writeObject(student);
// close
out.close();
}
}
class Student implements Serializable {
// 显式声明 serialVersionUID
private static final long serialVersionUID = -6498018511052347936L;
String name;
int age;
// 使用transient修饰不想被序列化的成员
transient int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
设置自动生成SeriaversionUID
注意
- java.io.NotSerializableException 没有实现Serializable接口
- java.io.InvalidClassException: _18io03.com.cskaoyan._04serialize.Student; local class incompatible: stream classdesc serialVersionUID = -7889256375299507710, local class serialVersionUID = 7388140007375758175 SerialVersionUID不匹配
- transient修饰不想被序列化的成员变量
/**
* @package: _01code._01io._04Object
* @description:
* @author: Yunyang
* @date: 2023/11/3 10:12
* @version:1.0
**/
/*
将多个对象序列化与反序列化*/
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化操作
serizalize();
// 反序列化操作
unserizalize();
}
private static void unserizalize() throws IOException, ClassNotFoundException {
// 创建反序列化对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("a.txt"));
// 方法一:将各个对象逐一反序列化,再打印出来
// Object o1 = in.readObject();
// Object o2 = in.readObject();
// Object o3 = in.readObject();
// Object o4 = in.readObject();
// System.out.println(o1);
// System.out.println(o2);
// System.out.println(o3);
// System.out.println(o4);
// 方法二:将整个数组反序列化,再用Arrays中toString方法打印出来
Teacher[] teachers = (Teacher[]) in.readObject();
System.out.println(Arrays.toString(teachers));
// close
in.close();
}
private static void serizalize() throws IOException {
// 创建序列化对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt"));
// 创建要保存的对象
Teacher t1 = new Teacher("zs", 20);
Teacher t2 = new Teacher("zs", 20);
Teacher t3 = new Teacher("zs", 20);
Teacher t4 = new Teacher("zs", 20);
// 方法一:将所有对象逐个序列化
// out.writeObject(t1);
// out.writeObject(t2);
// out.writeObject(t3);
// out.writeObject(t4);
// 方法二:将要保存的对象放入数组,再将整个数组序列化
Teacher[] teachers = {t1, t2, t3, t4};
// writeObject
out.writeObject(teachers);
// close
out.close();
}
}
class Teacher implements Serializable{
// 显式声明 serialVersionUID
private static final long serialVersionUID = 5481967287707847883L;
String name;
int age;
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
5 总结
类型 | 字节输出流 | 字节输入流 | 字符输出流 | 字符输入流 |
---|---|---|---|---|
抽象基类 | OutputStream | InputStream | Writer | Reader |
文件相关 | FileOutputStream | FileInputStream | FileWriter | FileReader |
缓冲相关 | BufferedOutputStream | BufferedInputStream | BufferedWriter | BufferedReader |
转换相关 | OutputStreamWriter | InputStreamReader | ||
数据相关 | DataOutputStream | DataInputStream | ||
打印相关 | PrintStream | PrintWriter | ||
对象相关 | ObjectOutpuStream | ObjectInputStream |