文章目录
参考资料:《Java编程思想(第4版)》、《Java核心技术 卷Ⅱ 高级特性(原书第9版)》
学习视频:b站《尚硅谷Java入门视频教程》(主讲:宋红康老师)
一、流
1、流的概述
✒️编程语言的I/O类库中常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。"流"屏蔽了实际的I/O设备中处理数据的细节。Java类库中的I/O类分成了输入和输出两部分。
—— 《Java编程思想(第4版)》
✒️输出流:在Java API中,可以从其中读入一个字节序列的对象称作输入流。
✒️输入流:在Java API中,可以向其中写入一个字节序列的对象称作输出流。
✒️这些字节序列的来源地和目的地通常是文件,也可以是网络连接和内存块(到这里所说的我想应该是字节流);由于面向字节的流不便于处理以Unicode形式存储的信息(Unicoide中每个字符都使用了多个字节表示),所以Java抽象类Reader和Writer中继承出用于处理Unicode字符的单独类层次结果,这些类读入和写出操作的都是基于两字节的Unicode码元,而不是基于单字节。(这里应该说的是字符流)
—— 《Java核心技术 卷Ⅱ 高级特性(原书第9版)》
✒️输入input和输出output是相对的概念,我们作为开发者,所站的角度应该是程序(或者说是内存)。也就是,将程序(内存)的数据读出写入到其他存储设备,称作输出;将其他存储设备的数据读出写入内存,称作输入。
2、流的体系和分类
✒️按操作数据单位不同分:字节流(8 bit)、字符流(16 bit)
✒️按数据流的流向不同分:输入流、输出流
✒️按流的角色不同分:节点流、处理流
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符数输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputSteam | Reader | Writer |
访问文件 | FileInputStream | FileInputStream | FileReader | FIleWriter(节点流) |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
字符串 | —— | —— | StringReader | StringWriter |
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter(处理流) |
转换流 | —— | —— | InputStreamReader | OutputStreamWrite(处理流) |
标准IO | System.in | System.out | —— | —— |
…… | …… | …… | …… | …… |
3、读取文件内容输出到控制台(读操作)
✒️int read( )
✒️int read(char [ ] cbuf)
✒️int read(char [ ] cbuf,int off, int len)(不常用)
package com.ioStream.java;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class FileReadTest {
public static void main(String[] args) {
FileReader fr = null;
try {
/* Step1:实例化File类对象,指明文件路径 */
File file = new File("Test.txt");
/* Step2:实例化具体的流 */
fr = new FileReader(file);
/* Step3:读文件,read方法,-1文件末尾 */
/* 第一种写法:int read() */
// int datas = fr.read();
// while(datas != -1){
// System.out.print((char)datas);
// datas = fr.read();
// }
/* 第二种写法 int read(char[] cbuf) */
char[] buffer = new char[5];
int readLenth;
boolean flag = true;
while(flag){
readLenth = fr.read(buffer);
if(readLenth == -1)flag = false;
for(int i=0;i<readLenth;i++) //遍历每次读的buffer,读多少取多少
System.out.print(buffer[i]);//和C的read()类似
}
}catch(IOException e) {
e.printStackTrace();
}finally{
try {
/* Step4:资源关闭 */
if(fr != null)
fr.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
4、从控制台写数据到文件(写操作)
✒️写操作:对应的文件可以不存在,不会报异常;文件如果不存在,会自动创建该文件。
✒️流的构造器FileWriter(File file, boolean append); append不传默认为false,也就是默认覆盖写;true是追加写
package com.ioStream.java;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
public class FileWriteTest {
public static void main(String[] args) {
FileWriter fw = null;
try{
File file = new File("Test.txt");
fw = new FileWriter(file,true);
Scanner scan = new Scanner(System.in);
fw.write(new String(scan.nextLine()));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
二、字符流和字节流的使用
✒️对于文本文件(txt、java、cpp文件),使用字符流处理
✒️非文本文件(docx、jpg、mp3、wmv等文件),使用字节流处理
1、文本文件复制操作(字符流)
package com.ioStream.java;
import java.io.*;
public class FileCopy1 {
public static void main(String[] args){
FileReader fr = null;
FileWriter fw = null;
try{
/* 实例化File类对象,创建源文件和目标文件 */
File srcFile = new File("Test.txt");
File desFile = new File("TestCopy.txt");
/* 输出(write)、输入(read)流 */
fr = new FileReader(srcFile);
fw = new FileWriter(desFile);
/* 读写操作 */
char[] buff = new char[36];
int lenth;
while((lenth = fr.read(buff)) != -1){
//for(int i=0;i<lenth;i++) fw.write(buff[i]);
fw.write(buff,0,lenth); //两种写法
}
}catch(IOException e){
e.printStackTrace();
}finally {
/* 关闭资源 */
try{
if(fw != null) fw.close();
if(fr != null) fr.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
2、视频文件复制操作(字节流)
package com.ioStream.java;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy2 {
public static void main(String[] args) {
long start = System.currentTimeMillis();
fileCopy("2分半的测试视频.wmv","2分半的测试视频2.wmv");
long stop = System.currentTimeMillis();
System.out.println(stop - start+"ms");
}
public static void fileCopy(String srcPath,String desPath){
FileInputStream fis = null; //输出流
FileOutputStream fos = null;//输入流
try{
File srcFile = new File(srcPath);
File desFile = new File(desPath);
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(desFile);
byte[] buff = new byte[1024];
int lenth;
while((lenth = fis.read(buff)) != -1){
fos.write(buff,0,lenth);
}
System.out.println("Copy succeed");
}catch (IOException e) {
e.printStackTrace();
} finally{
try{
if(fis != null)fis.close();
if(fos != null)fos.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
- 复制2分半时长的视频约10MB,使用字节流时间为197ms左右。下面使用缓冲流处理视频的复制操作,看看复制同一个视频花费的时间为多少,比较各自效率。
三、缓冲流的使用
1、视频文件复制操作(缓冲流+字节流)
✒️缓冲流作用:提高流的读取、写入的速度(内部提供了一个缓冲区,缓冲区一次性写满后再写入目标文件)
package com.ioStream.java;
import java.io.*;
public class BufferedTest {
public static void main(String[] args){
long start = System.currentTimeMillis();
String srcFile = "2分半的测试视频.wmv";
String desFile = "2分半的测试视频2.wmv";
FileCopy(srcFile,desFile);
long stop = System.currentTimeMillis();
System.out.println(stop-start+" ms");
}
public static void FileCopy(String srcPath,String desPath){
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try{
/* 实例化File类 */
File srcFile = new File(srcPath);
File desFile = new File(desPath);
/* 输入、输出字节流 */
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(desFile);
/* 缓冲流 */
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
/* 使用缓冲流读写操作文件 */
byte[] buffer = new byte[1024];
int lenth;
while((lenth = bis.read(buffer)) != -1){
bos.write(buffer,0,lenth);
}
System.out.println("Copy succeed");
}catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(bos != null)bos.close();
if(bis != null)bis.close();
/* 关闭外层流时候,内层流会自动地关闭,这里可以省略 */
// if(fos != null)fos.close();
// if(fis != null)fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 使用缓冲流处理视频的复制操作,明显效率比上面单独的字节流高很多,同一个视频大约花费45ms。效率提升3倍多。
- 使用过程中,可以看出处理流就是"套接"在已有的流的基础上
2、文本文件复制操作(缓冲流+字符流)
package com.ioStream.java;
import java.io.*;
public class BufferedTest2 {
public static void main(String[] args) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(new File("Test.txt")));
bw = new BufferedWriter(new FileWriter(new File("Test2.txt")));
char[] buffer = new char[1024];
int lenth;
while ((lenth = br.read(buffer)) != -1){
bw.write(buffer,0,lenth);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(bw != null)bw.close();
if(br != null)br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四、其他处理流的使用
1、文本文件的字符编码转化(转换流)
✒️有时我们必须把来自于"字节"层次结构中的类和"字符"层次结构中的类结合起来使用。为了实现这个目的,要用到"适配器"(adapter)类:InputStreamReader可以把InputStream转化为Reader,而OutputStreamWriter可以把OutputStream转化为Writer。设计Reader和Writer继承层次结构主要是为了国际化,使其在所有I/O操作中都支持16位的Unicode字符。 —— 《Java编程思想(第4版)》18.4节
✒️转换流作用:提供字节流与字符流之间的转换
✒️InputStreamReader:将一个字节的输入流转换为字符的输入流
OutputStreamWriter:将一个字符的输出流转换为字节的输出流(均属于处理流,属于字符流)
✒️解码:字节、字节数组 ——> 字符串、字符数组(InputStreamReader)
✒️编码:字符串、字符数组 ——> 字节、字节数组(OutputStreamWriter)
package com.ioStream.java;
import java.io.*;
public class InputStreamReaderTest {
public static void main(String[] args) throws IOException {
/* 字节流 */
/* 说明:这里的文件Test.txt是以UTF-8的编码保存的 */
File scrFile = new File("Test.txt");
File desFile = new File("Test_GBK.txt");
FileInputStream fis = new FileInputStream(scrFile);
FileOutputStream fos = new FileOutputStream(desFile);
/* 转换流 */
/* 以UTF-8读出,以GBK写入 */
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
char[] buffer = new char[20];
int lenth;
while((lenth = isr.read(buffer)) != -1){
osw.write(buffer,0,lenth);
}
/* 这里异常应该try-catch处理,为了方便throws掉了 */
isr.close();
osw.close();
/* 流要关闭,否则读写不正常 */
}
}
2、标准输入、输出流
✒️System.in 标准的输入流,默认从键盘输入,可以通过System类的setIn( )更改
✒️System.out 标准的输出流,默认输出到控制台,可以通过System类的setOut( )更改
package com.ioStream.java;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/* 从键盘输入字符串,要求将读取到的整行字符串转换成大写输出到控制台,
直到输入"e"或者"exit"程序结束(注:不用Scanner类) */
/* System.in ---> 转换流 ---> BufferedReader.readLine() */
/* 字节流 -------> 转换流 ----> 字符流 */
public class stdioTest {
public static void main(String[] args){
BufferedReader br = null;
try {
/* 输入字节流转换为输入的字符流 */
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);//缓冲流
while(true) {
System.out.println("输入字符串:");
String data = br.readLine(); //调缓冲流的readLine方法
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
System.out.println("程序退出");
break;
}else{
System.out.println(data.toUpperCase());
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(br != null)br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3、打印流PrintStream、PrintWriter
✒️提供了一系列重载的print()和println()方法,用于多种数据类型的输出
✒️PrintStream和PrintWriter的输出不会抛出IOException异常
✒️PrintStream和PrintWriter有自动flush功能
✒️PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。
✒️在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
package com.ioStream.java;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class PrintStreamTest {
public static void main(String[] args) {
PrintStream ps = null;
try{
FileOutputStream fos = new FileOutputStream(new File("test.txt"));
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
ps = new PrintStream(fos, true);
if (ps != null) {// 把标准输出流(控制台输出)改成文件
System.setOut(ps);
}
for (int i = 0; i <= 255; i++) { // 输出ASCII字符
System.out.print((char) i);
if (i % 50 == 0) { // 每50个数据一行
System.out.println(); // 换行
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
}