46-字节流与字符流

字节流与字符流

流的基本概念

  在java.io包中File类是唯一一个与文件有关的程序处理类,但是File类只能操作文件本身,但不能操作文件的内容,实际开发中IO操作的核心意义在于:输入与输出操作。
  对于程序而言,输入与输出可能来自于不同的环境,例如:通过电脑连接服务器进行浏览的时候,此时客户端发出一个信息,服务器接收后进行回应处理。
  对于服务器或是客户端而言实质上传递的就是一种数据流的处理形式,而所谓的数据流指的就是字节数据。而对于这种流的处理形式,在java.io包中提供两类支持:

  • 字节处理流:OutputStream(输出字节流),InputStream(输入字节流);
  • 字符处理流:Writer(字符输出流),Reader(字符输入流);

sgamTJ.png
  所有流操作都应该采用如下统一步骤进行,下面以文件处理的流程为例:

  1. 如果现在要进行的是文件的读写操作,则一定要通过File类找到一个文件;
  2. 通过字符流或字节流的子类为父类对象实例化;
  3. 利用字节流\字符流的方法实现数据的输入与输出操作;
  4. 流的操作属于资源操作,资源操作必须进行关闭处理

OutputStream直接输出流

  字节的数据是以byte数据类型为主实现的操作,在进行字节内容输出的时候可以使用OutputStream类来完成,这个类基本定义如下:

  • 定义:public abstract OutputStream extends Object implements Closeable,Flushable;

首先可以发现这个类实现了两个接口,于是基本的对应关系如下:

Closeable Flushable
public interfae Closeable extends AutoCloseable{public void close() throws IOException;} public interfae Flushable{ public void flush() throws IOException;}

sW2UJ0.png
OutputStream类定义的是一个公共的输出操作标准,而在这个操作标准里面一共定义有三个内容输出方法:

方法名称 类型 功能
public abstract void write(int b) throws IOException; 普通 输出单个字节数据
public abstract void write(byte[] b) throws IOException; 普通 输出一组字节数据
public abstract void write(byte[] b,int off, int len) throws IOException; 普通 输出部分字节数据

  但是需要注意的一个核心问题在于:OutputStream类毕竟是一个抽象类,而这个抽象类要想获得实例化对象应该通过子类实例向上转型完成,如果说现在要进行的是文件处理操作,则可以使用FileOutputStream子类。

  因为最终都需要发生向上转型的处理关系,所以对于此事FileOutputStream子类核心的关注点就可以放在构造方法上:

  • [覆盖]构造方法:public FileOutputStream(File file) throws FileNotFoundException;
  • [追加]构造方法:public FileOutputStream(File file,boolean append) throws FileNotFoundException;

使用OutputStream类实现内容的输出

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class Flow_OutputStream {
    
    

	public static void main(String[] args) throws Exception {
    
    
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");		//1.指定文件路径
		if(!file.getParentFile().exists()) {
    
    		//文件不存在
			file.getParentFile().mkdirs();		//创建父目录
		}
		OutputStream output = new FileOutputStream(file);		//2.通过子类实例化
		String str = "test";		//要输出的文件内容
		output.write(str.getBytes());		//3.将字符串变为字节数组并输出
		output.close();		//4.关闭资源
	}
}

  本程序使用最为标准的程序实现了输出的操作处理,并且在整体的处理之中,只是创建了文件的父目录,而并未创建文件,但执行后会发现文件可以自动创建。另外需要提醒的是,由于OutputStream子类也属于AutoCloseable接口子类,所以对于close()方法也可以简化使用。
自动关闭处理

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Flow_OutputStream {
    
    

	public static void main(String[] args) throws Exception {
    
    
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");		//1.指定文件路径
		if(!file.getParentFile().exists()) {
    
    		//文件不存在
			file.getParentFile().mkdirs();		//创建父目录
		}
		try(OutputStream output = new FileOutputStream(file,true)){
    
    
			String str = "!!!!!!!!!!!!!!!!\r\n";		//要输出的文件内容
			output.write(str.getBytes());		//3.将字符串变为字节数组并输出
		} catch(IOException e) {
    
    
			e.printStackTrace();
		}
	}
}

  是否使用自动关闭取决于项目整体结构,另外还需要提醒的是:整个的程序里面最终是输出一组字节数据,但是千万不要忘记,OutputStream类中定义的输出方法一共有三个

InputStream字节输入流

  与OutputStream类对应的一个流就是字节输入操作刘,InputStream类主要实现的就是字节数据读取,该类定义如下:public abstract class InputStream extends Object implements Closeable;
  在InputStream类中定义有如下几个核心方法:

方法名称 类型 功能
public abstract int read() throws IOException 普通 读取单个字节数据,如果现在已经读取到底,返回-1
public int read(byte[] b) throws IOException 普通 读取一组字节数据,返回的是读取的个数,如果数据读取到底,返回-1
public int read(byte[] b,int off, int len) throws IOException 普通 读取一组字节数据(只占数组的部分)

sfk3Cj.png
sfkIGd.png

  InputStream类属于一个抽象类,这时应该依靠它的子类来实例化对象,如果要从文件读取一定要使用FileInputStream子类,对于子类而言只关心父类对象实例化,构造方法:public FileInputStream(File file) throws FileNotFoundException;
读取数据

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class Flow_InputStream {
    
    

	public static void main(String[] args) throws Exception {
    
    
		// TODO Auto-generated method stub
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
		InputStream input = new FileInputStream(file);
		byte data[] = new byte[1024];		//开辟一个缓冲区读取数据
		int len = input.read(data);		//读取数据,全部保存在字节数组中,返回读取个数
		System.out.println("["+ new String(data,0,len) + "]");
		input.close();
	}
}

  对于字节输入流里面最为麻烦的问题在于:使用read()方法读取时,只能够以字节/字节数组进行接收。
  特别需要注意的是从JDK1.9开始在InputStream类里面增加了一个新的方法:public byte[] readAllBytes() throws IOException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

public class Flow_InputStream {
    
    

	public static void main(String[] args) throws Exception {
    
    
		// TODO Auto-generated method stub
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
		InputStream input = new FileInputStream(file);
		byte data[] = input.readAllBytes();
		System.out.println("["+ new String(data) + "]");
		input.close();
	}
}

如果现在要读取的内容很大,那么这种读取会直接搞死程序。

Writer字符输出流

  使用OutputStream字节输出流进行数据输出的时候使用的都是字节类型的数据,而很多情况下很多字符串的输出是比较方便的,所以对于java.io包中,在JDK1.1的时候又推出了字符输出流:Writer,该类定义如下:public abstract class Writer extends Object implements Appendable,Closeable,Flushable;

s55ul9.png

s55ul9.png

在Writer类中提供许多的输出的操作方法,重点来看两个:

  • 输出字符数组:public void write(char[] cbuf) throws IOException;
  • 输出字符串public void write(String str) throws IOException;

使用Writer输出

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Flow_Writer {
    
    

	public static void main(String[] args) throws Exception {
    
    
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
		if(!file.getParentFile().exists()) {
    
    		//保证父目录存在
			file.getParentFile().mkdirs();
		}
		Writer out = new FileWriter(file,true);		//追加
		String str = "test!\r\n";
		out.write(str);
		out.append("tesstsasa!");		//追加输出内容
		out.close();
	}
}

  使用Writer输出的最大优势在于可以直接利用字符串完成。Writer是字符流,字符处理的优势在于中文数据上。

Reader字符输入流

  Reader是实现字节输入流的一种类型,其本身属于一个抽象类,这个类的定义如下:public abstract class Reader extends Object implements Readable,Closeable;

sTCXDI.png
  Reader类中并没有像Writer一样提供有整个字符串的输入处理操作,只能够利用字符数组来进行接收:

  • 接收数据:public int read(char[] cbuf) throws IOException;

实现数据读取

import java.io.File;
import java.io.FileReader;
import java.io.Reader;

public class Flow_Reader {
    
    

	public static void main(String[] args) throws Exception{
    
    
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
		if(file.exists()) {
    
    		//文件存在则读取
			Reader in = new FileReader(file);
			char data[] = new char[1024];
			int len = in.read(data);		//返回读取数据长度
			System.out.println(new String(data,0,len));
		}
	}
}

  字符流读取的时候只能够按照数组的形式来实现处理操作。

字节流与字符流的区别

  现在已经可以清楚字节流与字符流的基本操作,但是对于这两类流仍旧存在区别,重点看一下输出的处理操作。在使用OutputStream和Writer输出的最后都使用了close()方法进行了关闭处理。
  在使用OutputStream类输出的时候如果没有使用close()方法进行关闭处理,发现内容仍然可以进行正常输出,但是在使用Writer时没有使用close()关闭输出流,那么此时内容无法进行输出,因为Writer使用到了缓冲区
  当使用了close()方法的时候实际上会出现强制刷新缓冲区的情况,所以这个时候会将内容进行输出,如果没有关闭,那么将无法进行输出操作,所以此时如果再不关闭的情况下想将全部内容输出可以使用flush()方法记性强制性清空。

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

public class Flow_Writer {
    
    

	public static void main(String[] args) throws Exception {
    
    
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
		if(!file.getParentFile().exists()) {
    
    		//保证父目录存在
			file.getParentFile().mkdirs();
		}
		Writer out = new FileWriter(file,true);		//追加
		String str = "test!\r\n";
		out.write(str);
		out.append("tesstsasa!");
		out.flush();		//强制新刷新缓冲区
	}
}

  字节流在进行处理的时候并不会使用到缓冲区,而字符流会使用缓冲区。另外使用缓冲区的字符流更加适合中文数据的处理,所以日后程序开发中,如果涉及到包含有中文信息的输出一般都会有使用字符流进行处理,但是从另外一方面来讲,字节流和字符流的基本处理形式是相似的,由于IO很多情况下都是进行数据的传输使用(二进制)所以本次讲解将以字节流为主。

转换流

  所谓的转换流指的是可以实现字节流与字符流操作的功能转换,例如:进行输出的时候OutputStream需要将内容变为字节数组才可以数组,而Writer可以直接输出字符串,这一点是方便的,所以很多人就认为需要提供有一种转化的机制,来实现不同流类型的转换操作,为此在java.io包里面提供两个类:InputStreamReader,OutputStreamWriter。

名称 OutputStreamWriter InputStreamReader
定义 public class OutputStreamWriter extends Writer; public class InputStreamReader extends Reader;
构造方法 public OutputStreamWriter(OutputStream out); public InputStreamReader(InputStream in);

s7srEF.png
  通过类的继承结构与构造方法可以实现,所谓的转换处理就是将接收到的字节流对象向上转型变为字符流对象。
s76sp9.png
观察转换

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class Flow_Convert {
    
    

	public static void main(String[] args) throws Exception {
    
    
		File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");		//1.指定文件路径
		if(!file.getParentFile().exists()) {
    
    
			file.getParentFile().mkdirs();
		}
		OutputStream output = new FileOutputStream(file,true);
		Writer out = new OutputStreamWriter(output);		//字节流变为字符流
		out.write("适合输出中文,");
		out.close();
	}
}

  讲解转换流的主要目的基本上不是为了让开发者去记住它,而是知道有这样一种功能,但同时更多的是需要进行结构的分析处理。通过之前的字节流与字符流一系列分析后发现OutputStream类有FileOutputStream直接子类,InputStream类FileInputStream直接子类,但是观察FileWriter、FileReader类的继承关系。

  • FileWriter:public class FileWriter extends OutputStreamWriter;
  • FileReader:public class FileReader extends InputStreamReader;

s7czVK.png
FileReader继承结构
s7gp5D.png
实际上所谓的缓存是指的是程序中间的一道处理缓冲区。
s72a0P.png

综合实战:文件拷贝

  在操作系统中有一个copy命令,该命令的主要功能是可以实现文件的拷贝处理,现在要求模拟这个命令,通过初始化参数输入拷贝的源文件路径与拷贝的目标路径实现文件的拷贝处理。
需求分析:

  • 需要实现文件的额拷贝操作,那么需要拷贝各种文件类型,所以需要使用字节流;
  • 在进行拷贝的时候需要考虑到大文件的拷贝问题;

实现方案:

  • 方案一:使用InputStream将全部要拷贝的内容直接读取到程序里面,而后一次性输出到目标文件;
    • 缺点:文件过大无法处理;
  • 方案二:采用部分拷贝,读取一部分输出一部分。(合适)
    • InputStream:public int read(byte[] b) throws IOException;
    • OutputStream:public void write(byte[] b,int off,int len) throws IOException;

实现文件拷贝处理

import java.io.*;

class FileUtil{
    
    		//定义一个文件操作工具类
	private File srcFile;		//源文件路径
	private File dstFile;		//目标文件路径
	public FileUtil(File file, File file2) {
    
    
		this.srcFile = file;
		this.dstFile = file2;
	}
	public boolean copy() throws Exception{
    
    
		if(!this.srcFile.exists()) {
    
    
			System.out.println("文件不存在!");
			return false;		//文件不存在
		}
		if(!this.dstFile.getParentFile().exists()) {
    
    
			this.dstFile.getParentFile().mkdirs();
		}
		byte data[] = new byte[1024];		//开辟一个拷贝缓冲区
		InputStream input = null;
		OutputStream output = null;
		try {
    
    
			input = new FileInputStream(this.srcFile);
			output = new FileOutputStream(this.dstFile);
			int len = 0;
			//1.读取数据到数组之中,随后返回读取的个数
			//2.判断按个数是否是-1,不是则进行写入
			while((len = input.read(data))!=-1) {
    
    
				output.write(data,0,len);
			}
			return true;
		} catch (Exception e) {
    
    
			throw e;
		} finally {
    
    
			if(input != null) {
    
    
				input.close();
			}
			if(output != null) {
    
    
				output.close();
			}
		}
	}
}

public class Example_FileCopy {
    
    

	public static void main(String[] args) throws Exception{
    
    
		
		File src = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
		File dst = new File("C:\\Project\\Java_study\\src\\文件\\test1.txt");
		long start = System.currentTimeMillis();
		FileUtil fu = new FileUtil(src, dst);
		System.out.println(fu.copy()? "成功":"失败");
		long end = System.currentTimeMillis();
		System.out.println("Time:"+ (end-start));
	}
}

  需要注意的是,以上的做法属于文件拷贝的最原始实现,而从JDK1.9开始InputStream和Reader类中都追加有数据转存的处理操作方法:

  • InputStream:public long transferTo(OutputStream out) throws IOException;
  • Reader:public long transferTo(Writer out) throws IOException;

使用转存的方式处理

import java.io.*;

class FileUtil{
    
    		//定义一个文件操作工具类
	private File srcFile;		//源文件路径
	private File dstFile;		//目标文件路径
	public FileUtil(File file, File file2) {
    
    
		this.srcFile = file;
		this.dstFile = file2;
	}
	public boolean copy() throws Exception{
    
    
		if(!this.srcFile.exists()) {
    
    
			System.out.println("文件不存在!");
			return false;		//文件不存在
		}
		if(!this.dstFile.getParentFile().exists()) {
    
    
			this.dstFile.getParentFile().mkdirs();
		}
		InputStream input = null;
		OutputStream output = null;
		try {
    
    
			input = new FileInputStream(this.srcFile);
			output = new FileOutputStream(this.dstFile);
			input.transferTo(output);
			return true;
		} catch (Exception e) {
    
    
			throw e;
		} finally {
    
    
			if(input != null) {
    
    
				input.close();
			}
			if(output != null) {
    
    
				output.close();
			}
		}
	}
}

public class Example_FileCopy {
    
    

	public static void main(String[] args) throws Exception{
    
    
		
		File src = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
		File dst = new File("C:\\Project\\Java_study\\src\\文件\\test1.txt");
		long start = System.currentTimeMillis();
		FileUtil fu = new FileUtil(src, dst);
		System.out.println(fu.copy()? "成功":"失败");
		long end = System.currentTimeMillis();
		System.out.println("Time:"+ (end-start));
	}
}

  此时千万注意程序运行版本问题。那么如果说对此程序进行进一步扩展,可以实现一个文件目录的拷贝?一旦进行文件目录的拷贝还需要拷贝所有的子目录中的文件。
文件夹拷贝

import java.io.*;

class FileUtil{
    
    		//定义一个文件操作工具类
	private File srcFile;		//源文件路径
	private File dstFile;		//目标文件路径
	public FileUtil(File file, File file2) {
    
    
		this.srcFile = file;
		this.dstFile = file2;
	}
	public FileUtil(String src, String dst) {
    
    
		this(new File(src),new File(dst));
	}
	public boolean copy() throws Exception{
    
    
		if(!this.srcFile.exists()) {
    
    
			System.out.println("文件不存在!");
			return false;		//文件不存在
		}
		
		return this.copyFileImpl(this.srcFile, this.dstFile);
	}
	
	private void copyImpl(File file) throws Exception{
    
    		//递归操作
		if(file.isDirectory()) {
    
    		//是目录
			File results[] = file.listFiles();		//列出全部目录组成
			if(results != null) {
    
    
				for(int x=0;x<results.length;x++) {
    
    
					copyImpl(results[x]);
				}
			}
		}else {
    
    		//是文件
			String newFilePath = file.getPath().replace(this.srcFile.getPath() + File.separator, "");
			File newFile = new File(this.dstFile,newFilePath);		//拷贝的目标路径
			this.copyFileImpl(file, newFile);
		}
		
	}
	private boolean copyFileImpl(File srcFile,File dstFile) throws Exception{
    
    
		if(!dstFile.getParentFile().exists()) {
    
    
			dstFile.getParentFile().mkdirs();
		}
		InputStream input = null;
		OutputStream output = null;
		try {
    
    
			input = new FileInputStream(srcFile);
			output = new FileOutputStream(dstFile);
			input.transferTo(output);
			return true;
		} catch (Exception e) {
    
    
			throw e;
		} finally {
    
    
			if(input != null) {
    
    
				input.close();
			}
			if(output != null) {
    
    
				output.close();
			}
		}
	}
	public boolean copyDir() throws Exception{
    
    
		try {
    
    
			this.copyImpl(this.srcFile);
			return true;
		}catch(Exception e){
    
    
			e.printStackTrace();
			return false;
		}
	}
}

public class Example_FileCopy {
    
    

	public static void main(String[] args) throws Exception{
    
    
		String src = "C:\\Project\\Java_study\\src\\文件";
		String dst = "C:\\Project\\Java_study\\src\\文件copy";
		long start = System.currentTimeMillis();
		FileUtil fu = new FileUtil(src, dst);
		if(new File(src).isFile()) {
    
    		//文件拷贝
			System.out.println(fu.copy()? "成功":"失败");
		}else {
    
    		//目录拷贝
			System.out.println(fu.copyDir()? "成功":"失败");
		}
		
		long end = System.currentTimeMillis();
		System.out.println("Time:"+ (end-start));
	}
}

本程序是IO操作的核心代码,本程序可以理解,整个IO处理机制就非常容易理解了。

猜你喜欢

转载自blog.csdn.net/MARVEL_3000/article/details/114415660