JavaSE基础篇之-Java 流(Stream)、文件(File)和IO

发布于:2023-02-11 ⋅ 阅读:(831) ⋅ 点赞:(0)

这篇博文主要对Java 流(Stream)、文件(File)和IO进行了讲解从相关的概念到代码实例,全文1万字左右,整个过程通过查询大量的官方文档,相关博文,如果有什么不对的地方,希望你能提出宝贵的建议,让我及时更正,以免误人子弟,如果你觉得这篇博文不错的话,请给博主点赞,收藏,加关注喔~。

一.Stream流

  • IO流
  • Java8特性Stream

(一) IO流

1.IO流概念

我觉得IO里的流概念就是对数据传输的一种抽象比喻,当不同的介质之间有数据交互的时候,JAVA就使用流来实现,按照流的流向可以把流分为输入流输出流,按照流的数据类型可以把流分为字节流字符类,按照流的功能可以把流分为节点流处理流

2.输入,输出流

(1) 输入流:
应用程序端要接受数据,数据源端的数据输出,进入到数据源和应用程序的通道管子中,管子中的流流入到应用程序中,应用程序读取对应的流(数据)—数据读取操作。
在这里插入图片描述

(2) 输出流:
应用程序的数据输出,进入到通道管子中,数据流入(输入)到目的地,目的地写入对应的流(数据)—数据保存操作。
在这里插入图片描述

3.字节流,字符类

(1) 字节流
字节流就是指我们决定好了这个流按字节进行传输,即传输的数据是二进制数据。

(2) 字符类
字符流就是指我们决定好了这个流按字符进行传输,即传输的数据是字符数据。

注意:在计算机中所有的数据都是二进制,即按字节流的方式进行传输可以传输所有的数据类型,包括字符类型,而按字符进行传输只能传输字符类型(.txt等文本数据)并且字符方式传输还需注意字符的编码。

4.节点流,处理流

(1) 节点流
可以从或向一个特定的地方(节点)读写数据,如FileReader
通俗一点:程序用于直接操作目标设备所对应的类叫节点流

JAVA常用的节点流:

  • 文 件 FileInputStream FileOutputStrean FileReader FileWriter
    文件进行处理的节点流。
  • 字符串 StringReader StringWriter 对字符串进行处理的节点流。
  • 数 组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader
    CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
  • 管 道 PipedInputStream PipedOutputStream
    PipedReaderPipedWriter对管道进行处理的节点流。

(2) 处理流
是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
通俗一点:程序通过一个间接流类去调用节点流类,以达到更加灵活方便地读写各种类型的数据,这个间接流类就是处理流

常用处理流

  • 缓冲流:BufferedInputStrean BufferedOutputStream BufferedReader
    BufferedWriter—增加缓冲功能,避免频繁读写硬盘。
  • 转换流:InputStreamReader OutputStreamReader实现字节流和字符流之间的转换。
  • 数据流 DataInputStream DataOutputStream 等-提供将基础数据类型写入到文件中,或者读取出来.

(3) 流的关闭顺序:

  • 一般情况下是:先打开的后关闭,后打开的先关闭

  • 另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如,处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

  • 可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。

注意:
如果将节点流关闭以后再关闭处理流,会抛出IO异常。
如果关闭了处理流,在关闭与之相关的节点流,也可能出现IO异常。(hadoop编程文件流操作中遇到了。)

------相关连接:
上面的内如来源于:JAVA的节点流和处理流
Java语言中的关联与依赖关系

5.缓冲区,缓存流

(1) 缓冲区
对I/O进行缓冲是一种常见的性能优化,缓冲流为I/O流增加了内存缓冲区,增加缓冲区的两个目的:

  • 允许Java的I/O一次不只操作一个字符,这样提高䇖整个系统的性能;
  • 由于有缓冲区,使得在流上执行skip、mark和reset方法都成为可能。

(2) 缓存流
缓冲流:它是要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,
提高了读写的效率,同时增加了一些新的方法。例如:BufferedReader中的readLine方法,
BufferedWriter中的newLine方法。

(3) 四种缓冲流
J2SDK提供了4种缓存流,常用的构造方法为:

//字符输入流
BufferedReader(Reader in)//创建一个32字节的缓冲区
BufferedReader(Reader in, int size)//size为自定义缓存区的大小

//字符输出流
BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

//字节输入流
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)

//字节输出流
BufferedOutputStream(OutputStream in)
BufferedOutputStream(OutputStream in, int size)

(4) 其他

  • 缓冲输入流BufferedInputSTream除了支持read和skip方法意外,还支持其父类的mark和reset方法;
  • BufferedReader提供了一种新的ReadLine方法用于读取一行字符串(以\r或\n分隔);
  • BufferedWriter提供了一种新的newLine方法用于写入一个行分隔符;
  • 对于输出的缓冲流,BufferedWriter和BufferedOutputStream,写出的数据会先在内存中缓存,
    使用flush方法将会使内存的数据立刻写出。

----相关链接:
上面的内容来源于:JAVA:IO流 之 节点流与处理流(2)

6.转换流

转换流是字节流通向字符流的桥梁,可以将字节流转换为字符流,原理其实就是在字节流的基础上增加了编解码的操作。

字符流传输的本质是通过字节流传输方式进行传输,因此只需要在字节流的基础上增加相应的编码器(字符流转换为字节流)、解码器(字节流转换为字符流),这样字节流和字符流就可以进行相互转换,但是需要注意字节流转换为字符流需要明确该字节流是否为字符流,并且该字节流的字符使用的编码必须一致不然会出现乱码,出现乱码不能说明一定是编码的问题,也有可能是该字节流存放的流本就不是字符流。
InputStreamReader:将字节流转换为字符流;
OutputStreamWriter:将字符流转换为字节流。

相关链接:
IO流-转换流

(二) Java8特性Stream

Java8特性Stream和IO流没有任何关系。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
----相关学习链接;
上面的内容来源于:Java8中Stream详细用法大全
Java Stream的基本概念以及创建方法
原来你是这样的 Stream —— 浅析 Java Stream 实现原理
Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合

二.File文件类

(一) File类

1.基本信息

  • File类位于Java.IO包下
  • File是一个没有任何修饰的普通类
  • 实现了Serializable, Comparable接口
  • 文件和目录路径名的抽象表示形式。

在这里插入图片描述

2.构造方法

创建一个新的File来自父抽象路径名和子路径名字符串的实例。

File(File parent, String child)	

创建一个新的File通过将给定的路径名字符串转换为抽象路径名来实现。

File(String pathname)	

创建一个新的File来自父路径名字符串和子路径名字符串的。

File(String parent, String child)	

创建一个新的File通过转换给定的file:URI为抽象路径名。

File(URI uri)	

3.常用方法

(1) 创建功能

  • public boolean createNewFile() throws IOException 创建新文件
  • public boolean mkdirs() 创建新的目录,若父目录不存在,会自动创建
  • public boolean renameTo(File dest) 重命名文件

(2) 判断功能

  • public boolean isFile() 判断是否是文件
  • public boolean isDirectory() 判断是否是目录
  • public boolean exists() 判断文件或者目录是否存在
  • public boolean canRead() 判断文件是否可读
  • public boolean canWrite() 判断文件是否可写
  • public boolean isHidden() 判断文件是否隐藏

(3) 获取功能

  • public String getAbsolutePath() 获取绝对路径
  • public String getPath() 获取相对路径
  • public String getName() 获取文件或目录名
  • public long length() 获取文件大小(应用例如:用于限制上传文件大小)
  • public long lastModified() 获取文件最后一次修改的时间(单位,毫秒)

(4) 高级获取功能

  • public String[] list() 获取路径表示目录下的所有文件和目录名称
  • public String[] list(FilenameFilter filter) 获取满足过滤器FilenameFilter条件的所有目录或文件
  • public File[] listFiles() 获取路径表示目录下的所有文件和目录对象(文件类型)
  • public File[] listFiles(FilenameFilter filter)
    获取满足过滤器FilenameFilter条件的所有目录或文件对象(文件类型)

(二) 常用操作

项目结构:
在这里插入图片描述

1. 文件建立

创建文件夹:
FileUtil.java中创建下面二个方法:

	//创建文件
	public static File createFile(String path){
		File file=new File(path);
		if(!file.exists())//判断文件是否存在
		{
			try{
				new File(file.getParent()).mkdirs();//建立上层文件夹
				file.createNewFile();//创建文件,执行此语句才产生该文件
			}
			catch(IOException e){
				e.printStackTrace();
				return null;
			}
		}
		else
		{
			System.out.println("文件已存在");
		}
		return file;
	}

获取文件的详细信息:

//获取文件的详细信息
	public static void getFileInfo(File file){
				//获取文件名
				String filename=file.getName();
				//获取文件路径
				String filePath=file.getPath();
				//获取文件绝对路径
				String fileAbsolutePath=file.getAbsolutePath();
				//获取父亲文件路径
				String parentPath=file.getParent();
				//获取父亲文件名
				String parentName=new File(file.getParent()).getName();
				//获取文件大小
				long size=file.length();
				//获取最后一次修改时间
				long lastTime=file.lastModified();
				 String filemsg="文件名"+filename+"\n路径"+filePath+"\n绝对路径"+fileAbsolutePath+"\n父文件路径"+parentPath;
				filemsg+="\n文件大小"+size+"\n最后修改时间"+new Timestamp(lastTime);
				System.out.println(filemsg);
				System.out.println("父亲节点文件夹名称"+parentName);
	}

FileTest.java中:
在这里插入图片描述
运行效果:
在这里插入图片描述

2. 文件移动

FileUtil.java中:

//修改文件路径及名称内容也一块移动过去了相当于“移动”
	public static boolean refileName(String fromPath,String toPath){
		File file1=new File(fromPath);
		if (!file1.exists()) {
			return false;
		}
		File file2=new File(toPath);
		//判断file2路径是否存在不存在则创建
		if(!file2.exists()){
			new File(file2.getParent()).mkdirs();
		}
		return file1.renameTo(file2);//修改文件名称及路径
	}

FileTest.java中:
在这里插入图片描述
运行效果:
在这里插入图片描述

3. 文件夹遍历文件

FileUtile.java中:

	/**
	* 遍历一个文件夹下的文件并显示文件名称
	* @param path 对应路径
	*/
	public static void searchFile(String path){
		File file=new File(path);
		File[] files=file.listFiles();//file下的文件或文件夹
		System.out.println(path+"目录下的文件数有"+files.length);
		for(File f:files){
			if(f.isFile()){//判断是否为文件
				System.out.println(f.getName());
			}
			else if(f.isDirectory()){//判断是否为目录
				searchFile(f.getPath());//嵌套
			}
		}
	}

FileTest.java中:
在这里插入图片描述
运行效果:
在这里插入图片描述
相关链接:
上面的内如来源于:Java实用方法整理(十七)——File类常用方法总结
Java中File类对文件的几个常见操作实例总结

三.IO框架

(一).IO流类的梳理

1.IO流所有接口,类

IO的所有操作接口都位于Java.IO包下
在这里插入图片描述
IO包下所有的接口:
在这里插入图片描述
IO包下所有的类:
在这里插入图片描述
上面的类是不是很恐怖,下面通过思维导图整理一下(图一):
在这里插入图片描述

2.字节,字符超类

(InputStream,OutputStream),(Reader,Writer)分别为字节,字符流的超类,该超类的子类根据不同的业务场景进行编写,并实现(重写)基类的所有抽象方法

下面我们通过官方文档来看看这四个超类:

(1) InputStream

  • 字节输入流的所有类的超类
  • InputStream是一个抽象类
  • 实现Closeable接口

在这里插入图片描述

构造方法:
在这里插入图片描述
所有方法:
从下面的所有的方法中我们可以看到只有一个抽象类abstract int read(),其他所有的InputStream子类都要重写该方法(该超类的子类根据不同的业务场景进行编写,并实现(重写)基类的所有抽象方法)。
在这里插入图片描述
Closeable接口:

  • close():关闭此流并释放与之关联的所有系统资源。

在这里插入图片描述
看看InputStream的子类:
FileInputStream:

  • FileInputStream是一个无任何修饰的普通类
  • 继承InputStream类,重写abstract int read()方法
  • 从文件系统中的文件获取输入字节(这里的业务场景就是文件系统)。

在这里插入图片描述

来看看子类的构造方法:
在这里插入图片描述子类的所有方法:
这里的int read()就是重写父类(InputStream)的abstract int read()方法
在这里插入图片描述
(2) OutputStream

  • 字节输出流的所有类的超类
  • OutputStream是一个抽象类
  • 实现Closeable,Flushable接口

在这里插入图片描述

构造方法:
在这里插入图片描述
所有的方法:
同样的abstract void write​(int b)对于所有继承OutputStream的子类都要重写该抽象方法。
在这里插入图片描述
Flushable接口:

  • flush()通过将任何缓冲的输出写入基础流来刷新此流(并不是所有OutputStream的子类调用flush()都有效)。

在这里插入图片描述

关于flush()方法可以看看这篇博文:java flush()函数的作用

(3) Reader

  • 读取字符流的抽象类
  • 实现Readable, Closeable接口

在这里插入图片描述

构造方法:
在这里插入图片描述
所有的方法:
这里abstract int read​(char[] cbuf, int off, int len)为Reader的抽象方法,对应的所有继承该类的子类都要重写该抽象方法。
在这里插入图片描述
Readable接口:

  • Readable接口位于Java.lang包下
  • Readable是字符的来源
  • read​(CharBuffer cb) 尝试将字符读入指定的字符缓冲区。

在这里插入图片描述

CharBuffer:
从前面Readable接口中read​(CharBuffer cb),可知Readable接口是依赖于CharBuffer,及CharBuffer决定Readable,通俗理解:Readable的read​(CharBuffer cb)是通过CharBuffer实现的.
在这里插入图片描述

  • CharBuffer位于nio包下
  • CharBuffer是一个抽象类
  • CharBuffer继承Buffer类
  • CharBuffer实现Readable接口,并实现read​(CharBuffer cb)

在这里插入图片描述
----相关链接:
java Readable实现 Scanner使用
NIO - Buffer
JAVA中的抽象接口
(4) Writer

  • 用于写入字符流的抽象类
  • 实现Appendable, Closeable, Flushable接口

在这里插入图片描述
Writer的构造方法:
在这里插入图片描述
Writer的所有方法:
Writer有三个抽象方法 close() flush() write​(char[] cbuf, int off, int len)所有继承Writer的子类都要重写该方法。
在这里插入图片描述

Appendable接口:

  • 将指定的字符序列追加到Appendable

在这里插入图片描述
并不是所有都可以追加:
在这里插入图片描述

Appendable的所有方法:
在这里插入图片描述

(二).IO流的使用

1.IO使用原则

前面我们虽然对IO了进行划分,但是实际上要用起来还是挺复杂的,下面我们通过相关的原则来看看这些IO是怎么进行使用的吧。
(1) 按数据来源(去向)分类:【原则一】

  1. 是 文 件 : FileInputStream, FileOutputStream, ( 字 节 流 ) FileReader,
    FileWriter( 字符 )
  2. 是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 字节流 )
  3. 是 Char[]: CharArrayReader, CharArrayWriter( 字符流 )
  4. 是 String: StringBufferInputStream, StringBufferOuputStream ( 字 节流 )
    StringReader, StringWriter( 字符流 )
  5. 网络数据流: InputStream, OutputStream, ( 字节流 ) Reader, Writer( 字符流 )

(2) 按是否格式化输出分:【原则二】

  1. 要格式化输出: PrintStream, PrintWriter

(3) 按是否要缓冲分:【原则三】

  1. 要缓冲: BufferedInputStream, BufferedOutputStream, ( 字节流 )
    BufferedReader,BufferedWriter( 字符流 )

(4) 按数据格式分:【原则四】

  1. 二进制格式(只要不能确定是纯文本的) : InputStream, OutputStream 及其所有带Stream 结束的子类
  2. 纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer 及其所有带 Reader,Writer 的子类

(5) 按输入输出分: 【原则五】

  1. 输入: Reader, InputStream 类型的子类
  2. 输出: Writer, OutputStream 类型的子类

(6) 特殊需要:【原则六】

  1. 从 Stream 到 Reader, Writer 的转换类: InputStreamReader,
    OutputStreamWriter

  2. 对象输入输出: ObjectInputStream, ObjectOutputStream

  3. 进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter

  4. 合并输入: SequenceInputStream

  5. 更特殊的需要: PushbackInputStream, PushbackReader,
    LineNumberInputStream,LineNumberReader

决定使用哪个类以及它的构造进程的一般准则如下(不考虑特殊需要):

  1. 考虑最原始的数据格式是什么: [原则四]
  2. 是输入还是输出: 【原则五】
  3. 是否需要转换流: 【原则六】第 1 点
  4. 数据来源(去向) 是什么: 【原则一】
  5. 是否要缓冲: 【原则三】 (特别注明: 一定要注意的是 readLine() 是否有定义, 有什么比 read, write更特殊的输入或输出方法)
  6. 是否要格式化输出: 【原则二】

2.实例练习

(1) 向文件test.txt中写入HelloWorld! 【字符流练习】
分析:

  1. 纯文本格式
  2. 输出
  3. 不需要
  4. 文件
  5. 不需要
  6. 不需要

上面我们可以得出,使用超类Writer,子类:FileWriter(Writer writer=new FileWriter(new File(path),这种方式就是多态的运用 )
代码分三步:

  • 创建流
  • 写入数据
  • 关闭流

代码详情:

package com.zhumin.test;

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

import com.zhumin.util.FileUtil;

public class FileTest {

	public static void main(String[] args) throws IOException {
		//先创建test.txt文件
		createFile("C:\\Users\\Administrator\\Desktop\\test\\test.txt");
		//【1】创建Writer流
		Writer writer=null;
		try {
			 writer=new FileWriter(new File("C:\\Users\\Administrator\\Desktop\\test\\test.txt"));
			//【2】向流中写入字符串数据
			 writer.write("HelloWorld!");
			 System.out.println("写入成功");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}catch (Exception e) {
			// TODO: handle exception
		}
		finally {
			//【3】关闭流
			writer.close();
		}
		
		
	}
	
	//创建文件
	public static File createFile(String path){
		File file=new File(path);
		if(!file.exists())//判断文件是否存在
		{
			try{
				new File(file.getParent()).mkdirs();//建立上层文件夹
				file.createNewFile();//创建文件,执行此语句才产生该文件
			}
			catch(IOException e){
				e.printStackTrace();
				return null;
			}
		}
		else
		{
			System.out.println("文件已存在");
		}
		return file;
	}
	
	

}

运行效果:
在这里插入图片描述
在这里插入图片描述
这里还可以这样写,这种写法无须手动关闭流(当try块退出时,会自动调用writer.close()方法,关闭资源。):
在这里插入图片描述
(2) 读取文件test.txt的内容 【字符流练习】
分析:

  1. 纯文本格式
  2. 输入
  3. 不需要
  4. 文件
  5. 不需要
  6. 不需要

代码如下:
在这里插入图片描述

运行效果:
在这里插入图片描述

在Reader.class中我们可以看到int read()是一个字符,一个字符进行读取。
在这里插入图片描述

在Reader.class中还有一个int read(char cbuf[])可以自定义缓冲区大小
在这里插入图片描述

代码如下:
在这里插入图片描述

由于只有一条字符可能看不出什么效果,下面我们通过向test.txt中写入一千条数据,然后分别使用这二种方式进行读取,看看二者的耗时情况:

向test.txt中写入一千条HelloWorld!:
在这里插入图片描述
FileWriter(a,b),这里b参数表示是否追加字符true:追加,false:不追加,默认不追加:
在这里插入图片描述

运行效果:
在这里插入图片描述
方式一进行读取:
在这里插入图片描述
耗时:27秒!
在这里插入图片描述
方式二进行获取:
在这里插入图片描述
耗时:1秒!
在这里插入图片描述
从上面我们可以明显看出,通过自定义缓冲区的方式进行读取的速度是远远大于一个字符一个字符进行读取(缓冲区:允许Java的I/O一次不只操作一个字符,这样提高䇖整个系统的性能)。

(3) 复制一个文件 【字节流练习】
复杂文件方法copyFile(String src, String des):

/**复制一个文件
	*
	* @param src
	* @param to
	*/
	public static void copyFile(String src, String des) {
		File file1 = new File(src);
		File fileDir=new File(des);//产生文件的上层文件夹对象
		String topath = des + "\\" + file1.getName();
		File file2 = new File(topath);//产生文件对象
		if(!fileDir.exists()){
			fileDir.mkdirs();//创建文件夹
		}
		if(file2.exists())
		{
			file2.delete();//目标位置有该文件则删除
		}
		try{
			InputStream inStream = new FileInputStream(src); // 读入原文件
			FileOutputStream fs = new FileOutputStream(topath);
			byte[] buffer = new byte[4098];
			int length;
			int byteread=0;
			int bytesum=0;
			while ((byteread = inStream.read(buffer)) != -1) {
				bytesum += byteread; // 字节数 文件大小
				fs.write(buffer, 0, byteread);
			}
			inStream.close();
			fs.close();
			System.out.println("文件复制成功,新路径为"+topath);
		}catch(IOException e){
			e.printStackTrace();
		}
	}

测试类:
在这里插入图片描述

运行效果:
在这里插入图片描述
在这里插入图片描述
(4) 复杂一个目录
复制目录copyDir(String src,String des)方法:

	/**
	* 复制一个文件夹从src拷贝到des,src里面可能是文件夹或文件。
	* @param src
	* @param des
	*/
	public static void copyDir(String src,String des){
		File file1=new File(src);
		File file2=new File(des);
		if(!file2.exists()){
		 file2.mkdirs();
		}
		File[] files=file1.listFiles();
		for(File f:files){
			if(f.isFile()){
				copyFile(f.getPath(),des+"\\"+new File(f.getParent()).getName());//注意目标路径要加上这一层文件夹的名称作为下次调用的新目标路径
			}
			else if(f.isDirectory()){
				copyDir(f.getPath(),des+"\\"+new
						File(f.getParent()).getName());//嵌套
			}
		}
	}

测试类:
在这里插入图片描述
IDcard-compound-master目录下的内容:
在这里插入图片描述
运行效果:
在这里插入图片描述
在这里插入图片描述

----相关链接:
Java IO 的一般使用原则
Writer官方API
Reader官方API
InputStream官方API
OutputStream官方API

(三).缓冲流

对于这一部分通过网上找到一些别人写的博客来学习这部分的内容

(一个京东的快递小哥,开始骑自行车送快递,一趟只能送一个,但是后来好老板刘强东看他们太幸苦了,给他们配备了一辆五菱宏光,一次可以送好几十个,这就大大提高了效率。)
java 缓冲流+原理图解
下面这篇博客主要讲了一些缓冲流的使用
详解java IO流之缓冲流的使用
下面这篇博文中IO流效率对比,缓冲流并不意味着效率是最优:
【Java基础-3】吃透Java IO:字节流、字符流、缓冲流


网站公告

今日签到

点亮在社区的每一天
去签到