目录
2.7 字节缓冲流(BufferedInputStream/BufferedOutputStream)
1 IO流概述和分类
IO流就是用来处理设备间数据传输问题的
常见应用:文件复制,文件上传,文件下载
IO流分类
1)按照数据的流向
输入流:读数据
输出流:写数据
2)按照数据类型
字节流:字节输入流,字节输出流
字符流:字符输入流,字符输出流
一般来说,我们说IO流的分类是按照数据类型来分的
问题:这两种流在什么情况下使用呢?
如果数据可以通过windows自带的记事本软件打开且可以读懂里面的内容(没有乱码),就是用字符流,
否则使用字节流。如果不知道用那种流,就用字节流(万能流)
2 字节流
2.1 字节流写数据
字节流抽象基类
InputStream:表示字节输入流的所有类的超类
outputStream:表示字节输出流的所有类的超类
子类名特点:子类名都是以父类为为子类后缀
使用字节输出流写数据的步骤
1)创建字节输出流对象(调用系统功能创建文件,创建字节输出流对象,让字节输出流对象指向文件)
2)调用字节输出流对象的的写数据方法
3)释放资源(关闭此文件输出流并释放与此流相关的任何系统资源)
2.2 字节流写数据的3种方法
/**
* Created by caimh on 2019/9/11.
*/
public class FileOutPutStreamDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("cmh.txt");
//写数据
//方法1
//fos.write(97);
//fos.write(98);
//fos.write(99);
//fos.write(100);
//fos.write(101);
//fos.write(102);
//方法2
final byte[] bytes = {97, 98, 99, 100, 101, 102};
//fos.write(bytes);
//方法3
fos.write(bytes, 3, 2);
//释放资源
fos.close();
}
}
2.3 字节流写数据的两个小问题
1)字节流写数据如何实现换行
不同系统对换行识别符是不一样的。idea编辑器自带的记事本可以识别不同系统换行符
windows:\r\n
linux:\n
mac:\r
2)字节流写数据如何实现追加写入
/**
* Created by caimh on 2019/9/11.
*/
public class FileOutPutStreamDemo {
public static void main(String[] args) throws IOException {
//创建字节输出流
//FileOutputStream fos = new FileOutputStream("cmh.txt"); //非追加写入
FileOutputStream fos = new FileOutputStream("cmh.txt", true);//追加写入
//写数据(换行)
for (int i = 0; i < 5; i++) {
fos.write("hello world".getBytes());
fos.write("\r\n".getBytes());//windows系统换行符
}
//释放资源
fos.close();
}
}
2.4 字节流写数据加异常处理
finally:在异常处理时提供finally块来执行所有清除操作。比如IO流中的释放资源
特点:被finally控制的语句一定执行,除非JVM退出
try{
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}finally{
执行所有清除操作;
}
2.5 字节流读数据(一次读一个字节数据)
1)使用字节输入流对象
2)调用字节输入流对象的读数据方法
3)释放资源
/**
* Created by caimh on 2019/9/11.
*/
public class FileOutPutStreamDemo {
public static void main(String[] args) throws IOException {
//创建字节输入流
final FileInputStream fis = new FileInputStream("cmh.txt");
//读数据(如果到达文件的末尾,fis.read()=-1)
int b;
while ((b = fis.read()) != -1) {
System.out.println(b + "-" + (char) b);
}
//释放资源
fis.close();
}
}
案例:把“F:\devTools\IDEA\HDFSClientDemo\cmh.txt”复制到模块目录下的“F:\devTools\IDEA\HDFSClientDemo\cmh_cp.txt”
思路:
1)根据数据源创建字节输入流对象
2)更具目的地创建字节输出流对象
3)读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
4)释放资源
/**
* Created by caimh on 2019/9/11.
*/
public class FileOutPutStreamDemo {
public static void main(String[] args) throws IOException {
//根据数据源创建字节输入流
final FileInputStream fis = new FileInputStream("F:\\devTools\\IDEA\\HDFSClientDemo\\cmh.txt");
//根据目的地创建字节输出流
final FileOutputStream fos = new FileOutputStream("F:\\devTools\\IDEA\\HDFSClientDemo\\cmh_cp.txt");
//读写数据(如果到达文件的末尾,fis.read()=-1)
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
//释放资源
fis.close();
fos.close();
}
}
2.6 字节流读数据(一次读取一个字节数组数组)
/**
* Created by caimh on 2019/9/11.
*/
public class FileOutPutStreamDemo {
public static void main(String[] args) throws IOException {
//创建字节输入流
final FileInputStream fis = new FileInputStream("F:\\devTools\\IDEA\\HDFSClientDemo\\cmh.txt");
//读取数据
byte[] bytes = new byte[1024];//定义容器,默认数组大小1024
int len;//读取字节的长度,-1表示没有数据
while ((len= fis.read(bytes)) != -1) {
//String(byte bytes[], int offset, int length),把读取的字节数组转换为字符串
System.out.println(new String(bytes, 0, len));
}
//释放资源
fis.close();
}
}
案例:复制图片(把源“E:\tea.jpg”复制到目的地“E:\tea_cp.jpg”)
/**
* Created by caimh on 2019/9/11.
*/
public class FileOutPutStreamDemo {
public static void main(String[] args) throws IOException {
//根据数据源创建字节输入流
FileInputStream fis = new FileInputStream("E:\\tea.jpg");
//根据目的地创建字节输出流
FileOutputStream fos = new FileOutputStream("E:\\tea_cp.jpg");
//读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
byte[] bytes = new byte[1024];//定义容器,
int len;//读取字节的长度,-1表示没有数据
while ((len= fis.read(bytes)) != -1) {
fos.write(bytes,0,len);
}
//释放资源
fis.close();
fos.close();
}
}
2.7 字节缓冲流(BufferedInputStream/BufferedOutputStream)
BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组,当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节。
BufferedOutputStream:通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用。
构造方法:
字节缓冲输入流:BufferedInputStream(InputStream in)
字节缓冲输出流:BufferedOutputStream(OutputStream out)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢/
字节缓冲流仅仅提供缓冲区,而真正读写数据还得依靠基本的字节流对象进行操作。
字节缓冲流只是对InputStream/OutputStream的一次封装
案例:复制视频(比较四种方式性能)
把源“"E:\\movie.mp4"”复制到目的地“"E:\\movie_cp.mp4"”
/**
* Created by caimh on 2019/9/11.
*/
public class IOStreamDemo {
/**
* 复制视频,四种方式实现,比较性能
* 方式1:字节流一次读写一个字节(总共耗时:327147ms)
* 方式2:字节流一次读写一个字节数组(总共耗时:689ms)
* 方式3:缓冲字节流一次读写一个字节(总共耗时:2625ms)
* 方式4:缓冲字节流一次读写一个字节数组(总共耗时:159ms)
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//开始时间
final long start = System.currentTimeMillis();
//复制视频
method1();
// method2();
// method3();
// method4();
//结束时间
final long end = System.currentTimeMillis();
System.out.println("总共耗时:" + (end - start));
}
/**
* 基本字节流一次读写一个字节
* @throws IOException
*/
public static void method1() throws IOException {
//创建字节流对象
FileInputStream fis = new FileInputStream("E:\\movie.mp4");
FileOutputStream fos = new FileOutputStream("E:\\movie_cp.mp4");
//读写数据
int b;
while ((b=fis.read())!=-1)
{
fos.write(b);
}
//关闭资源
fis.close();
fos.close();
}
/**
* 基本字节流一次读写一个字节数组
* @throws IOException
*/
public static void method2() throws IOException {
//创建字节流对象
FileInputStream fis = new FileInputStream("E:\\movie.mp4");
FileOutputStream fos = new FileOutputStream("E:\\movie_cp.mp4");
//读写数据(字节数组)
byte[] bytes = new byte[1024];
int len;
while ((len=fis.read(bytes))!=-1)
{
fos.write(bytes,0,len);
}
//关闭资源
fis.close();
fos.close();
}
/**
* 缓冲字节流一次读写一个字节
* @throws IOException
*/
public static void method3() throws IOException {
//创建缓冲字节流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\movie.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\movie_cp.mp4"));
//读写数据
int b;
while ((b=bis.read())!=-1)
{
bos.write(b);
}
//关闭资源
bis.close();
bos.close();
}
/**
* 缓冲字节流一次读写一个字节数组
* @throws IOException
*/
public static void method4() throws IOException {
//创建缓冲字节流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\movie.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\movie_cp.mp4"));
//读写数据(字节数组)
byte[] bytes = new byte[1024];
int len;
while ((len=bis.read(bytes))!=-1)
{
bos.write(bytes,0,len);
}
//关闭资源
bis.close();
bos.close();
}
}
3 字符流
3.1 为什么会出现字符流
由于字节流操作中文不是特别方便,所以Java就提供字符流
字符流=字节流+编码表
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,
如何识别是中文呢?
汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
一个汉字存储:
如果是GBK编码,占用2个字节;
如果是UTF-8编码,占用3个字节
3.2 编码表
1)计算机中存储的信息都是以二进制数表示的
2)按照某种规则,将字符存储到计算机中,称为编码;反之,将存储在计算机中的二进制数按照某种规则解析出来,称为解码。注意:按照A编码存储,必须按照A编码解析,否则会导致乱码现象。
3)字符编码:就是一套自然语言的字符与二进制数之间的对应规则(如A,65)
4)字符集:是一个系统支持的所有字符的集合,包括各国家文字,标点符号,图形符号,数字等。计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集,GBXXX字符集(常用是GBK),Unicode字符集(UTF-8,UTF-16,UTF32,常用的是UTF-8)等。
UTF-8:是电子邮件,网页及其他存储或传送文字的应用中,优先采用的编码。
UTF-8编码规则:
128个US-ASCII字符,只需要一个字节编码
拉丁文等字符,需要2个字节编码
大部分常用字(含中文),使用3个字节编码
其他极少使用的Unicode辅助字符,使用4字节编码
3.3 字符串编码解码
编码:
byte[] getBytes():使用平台默认字符集将String编码为一系列字节
byte[] getBytes(String charsetName):使用指定的字符集将String编码为一系列字节
解码:
String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String
String(byte[] bytes,String charsetName):通过指定的字符集解码指定的字节数组来构造新的String
3.4 字符流中的编码解码
字符流基类:
Reader:字符输入流的抽象类
Writer:字符输出流的抽象类
字符流中和编码解码问题相关的两个类:
InputStreamReader:是从字节流到字符流的桥梁,它读取字节,并使用指定的charset将其解码为字符。
OutputStreamWriter:是字符流到字节流的桥梁,使用指定的charset将写入的字符编码为字节。
/**
* Created by caimh on 2019/9/11.
*/
public class IOStreamDemo {
/**
* 字符流操作
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//创建字符输出流对象
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("cmh.txt"));//IDEA平台默认UTF-8
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("cmh.txt"), "GBK");
//读写操作
osw.write("你好,中国!");
osw.close();
InputStreamReader isr = new InputStreamReader(new FileInputStream("cmh.txt"),"GBK");
//一次读取一个字符数据
int ch;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
//释放资源
isr.close();
}
}
3.5 字符流写数据的5种方式
/**
* 字符流写数据的5种方式:
*/
public class IOStreamDemo {
public static void main(String[] args) throws IOException {
//创建字符输出流对象(默认字符编码)
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("cmh.txt"));//IDEA平台默认UTF-8
//写数据
//method1(osw);
method2(osw);
method3(osw);
//释放资源
osw.close();//关闭流,先刷新流
}
/**
* 写一个字符
* void write(int c)
* @param osw
* @throws IOException
*/
public static void method1(OutputStreamWriter osw) throws IOException {
osw.write(97);//字符流写数据,并不能直接写到文件中,最终需要通过字节流去写数据,目前还在缓冲区。
osw.flush();//刷新流,将缓冲区数据写到文件中
osw.write(98);
osw.flush();
}
/**
* 写一个字符数组
* void write(char cbuf[])
* void write(char cbuf[], int off, int len)
* @param osw
* @throws IOException
*/
public static void method2(OutputStreamWriter osw) throws IOException {
char[] chars = {'a', 'b', 'c', 'd', 'e', 'f'};
//osw.write(chars);
osw.write(chars, 0, 3);//写一个字符数组的一部分
}
/**
* 写一个字符串
* void write(String str)
* void write(String str, int off, int len)
* @param osw
* @throws IOException
*/
public static void method3(OutputStreamWriter osw) throws IOException {
String str = "abcde";
//osw.write(str);
osw.write(str, 0, 3);//写一个字符串的一部分
}
}
3.6 字符流读字符的2种方式
/**
* 字符流读数据的2种方式:
*/
public class IOStreamDemo {
public static void main(String[] args) throws IOException {
//创建字符流对象(默认字符编码)
InputStreamReader isr = new InputStreamReader(new FileInputStream("cmh.txt"));
//读数据
//method1(isr);
method2(isr);
//释放资源
isr.close();
}
/**
* 一次读一个字符
* int read()
*
* @param isr
* @throws IOException
*/
public static void method1(InputStreamReader isr) throws IOException {
int ch;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
}
/**
* 一次读一个字符数组
* int read(char cbuf[])
*
* @param isr
* @throws IOException
*/
public static void method2(InputStreamReader isr) throws IOException {
char[] chs = new char[1024];
int len;
while ((len = isr.read(chs)) != -1) {
System.out.print(new String(chs, 0, len));
}
}
}
案例:复制文件(改进版)
把源文件“F:\devTools\IDEA\HDFSClientDemo\src\main\java\com\caimh\mr\WordCountDriver.java”复制到“F:\devTools\IDEA\HDFSClientDemo\WordCountDriver_cp.java”
分析:
1)转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化书写,转换流提供了对应的子类
2)FileWriter:用于读取字符文件的便捷类(extends OutputStreamWriter)。FileWriter(String fileName)
3)FileReader:用于读取字符文件的便捷类(extends InputStreamWriter)。FileReader(String fileName)
4)源文件(FileReader)--->目的文件(FileWriter)
注意:如果字符流中涉及到编码解码问题,还是需要OutputStreamWriter,InputStreamWriter操作。
/**
* 复制Java文件
*/
public class IOStreamDemo {
public static void main(String[] args) throws IOException {
//根据数据源创建字符输入流对象(默认字符编码)
FileReader fr = new FileReader("F:\\devTools\\IDEA\\HDFSClientDemo\\src\\main\\java\\com\\caimh\\mr\\WordCountDriver.java");
//根据目的地创建字符输出流对象
FileWriter fw = new FileWriter("F:\\devTools\\IDEA\\HDFSClientDemo\\WordCountDriver_cp.java");
//读写数据
char[] chs = new char[1024];
int len;
while ((len = fr.read(chs)) != -1) {
fw.write(chs, 0, len);
}
//释放资源
fr.close();
fw.close();
}
}
3.7 字符缓冲流
BufferedReader:从字符输入流读取文本,缓冲数据,以提供字符,数组和行的高效读取。可以指定缓冲区大小。
BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。可以指定缓冲区大小。
构造方法:
BufferedReader(Reader in) --参数可以是InputStreamReader,也可以是FileReader
BufferedWriter(Writer out) --参数可以是OutputStreamWriter,也可以是FileWriter
案例:复制文件(缓冲字符流改进版)
/**
* 复制Java文件(缓冲字符流改进版)
*/
public class IOStreamDemo {
public static void main(String[] args) throws IOException {
//根据数据源创建字符输入流对象(默认字符编码)
FileReader fr = new FileReader("F:\\devTools\\IDEA\\HDFSClientDemo\\src\\main\\java\\com\\caimh\\mr\\WordCountDriver.java");
//根据目的地创建字符输出流对象
FileWriter fw = new FileWriter("F:\\devTools\\IDEA\\HDFSClientDemo\\WordCountDriver_cp.java");
//缓冲字符输入流
BufferedReader br = new BufferedReader(fr);
//缓冲字符输出流
BufferedWriter bw = new BufferedWriter(fw);
//读写数据
char[] chs = new char[1024];
int len;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
}
//释放资源
br.close();
bw.close();
}
}
3.8 字符缓冲流的特有功能
BufferedWriter:
void newLine():写一行行分隔符,行分隔符字符串由系统属性定义,可以适应不同的系统。
BufferedReader:
public String readLine():读一行文字。结果包含行的内容的字符串,不包括任何终止符,如果流的结尾已经到达,则为null.
案例:复制文件(字符缓冲流特有功能改进版)-- 优先选择这种形式
/**
* 复制Java文件(缓冲字符流特有功能改进版)
*/
public class IOStreamDemo {
public static void main(String[] args) throws IOException {
//缓冲字符输入流(默认字符编码)
BufferedReader br = new BufferedReader(new FileReader("F:\\devTools\\IDEA\\HDFSClientDemo\\src\\main\\java\\com\\caimh\\mr\\WordCountDriver.java"));
//缓冲字符输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("F:\\devTools\\IDEA\\HDFSClientDemo\\WordCountDriver_cp.java"));
//读写数据(使用字符缓冲流特有功能)
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();//换行,否则,数据写到一行了
bw.flush();
}
//释放资源
br.close();
bw.close();
}
}
案例:点名器
需求:一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器。
案例:文件到集合
4 IO流小结