接下来的两篇文章我们将系统化的详细学习IO流的内容:
基本流 | JAVA进阶(IO流) —— 基本流 |
高级流 | JAVA进阶(IO流) —— 高级流 |
目录
一、前言
二、IO流的分类
三、 字节流
1. 输出流 FileOutputStream
1.1 FileOutputStream 写数据的三种方式
1.2 FileOutputStream 写数据的两个小问题
2. 输入流 FileInputStream
2.1 FileInputStream 循环读取
2.2 FileInputStream 读数据的两种方式
3. 练习:文件拷贝
四、字符流
1. 字符输入流 FileReader
2. 字符输出流 FileWriter
3. 字符流原理分析
五、综合练习
1. 拷贝
2. 文件加密
3. 修改文件中的数据
File:表示系统中的文件或者文件夹的路径。(详细见:JAVA进阶 —— File)
注意: File类只能对文件本身进行操作,不能对写文件里面存储的数据。
IO流:用来读写文件中的数据(可以读写文件、或者网络中的数据 . . . )
按流的方向分 | ![]() |
按操作文件类型分 | ![]() |
纯文本文件:Windows自带的记事本打开能读懂的文件。
注意:字节流读取文件的时候,文件中不要有中文。
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。
书写步骤:
① 创建字节输出流对象
- 细节1:参数是字符串表示的路径或者File对象都是可以的。
- 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
- 细节3:如果文件已经存在,则会清空文件。
② 写数据
- 细节:write方法的参数是整数,但是实际上写到本地文件中的是整数ASCII上对应的字符。
③ 释放资源
- 细节:每次使用完流之后都要释放资源。
public class ByteStreamDemo1 {
public static void main(String[] args) throws IOException {
// 需求: 写一段文字到本地文件中
// 1.创建对象
// 写出 -> 输出流 OutputStream
// 编译时期异常 throws FileNotFoundException
FileOutputStream fos = new FileOutputStream("java02\\a.txt");
// 2.写出数据
fos.write(97);
// 3.释放资源
fos.close();
}
}
方法名称 | 说明 |
void write ( int b ) | 一次写一个字节数据 |
void write ( byte [ ] b ) | 一次写一个字节数组数据 |
void write ( byte [ ] b , int off, int len ) | 一次写一个字节数组的部分数据 |
public class ByteStreamDemo2 {
public static void main(String[] args) throws IOException {
// 1.创建对象
FileOutputStream fos = new FileOutputStream("java02\\a.txt");
// 2.写出数据
// ①、一次写一个字符数据
fos.write(97); // a
fos.write(98); // b
// ②、一次写入一个字符数组
byte[] bytes = { 97, 98, 99, 100, 101 };
fos.write(bytes);
// ③、一次写一个字符数组的部分数据
// 参数一:数组 ;参数二:起始索引 ; 参数三:个数
fos.write(bytes, 1, 2); // b c
// 3.释放资源
fos.close();
}
}
- 换行写:换行符 \r \n
- 续写:FileOutputStream("路径", 参数);
public class ByteStreamDemo3 {
public static void main(String[] args) throws IOException {
// 1.创建对象
FileOutputStream fos = new FileOutputStream("java02\\a.txt", true);
// 2.写出数据
String str = "abcdefghijklmn";
byte[] bytes = str.getBytes(); // 字符串转换成字节数组
fos.write(bytes);
// 3.换行
// 换行符
// windows:00\r\n Linux: \n Mac:\r
String str3 = "\r\n";
byte[] bytes3 = str3.getBytes();
fos.write(bytes3);
String str2 = "666";
byte[] bytes2 = str2.getBytes();
fos.write(bytes2); // 此时一开始并没有换行 需要上写一个换行符
// 4.续写
// 如果想要续写,打开续写开关即可
// 打开位置,创建对象的第二个参数
// 默认false:表示关闭,此时创建对象会清空文件
// 手动true:表示打开续写,此时创建对象不会清空文件
// new FileOutputStream("java02\\a.txt",true);
// 5.释放资源
fos.close();
}
}
- 操作本地文件的字符输入流,可以把本地文件中的数据读取到程序当中。
书写步骤:
① 创建字节输入流对象
- 细节:如果文件不存在,就直接报错。
② 读数据
- 细节1:一次读一个字节,读出来的是数据在ASCII上对应的数字。
- 细节2:读取到文件末尾时,read方法返回 -1。
③ 释放资源
- 细节:每次使用完流必须要释放资源。
public class ByteStreamDemo4 {
public static void main(String[] args) throws IOException {
// 1.创建对象
FileInputStream fis = new FileInputStream("java02\\a.txt");
// a.txt : abcde
// 2.读取数据
int b1 = fis.read();
System.out.println(b1); // 97
System.out.println((char) b1); // 强转: a
// 读取不到就会返回 -1
// 3.释放资源
fis.close();
}
}
read方法:读取数据,而且是读取一个数据移动一次指针
public class ByteStreamDemo5 {
public static void main(String[] args) throws IOException {
// 1.创建对象
FileInputStream fis = new FileInputStream("java02\\a.txt");
// a.txt : abcde
// 2.循环读取
//定义第三方变量
int b;
while ((b = fis.read()) != -1) {
System.out.println((char) b);
}
// 以下读取方式是错误的
//read方法:读取数据,而且是读取一个数据移动一次指针
//相当于迭代器的next方法
// while (fis.read() != -1) {
// System.out.println(fis.read()); // 98 100 -1
// }
// 释放资源
fis.close();
}
}
方法名称 | 说明 |
public int read ( ) | 一次读一个字节数据 |
public int read ( byte [ ] buffer ) | 一次读一个字节数组数据 |
注意:一次读一个字节数组的数据,每次读取会尽可能把数组填满。
public class ByteStreamDemo6 {
public static void main(String[] args) throws IOException {
// 1.创建对象
FileInputStream fis = new FileInputStream("java02\\a.txt");
// 2.读取数据
byte[] bytes = new byte[2];
//一次读取多个字节数据:具体读多少,跟数组的长度有关
//返回值:本次读取到了多少个字节数据
int len = fis.read(bytes);
System.out.println(len);
String str= new String(bytes);
System.out.println(str);
//3.释放资源
fis.close();
}
}
需求:把D: \aaa\movie . mp4拷贝到当前模块下。
注意:选择一个比较小的文件.不要太大。
public class ByteStreamDemo6 {
public static void main(String[] args) throws IOException {
// 1.创建对象
FileInputStream fis = new FileInputStream("D:\\aaa\\movie.mp4");
FileOutputStream fos = new FileOutputStream("java02\\copy.mp4");
// 2.拷贝
// 需要边读边写
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
// 3.释放资源
// 规则: 先开的最后关闭
fos.close();
fis.close();
}
}
弊端: FileInputStream 一次读写一个字节,速度慢。
解决方案:FileInputStream 使用 byte[] 数组一次遍历多个数据。
public class ByteStreamDemo6 {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
// 1.创建对象
FileInputStream fis = new FileInputStream("D:\\aaa\\movie.mp4");
FileOutputStream fos = new FileOutputStream("java02\\copy.mp4");
// 2.拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5]; // 5兆大小
while ((len = fis.read(bytes)) != -1) {
fos.write(len);
}
// 3.释放资源
fos.close();
fis.close();
long end = System.currentTimeMillis();
//运行时间
System.out.println(end - start);
}
}
字符流的底层其实就是字节流。
- 字符流 = 字节流 + 字符集
特点:
- 输入流:一次读取一个字节,遇到中文时,一次读多个字节再写到文件中。
- 输出流:底层会把数据按照指定的编码方式,变成字节再写到文件中。
使用场景: 对于纯文本文件进行读写操作。
书写步骤:
① 创建字符输入流对象
构造方法 说明 public FileReader ( File file ) 创建字符输入流关联本地文件 public FileReader ( String pathname ) 创建字符输入流关联本地文件
- 细节:如果文件不存在,就直接报错。
② 读取数据
成员方法 说明 public int read ( ) 读取数据,读到末尾返回 -1 public int read ( char [ ] buffer ) 读取多个数据,读到末尾返回 -1
- 细节1:按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数。
- 细节2:读到文件末尾时,read方法返回 -1。
③ 释放资源
成员方法 说明 public int close ( ) 释放资源 / 关流
public class CharStreamDemo1 {
public static void main(String[] args) throws IOException {
// 1.创建对象并关联本地文件
FileReader fr = new FileReader("java02\\a.txt");
// 2.读取数据
// 细节1:字符流的底层就是字节流
// 默认一个字节一个字节的读取的
// 如果遇到中文,就会一次读取多个字节,GBK一次两个字节 UTF-8一次三个字节
// 细节2:读取之后,方法底层会进行解码并转换成十进制
// 十进制作为返回值 并作为字符集上的数字
// 细节3:想要看中文 可以对十进制进行强转
//空参read
int ch;
while ((ch - fr.read()) != -1) {
// System.out.println(ch);
System.out.println((char) ch);
}
//带参read:读取数据、解码、强转三者合并,把强转之后字符放进数组
//空参的read + 强转类型转换
char[] chars = new char[2];
int len;
while((len = fr.read(chars)) != -1) {
//把数组中的数据变成字符串再进行打印
System.out.println(new String(chars,0,len));
}
// 3.释放资源
fr.close();
}
}
书写步骤:
① 创建字符输出流对象
构造方法 说明 public Filewriter ( File file ) 创建字符输出流关联本地文件 public Filewriter ( String pathname ) 创建字符输出流关联本地文件 public Filewriter ( File file , Boolean append ) 创建字符输出流关联本地文件,续写 public Filewriter ( String pathname , Boolean append ) 创建字符输出流关联本地文件,续写
- 细节1:参数是字符串表示的路径或者File对象都是可以的。
- 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
- 细节3:如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关。
② 写数据
成员方法 说明 void write ( int c ) 写出一个字符 void write ( String str ) 写出一个字符串 void write( String str, int off,int len ) 写出一个字符串的一部分 void write( char [ ] cbuf) 写出一个字符数组 void write( char [ ] cbuf,int off, int len) 写出字符数组的一部分
- 细节:如果write方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符。
③ 释放资源
- 细节:每次使用完流之后都要释放资源
public class CharStreamDemo2 {
public static void main(String[] args) throws IOException {
// 1.创建对象 续写开关打开
FileWriter fw = new FileWriter("java02\\a.txt", true);
// 2.写数据
fw.write(25105); // 写一个字符
fw.write("你好?"); // 写一个字符串 9个字节
char[] chars = { 'a', 'b', 'c', '我' };
fw.write(chars); // 写一个字符数组
// 3.释放资源
fw.close();
}
}
① 创建字符输入流对象
- 底层:关联文件,并创建缓冲区(长度为8192的字节数组)
② 读取数据
- 底层:
- 1. 判断缓冲区中是否有数据可以读取
- 2. 缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区;如果文件中也没用数据时,返回 -1。
- 3. 缓冲区有数据:就从缓冲区中读取。 空参的read方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回。 有参的read方法:把读取字节、解码、强转三步合并,强转之后的字符放到数组当中。
需求:拷贝一个文件夹,考虑子文件夹。
public class Test01 {
public static void main(String[] args) {
// 1.创建对象表示数据源
File src = new File("D:\\aaa\\src");
// 2.创建对象表示目的地
File dest = new File("D:\\aaa\\dest");
// 3.调用方法开始拷贝
copydir(src, dest);
}
public static void copydir(File src, File dest) throws IOException {
// 判断目的文件夹是否存在
dest.mkdir();
// 递归
// 1.进入数据源
File[] files = src.listFiles();
// 2.遍历数组
for (File file : files) {
if (file.isFile()) {
// 是文件 开始拷贝
FileInputStream fis = new FileInputStream(file);
//dest 是文件夹 不是最终目的地 需要从文件开始到文件结束
FileOutputStream fos = new FileOutputStream
(new File(dest, file.getName()));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
} else {
// 是文件夹 递归
copydir(file, new File(dest, file.getName()));
}
}
}
}
需求:
为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理。加密原理:
对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。
解密原理:
读取加密之后的文件,按照加密的规则反向操作,变成原始文件。
public class Test2 {
public static void main(String[] args) throws IOException {
// ^ 异或:两边相同false 两边不同true
// 1.创建对象关联原始文件
FileInputStream fis = new FileInputStream("java02\\girl.jpg");
// 2.创建对象关联加密文件
FileOutputStream fos = new FileOutputStream("java02\\ency.jpg");
// 3.机密过程
int b;
while ((b = fis.read()) != -1) {
fos.write(b ^ 2);
}
// 4.释放资源
fos.close();
fis.close();
// 解密过程
FileInputStream fis = new FileInputStream("java02\\ency.jpg");
FileOutputStream fos = new FileOutputStream("java02\\redu.jpg");
int b;
while ((b = fis.read()) != -1) {
fos.write(b ^ 2);
}
fos.close();
fis.close();
}
}
需求:
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:
1-2-4-7-8-9
public class Test3 {
public static void main(String[] args) throws IOException {
// 1.读取数据
FileReader fr = new FileReader("java02\\a.txt");
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = fr.read()) != -1) {
sb.append((char) ch);
}
fr.close();
System.out.println(sb);
// 2.排序
String str = sb.toString();
String[] arrStr = str.split("-");
// 定义数组用于存储数据进行排序
ArrayList list = new ArrayList<>();
for (String s : arrStr) {
int i = Integer.parseInt(s);
list.add(i);
}
System.out.println(list);
// sort: 默认升序排序
Collections.sort(list);
System.out.println(list);
// 3.写出数据
FileWriter fw = new FileWriter("java02\\a.txt");
// 打印结果: 1-2-4-7-8-9 通过索引遍历 -》 普通for循环
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) {
fw.write(list.get(i) + "-");
} else {
fw.write(list.get(i));
}
}
fw.close();
}
}