IO流
IO概述和分类
IO流概述
- IO流可以将数据写到文件中,实现数据永久化存储
- 读取文件中已经存在的数据
- 输入与输出相对于内存来说:从内存提出来时输出,存入内存时输入。
I :表示input,是数据从硬盘到内存的过程,称之为读,读是读到内存。
O :表示output,是数据从内存到硬盘的过程,称之为写,写为写到次磁盘。
过程:数据的流动,内存在读,内存在写。

IO流分类

IO流相关知识
-
纯文本文件:用windows记事本打开能读懂(可以用记事本代开,并且能读懂的文件),那么这样的文件就是纯文本文件。
-
doc、excel(office)等可以用字符流操作
IO流具体分类和使用场景
按数据的流向
-
输入流:读数据
-
输出流:写数据
按数据类型来分
-
字节流
-
字节流输入
-
字节流输出
-
-
字符流
-
字符流入流
-
字符流出流
-
使用场景
-
如果操作的是纯文本文件(可以使用记事本打开),优先使用字符流
-
如果操作的是图片、视频、音频等二进制文件,优先使用字节流
-
如果不确定文件类型,优先使用字节流,字节流是万能的流。
字节流
字节流的创建

(右侧写入出应为:outputStream)
-
字节流抽象基类
-
InputStream:这个抽象类是表示字节输入流的所有类的超类。
-
OutputStream:这个抽象类是表示字节输出流的所有类的超类。
-
子类名特点:子类名称都是以其父类名作为子类名的后缀。
-
-
字节输入/出流
-
FileInputStream(String name): 创建文件输入流以指定的名称读取文件。
-
FileOutputStream(String name):创建文件输出流以指定的名称写入文件。
-
案例:往images文件夹中的a.txt文件中写入数据
往images文件夹中的a.txt文件中写入数据(只能写英文,中文乱码)

package IO.Test01;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputStream01 {
public static void main(String[] args) throws IOException {
//1.创建文件输出流对象
FileOutputStream fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest01");
//2.写出内容
fileOutputStream.write(98);
fileOutputStream.write(97);
fileOutputStream.write(96);
fileOutputStream.write('I');
fileOutputStream.close();
}
}
字节流写数据步骤
-
创建字节输出对象。
注意事项:
如果文件不存在,就创建 -
写数据
注意事项:
写出的整数,实际上写道文件中,是在ASCII码表中的那个字符。 -
释放资源
注意事项:
每次使用完流必须要释放资源。
字节流写入数据的3种方法
void write(int b): 一次写一个字节数据
void write(byte[] b): 一次写一个字节数组数据
void write(byte[] b,int off,int len):一次写一个字节数组的部分数据,从off索引开始,写len长度,
示例代码
package IO.Test01;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutPutStream02 {
public static void main(String[] args) throws IOException {
//创建文件输出对象
FileOutputStream fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest01");
byte a = 10;
fileOutputStream.write(a);
byte[] bytes = {1, 2, 3, 4};
fileOutputStream.write(bytes);
fileOutputStream.write(bytes, 1, 2);//bytes数组,索引从1开始,存入2个长度
}
}
- 注意:当复制图片或者其他文件时,如果输入流用到了字节数组,那么输出流也必须用字节数组(方法中含有byte[]的),不然会复制0字节,导致复制失败。
字节流写数据的三个问题(换行、追加写入、写入字符串)
换行
字节流写数据如何实现换行呢?
- 写完数据后,加换行符
windows:\r\n 单写\r \n 也可以
linux:\n
mac:\r
实例代码
package IO.Test01;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream03 {
public static void main(String[] args) throws IOException {
//1.创建文件输出流对象
FileOutputStream fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest01.txt");
//2.写出内容
byte[] bytes ={97,98,99,100,101};
fileOutputStream.write(bytes);
//加入换行符 getBytes()能够在字符串转为byte类型
fileOutputStream.write("\r\n".getBytes());
//存入bytes数组,索引从off开始,存入len个长度
fileOutputStream.write(bytes,1,3);
fileOutputStream.write("\r\n".getBytes());
//3.关闭流
fileOutputStream.close();
}
}

追加写入
字节流写数据如何实现追加写入呢?
-
public FileOutputStream(String name,boolean append):append追加,ture代表追加,false代表不追加。该方法默认为false,不开启追加。
-
创建文件输出流以指定的名称写入文件。如果第二个参数为true ,不会清空文件里面的内容
实例代码
package IO.Test01;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream04 {
public static void main(String[] args) {
//1.创建对象
FileOutputStream fileOutputStream = null;
try {
File file = new File("D:\\Test01\\InputStreamTest01.txt");
//第一次捕获异常
fileOutputStream = new FileOutputStream(file,true);//也可以直接写路径,但是船舰File对象好处是,可以进行非空判断
if (file != null && file.length() >= 0) {
//2.写数据
String str = "energy in my heart";
//3.将字符串转为字节类型数组 调用getbytes()方法
fileOutputStream.write(str.getBytes());//第二次捕获异常
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();//第三次捕获异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
写入字符串
字节流写入字符串怎么写?
- 利用getByte()方法,将字符串转换位byte类型存入
实例代码
package IO.Test01;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStream04 {
public static void main(String[] args) {
//1.创建对象
FileOutputStream fileOutputStream = null;
try {
File file = new File("D:\\Test01\\InputStreamTest01.txt");
//第一次捕获异常
fileOutputStream = new FileOutputStream(file,true);//也可以直接写路径,但是船舰File对象好处是,可以进行非空判断
if (file != null && file.length() >= 0) {
//2.写数据
String str = "energy in my heart";
//3.将字符串转为字节类型数组 调用getbytes()方法
fileOutputStream.write(str.getBytes());//第二次捕获异常
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();//第三次捕获异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字节流写数据加try…catch异常处理

- 思考:那么我们如何操作才能让close方法一定执行呢?
- finally:在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源
特点:被finally控制的语句一定会执行,除非JVM退出
异常处理标准格式:try….catch…finally
package IO.Test01;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class TryCatch {
public static void main(String[] args) {
//创建文件输出流对象
// 第一次报错创建输出/处流时报错,直接捕获异常即可
//在外层创建对象,防止因为作用域(在try种创建对象,那么就只能在被try包围的大括号内调用)导致对象不能被调用
FileOutputStream fo = null;
try {
fo = new FileOutputStream("D:\\Test01");
//2.写数据
fo.write(98);//第二次报错,追加catch即可
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {//手动追加final,无论是否出错,都要关闭流
try {
fo.close();//第三次报错,直接捕获异常即可
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
小结
字节流读数据 一次读一个字节
步骤:
-
创建字节输出流对象
文件不存在,就创建。 注意:如果文件不存在,就直接报错。
文件存在就清空。如果不想被清空则加true -
写数据
可以写一个字节,写一个字节数组,写一个字节数组的一部分
写一个回车换行:\r\n 注意:读出来的是文件中数据的ASCII码表值。 -
释放资源 注意事项:每次使用完流必须要释放资源。
字节流读取数据
| 方法名 | 说明 |
|---|---|
| int read() | 一次读取一个字节数据,每读取一次,索引向下移动一。 |
| int read(byte[] b) | 一次读一个字节组数据(通常数组长度为1024,1024b=1kb,可以提高输入输出效率。),把数据封装到参数b中,返回值为本次读取到的字节个数,b默认值为0 |
- 读取到最后返回值为:-1
int read()实例代码
package IO.Test01;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStream01 {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
//第一次捕获异常
fileInputStream = new FileInputStream("D:\\Test01\\InputStreamTest01.txt");
//实例1
int read = fileInputStream.read();//第二次捕获异常,捕获第一个字节,然后使索引加1
System.out.println((char) read);//是的read输出为char型
//最佳使用方法while循环遍历
int len;
while ((len = fileInputStream.read()) != -1) {
System.out.println(len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();//第三次捕获异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
int read(byte[] b)实例代码
package IO.Test01;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileInputStream02 {
public static void main(String[] args) {
FileInputStream fileInputStream = null;//输入
FileOutputStream fileOutputStream = null;//输出
try {
fileInputStream = new FileInputStream("D:\\Test01\\InputStreamTest01.txt");//输入
fileOutputStream = new FileOutputStream("D:\\Test01\\InputStreamTest02.txt");//输出
// byte[] bytes = {97, 98, 99};
// int read = fileInputStream.read(bytes);//read(byte[] b)返回读取到的字节数
// System.out.println(read);//3
// System.out.println(fileInputStream.read());//100,读取的第四个字节,每次跨国len个字节,len为byte[]的长度
//文件之间的拷贝
byte[] bytes = new byte[1024];//1024b=1kb
int len;
// int read(byte[] b) : 一次读一个字节组数据(通常数组长度为1024),把数据封装到参数b中,返回值为本次读取到的字节个数,b默认值为0
while ((len = fileInputStream.read(bytes)) != -1) {//fileInputStream.read(bytes)会将得到的数据封装到bytes中
fileOutputStream.write(bytes, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
-
注意:read()方法在一次使用里面最好只调用一次
-
注意:当复制图片或者其他文件时,如果输入流用到了字节数组,那么输出流也必须用字节数组(方法中含有byte[]的),不然会复制0字节,导致复制失败。
字节流读取数据步骤
-
创建字节输入流对象。
注意事项:
如果文件存在则读取。 -
读取数据
注意事项:
读取的是字节的ASCII码需要转换为char类型
读取中文会乱码。 -
释放资源
注意事项:
每次使用完流必须要释放资源。
字节缓冲流
类似于看视频的时候,先将视频缓冲一下,即使网速慢,也可以向看缓冲的一部分。
-
字节缓冲流:
-
BufferOutputStream:缓冲输出流
-
BufferedInputStream:缓冲输入流
-
-
构造方法:
-
字节缓冲输出流:BufferedOutputStream(OutputStream out)
-
字节缓冲输入流:BufferedInputStream(InputStream in)
-
示例代码
package IO.BufferedOutStream;
import java.io.*;
public class BufferedOutputStream01 {
public static void main(String[] args) {
//创建 字节缓冲流对象
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
try {
bos = new BufferedOutputStream(new FileOutputStream("D:\\Test01\\InputStreamTest01.txt"));
bis = new BufferedInputStream(new FileInputStream("D:\\Test01\\InputStreamTest01.txt"));
//实现对拷操作
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
//将数据写出到file文件中
bos.write(bytes, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字节缓冲流复制多级文件夹(有点小难)
package Homework.IO;
import java.io.*;
import java.nio.file.Files;
//把一个文件夹目录 拷贝到项目模块目录下。(要求:文件夹及其子文件全部拷贝)
//尝试使用缓冲流完成这个copy操作。
public class Demo03 {
public static void main(String[] args) throws IOException {
File srcFile = new File("D:\\Test01");
File destFile = new File("D:\\IOTest");
create(srcFile, destFile);
}
public static void create(File srcFile, File destFile) throws IOException {
//创建输入输出流
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
File[] files = srcFile.listFiles();//获取源路径下的所有路径名
for (File file : files) {//循环遍历源路径下的所有一级文件
if (file.isDirectory()) {
String name = srcFile.getName();//获取目标名
File file2 = new File(destFile.getPath() + File.separator + file.getName());//防止拒绝访问
if (!file2.exists()) {//判断该文件夹是否存在
//如果不存在则创建该文件夹
file2.mkdir();
//递归调用该方法,继续复制
create(file, file2);
}
} else {
//如果不是文件夹,则复制文件
try {//创建输入输出流
File newFile = new File(destFile.getPath() + File.separator + file.getName());//防止拒绝访问
bis = new BufferedInputStream(new FileInputStream(file));
bos = new BufferedOutputStream(new FileOutputStream(newFile));
//写数据
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bos.flush();
bos.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
答:字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作


提高拷贝速度的解决方案

为了解决速度问题,字节流通过创建字节数组,可以一次读写多个数据。
一次读一个字节数组的方法:
- public int read(byte[] b):从输入流读取最多b.length个字节的数据
- 返回的是读入缓冲区的总字节数,也就是实际的读取字节个数

小结
| 方法名 | 说明 |
|---|---|
| void write(int b) | 一次写一个字节数据 |
| void write(byte[] b) | 一次写一个字节数组数据 |
| void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
| int read() | 一次读取一个字节数据 |
| int read(byte[] b) | 一次读一个字节组数据,把数据封装到参数b中,返回值为本次读取到的字节个数 |
- 操作所有类型的文件 没有读取到位-1
- 字节缓冲流:可以提高读写效率
- 流:后用先关
字符流
字符流概述以及相关理论知识
- 字符流的作用
把文件中的数据读取到内存时,如果此时文件中出现了中文,那么字节流就会出现乱码现象。所以纯文本的文件,我们就需要使用字符流来进行操作。
编码表
基础知识:
-
计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
-
按照某种规则,将字符存储到计算机中,称为编码。
-
按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码 。
-
编码和解码的方式必须一致,否则会导致乱码。
简单理解:
存储一个字符a,首先需在码表中查到对应的数字是97,然后按照转换成二进制的规则进行存储。
读取的时候,先把二进制解析出来,再转成97,通过97查找到对应的字符是a。
ASCII字符集:
-
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字,大小写字符和一些常见的标点符号。
-
注意:ASCII码表中是没有中文的。
-
GBK:window系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字。(GBK在电脑种也称为ANSI)
-
注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
Unicode码表:
-
由国际组织ISO 制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号。
但是因为表示的字符太多,所以Unicode码表中的数字不是直接以二进制的形式存储到计算机的,会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及 UTF-32的编码方式再存储到计算机,其中最为常见的就是UTF-8。
注意: Unicode是万国码,以UTF-8编码后一个中文以三个字节的形式存储 -
idea中更换编码表:

汉字存储和展示过程解析

字符串中的编码解码问题
编码:将数据编写成对应的编码
-
byte[] getBytes():使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
-
byte[] getBytes(String charsetName):使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
解码:将码值解出来,获得对应的数据
-
String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的 String
-
String(byte[] bytes, String charsetName):通过指定的字符集解码指定的字节数组来构造新的 String
实例代码
package ReaderAndWirter;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Code_Learn {
public static void main(String[] args) throws UnsupportedEncodingException {
//编码byte[] getBytes(String charsetName)
String str = "小黑子树枝666";//想要输入的值
byte[] bytes = str.getBytes();//将想要输入的值转换为字节类型,存入字节数组,UTF-8默认编码格式,一个中文三个字节
System.out.println(Arrays.toString(bytes));//输出bytes集合,中文会变成负数
//[-27, -80, -113, -23, -69, -111, -27, -83, -112, -26, -96, -111, -26, -98, -99, 54, 54, 54]
byte[] bytes1 = str.getBytes("UTF-8");//抛出异常,UnsupportedEncodingException
System.out.println(Arrays.toString(bytes1));
//[-27, -80, -113, -23, -69, -111, -27, -83, -112, -26, -96, -111, -26, -98, -99, 54, 54, 54]
byte[] gbks = str.getBytes("GBK");//GBK中,一个中文两个字节
System.out.println(Arrays.toString(gbks));
//[-48, -95, -70, -38, -41, -45, -54, -9, -42, -90, 54, 54, 54]
//解码String(byte[] bytes, String charsetName)
String str1 = new String(bytes);
System.out.println(str1);//默认编码 UTF-8
//小黑子树枝666
String str2 = new String(bytes, "GBK");
System.out.println(str2);//默认编码 UTF-8,用GBK去解码,会出现乱码
// 灏忛粦瀛愭爲鏋�666
}
}
字符流的创建
- FileReader字符输入流
| 构造器 | 说明 |
|---|---|
| public FileReader(File file) | 创建字符输入流管道与源文件对象接通 |
| public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
- FileWriter字符输出流
| 构造器 | 说明 |
|---|---|
| public FileWriter(File file) | 创建字符输出流管道与源文件对象接通 |
| public FileWriter(File file,boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
| public FileWriter(String filepath) | 创建字符输出流管道与源文件路径接通 |
| public FileWriter(String filepath,boolean append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
实例代码
package ReaderAndWirter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class ReadAndWrite01 {
public static void main(String[] args) {
//创建字符输入流对象
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
fileReader = new FileReader("src/ikun");
fileWriter = new FileWriter("src/abc.txt");//没有文件的话,会先创建
//开始读取 写入文件
int len;
while ((len=fileReader.read())!=-1){
fileWriter.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关流
try {
fileWriter.close();
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 字符流写数据步骤:
- 创建字符输出流对象
注意事项:
如果文件不存在,就创建。但是要保证父级路径存在。
如果文件存在就清空。 - 写数据
注意事项:
1. 写出int类型的整数,实际写出的是整数在码表上对应的字母。
2. 写出字符串数据,是把字符串本身原样写出。 - 释放资源
注意事项:
每次使用完流必须要释放资源。
字符流写数据的5种方式
| 方法名 | 说明 |
|---|---|
| void write(int c) | 写一个字符 |
| void write(char[] cbuf) | 写入一个字符数组 |
| void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
| void write(String str) | 写一个字符串 |
| void write(String str, int off, int len) | 写一个字符串的一部分 |
点击查看代码
package ReaderAndWirter;
import java.io.FileWriter;
import java.io.IOException;
public class ReaderAndWriter01 {
public static void main(String[] args) throws IOException {//本示例代码直接抛出异常
FileWriter fileWriter = null;
fileWriter = new FileWriter("src/a");
char[] chars = {97, 98, 99, 100};
String str = "ikun";
fileWriter.write(98);// void write(int c)
fileWriter.write(chars);//void write(char[] cbuf)
fileWriter.write(chars, 1, 2);//void write(char[] cbuf, int off, int len)
fileWriter.write("98");//void write(String str)
fileWriter.write(str);//void write(String str, int off, int len)
fileWriter.flush();
fileWriter.close();
}
}
字符流读和关闭流、刷新流
| 方法名 | 说明 |
|---|---|
| flush() | 刷新流,还可以继续写数据 |
| close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
| int read() | 一次读一个字符数据 |
| int read(char[] cbuf) | 一次读一个字符数组数据 |
flush作用:清除缓冲流中的残留数据。当close被调用时,输出流会被关闭,此时缓冲区可能还会有数据每存入。此时就要调用flush,清空缓冲区,在关闭close。
账号案例代码
package ReaderAndWirter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
public class ReaderAndWriterTest01 {
public static void main(String[] args) {
//1.创建扫描器 键盘录入 用户名密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.next();
System.out.println("请输入密码:");
String password = scanner.next();
//2.创建 输出流 将用户数据存入本地文件夹中
FileWriter fw = null;
try {
fw = new FileWriter("src/abc.txt");
byte[] bytes = new byte[1024];
fw.write("username:"+username);
fw.write("\r\n");
fw.write("password:"+password);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字符流的缓冲流
-
BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
-
BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
构造方法:
-
BufferedWriter(Writer out)
- void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
-
BufferedReader(Reader in)
- public String readLine() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null,方法开始时便先进入下一行,所以想要读取全部数据,第一行应为/r(空行)
取到没有数据时就返回null(因为其它read()方法当读到没有数据时返回-1),而实际上readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null。
- public String readLine() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null,方法开始时便先进入下一行,所以想要读取全部数据,第一行应为/r(空行)
CSDN博主关于readLine的解析:https://blog.csdn.net/qq30211478/article/details/78500315
示例代码1
package Buffered;
import java.io.*;
/*构造方法:
* BufferedWriter(Writer out)
* void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
* BufferedReader(Reader in)
* public String readLine() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
*/
public class BufferedReaderAndWriter {
public static void main(String[] args) {
//创建缓冲流字符输入流/输出流
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
bufferedReader = new BufferedReader(new FileReader("src/a"));
bufferedWriter = new BufferedWriter(new FileWriter("src/aaaa"));
//普通方法
// int len;
// while ((len = bufferedReader.read()) != -1) {
// bufferedWriter.write(len);
// }
String line;
while ((line = bufferedReader.readLine()) != null) {//public String readLine()
bufferedWriter.write(line);//写一行数据
bufferedWriter.newLine();//换行
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedWriter.flush();
bufferedWriter.close();
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
示例代码2
package ReaderAndWirter;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Demo01 {
public static void main(String[] args) {
//创建输入和输出流对象
BufferedWriter bw = null;
BufferedReader br = null;
//创建集合,便于排序
ArrayList<String> arrayList = new ArrayList<>();
try {
br = new BufferedReader(new FileReader("src/a"));
bw = new BufferedWriter(new FileWriter("src/a", true));//添加true使得写文件时,不会清空文件夹的内容
//读取数据
String line;
while ((line = br.readLine()) != null) {
//取出数据 添加到集合中 并排序
arrayList.add(line);
// bw.write(line);
}
//对集合中的元素 完成排序
Collections.sort(arrayList, new Comparator<String>() {//Collections.sort(list,Comparator<String>())自定义集合排序方式
@Override
public int compare(String o1, String o2) {
return o1.hashCode() - o2.hashCode();//利用哈希值进行比较
}
});
System.out.println("排序后:" + arrayList);//打印集合内容
//将集合中的内容写入到文件夹中
/* for (String s : arrayList) {
bw.write(s);
bw.newLine();
}
*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
bw.flush();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
IO流小结

转换流
转换流概述
- 转换输入/输出的编码格式

转换流构造方法以及创建
| 方法名 | 说明 |
|---|---|
| InputStreamReader(InputStream in) | 使用默认字符编码创建InputStreamReader对象 |
| InputStreamReader(InputStream in,String chatset) | 使用指定的字符编码创建InputStreamReader对象 |
| OutputStreamWriter(OutputStream out) | 使用默认字符编码创建OutputStreamWriter对象 |
| OutputStreamWriter(OutputStream out,String charset) | 使用指定的字符编码创建OutputStreamWriter对象 |
package ReaderAndWirter;
//读取文件中的数据,排序后再次写到本地文件
import java.io.*;
import java.nio.charset.Charset;//jdk 11之后
public class Transfer {
public static void main(String[] args) throws IOException {
//创建输入流对象 默认UTF-8
InputStreamReader irs = new InputStreamReader(new FileInputStream("src/abc.txt"));
//创建输出流对象 默认UTF-8
OutputStreamWriter ows = new OutputStreamWriter(new FileOutputStream("src/ikun"));
int len;
while((len=irs.read())!=-1){
ows.write(len);
}
ows.close();
irs.close();
//JDK11之后提供的在创建字符流的时候 指定编码规则
FileReader fr=new FileReader("src/ikun", Charset.forName("UTF-8"));
}
}
转换流的使用场景
在JDK11之前,指定编码读写
JDK11之后,
-
FileReader(File file, Charset charset):创建一个新的FileReader,给出File读取和创建charset
-
FileReader(String fileName, Charset charset):创建一个给定文件名称的FileReader,给出File读取和创建charset
-
【注意】:
charset需要通过Charset.forName(“”)获取
小结
转换流就是来进行字节流和字符流之间转换的
-
InputStreamReader是从字节流到字符流的桥梁
它读取字节,并使用指定的编码将其解码为字符。
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。 -
OutputStreamWriter是从字符流到字节流的桥梁
是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节。
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
对象操作流
对象操作流的概述
了解对象操作流
- 特点:可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中。


对象操作流概述
-
对象操作流分为两类:对象操作输入流和对象操作输出流
-
对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
-
对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
对象序列化和发序列化概述
-
对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
-
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
-
字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
-
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
对象输入流
-
对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
-
构造方法
| 方法名 | 说明 |
|---|---|
| ObjectOutputStream(OutputStream out) | 创建一个写入指定的OutputStream的ObjectOutputStream |
- 序列化对象
| 方法名 | 说明 |
|---|---|
| void writeObject(Object obj) | 将指定的对象写入ObjectOutputStream |
- 注意事项
- 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
对象输出流
-
对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
-
构造方法
| 方法名 | 说明 |
|---|---|
| ObjectInputStream(InputStream in) | 创建从指定的InputStream读取的ObjectInputStream |
- 反序列化对象的方法
| 方法名 | 说明 |
|---|---|
| Object readObject() | 从ObjectInputStream读取一个对象 |
自定义类代码
package ObjectManipilationStream;
import java.io.Serializable;
//学生对象
public class Students implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "Students{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Students(String name, int age) {
this.name = name;
this.age = age;
}
}
对象流操作对象代码
package ObjectManipilationStream;
import java.io.*;
/*
* 对象操作流 用来操作对象
* 1. 对象输入流 将对象从文件中读取出来 反序列化过程
* 2. 对象输出流 将对象保存到文件中 序列化的过程
* 序列化: 将对象保存到磁盘
* 反序列化:将对象从磁盘中取出
*
* 前提条件:在向文件中存储对象时,必须将类实现序列化接口
* */
public class ObjectManipulationStream01 {
public static void main(String[] args) {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
//1.创建输入输出流
ois = new ObjectInputStream(new FileInputStream("src/ObjectManipilationStream/Object01"));
oos = new ObjectOutputStream(new FileOutputStream("src/ObjectManipilationStream/Object01"));
//2.写数据
Students s1 = new Students("撒旦", 98);
oos.writeObject(s1);//形参为对象类型
//读数据
Students s2 = (Students) ois.readObject();//强制类型转换才可以用Students接收
System.out.println(s2);//Students{name='撒旦', age=98}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
ois.close();
oos.flush();
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象流操作其他基本数据类型代码
package ObjectManipilationStream;
import IO.Demo01.Student;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
public class ObjectManipulationStream02 {
public static void main(String[] args) throws IOException {
//创建
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/a"));
//写
oos.writeInt(123);//注意方法名
oos.writeObject(new Date());
oos.writeObject(new Students("小黑子", 666));
//关
oos.flush();
oos.close();
}
}
对象操作流的注意事项
注意:
-
对象流不仅可以读写对象,还可以读写基本数据类型。
-
使用对象流读写对象时,该对象必须序列化与反序列化。
-
系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。
对象操作流问题
用对象序列化流序列化了一个对象后,我们再去修改对象所属的Javabean类,比如添加一个属性,读取数据会不会出问题呢?
- 会出问题,会抛出InvalidClassException异常
如果出问题了,如何解决呢? - 1.给对象所属的类加一个serialVersionUID
private static final long serialVersionUID = 42L;
2.自动生成
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
- 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
自定义类代码
package ObjectManipilationStream;
import java.io.Serializable;
//学生对象
/*生成serialVersionUID两种方法
* 1.死值 private static final long serialVersionUID = 42l;
* 2.系统生成
* */
public class Students implements Serializable {
//写一个死值
private static final long serialVersionUID = 42l;
//serialVersionUID = -6439917428245416448
private String name;
private int age;
private String sex;//二次修改后增添的值
@Override
public String toString() {
return "Students{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Students(String name, int age) {
this.name = name;
this.age = age;
}
}
测试代码
package ObjectManipilationStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectManipulationStream03 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/a"));
Students s2 = (Students) ois.readObject();
System.out.println(s2);
ois.close();
}
/*
Exception in thread "main" java.io.InvalidClassException: ObjectManipilationStream.Students;
local class incompatible: stream classdesc serialVersionUID = -6439917428245416448(存储时的序列化ID), local class serialVersionUID = -8371228507770588511(修改后新的序列化ID)二者不相同,程序报错
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2028)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1875)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2209)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1692)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:508)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:466)
at ObjectManipilationStream.ObjectManipulationStream03.main(ObjectManipulationStream03.java:11)
*/
}
Properties
Porperties概述
-
是一个Map体系的集合类 继承了Hashtable implements Map接口
-
Properties中有跟IO相关的方法
-
只存字符串
-
Properties通常用来读取,properties后缀的文件,称之为java中的配置文件

Properties方法
- Properties作为集合的特有方法:
| 方法名 | 说明 |
|---|---|
| Object setProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put |
| String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
| Set |
从该属性列表中返回一个不可修改的键集,其中键,及其对应的值是字符串 |
-
Properties常用其他方法: Properties底层有Map接口,所以有Map的方法例如
-
remove(String key),清除键值,返回键对应的值
-
getKey()。获取键值
-
Properties作为Map集合使用(增删改查)
package Properties;
import java.util.Properties;
import java.util.Set;
/*Properties作为Map集合实现增删改查
* 只能存储字符串的
* Object setProperty(String key, String value) 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
* String getProperty(String key) 使用此属性列表中指定的键搜索属性
* Set<String> stringPropertyNames() 从该属性列表中返回一个不可修改的键集,其中键集其对应的值是字符串
* */
public class Properties01 {
public static void main(String[] args) {
Properties properties = new Properties();//{age=18, name=小黑子, sex=男}
//增
properties.setProperty("name","小黑子");
properties.setProperty("age","18");
properties.setProperty("sex","男");
//删
Object name = properties.remove("name");//{age=18, sex=男}
System.out.println(name);//张三 删除键,返回的是键对应的值
//改
properties.setProperty("age","19");//{age=19, sex=男} 键名不能重复
//查
Set<Object> set = properties.keySet();//获取键,但是没法获取值,因为keySet返回类型为Object
for (Object o : set) {//只有键名,没有值
// System.out.println(o);
}
Set<String> set1 = properties.stringPropertyNames();//从该属性列表中返回一个不可修改的键集,其中键,及其对应的值是字符串
for (String s : set1) {//获取值
String value = properties.getProperty(s);
System.out.println("Key:"+s+"Value"+"\t"+value);
}
System.out.println(properties);
}
}
Properties和IO流结合的方法
- Properties和IO流结合的方法:
| 方法名 | 说明 |
|---|---|
| void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
| void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
| void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 |
示例代码
package Properties;
import java.io.*;
import java.util.Properties;
import java.util.Set;
/*
* void load(InputStream inStream) 从输入字节流读取属性列表(键和元素对)
void load(Reader reader) 从输入字符流读取属性列表(键和元素对)
void store(Writer writer, String comments) 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流
*Properties通产用来读取,properties后缀的文件,称之为java中的配置文件
*
* */
public class Properties02 {
public static void main(String[] args) throws IOException {
//创建Properties 集合对象
Properties properties = new Properties();
properties.setProperty("ikun01","树枝666");
properties.setProperty("ikun02","食不食油饼");
properties.setProperty("ikun03","香精监狱");
properties.setProperty("ikun04","香翅捞饭");
System.out.println(properties);//{ikun=香翅捞饭}
//{ikun04=香翅捞饭, ikun03=香精监狱, ikun02=食不食油饼, ikun01=树枝666}
//创建输入流
FileInputStream fis = new FileInputStream("src/Prop.properties");
//加载
properties.load(fis);
System.out.println(properties);
//{ikun04=香翅捞饭, ikun03=香精监狱, ikun02=食不食油饼, ikun01=树枝666}
//将properties集合中的内容存储到配置文件中 以下创建的为字节流 想要存储汉字需要字符流
properties.store(new FileOutputStream("src/Prop.properties"),"abc");//comments为备注,会自动在文件中生成‘#comments’,还会自动生成时间备注
Set<String> set = properties.stringPropertyNames();
for (String s : set) {
String value = properties.getProperty(s);
System.out.println("Key:"+s+"Value"+"\t"+value);
}
/*
Key:ikun04Value 香翅捞饭
Key:ikun1Value 123
Key:ikun03Value 香精监狱
Key:ikun02Value 食不食油饼
Key:ikun01Value 树枝666
Key:ikun2Value 246
*/
}
}
案例代码
package Properties;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Properties;
/*
* - 案例需求
xx.properties:
jdbc.username=root
jdbc.password=1234
在Properties文件中手动写上用户名密码,读取到集合中,将该数据封装成用户对象,写到本地文件.
- 实现步骤
- 创建Properties集合,将本地文件中的数据加载到集合中.
- 获取集合中的键值对数据,封装到用户对象中.
- 创建序列化流对象,将用户对象序列化到本地文件中.
* */
public class Properties03 {
public static void main(String[] args) throws IOException {
//创建properties集合对象
Properties properties = new Properties();
//2.存储内容
properties.setProperty("jdbc.username", "rppt");
properties.setProperty("jdbc.password", "1234");
//根据键名 获取值 存入对象中
User user = new User();
user.setUsername(properties.getProperty("jdbc.username"));
user.setPassword(Integer.parseInt(properties.getProperty("jdbc.password")));
//将对象存入到文件中 对象流要注意实现接口
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("src/jdbc.properties"));
oos.writeObject(user);
//3.读取集合内容 写入到配置文件中 创建字符流
FileWriter fw = new FileWriter("src/jdbc.properties");
properties.store(fw, "jdbc");
//关闭流
oos.flush();
oos.close();
fw.close();
}
}
- 案例图片

登陆账户(判断用户名和密码)案例代码
package Properties;
import java.io.*;
import java.util.*;
/*
* 案例:使用字符缓冲流读取user.txt中用户名和密码
* 需求:读取用户名和密码,判断用户输入数据是否正确
* 步骤:
* 用户通过控制台输入数据
* 读取文件内容,进行数据撕裂获取username, password
* 比较用户输入数据是否正确
*/
public class Demo02 {
public static void main(String[] args) throws IOException {//为了减少代码行数,本示例代码选择直接抛出异常
//定义限制次数
int time=3;
//创建扫描器,获取用户输入数据
Scanner scanner = new Scanner(System.in);
while (true){//循环输入用户名和密码
//输入用户名
System.out.println("请输入用户名:");
String username = scanner.next();
//输入密码
System.out.println("请输入密码:");
String password = scanner.next();
if (getTarget(username,password)==1){
System.out.println("登陆成功");
break;
}else if (getTarget(username,password)==0){
--time;
System.out.println("密码错误,您当前还有"+time+"次输入次数");
}else{
System.out.println("用户名不存在");
}
if (time==0){
break;
}
}
}
//创建方法,判断用户输入是否在本地中存在
public static int getTarget(String username, String password) throws IOException {
//比较次数结果
int size = 0;
//创建properties操作流
Properties properties = new Properties();
//创建字符缓冲流,获取保存的用户名和密码
BufferedReader bf = new BufferedReader(new FileReader("src/ProtertiesTest.protetties"));
//加载
properties.load(bf);
//对properties获取的值存入到集合中
Set<String> set = properties.stringPropertyNames();
//循环判断用户输入是否正确
for (String s : set) {
size++;
if (s.equals(username)) {//判断用户名是否正确
if (properties.getProperty(s).equals(password)) {//密码正确
return 1;
}
} else if (s.equals(username) && !properties.getProperty(s).equals(password))//密码不正确
{
return 0;
}
if (size == set.size()) {//未找到相关用户名
return -1;
}
}
return -1;
}
}
输入、输出流拒绝访问解决方案
- IO流是对文件进行操作,直接对文件夹操作就会因为没有系统权限而被阻拦,但是通过复制路径的方法可以绕过阻拦。
package Homework.File.File01;
import java.io.*;
public class Test {//该代码较为简单,没有明显体现出复制转换法的作用,但是到了较大的问题,比如本博客中的字节缓冲流中的,复制多级文件夹代码,就体现出来了。
public static void main(String[] args) throws IOException {//为了减少行数本代码直接抛出异常
//注意:两路径均以文件夹为结尾,IO流输出时会被拒绝访问
File srcFile = new File("D:\\Test01 - 副本");//源路径
File destFile = new File("D:\\Test01");//目标路径
//输入缓冲流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
//输出缓冲流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
//读写文件,正常状况下会报错,拒绝访问
File[] files = srcFile.listFiles();
for (File file : files) {
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1){
bos.write(bytes);
}
}
//我们复制路径,绕过权限访问(直接确定到文件夹,中的文件名)
//newFile是由,目标路径的路径+分隔符+源路径下文件名组成,用newFile替换destFile绕过权限访问
File newFile = new File(destFile.getPath()+File.separator+srcFile.getName());
//修改后的写入方法
File[] files1 = srcFile.listFiles();
for (File file : files1) {
int len;
bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1){
bos.write(bytes);
}
}
}
}
输入输出时,字节数组和字节的应用问题
-
在输入输出时,如果字节数组和字节运用不好,会导致复制0字节的内容(复制的结果仅仅为创建了一个文件名而已,里面没有内容)
-
正确格式1:
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes);
}
- 正确格式2
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
- 正确格式3:
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read()) != -1) {
bos.write(len);
}
- 离奇错误:复制后的文件大小扩大不少
int len;//错误方法!!!!
byte[] bytes = new byte[1024];
while ((len = bis.read()) != -1) {
bos.write(bytes);
}
close和flush
- 输出流必须夹close,不然最后数据不会被写入文件,flush也可以使写入的数据进入文件。所以不想关闭输入流时,可以先flush一下,存入数据,以后在关闭close。