IO流是程序与外部系统进行数据互通的核心机制。按照流的方向,IO流分为输入流和输出流。按照流的内容,IO流分为字节流和字符流。字节流适合操作所有类型的文件,比如音频,视频,图片,文本文件的复制,转移等。字符流只适合操作纯文本文件,比如读写txt,java文件等。io流有四大体系,分别为字节输入流(读字节数据),字节输出流(写字节数据出去),字符输入流,字符输出流。
文件字节输入流/输出流(FileInputStream/FileOutputStream)
文件字节输入流/输出流是字节输入流和字节输出流的实现类,具有把磁盘文件中的数据以字节的形式读入到内存中的作用(输出流是将内存中的数据以字节的形式输出到磁盘文件中)
创建文件字节输入流管道
相对路径是相对于当前工作目录的路径,指向文件或目录的位置。
绝对路径是从文件系统的根目录开始的完整路径,指向文件或目录的具体位置。
InputStream 对象名 = new FileInputStream("需要读取的文件的相对/绝对路径");
用这个管道输入数据有两种方案,一种是一个字节一个字节的输入,另一种是创建一个字节数组,每次接收多个字节,几个字节几个字节的输入。第一种方案的效率较低(需要多次进行内存与硬盘的交互),而且读取汉字一定乱码。因为汉字在存储方式UTF-8(国际通用编码方案)中占3个字节,一次读入一个字节会导致翻译错误。
第二种方案每次读取多个字节,性能得到提升,因为每次读取多个字节,可以减少硬盘和内存的交互次数,从而提升性能。
但是依然无法避免读取汉字输出乱码的问题:存在截断汉字字节的可能性。
代码示例
package FileInputStreamDemo;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileInputStreamDemo1 {
public static void main(String[] args) throws Exception{
//目标:掌握文件字节输入流读取文件中的字节数组到内存中去
//1、创建文件字节输入流管道于源文件接通
InputStream is = new FileInputStream("day03-file-io\\src\\FileInputStreamDemo\\CodeBlossom.txt");
//FileInputStream括号内用绝对路径/相对路径或者文件对象
//2、读取文件中的字节并输出:每次读一个
//定义一个变量记住每次读取的一个字节(没有数据返回-1)
// int b;
// while((b=is.read())!=-1)
// {
// System.out.print((char)b);
// }
//这个方法的问题:性能较慢,读取汉字输出一定会乱码(因为汉字在UTF-8中是三个字节存储,而这个方法一个一个读)
//每次读多个字节
//定义一个字节数组用于每次读取字节
byte[] buffer = new byte[3];
//定义一个变量记住每次读取了多少个字节
int len;
while((len=is.read(buffer))!=-1)
{
//3、读取到的字节数组转为字符串输出,读多少倒多少
String str = new String(buffer,0,len);
System.out.print(str);
}
//每次读取多个字节,性能得到提升,因为每次读取多个字节,可以减少硬盘和内存的交互次数,从而提升性能。
//依然无法避免读取汉字输出乱码的问题:存在截断汉字字节的可能性。
}
}
解决方案是定义一个足够大小的字节数组,可以一次性存储完文件内的所有信息,不会有汉字截断风险,但容易内存超限。
读取文件中的所有字节的方法
变量名.readAllBytes();
一般用字节数组进行储存其返回值。
package FileInputStreamDemo;
import java.io.FileInputStream;
import java.io.InputStream;
public class FileInputStreamDemo2 {
public static void main(String[] args) throws Exception{
//目标:掌握文件字节输入流读取文件中的字节数组到内存中去
//1、创建文件字节输入流管道于源文件接通
InputStream is = new FileInputStream("day03-file-io\\src\\FileInputStreamDemo\\CodeBlossom02.txt");
//一次性读完文件的所有字节,可以避免读取汉字出现乱码的问题,但如果文件过大,创造的字节数组也过大,可能导致内存溢出
//读取文本适合用字符流,字节流适合用于数据的转移,比如文件复制。
byte[] bytes = is.readAllBytes();
String rs = new String(bytes);
System.out.println(rs);
}
}
文件字节输出流的语法与文件字节输入流的语法类似
但在创建文件字节输出流管道的时候有点不同,我们可以选择清空原先文件的信息然后再写入和追加数据。追加数据管道需要在绝对路径/相对路径后添加个true。
package FileOutputStreamDemo;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class FileOutputStreamDemo1{
public static void main(String[] args) throws Exception{
//目标:学会使用文件的字节输出流
//1、创建文件字节输出流管道与目标文件接通
//无需目标文件存在,若文件不存在会创造一个文件
//OutputStream os = new FileOutputStream("day03-file-io/src/CodeBlossom-out.txt"); //覆盖管道(原先的数据被清除)
OutputStream os = new FileOutputStream("day03-file-io/src/CodeBlossom-out.txt",true);//追加数据管道
//2、写入数据
os.write(97);//写一个字节数据
os.write('b');//写一个字符数据
os.write("\r\n".getBytes());//换行
//os.write('代');//写一个字符数据,会乱码
//3、写一个字节数组出去
byte[] bytes = "我爱你中国666".getBytes(); //转化为字节数组
os.write(bytes);
os.write("\r\n".getBytes());//换行
//4、字节数组的一部分
os.write(bytes,0,3);//3个字节,只有一个我
os.close();//关闭管道,释放资源
}
}
文件字符输入流/文件字符输出流
文件字符输入流/文件字符输出流与字节流的区别就是字符流是一个一个字符进行读取,更适合用于读取/输入纯文本文件。
文件字符输入流
package FileReaderDemo;
import java.io.FileReader;
import java.io.Reader;
public class FileReaderDemo1 {
public static void main(String[] args) {
//目标:掌握文件字符输入流读取字符内容到程序中来
try (
//1、创建文件字符输入流与源文件接通
Reader fr = new FileReader("day03-file-io\\src\\CodeBlossom01.txt")){
//定义一个字符数组每次读取多个字符
//内存尽量设置的合适,过小多次与硬盘交流数据,影响性能,过大浪费内存。
char[] chs= new char[3];
int len;
while((len=fr.read(chs))!=-1)
{
String str = new String(chs,0,len);
System.out.print(str);
}
//文件字符输入流每次读取多个字符,性能较好,而且读取中文
//是按照字符读取,不会出现乱码!这是一种读取中文很好的方案
}catch (Exception e){
e.printStackTrace();
}
}
}
文件字符输入流读取中文没有乱码的风险,因为它是一个字符一个字符的读取。
文件字符输出流
package FileWriterDemo;
import java.io.FileWriter;
import java.io.Writer;
public class FileWriterDemo1 {
public static void main(String[] args) {
//目标:搞清楚文件字符输出流的使用:写字符出去的流。
//1、创建一个字符输出流对象,指定写出的目的地
try(
//Writer fw = new FileWriter("day03-file-io\\src\\CodeBlossom01.txt")//覆盖管道
Writer fw = new FileWriter("day03-file-io\\src\\CodeBlossom01.txt",true)//追加数据管道
){
//2、写数据(字符,字符串都可以)
//分行
fw.write('a');
fw.write("\r\n");
fw.write("你好");
fw.write("hello world");
fw.write("java");
//写一个字符数组出去
char[] chars = {'a','b','c','d','e'};
fw.write(chars);
fw.write("\r\n");
//写一个字节数组的一部分出去
fw.write(chars,0,3);
fw.write("\r\n");
//写字符串出去
fw.write("hello world");
fw.write("\r\n");
//写字符串的部分出去
fw.write("hello world",0,5);
//字符输出管道生效需要关闭流或刷新流,原因是:管道内有内存缓冲区,相对于每次都调用io流输入数据速度较快
//当刷新流或关闭流时,管道内缓冲区内的数据会全部输出,刷新管道。
fw.flush(); //刷新管道,将缓冲区的数据全部写出去
//刷新后,缓冲区清空,流还可以继续使用,关闭包含了刷新,但关闭后流不能继续使用
}catch (Exception e){
e.printStackTrace();
}
}
}
文件字符输出流的生效需要需要关闭管道或者刷新管道,因为管道内有内存缓冲区,相对于每次都调用io流输入数据速度较快,刷新管道或关闭管道可以让缓冲区内的数据进入内存,如果没有刷新或关闭管道就查看文件会因为缓冲区内的数据还没进入文件而看不到输入内容。
刷新管道
变量名.flush();
文件字符输出流
package FileWriterDemo;
import java.io.FileWriter;
import java.io.Writer;
public class FileWriterDemo1 {
public static void main(String[] args) {
//目标:搞清楚文件字符输出流的使用:写字符出去的流。
//1、创建一个字符输出流对象,指定写出的目的地
try(
//Writer fw = new FileWriter("day03-file-io\\src\\CodeBlossom01.txt")//覆盖管道
Writer fw = new FileWriter("day03-file-io\\src\\CodeBlossom01.txt",true)//追加数据管道
){
//2、写数据(字符,字符串都可以)
//分行
fw.write('a');
fw.write("\r\n");
fw.write("你好");
fw.write("hello world");
fw.write("java");
//写一个字符数组出去
char[] chars = {'a','b','c','d','e'};
fw.write(chars);
fw.write("\r\n");
//写一个字节数组的一部分出去
fw.write(chars,0,3);
fw.write("\r\n");
//写字符串出去
fw.write("hello world");
fw.write("\r\n");
//写字符串的部分出去
fw.write("hello world",0,5);
//字符输出管道生效需要关闭流或刷新流,原因是:管道内有内存缓冲区,相对于每次都调用io流输入数据速度较快
//当刷新流或关闭流时,管道内缓冲区内的数据会全部输出,刷新管道。
fw.flush(); //刷新管道,将缓冲区的数据全部写出去
//刷新后,缓冲区清空,流还可以继续使用,关闭包含了刷新,但关闭后流不能继续使用
}catch (Exception e){
e.printStackTrace();
}
}
}
try(){
}catch(){ }方法
因为在使用管道后需要关闭管道释放资源,而单纯的用变量名,close()关闭管道时,如果前面的代码出了故障导致没有执行关闭操作,就会浪费资源,因此用这个方法来使用输入/输出流。当退出try方法时会自动关闭管道,创建管道在try后的括号中,创建的最后一个管道可以不写;作为分隔符。
字节流做复制
package CopyDemo;
import java.io.*;
public class CopyDemo3 {
public static void main(String[] args) {
//目标:使用字节流完成文件的复制操作
//源文件:E:\材料.docx
//目标文件:D:\材料1.docx(复制过去必须带文件名)
copyFile("E:\\材料.docx","D:\\材料1.docx");
}
//复制文件
public static void copyFile(String srcPath,String destPath){
//1、创建一个文件字节输入流管道与源文件接通
try(
//这里只能放置资源对象,用完后会自动调用close方法关闭
InputStream fis=new FileInputStream(srcPath);
OutputStream fos= new FileOutputStream(destPath);
MyConn conn = new MyConn();
){
//2、读取一个字节数组,写入一个字节数组
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer))!=-1) {
fos.write(buffer,0,len);
}
System.out.println("复制成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MyConn implements Closeable{
@Override
public void close() throws IOException{
System.out.println("关闭了");
}
}
缓冲输入流
因为内存与硬盘的交互需要的性能较高,因此可以用缓冲流来包装低级流,来减少内存损耗。缓冲流内默认有8KB的缓冲区,当与输入流连接时数据先进入缓冲区再进入内存,可以减少内存与硬盘的交互次数。
缓冲字节输入流/缓冲字节输出流
package BufferedInputStreamDemo;
import java.io.*;
public class CopyDemo3 {
public static void main(String[] args) {
//目标:字节缓冲
//源文件:E:\材料.docx
//目标文件:D:\材料1.docx(复制过去必须带文件名)
copyFile("E:\\材料.docx","D:\\材料1.docx");
}
//复制文件
public static void copyFile(String srcPath,String destPath){
try(
InputStream fis=new FileInputStream(srcPath);
//把低级的字节输入流包装成高级的缓冲字节输入流
InputStream bis = new BufferedInputStream(fis);//有8kB的缓冲区
OutputStream fos= new FileOutputStream(destPath);
//把低级的字节输出流包装成高级的缓冲字节输出流
OutputStream bos = new BufferedOutputStream(fos);//有8kB的缓冲区
MyConn conn = new MyConn();
){
//2、读取一个字节数组,写入一个字节数组
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer))!=-1) {
bos.write(buffer,0,len);
}
System.out.println("复制成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MyConn implements Closeable{
@Override
public void close() throws IOException{
System.out.println("关闭了");
}
}
MyConn类可以用于验证前面对try(){}catch(){}方法的解释。管道关闭的方法接入了Closeable接口,这里这个类模拟了管道关闭的过程,当try方法结束管道关闭会自动执行close方法。
缓冲字符输入流
具有按行读取文本的能力
语法:对象名.readLine()
ackage BufferedReaderDemo;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;
public class BufferedReaderDemo1 {
public static void main(String[] args) {
//搞清楚缓冲字符输入流读取字符内容,性能提升了,多了按行读取文本的能力
try (
//1、创建文件字符输入流与源文件接通
Reader fr = new FileReader("day03-file-io\\src\\CodeBlossom02.txt");
//创建缓冲字符输入流包装低级的字符输入流
BufferedReader br = new BufferedReader(fr) //要用独有的换行功能,所以不用多态
){
//定义一个字符串变量用于记住每次读取的一行数据
String line;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
//目前读取文本最优雅的方案:性能好、不乱码、可以按行读取
}catch (Exception e){
e.printStackTrace();
}
}
}
缓冲字符输出流
多了换行功能:对象名.nextLine()
package BufferedWriterDemo;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
public class BufferedWriterDemo1 {
public static void main(String[] args) {
//目标:搞清楚缓冲字符输出流的使用:提升了字符输出流的写字符的功能,多了换行功能
try(
//1、创建一个字符输出流对象,指定写出的目的地
//Writer fw = new FileWriter("day03-file-io\\src\\CodeBlossom01.txt")//覆盖管道
Writer fw = new FileWriter("day03-file-io\\src\\CodeBlossom02.txt",true);//追加数据管道
//2、创造一个缓冲字符串输出流对象,把字符输出流对象作为构造器参数传递给缓冲字符输出流对象
BufferedWriter bw = new BufferedWriter(fw)
){
//2、写数据(字符,字符串都可以)
//分行
bw.write('a');
bw.write("\r\n");
bw.write("你好");
bw.write("hello world");
bw.write("java");
//写一个字符数组出去
char[] chars = {'a','b','c','d','e'};
bw.write(chars);
bw.newLine();//换行
//写一个字节数组的一部分出去
bw.write(chars,0,3);
bw.newLine();//换行
//写字符串出去
bw.write("hello world");
bw.newLine();//换行
//写字符串的部分出去
bw.write("hello world",0,5);
//字符输出管道生效需要关闭流或刷新流,原因是:管道内有内存缓冲区,相对于每次都调用io流输入数据速度较快
//当刷新流或关闭流时,管道内缓冲区内的数据会全部输出,刷新管道。
bw.flush(); //刷新管道,将缓冲区的数据全部写出去
//刷新后,缓冲区清空,流还可以继续使用,关闭包含了刷新,但关闭后流不能继续使用
}catch (Exception e){
e.printStackTrace();
}
}
}
特殊流:打印流
可以实现更方便,更高效的打印数据,能实现打印的是什么出去的就是什么。
package PrintStreamDemo;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
public class PrintStreamDemo1 {
public static void main(String[] args) {
//目标:掌握PrintStream的简单使用
try(
//PrintStream ps = new PrintStream("day03-file-io/src/ps.txt");//字节流
//PrintWriter ps = new PrintWriter("day03-file-io/src/ps.txt");//字符流
//低级管道才支持追加
PrintStream ps = new PrintStream(new FileOutputStream("day03-file-io/src/ps.txt",true));
){
ps.println("hello");
ps.println("world");
ps.println(97);
}catch (Exception e) {
e.printStackTrace();
}
}
}
特殊流:数据输出流
允许把数据和其类型一起写进去,读取时可以保持其原本的数据类型,但得用数据输入流用对应的数据类型读取,不然会乱码
package PrintStreamDemo;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class DataStreamDemo2 {
public static void main(String[] args) {
//特殊数据流的使用
//不仅记载数据,还可以保存数据类型
try(
DataOutputStream dos = new DataOutputStream(new FileOutputStream("day03-file-io/src/ps.txt"));
){
dos.writeInt(100);
dos.writeDouble(3.14);
dos.writeBoolean(true);
dos.writeUTF("hello world");
}catch (Exception e){
e.printStackTrace();
}
}
}
package PrintStreamDemo;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class DataStreamDemo3 {
public static void main(String[] args) {
//特殊数据流的使用
//不仅记载数据,还可以保存数据类型
try(
DataInputStream dis = new DataInputStream(new FileInputStream("day03-file-io/src/ps.txt"));
){
System.out.println(dis.readInt());
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
System.out.println(dis.readUTF());
}catch (Exception e){
e.printStackTrace();
}
}
}
重点:IO框架
框架是一个预先写好的代码库或一组工具,旨在简化和加速开发功能。
IO框架封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。
导入IO框架的步骤
Download Apache Commons IO – Apache Commons IO
在这个网站下载
这个压缩包。
在项目中创造一个lib文件夹,把压缩包解压得到的common-io-2.19.0.jar复制到lib文件夹,在jar文件上点击右键,选择加入库然后确定,就能在类里导包使用了。
以上是IO流中的部分方法
package CommonsioDemo;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
public class CommonsIoDemo1 {
public static void main(String[] args) throws Exception{
//目标:IO框架
//拷贝文件
//被拷贝文件 //拷贝到哪个位置
FileUtils.copyFile(new File("day03-file-io\\src\\csb_copy"),new File("day03-file-io\\src\\csb1.txt"));
//JDK7提供的
//Files.copy(Path.of("day03-file-io\\src\\csb_copy"),Path.of("day03-file-io\\src\\csb2.txt"));
//删除文件夹(只能删文件夹,文件不能删)\
FileUtils.deleteDirectory(new File("E:\\aaa"));
}
}
在开发中,我们通常使用IO框架,以上的各种流则是实现框架的底层原理。