IO流(掌握)_字节流和字节缓冲区流
流的分类
a) 方向:输入流(读)、输出流(写)
b) 单位:字节流(操作二进制文件)、字符流(操作文本文件)
c) 功能:节点流,管道流(处理流)
一、 IO流用来处理设备之间的数据传输(上传文件和下载文件)。
Java对数据的操作的通过流的方式。
Java用于操作流的对象都在IO包中。
二、 分类:
一般我们在讨论IO流的时候,如果没有明确说明按照什么分,默认按照数据类型分。
A:数据流向(方向)
输入流 读取数据
输出流 写出数据
B:数据类型
字节流(操作二进制文件,用Windows记事本打开读不懂的文件,英文也可以用字节流)
字节输入流 InputStream
|-- FileInputStream
|-- BufferedInputStream
字节输出流 OutputStream
|-- FileOutputStream
|-- BufferedOutputStream
字符流(操作文本文件,用Windows记事本打开能读懂的文件,包括中文、英文等各国语言)
字符输入流 Reader
|-- InputStreamReader
|-- FileReader
|-- BufferedReader
字符输出流 Writer
|-- OutputStreamWriter
|-- FileWriter
|-- BufferedWriter
C:输入流与输出流的区别
1).无论文件是否存在,输出流会自动创建文件。而输入流不会自动创建文件。
2).输出流有flush()方法,输入流没有此方法。
三、 FileOutputStream写入数据
构造方法:(没有无参构造,必须要知道往哪里写)
public FileOutputStream(File file) :创建一个向指定 File 对象表示的文件中写入数据的文件输出流。参数:file - 为了进行写入而打开的文件。
public FileOutputStream(String name) :创建一个向具有指定名称的文件中写入数据的输出文件流。参数:name - 与系统有关的文件名 。
public FileOutputStream(File file, boolean append): 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
public FileOutputStream(String name, boolean append):创建一个向具有指定 name 的文件中写入数据的输出文件流。
如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
成员方法:
public void write(int b): 将指定字节写入此文件输出流。参数:b - 要写入的字节。
public void write(byte[] b): 将 b.length 个字节从指定 byte 数组写入此文件输出流中。
public void write(byte[] b,int off,int len): 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
public void close():关闭此文件输出流并释放与此流有关的所有系统资源。此文件输出流不能再用于写入字节。如果此流有一个与之关联的通道,则关闭该通道。
操作步骤
1:创建字节输出流对象
2:写数据,调用write()方法
3:释放资源
/*****************************************************************************************************************************************/
/*
* 查看FileOutputStream的构造方法:
* FileOutputStream(File file)
* FileOutputStream(String name)// 看源码,其实本质是一样的。
*/
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
// FileOutputStream(File file)
// File file = new File("fos.txt");
// FileOutputStream fos = new FileOutputStream(file);
// FileOutputStream(String name)
FileOutputStream fos = new FileOutputStream("fos.txt");
// 实现数据的追加写入,用构造方法带第二个参数是true即可。
FileOutputStream fos2 = new FileOutputStream("fos.txt", true);
/*
创建字节输出流对象了做了几件事情:
A:调用系统功能去创建文件
B:创建fos对象
C:把fos对象指向这个文件
*/
//写数据
fos.write("hello,IO".getBytes());
fos.write(97); // 97 -- 底层二进制数据 -- 通过记事本打开 -- 找97对应的字符值 -- a
fos.write(57); // 97 -- 底层二进制数据 -- 通过记事本打开 -- 找57对应的字符值 -- 9
fos.write(55); // 97 -- 底层二进制数据 -- 通过记事本打开 -- 找55对应的字符值 -- 7
byte[] bys={97,98,99,100,101};
fos.write(bys);
fos.write(bys,1,3);
/*
实现数据的换行,不同的系统针对不同的换行符号识别是不一样的
windows:\r\n
linux:\n
Mac:\r
一些常见的高级记事本,是可以识别任意换行符号的。
*/
for (int x = 0; x < 10; x++) {
fos.write(("hello" + x).getBytes());
fos.write("\r\n".getBytes());
}
//释放资源
//关闭此文件输出流并释放与此流有关的所有系统资源。
fos.close();
/*
为什么一定要close()呢?
A:让流对象变成垃圾(fos),这样就可以被垃圾回收器回收了
B:通知系统去释放跟该文件相关的资源
*/
//java.io.IOException: Stream Closed
fos.write("java".getBytes());//因为流已关闭,所以写不进去。
}
}
/*****************************************************************************************************************************************/
/*
* 加入异常处理的字节输出流操作
*/
public class FileOutputStreamDemo4 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("z:\\fos.txt");// 报两个异常
// fos = new FileOutputStream("fos.txt");
fos.write("java".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 如果fos不是null,才需要close()
// if (fos != null) {
try {
fos.close();// 为了保证close()一定会执行,就放到fianlly里。
} catch (IOException e) {
e.printStackTrace();
}
// }
}
}
}
/*****************************************************************************************************************************************/
四、 FileInputStream读取数据
构造方法:(没有无参构造,必须要知道从哪里写)
public FileInputStream(File file):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
参数: file - 为了进行读取而打开的文件。
public FileInputStream(String name):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
参数: name - 与系统有关的文件名。
成员方法:
public int read():一次读一个字节,换行符号也能读到。指针指向下一个字节,有点类似迭代器的next()方法。
从此输入流中读取一个数据字节。如果没有输入可用,则此方法将阻塞。返回:下一个数据字节;如果已到达文件末尾,则返回 -1。
public int read(byte[] b):一次读一个字节数组。
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。
返回:读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1。
public int read(byte[] b, int off, int len):从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
如果 len 不为 0,则在输入可用之前,该方法将阻塞;否则,不读取任何字节并返回 0。
返回: 读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1。
操作步骤
1:创建字节输入流对象
2:读数据,调用read()方法,并把数据显示到控制台
3:释放资源
/*****************************************************************************************************************************************/
public class FileInputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建字节输入流对象
FileInputStream fis = new FileInputStream("fis.txt");
// 方式一:一次读一个字节
int by = 0;
// 读取,赋值,判断
while ((by = fis.read()) != -1) {
System.out.print((char) by); // 输出到控制台,中文会乱码。但是,和输出流一起使用,复制中文文件是没问题的。
}
// 方式二:一次读一个字节数组(每次可以读取多个数据,提高了操作效率)
// 数组的长度一般是1024或者1024的整数倍
byte[] bys = new byte[1024]; // 1K 理论上来说,比方式一的效率高1024倍。
int len = 0;
while ((len = fis.read(bys)) != -1) {
// System.out.print(len);
// System.out.print(new String(bys));
System.out.print(new String(bys, 0, len)); // 输出到控制台,中文有可能乱码(当第1024个字节刚好是一个中文的时候)。和输出流一起使用,复制中文文件是没问题的。
}
// 释放资源
fis.close();
}
}
/*****************************************************************************************************************************************/
五、 案例:2种实现
A:复制文本文件
B:复制图片
C:复制视频
/*****************************************************************************************************************************************/
案例A:复制文本文件
// 一次读一个字节,太慢了
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.txt");// java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.txt");// 输出流会自动创建文件
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
// 释放资源(先关谁都行)
fos.close();
fis.close();
}
}
这一次复制中文确没有乱码,为什么?
上一次我们出现问题的原因在于我们每次获取到一个字节数据,就把该字节数据转换为了字符数据,然后输出到控制台。而现在,通过IO流读取数据,写到文本文件,你读取一个字节,我就写入一个字节,你没有做任何的转换。它会自己做转换,两个字节拼接成一个字符。看下面代码。
/*
* 计算机是如何识别什么时候该把两个字节转换为一个中文呢?
* 在计算机中中文的存储分两个字节:
* 第一个字节肯定是负数。
* 第二个字节常见的是负数,可能有正数。但是没影响。
* 计算机遇到负数就会和后面的数(无论正负)拼接成一个字符
*/
public class StringDemo {
public static void main(String[] args) {
String s1 = "abcde";
byte[] by1 = s1.getBytes();
System.out.println(Arrays.toString(by1));// [97, 98, 99, 100, 101]
String s2 = "我爱你中国";
byte[] by2 = s2.getBytes();
System.out.println(Arrays.toString(by2));// [-50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
char[] ch = s2.toCharArray();
System.out.println(ch);// 我爱你中国
}
}
-------------------------------------------------------------------
// 一次读一个字节数组,效率高
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.txt");
FileOutputStream fos = new FileOutputStream("b.txt");
// 复制数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
// 释放资源
fos.close();
fis.close();
}
}
/*****************************************************************************************************************************************/
案例B:复制图片,只能用字节流
// 一次读一个字节,太慢了
public class CopyImageDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.jpg");
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.jpg");
// 复制数据
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
// 释放资源
fos.close();
fis.close();
}
}
-------------------------------------------------------------------
// 一次读一个字节数组,效率高
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.jpg");
FileOutputStream fos = new FileOutputStream("b.jpg");
// 复制数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
// 释放资源
fos.close();
fis.close();
}
}
/*****************************************************************************************************************************************/
案例C:复制视频
// 一次读一个字节,太慢了
public class CopyMVDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.avi");
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.avi");
// 复制数据
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
// 释放资源
fos.close();
fis.close();
}
}
-------------------------------------------------------------------
// 一次读一个字节数组,效率高
public class CopyMp4Demo {
public static void main(String[] args) throws IOException {
// 封装数据源
FileInputStream fis = new FileInputStream("a.avi");
// 封装目的地
FileOutputStream fos = new FileOutputStream("b.avi");
// 复制数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
// 释放资源
fos.close();
fis.close();
}
}
/*****************************************************************************************************************************************/
六、 字节缓冲流
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流(带缓冲区的字节类)这种类被称为:缓冲区类(高效类)
构造方法可以指定缓冲区的大小,但是我们一般用不上,因为默认缓冲区大小就足够了。
为什么不传递一个具体的文件或者文件路径,而是传递一个OutputStream对象呢?
原因很简单,字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是呢,真正的读写操作还得靠基本的流对象实现。
A:BufferedOutputStream字节缓冲输出流,写入数据。该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
构造方法:
public BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
public BufferedOutputStream(OutputStream out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
B:BufferedInputStream字节缓冲输入流,读取数据。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。(看API)
构造方法:
public BufferedInputStream(InputStream in):创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。创建一个内部缓冲区数组并将其存储在 buf 中。
public BufferedInputStream(InputStream in, int size):创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
创建一个长度为 size 的内部缓冲区数组并将其存储在 buf 中。
/*********************************************************************************************************************************************/
public class BufferedOutputStreamDemo {
public static void main(String[] args) throws IOException {
// FileOutputStream fos = new FileOutputStream("bos.txt");
// BufferedOutputStream bos = new BufferedOutputStream(fos);
// 简单写法
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
// 写数据
bos.write("hello".getBytes());
// 释放资源
bos.close();
}
}
/*********************************************************************************************************************************************/
/*
* 注意:虽然我们有两种方式可以读取,但是,请注意,这两种方式针对同一个对象在一个代码中只能使用一个。因为read()方法类似迭代器的next()方法。读到末尾无法再读。
*/
public class BufferedInputStreamDemo {
public static void main(String[] args) throws IOException {
// BufferedInputStream(InputStream in)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bos.txt"));
// 读取数据
// int by = 0;
// while ((by = bis.read()) != -1) {
// System.out.print((char) by);
// }
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
// 释放资源
bis.close();
}
}
/*********************************************************************************************************************************************/
七、 案例:4种实现
A:复制文本文件
B:复制图片
C:复制视频
/*********************************************************************************************************************************************/
案例C:
/*
* 需求:把G:\\a.avi复制到G:\\b.avi中
*
* 字节流四种方式复制文件:
* 基本字节流一次读写一个字节: 共耗时24199毫秒
* 基本字节流一次读写一个字节数组: 共耗时40毫秒
* 高效字节流一次读写一个字节: 共耗时200毫秒
* 高效字节流一次读写一个字节数组: 共耗时17毫秒
*/
public class CopyAVIDemo {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
method4("G:\\a.avi", "G:\\b.avi");
long end = System.currentTimeMillis();
System.out.println("共耗时" + (end - start) + "毫秒");
}
// 高效字节流一次读写一个字节数组,共耗时17毫秒
public static void method4(String string, String string2) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(string));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(string2,true));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
// 高效字节流一次读写一个字节,共耗时200毫秒
public static void method3(String string, String string2) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(string));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(string2));
int by = 0;
while ((by = bis.read()) != -1) {
bos.write(by);
}
bos.close();
bis.close();
}
// 基本字节流一次读写一个字节数组,共耗时40毫秒
public static void method2(String string, String string2) throws IOException {
FileInputStream fis = new FileInputStream(string);
FileOutputStream fos = new FileOutputStream(string2);
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
// 基本字节流一次读写一个字节,共耗时24199毫秒
public static void method1(String string, String string2) throws IOException {
FileInputStream fis = new FileInputStream(string);
FileOutputStream fos = new FileOutputStream(string2);
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
fos.close();
fis.close();
}
}