IO流
1.1 概念
File:只能操作文件或目录(文件中的内容不能读取到)
IO:Input/Output 即输入&输出,也称为流。指的是数据从一个地方到另一个地方
例如:计算机中文件拷贝过程,文件的编写及保存,显示文件的内容等都需要使用IO,我们将传输的过程看做是一个输入和输出的过程。
- 输入和输出的介质:
文件
网络
键盘(输入)
显示器(输出)
1.2 IO的分类
JAVA针对IO操作提供了相应的API,Java中几乎所有的IO操作都需要使用java.io包
对于流的分类包含各种方式:
按流的方向(输入输出的过程通常是站在程序(计算机)的角度考虑)
输入流(Input) 读read
输出流(Output) 写write
按流的处理类型
字节流(byte) 读写都是一个字节
字符流(char) 读取都是一个字符
按流的功能(看构造器,构造器参数是一个流,则就是处理流)
节点流 (直接跟输入输出源进行交互)
处理流(对其他流进行包装的流,也称包装流)
1.3 顶级父类
流的种类虽然很多,但是规律性极强,几乎所有的流都是从4个基类中继承而来的
输入流 输出流
字节流 java.io.InputStream java.io.OutputStream
字符流 java.io.Reader java.io.Writer
以上的4个流是所有java流的顶级父类,都是抽象类
一般以Stream结尾的都是字节流,一般以Reader/Writer结尾的都是字符流
1.4 IO使用场景
文件拷贝
文件上传和下载
网络程序中数据传输
Excel导入和导出
...
2. 字节流
2.1 字节概述
在计算机系统中,一切都是字节,系统中存储的各种文件(文本文档、图片、视频、音频等)的存储在计算机底层中,都是以字节的形式存储的,因此对于任何的文件操作都是可以使用一个一个字节来进行操作(读、写)
Java中的字节流顶层父类:InputStream/OutputStream
2.2 字节输入流
- FileInputStream 从文件中读
- ByteArrayInputStream 从byte[]数组中读
- BufferedInputStream 高效读取(带缓冲区)
- ObjectInputStream 需要写入对象时,才能使用到这个流读对象
- …
InputStream常用方法
int read() 从流中读取一个字节,返回当前读取的字节数据
int read(byte[] b) 从读取字节数据存储到自己缓冲区,并返回实际的读取字节总数
int available() 获取流中可读字节数
2.2.1 FileInputStream
FileInputStream 是一个针对字节输入流的实现流,主要用于对文件进行读取操作,内部的方法主要是针对父类的实现
常见构造器
FileInputStream(File file): 根据提供的文件构建的对象
FileInputStream(String filePath): 根据提供的文件路径构建对象
使用FileInputStream进行文件的读取操作
1、基本读取(每次读取一个字节)
//创建File对象
File file = new File("C:\\Users\\xxx\\Desktop\\新建 文本文档.txt");
//创建FileInputStream对象
FileInputStream fis = new FileInputStream(file);
//读取一个字节
int read = fis.read();
System.out.println((char)read);
2、使用字节缓冲区读取(缓冲区大小为总可读字节数)
由于上面的读取方式每次一个字节的读取,因此读取效率低,所有在实际开发中会使用一个字节缓冲区,提高读取效率。
FileInputStream fis = new FileInputStream("C:\\Users\\xxx\\Desktop\\新建 文本文档.txt");
//文件中可读的字节个数
System.out.println("字节个数:"+fis.availavle());
//创建一个byte数组
//相对于一个缓冲区(大小可以直接设置)
byte[] b = new byte[fis.available()];
int len = 0;
//read(b):把流中读取到的字节内容存储到数组中
while ((len = fis.read(b)) != -1){
//将数组中的有效内容转化为String类型
System.out.println(new String(b,0,len));
}
//关闭流
fis.close();
((len = fis.read(b))!=-1)
①执行fis=read(b),返回读取到的字节个数,若返回-1表示读取到文件末尾
②执行len=返回值结果
③判断len!=-1是否成立
3 使用合适的大小缓冲区中读取
以上读取是一次性将文件的数据读取到缓冲区中,因此,缓冲区的容量可以会很大,如果针对一个大文件的读取,使用一个过大的缓冲区,可能会造成空间的浪费以及不必要的麻烦,从而影响其他程序的执行,所有需要合适大小的缓冲区进行反复读取
FileInputStream fis = new FileInputStream("C:\\Users\\xxx\\Desktop\\新建 文本文档.txt");
byte[] b = new byte[fis.available()];
int len = 0;
while ((len = fis.read(b)) != -1){
System.out.println(new String(b,0,len));
}
2.3 字节输出流
根据数据的流向除了可以进行读取(输入)操作之外,数据的写(输出)操作也是十分常见的,java中的字节输出流都是从java.io.OutputStream继承过来。
由于OutputStream是一个抽象类,无法实例化对象,因此JDK也提供了针对该流的子类:
- FileOutputStream
- ByteOutputStream
- BufferedOutputStream
- ObjectOutputStream
- PrintStream
OutputStream类中常见的方法: - write(int b): 将一个字节通过输出流写出到目标输出源
- weite(byte b): 将一个字节数组通过输出流写出到目标输出源
- write(byte b, int offset, int len): 将一个数组的offset开始写出len个字节到目标输出源
2.3.1 FileOutputStream
FileOutputStream是一个针对字节输出流的实现流,主要用于对文件进行写入内容,内部的方法主要是针对父类的实现
- 常见构造器
FileOutputStream(String filePath): 基于一个标准文件的路径创建对其操作的输出流
FileOutputStream(String filePath,boolean append): 基于一个标准文件的路径创建对其操作的输出流,使用追加模式
FileOutputStream(File file): 基于一个标准文件的路径 创建对其操作的输出流
FileOutputStream(File file,boolean append): 基于一个标准文件的路径创建对其操作的输出流,使用追加模式 - 使用FileOutputStream进行文件写出操作
FileOutputStream fos = new FileOutputStream("d:/abv.txt",true);
/*fos.write(97);
fos.write('a');*/
String msg = "湖北大学";
byte[] bytes = msg.getBytes("gbk");
fos.write(bytes,0,4);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
读一个文件,若文件不存在,则会抛出FileNotFoundException
思考:向一个文件写入内容,文件可以不存在?
可以,若不存在则会创建一个文件
若文件不存在,则会帮我们创建
但是若文件的上一级目录不存在,则会抛出FileNotFoundException
FileOutputStream fos = new FileOutputStream("d:/abv.txt",true);
true:若文件里面有内容,再去写内容到这个文件中
默认的效果则是替换原来的内容,只会显示现在添加的内容
若想要保留原来的内容,追加新内容,则需要在FileOutputStream
构造方法中添加一个参数true,用来表示是追加的内容,而不是替换内容
2.4 文件拷贝(图片、视频、音频)
通过对于字节输入和输出流的使用,我们可以将这两个流结合起来实现对文件的拷贝操作:
try {
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("D:/HBuilder_Code/H5/day02_html/sp/九尾和他的小尾巴们-鱼大仙儿《我曾》(超清).mp4");
FileOutputStream fos = new FileOutputStream("d:/a/1.mp4");
byte[] b = new byte[1024];
int len = 0;
while ((len=fis.read(b))!= -1){
fos.write(b);
}
//刷新
fos.flush();
long end = System.currentTimeMillis();
System.out.println("所需时间"+ (end - start));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
3 字符流
3.1 字符概述
通常在文本文件中,文件内容的存在形式一般是以一个字符(一个汉字,一个英文字母、一个符号)的形式存在的,在GBK编码模式下通常一个字符=2个字节;字符流一般适应于对文本数据的处理,java中所有的字符流都是以Reader/Writer结尾,并且都是从这两个基本的抽象类中继承
- java.io.Reader:字符输入流
- java.io.Writer:字符输出流
3.2 字符输入流
字符输入流一般用于对文本数据进行读取操作,常见的字类:
- InputStreamReader:转换流,将字节流------>字符流
- FileReader:文件字符输入流,读文件中的内容(文本文件)
- BufferedReader:自带缓冲区的字符串输入流
- CharArrayReader:从字符数据中读取
3.2.1 FileReader
FileReader是一个以字符流的形式进行文件内容的读取操作的流,从InputStreamReader继承而来,内部没有多余的方法(与父类API一致)。
- 常用的构造方法
FileReader(File file): 根据提供的文件对象获取文件的字符输入流
FileReader(String filePath): 根据提供的文件路径获取文件字符串输入流
具体使用
File file = new File("d:/abc.txt");
try {
//创建字符输入流
FileReader fr = new FileReader(file);
//获取当前的编码方式
String encoding = fr.getEncoding();
System.out.println("encoding = " + encoding);
//创建char数组
char[] c = new char[100];
int len = 0;//数组中存储的有效字符个数
while ((len = fr.read(c))!=-1){
String s = new String(c,0,len);
System.out.println(s);
}
fr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
3.2.2 FileWriter
FileWriter是一个以字符流的形式进行文件内容写出的流,从OutputStreamWriter继承而来,内部没有多余的方法(与父类的API一致)。
- 常用的构造方法
FileWriter(File file): 根据提供的文件对象获取文件字符输出流
FileWriter(String filePath): 根据提供的文件路径获取文件字符输出流
FileWriter(File file, boolean append):根据提供的文件对象获取文件字符输出流,并且使用追加模式
FileWriter(String filePath, boolean append):根据提供的文件路径获取文件字符输出流,并使用追加模式
具体使用
try {
FileWriter fw = new FileWriter("d:/abc.txt",true);
fw.write("湖北大学");
//刷新
fw.flush();
//关闭
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
//用于保存字符串和单个字符写入的临时缓冲区
private char[] writeBuffer;
对于字符输出流,内部使用一个字符缓冲区(字符数组),在进行数据写出时,
通常是将需要写出的数据缓存存入到了字符数组中,然后在关闭流的时候(或者缓冲区存满时),
一次性将缓冲区中的数据写出到目标输出源中。
如果需要在流关闭之前(或者缓存区未满时),
强制的将字符串缓冲区的数据写出,可以手动调用flush()方法,强制输出。
4 处理流
对于流的处理类型(功能)来分为节点流和处理流:
- 节点流
也成为低级流,直接跟输入输出源 交互的流
如:FileInputStream、FileOutputStream、FileReader、FileWriter - 处理流
也称为高级流或包装流,即可以用于对其他节点流进行包装,以实现流的类型转换或效率的提升。
处理流主要由缓冲流和转换流组成
4.1 缓冲流
缓冲流是一种自带缓冲区的流,主要由四种构成:
BufferedInputStream:字节缓冲输入流
BufferedOutputStream:字节缓冲输出流
BufferedReader:字符缓冲输入流
BufferedWriter:字符缓冲输出流
try {
//字节输入流
FileInputStream fis = new FileInputStream("d:/abc.txt");
//创建字节缓冲输入流(把FileInputStream升级为BufferedInputStream)
BufferedInputStream bis = new BufferedInputStream(fis);
byte[] b = new byte[1024];
int len = 0;
while ((len = bis.read(b))!=-1){
System.out.println(new String(b,0,len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
BufferedReader br = new BufferedReader(new FileReader("d:/abd.txt"));
char[] b = new char[1024];
// int len = 0;
// while ((len = br.read(b))!=-1){
// System.out.println(new String(b,0,len));
// }
// readLine:读取一行的内容
String s = null;
while ((s = br.readLine()) != null){
System.out.println(s);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
缓冲流内容实现原理:
使用了一个默认大小为8KB的缓冲区,每次将缓冲区的空间存满之后,再将数据通过流对象完成读写操作
5 转换流
转换什么流 字节流和字符流
在一些需求中经常需要将一个字节流的数据转换为字符流,或又需要将一个字符流转换为字节流。
此时就需要通过转换流来完成此功能
一般转换流应用在:文件转码(文件的编码和解码)
从网络中读取的数据为字节流时,但是流中包含内容是字符时,可以使用转换流实现转换
若查看文件时,出现乱码的情况,则说明文件内容的编码方式和解码方式不一致
java.io主要有两个类完成转换的功能
InputStreamReader:用于将字节输入流转换为字符输入流(字节--->字符)
OutputStreamWriter:用于将字符输出流转换为字节输出流(字符--->字节)
解码:字节、字节数组------>字符数组、字符串 看不懂------>看得懂
编码:字符数组、字符串------>字节、字节数组 看得懂------>看不懂
字符集:
- ASCII:美国标准信息交换码,用一个字节的7位可以表示
- GB2312:中国的中文编码表,常用的汉字(不包括繁体字或复杂的字)
- GBK:中国的中文编码表升级
- ISO8859-1:拉丁码表。欧洲码表
- Unicode:国际标准码,融合了目前人类使用的所有字符,是一个字符集,不是一个编码方式
UTF-8:变长的编码方式,可用1-4字节来表示一个字符
UTF-16:…
UTF-32:…
public class InputStreamReaderDemo {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("d:/abc.txt");
//InputStreamReader(InputStream in)
//InputStreamReader(InputStream in,String charsetName)
/*
参数2:指定字符集,具体使用哪一个字符集,取决于文件保存时使用的字符集
若不指定字符集,默认使用平台的字符集
*/
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
char[] c = new char[10];
int len = 0;
while ((len = isr.read(c))!=-1){
String string = new String(c, 0, len);
System.out.println(string);
}
//关闭Io资源
isr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void test1() throws IOException {
//OutputStreamWriter(OutputStream out)
//OutputStreamWriter(OutputStream out,string charsetName)
FileOutputStream fos = new FileOutputStream("d:/abc.txt",true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
osw.write("加油!!!");
osw.flush();
osw.close();
}
}
6 打印流
6.1 标准的输入流、输出流
System.in:标准的输入流,默认从键盘输入
System.out:标准的输出流,默认从控制台输出
6.2
System类中的setln(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出流
练习:
从键盘录入输入字符串,要求将读取到的整行字符串转成大写输出,然后继续进行输入操作,直到输入exit是,退出程序。
方式一:使用Scanner类,调用next()返回一个字符串
方式二:使用System.in System.in返回的是InputStream流 ---> 转换流 ---> BufferedReader类中readLine()
public class OtherStreamDemo {
public static void main(String[] args) throws IOException {
//字节流
InputStream in = System.in;
//使用转换流:字节流转换为字符流
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
while (true){
System.out.println("请输入一个字符串:");
String data = br.readLine();
//equalsIgnoreCase:忽略大小写
if("exit".equalsIgnoreCase(data)){
break;
}
String s = data.toUpperCase();
System.out.println(s);
}
//关闭资源
br.close();
}
/*
练习:
scanner类: nextInt()\next()\nextDouble()
模拟scanner类完成类似的功能,输入什么类型
*/
使用setOut方法修改默认输出位置
@Test
public void test2() throws IOException {
FileOutputStream fos = new FileOutputStream("d:/abc.txt");
PrintStream ps = new PrintStream(fos,true);
if (ps != null){
System.setOut(ps);//把标准的输出流(默认输出在控制台)改为在文件去输出
}
//写内容
for (int i = 0; i < 255; i++) {
System.out.println((char)i);
}
}
}
7 数据流
7.1 DataOutputStream
DataOutputStream:数据专属流
这个流可以将数据连同数据的类型一并写入文件。
这个文件不是普通文本文档。(这个文件用记事本打不开)
/**
* DataOutputStream:数据专属流
* 这个流可以将数据连同数据的类型一并写入文件。
* 这个文件不是普通文本文档。(这个文件用记事本打不开)
*/
public class DataOutputStreamTest {
public static void main(String[] args) {
//创建数据专属的字节输出流
DataOutputStream dos = null;
try {
dos = new DataOutputStream(new FileOutputStream("E:\\IO测试\\data"));
//写数据
byte b = 100;
short s = 200;
int i = 300;
long l = 400L;
float f = 3.0F;
double d = 3.14;
boolean sex = false;
char c = 'a';
//写
dos.writeByte(b);//把数据以及数据的类型一并写入到文件当中。
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);
//刷新
dos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(dos!=null){
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
7.2 DataInputStream
DataInputStream:数据字节输入流
DataOutputStream写的文件,只能使用DataInputStream去读
并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致。才可以正常取出数据。
/**
* DataInputStream:数据字节输入流
* DataOutputStream写的文件,只能使用DataInputStream去读
* 并且读的时候你需要提前知道写入的顺序。
* 读的顺序需要和写的顺序一致。才可以正常取出数据。
*/
public class DataInputStreamTest {
public static void main(String[] args) {
DataInputStream dis = null;
try {
dis = new DataInputStream(new FileInputStream("E:\\IO测试\\data"));
//开始读
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean sex = dis.readBoolean();
char c = dis.readChar();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(sex);
System.out.println(c);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(dis!=null){
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
8 对象流
ObjectInputStream 和 ObjectOutputStream
作用:
用于存储和读取基本数据类型或对象的处理流,强大之处在于可以把Java中的对象写入到磁盘中,也能把对象读取出来
序列号过程:把Java中的对象写入到磁盘中
反序列化:从磁盘中读取对象到内存中
对象序列化的使用场景:
1、软件的配置信息
2、缓存实现
3、游戏存档
......
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException {
//创建对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/abc.txt"));
//调用方法
oos.writeObject(new String("hhhhh"));
oos.writeObject(new Person("文健溪",20,1));
oos.flush();
oos.close();
}
@Test
public void test1() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/abc.txt"));
Object object = ois.readObject();
System.out.println(object);
Object object1 = ois.readObject();
System.out.println(object1);
}
}
Person类需要满足如下的要求:才能进行序列化
1、需要实现接口:Serializable
2、当前类提供一个全局常量:SerialVersionUID
3、除了当前Person类需要实现Serializable接口之外,改必须保证其内部所有属性也能序列化(默认情况下,基本数据类型都是可以序列化的)