[Java I/O 流] 带你走进输入流与输出流
写在前面
- 阅读本篇前请先阅读<[Java I/O 流] 带你一起玩转输入/输出流(上)>https://blog.youkuaiyun.com/SolarL/article/details/88976894
- IO流是用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,流按流向分为两种:输入流,输出流。
- 流按操作类型分为两种:
- 字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的;
- 字符流 : 字符流只能操作纯字符数据,比较方便。
1. I/O 流体系中常用类
<1> 字节流
- 字节流的抽象父类为: InputStream、OutputStream;
- 若要向对文件进行操作,显然抽象类是无法实现的,只能使用其子类 FileInputStream 和 FileOutPutStream来实例化对象。
<2> 字符流
- 字符流的抽象父类为:Reader、Writer
- 同字节流,若要向对文件进行操作,显然抽象类是无法实现的,只能使用其子类 FileReader 和 FileWriter 来实例化对象。
<3> IO 流书写规范要求
这里有必要敲黑板强调: 所有的流操作,使用前,需要导入IO 包中的类,使用时,需进行IO 异常处理,使用后,需显式地关闭流来释放资源。
JDK 7 以后,只要实现AutoCloseable接口的类,都可以通过自动关闭资源的try 语句来关闭这些流。
<4> 输入输出流体系中常用流的分类
注:上表内容参考自《疯狂Java 讲义》,表中红色粗体标出的类代表节点流,必须直接与指定的物理节点关联;蓝色斜体标出的类代表抽象基类,无法直接创建实例。
2. InputStream 类和 Reader 类
- InputStream 类和 Reader 类都是抽象类,本身无法创建对象,可以通过它们用于读取文件的输入流FileInputStream 和 FileReader 来实现,但它们都是节点流,需要需要与指定的文件进行关联,这就我们之前所说的File 类密不可分。
- 通过API 对比两个类发现,这两个类的功能基本是一样的,都通过 read() 方法进行输入和读操作,直到返回-1,停止操作。
//创建一个文件输入流对象,并关联aaa.txt
FileInputStream fis = new FileInputStream( "aaa.txt" );
//定义变量,记录每次读到的字节
int b;
while((b=fis.read()) != -1){
//打印每一个字节
System.out.println(b);
}
3. OutputStream 类和 Writer 类
- OutputStream 类和 Writer 类都是抽象类,同样无法创建对象,可以通过它们用于读取文件的输入流FileOutputStream 和 FileWriter 来实现,但它们都是节点流,需要需要与指定的文件进行关联,这就我们之前所说的File 类密不可分。
- OutputStream 与Writer 也非常相似,都是通过 write() 方法进行输出和读操作。
- OutputStream类的write()方法
- Writer类的write()方法
FileWriter fr = new FileWriter( "bbb.txt" );
fr.write( 1 );
fr.write( "string" );
fr.write( new char[]{'a','b','c'} );
4. 字节流的多种拷贝方式及异常处理
<1> FileInputStream读取,FileOutputStream写出,字节流一次读写一个字节复制音频(效率太低)
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream( "XXX.mp3" ); //创建输入流对象,关联XXX.mp3
FileOutputStream fos = new FileOutputStream( "copy.mp3" );//创建输出流对象,关联copy.mp3
int b;
while ((b = fis.read()) != -1) {
fos.write( b );
}
fis.close(); //关闭流
fos.close(); //关闭流
}
}
**<2> 小数组读取(推荐)
- write(byte[] b)
- write(byte[] b, int off, int len)写出有效的字节个数
- 此处流的处理为 JDK 1.6 及之前的处理方法,try finally嵌套的目的是能关一个尽量关一个。也可以同第三种一样采用try 语句实现自动关闭流。
import java.io.*;
import java.nio.file.Paths;
/**
* @Auther: SolarL
* @Date: 2019/4/3
* @Description: com.sunlong.file
* @version: 1.0
*/
public class Main {
public static void main(String[] args) throws IOException {
//异常处理(小数组拷贝)
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = Paths.get( "E:", "XXX.mp3" ).toFile();
File descFile = new File( "E:" + File.separator + "copy.mp3" );
fis = new FileInputStream( srcFile );
fos = new FileOutputStream( descFile );
byte[] arr = new byte[1024 * 8];
int len;
while ((len = fis.read( arr )) != -1) {
fos.write( arr, 0, len );
}
} finally {
try {
if (fis != null)
fis.close();
} finally { //try finally嵌套的目的是能关一个尽量关一个
if (fos != null)
fos.close();
}
}
}
}
<3>采用带缓冲的字节流读取(BufferedInputStream 和 BufferedOutputStream)
- BufferedInputStream
- BufferedInputStream内置了一个缓冲区(数组),从BufferedInputStream中读取一个字节时, BufferedInputStream会一次性从文件中读取8192个, 存在缓冲区中, 返回给程序一个,程序再次读取时, 就不用找文件了, 直接从缓冲区中获取,直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个。
- C.BufferedOutputStream
- BufferedOutputStream也内置了一个缓冲区(数组),程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中,直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。
- 此处异常处理采用 JDK 7 提供的方法,在try()中创建的流对象必须实现了AutoCloseable这个接口,如果实现了,在try后面的{}(读写代码)执行后就会自动调用,流对象的close方法将流关掉 。
public class Main {
public static void main(String[] args) {
//实现AutoCloseable接口的类,都可以通过自动关闭资源的try语句来关闭这些流
try (
FileInputStream fis = new FileInputStream(
new File( "D:" + File.separator + "XXX.mp3" ) ); //创建文件输入流对象,关联XXX.mp3
BufferedInputStream bis = new BufferedInputStream( fis ); //创建缓冲区对fis装饰
FileOutputStream fos = new FileOutputStream( "D:" + File.separator +"copy.mp3" ); //创建输出流对象,关联copy.mp3
BufferedOutputStream bos = new BufferedOutputStream( fos ); //创建缓冲区对fos装饰 )
) {
int b;
while ((b = bis.read()) != -1) {
bos.write( b );
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 小数组的读写和带Buffered的读取哪个更快?
答:定义小数组如果是8192个字节大小和Buffered比较的话,定义小数组会略胜一筹,因为读和写操作的是同一个数组,而Buffered操作的是两个数组。
<4>案例:文件拷贝
从键盘接收两个文件夹路径,把其中一个文件夹包含内容拷贝到另一个文件夹中
package com.sunlong.java;
import java.io.*;
public class FileText3 {
//从键盘接收两个文件夹路径,把其中一个文件夹包含内容拷贝到另一个文件夹中
public static void main(String[] args) throws IOException {
//从键盘接收两个文件夹路径
File src = FileTest.getDir();
File dest = FileTest.getDir();
if (src.equals( dest )){
System.out.println("目标文件夹是原文件夹的子文件");
}else {
copyFileDir( src, dest );
}
}
//把其中一个文件夹包含内容拷贝到另一个文件夹中
/*
* 1.在目标文件夹中创建原文件夹
* 2.获取原文件夹中所有文件及文件夹,存储到File数组中
* 3.遍历数组
* 4.如果是文件就用IO流读写
* 5.如果是文件夹就递归调用
* */
public static void copyFileDir(File src, File dest) throws IOException {
// 1.在目标文件夹中创建原文件夹
File newFile = new File( dest, src.getName() );
newFile.mkdir();
File [] subFiles = src.listFiles();
for (File subFile : subFiles) {
if (subFile.isFile()){
BufferedInputStream bis = new BufferedInputStream( new FileInputStream( subFile ) );
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream( new File( newFile, subFile.getName() ) ) );
int b;
while ((b=bis.read()) != -1){
bos.write( b );
}
bis.close();
bos.close();
}else{
copyFileDir( subFile, newFile );
}
}
}
}
5. 带缓冲的字符流(BufferedReader 和 BufferedWriter)
<1> BufferedReader 和 BufferedWriter
- BufferedReader 解决的是 InputStream 类的缺陷,BufferedReader 的 readLine() 方法可以读取一行字符(不包含换行符号)
- BufferedWriter 的 newLine() 可以输出一个跨平台的换行符号 “\r\n”
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader( new FileReader( "aaa.txt" ) );
BufferedWriter bw = new BufferedWriter( new FileWriter( "ddd.txt" ) );
String line;
while((line = br.readLine()) != null){
bw.write( line );
//bw.write("\r\n"); //只支持windows系统
bw.newLine(); //写出回车换行符,跨平台支持
}
br.close();
bw.close();
}
- 案例:获取一个文本上每个字符出现的次数,将结果写在times.txt上(IO 与集合)
public static void demo04() throws IOException {
//1.创建带缓冲的输入流对象
BufferedReader br = new BufferedReader( new FileReader( "aaa.txt" ) );
//2.创建双列集合对象TreeMap
TreeMap<Character, Integer> tm = new TreeMap<>( );
//3.将读到的字符存储到双列集合,存储时要作判断,如果不包含这个键,将键和值1存储,包含则值加一
int ch;
while((ch= br.read()) != -1){
char c = (char) ch;
/* if(!tm.containsKey( c )){
tm.put( c,1 );
}else{
tm.put( c, tm.get( c ) + 1 );
}*/
tm.put( c,!tm.containsKey( c ) ? 1:tm.get( c ) +1 );
}
//4.关闭输入流
br.close();
//5.创建输出流对象
BufferedWriter bw = new BufferedWriter( new FileWriter( "Times.txt" ) );
// 6.遍历集合的内容写到times.txt中
for (Character key : tm.keySet()) {
switch (key){
case '\t':
bw.write( "\\t" + "="+ tm.get( key ) );
break;
case '\n':
bw.write( "\\n" + "="+ tm.get( key ) );
break;
case '\r':
bw.write( "\\r" + "="+ tm.get( key ) );
break;
default:
bw.write( key + "="+ tm.get( key ) );
break;
}
bw.newLine();
}
// 7.关闭输出流
bw.close();
}
<2> java.util.Scanner类
- Scanner解决了 BufferedReader 类的缺陷(替换了BufferedReader类,而且更好的实现了InputStream的操作),构造方法:public Scanner(InputStream source)。
- 使用Scanner还可以接收各种数据类型,并且帮助用户减少转型处理。
public static void main(String[] args) {
// 默认键盘输入
Scanner scanner = new Scanner( System.in );
//从指定文件text.txt 输入
try (Scanner scanner1 = new Scanner(
new FileInputStream(
new File( "E:" + File.separator + "text.txt" ) ) )) {
System.out.println( scanner1.nextLine() );
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
6. 打印流(PrintStream 和 PrintWriter)
- 打印流解决的就是OutputStream的设计缺陷,属于OutputStream功能的加强版。
- 打印流分为字节打印流:PrintStream、字符打印流:PrintWriter,以后使用PrintWriter几率较高。
- 打印流的设计属于装饰设计模式:核心依然是某个类的功能,但是为了得到更好的操作效果,让其支持的功能更多一些。(后续将会有专门文章详细介绍装饰设计模式)
- 方法实现
package com.sunlong.file;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.file.Paths;
/**
* @Auther: SolarL
* @Date: 2019/4/3
* @Description: com.sunlong.file
* @version: 1.0
*/
public class Print {
public static void main(String[] args) {
try (
PrintStream ps = new PrintStream(
new FileOutputStream( Paths.get( "E:", "text.txt" ).toFile() ) );
) {
ps.printf( "%.2f", 1.2333 );//格式化输出 1.23
ps.print( "aaa" ); //传入字符串
ps.print( true ); //传入布尔值
ps.print( 1110 ); //传入int数
ps.print( 'a' ); //传入字符
ps.print( new char[2] ); //传入字符数组
ps.print( new Person()); //传入自定义对象
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
class Person{
}
- 综合第五、第六点,以后除了二进制文件拷贝的处理之外,那么只要是针对程序的信息输出都是用打印流(PrintStream、PrintWriter),信息输出使用Scanner。
上篇及本篇就是Java I/O 的输入/输出体系相关知识,如何使用 File 类来访问本地文件系统,Java 不同IO 流的功能,以及几种典型流的用法,其余流在此不再介绍。
[Java I/O 流] 带你一起玩转输入/输出流(上)跳转链接:https://blog.youkuaiyun.com/SolarL/article/details/88976894