> 技术文档 > Java:IO流——基础篇_java基础io

Java:IO流——基础篇_java基础io


目录

前言

一、File 类

1、概述

①构造方法

②实例对象

2、使用

①查看名称、路径、长度

②判断、创建和删除操作

③目录遍历操作

二、IO流

1、流的概念

2、流的分类

①按数据流向

②按数据类型

③按功能

3、字节

⑴FileInputStream——文件输入流

⑵FileOutputStream——文件输出流

⑶ByteArrayInputStream——内存输入流

⑷ByteArrayOutputStream——内存输出流

4、字符流

⑴文件字符流

⑵操作字节文件?


前言

        IO(Input/Output)流的概念源于早期计算机系统对数据传输的抽象

        在Java诞生前的1990年代

        C/C++等语言处理文件操作需要直接调用操作系统API

        代码繁琐且不可移植

        Java的创造者们从Unix\"一切皆文件\"的哲学中获得灵感

        设计了统一的IO流模型

        使开发者能用一致的方式处理各种数据源

一、File 类

在介绍具体的IO操作前

我们先来学习一下File类

至于为什么,我们可以这么理解:

首先,File类并不是直接处理文件内容

而是专门用来操作文件和目录的路径信息

就像一个 导航 一样

所有的IO流操作都需要一个明确的目标

从哪个文件读取数据?

要向哪个文件写入数据?

这就需要File类来指定

1、概述

java.io.File 类

是文件和目录路径名的抽象表示

主要用于文件和目录的创建、查找和删除等操作

①构造方法

先来看一下源码:

package java.io;public class File implements Serializable, Comparable{    //通过将给定路径名字符串来创建新的 File实例 public File(String pathname) {        if (pathname == null) {            throw new NullPointerException();       }        this.path = fs.normalize(pathname);        this.prefixLength = fs.prefixLength(this.path);   }        //从【父级路径名和子路径名字符串】创建新的 File实例 public File(String parent, String child) {        //省略...   }     //用【父级对象和子路径】创建新的 File实例 public File(File parent, String child) {        //省略...   }    //省略...}

②实例对象

简单尝试一下:

import java.io.File;public class Test{ public static void main(String[] args){ String pathName = \"e:/vscode-java/day28/newFile.txt\"; String parent = \"e:/vscode-java/day28\"; String child = \"newFile.txt\"; //路径和文件名创建文件对象 File f1 = new File(pathName); //父级路径和子文件名创建文件对象 File f2 = new File(parent, child); //父级文件对象和子路径创建文件对象 File parentFile = new File(parent); File f3 = new File(parentFile, child); }}

一个 File 对象代表硬盘中实际存在的一个文件或者目录

无论该路径下是否存在文件或者目录,都不影响 File 对象的创建

2、使用

①查看名称、路径、长度

//File绝对路径名字符串public String getAbsolutePath();//File文件构造路径public String getPath();//File文件或目录的名称public String getName();//File文件或目录的长度public long length();

实操一下看看情况:

先来看文件:

此时假设文件 newFile.txt 中的内容为“hello world

package day28.fileimport java.io.File;public class Test { public static void main(String[] args) { String pathName = \"e:/vscode-java/day28/newFile.txt\"; String parent = \"e:/vscode-java/day28\"; String child = \"newFile.txt\"; //【针对文件】 //路径和文件名创建文件对象 File f1 = new File(pathName); //父级路径和子文件名创建文件对象 File f2 = new File(parent, child); //父级文件对象和子路径创建文件对象 File parentFile = new File(parent); File f3 = new File(parentFile, child); //返回文件的名称 System.out.println(\"fi.name:\" + f1.getName()); //返回文件的构造路径 System.out.println(\"fi.path:\" + f1.getPath()); //返回文件的绝对路径名字符串 System.out.println(\"fi.absolutePath:\" + f1.getAbsolutePath()); //返回文件的字节数 System.out.println(\"f1.length:\" + f1.length()); System.out.println(\"-----------------\"); //对比 getPath 和 getAbsolutePath File f4 = new File(\"e:/vscode-java/day28/newFile.txt\"); File f5 = new File(\"newFile.txt\"); //返回文件构造路径 System.out.println(\"f4.path:\" + f4.getPath()); System.out.println(\"f5.path:\" + f5.getPath()); //jvm会根据当前的工作目录来确定文件的绝对路径 System.out.println(\"f4.absolutePath:\" + f4.getAbsolutePath()); System.out.println(\"f5.absolutePath:\" + f5.getAbsolutePath()); }}

这里注意:

        在了解 getPath() getAbsolutePath() 区别前

        首先要了解一个java运行的机制

        虽然我们编译出了 Test.class 文件

        

        我们下意识认为这就是它的全名了

        但是java中会认为它的全名是包含包名的

        此时也就是 “ day28.file.Test.class ”

        所以运行的时候

        要去包名的根目录输入运行命令 “ java day28.file.Test ”

        此时的工作目录是:e:/vscode-java

那么这个时候就可以回去看这句话了:

        jvm会根据当前的工作目录来确定文件的绝对路径

我们发现,这样生成的绝对路径,好像并不是正确的绝对路径

期望路径:  e:/vscode-java/day28/newFile.txt

实际路径:  e:/vscode-java/newFile.txt

        

恭喜你,发现了一个常见的 getAbsolutePath() 使用陷阱!

        

解决办法也很简单:

1、使用相对路径指定完整目录结构

// 正确方式:指定相对路径包含目录File correctFile = new File(\"day28/newFile.txt\");System.out.println(\"正确路径: \" + correctFile.getAbsolutePath());// 输出: e:\\vscode-java\\day28\\newFile.txt

2、使用完整绝对路径(推荐)

// 最可靠方式:使用完整绝对路径File absoluteFile = new File(\"e:/vscode-java/day28/newFile.txt\");System.out.println(\"绝对路径: \" + absoluteFile.getAbsolutePath());// 输出: e:\\vscode-java\\day28\\newFile.txt

3、动态构建路径

// 根据当前工作目录动态构建正确路径String currentDir = System.getProperty(\"user.dir\");File correctFile = new File(currentDir, \"day28/newFile.txt\");System.out.println(\"动态构建路径: \" + correctFile.getAbsolutePath());

目录大差不差

大家自己尝试即可

需要注意的就是:

        如果File对象是目录时

        调用length()方法

        那么返回值为0

        

原因:

        public long length()

作用:返回由此抽象路径名表示的文件的长度(字节为单位)

返回值:文件大小(字节),如果文件不存在或者发生IO错误则返回 0L

如果是目录则没有指定返回值,也返回0

②判断、创建和删除操作

//判断文件或目录是否存在public boolean exists();//判断是否是文件public boolean isFile();//判断是否是目录public boolean isDirectory();
//当且仅当具有该名称的文件尚不存在时,创建一个新的空文件public boolean createNewFile();//创建目录public boolean mkdir();//创建多级目录public boolean mkdirs();//文件或目录的删除public boolean delete();

实操看一下效果:

package day28.file;import java.io.File;import java.io.IOException;public class Test { public static void main(String[] args) { String parent = \"e:/vscode-java/day28\"; File parentFile = new File(parent); File f4 = new File(\"e:/vscode-java/day28/newFile.txt\"); File f5 = new File(\"newFile.txt\"); //判断文件是否存在 System.out.println(\"f4.exists:\" + f4.exists()); System.out.println(\"f5.exists:\" + f5.exists()); System.out.println(\"-----------------\"); //判断是文件还是目录 System.out.println(\"f4.isFile:\" + f4.isFile()); System.out.println(\"f5.isFile:\" + f5.isFile()); System.out.println(\"f4.isDirectory:\" + parentFile.isDirectory()); System.out.println(\"f5.isDirectory:\" + f5.isDirectory()); System.out.println(\"-----------------\"); File f6 = new File(\"Test.txt\"); //创建文件,注意异常处理 try { System.out.println(\"f6.createNewFile:\" + f6.createNewFile()); } catch (IOException e) { e.printStackTrace(); } System.out.println(\"-----------------\"); //删除文件 System.out.println(\"f6.delete:\" + f6.delete()); System.out.println(\"-----------------\"); File f7 = new File(\"newDir\"); //创建目录 System.out.println(\"f7.mkdir:\" + f7.mkdir()); //创建多级目录,会自动创建不存在的父目录 File f8 = new File(\"newDir/newDir\"); System.out.println(\"f8.mkdirs:\" + f8.mkdirs()); System.out.println(\"-----------------\"); //删除目录,只能删除空目录 //虽然指向完整目录,但是删除的时候只能删最下面一层的空目录 System.out.println(\"f8.delete:\" + f8.delete()); System.out.println(\"f7.delete:\" + f7.delete()); }}

这里要注意的点就是:

1、创建文件的时候由于源码抛出了异常,所以记得异常处理

2、删除操作时,虽然一次性指向了整个目录,但是只会删除最底端的一个空目录

     目录必须为空才可以删除

③目录遍历操作

package java.io;public class File implements Serializable, Comparable{ //省略...        //目录文件调用该方法,获取目录中所有子文件名,返回String数组    //其他文件调用该方法,返回null public String[] list();        //目录文件调用该方法,获取目录中所有子文件,返回File数组    //其他文件调用该方法,返回null    public File[] listFiles();        //目录文件调用该方法,获取目录中符合筛选条件的子文件,返回File数组    //其他文件调用该方法,返回null    public File[] listFiles(FileFilter filter); //省略...}

案例:

准备目录 e:/vscode-java/test,放入各类文件,并对其遍历:

import java.io.File;import java.io.IOException;public class Test{ public static void main(String[] args){ File f9 = new File(\"test\"); File[] files = f9.listFiles(); for (File file : files) { System.out.println(file.getName()); } File f10 = new File(\"test\"); f10.mkdir(); //放入各类文件 File f11 = new File(\"test/13-类加载、反射\"); File f12 = new File(\"test/01-java基础入门.pdf\"); File f13 = new File(\"test/01-java基础入门思路.mp4\"); File f14 = new File(\"test/11-File、IO流.pdf\"); try { //创建目录 f11.mkdir(); //创建文件 f12.createNewFile(); f13.createNewFile(); f14.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } }

二、IO流

1、流的概念

老生常谈的话题,计算机中的概念基本都是抽象的

流就像日常生活中的水

只不过它是以二进制的形式在程序设备之间流动传输

这里的设备可以是文件、网络、内存等

流具有方向性,分为输入输出

这里是以Java程序为参照点的

以文件为例:

数据从程序“流向”文件——输出流

数据从文件“流向”程序——输入流

2、流的分类

①按数据流向

  • 输入流(InputStream):数据从其他设备上读取到程序中的流
  • 输出流(OutputStream):数据从程序中写出到其他设备上的流

②按数据类型

  • 字节流:以字节为单位(byte),读写数据的流,父类 InputStream OutputStream
  • 字符流:以字符为单位(char),读写数据的流,父类 Reader Writer

几乎所有的流,都是派生自这四个抽象的父类

  • InputStream:字节输入流类型
  • OutputStream:字节输出流类型
  • Reader:字符输入流类型
  • Writer:字符输出流类型

③按功能

  • 节点流(原始流)
  • 增强流(包装流)

节点流是最基本的 IO流,直接与数据源或目标进行交互,缺乏一些高级功能

增强流在节点流基础上提供了额外的功能和操作

本篇我们主要介绍节点流,下一篇介绍增强流

3、字节流

一切文件数据(文本、图片、视频等)都是以二进制数字的形式进行存储、传输

所以字节流可以传输任意文件数据

使用流操作数据基本步骤:

1、声明流

2、创建流

3、使用流

4、关闭流

InputStream OutputStream 有很多子类

我们先介绍最简单的:

⑴FileInputStream——文件输入流

用于从文件中读取字节数据的

源码(构造方法和 read()方法):

package java.io;public class FileInputStream extends InputStream{ //省略...     //构造方法    //通过File对象来创建一个 FileInputStream    public FileInputStream(File file) throws FileNotFoundException; //通过文件路径名(字符串)实例化FileInputStream对象 public FileInputStream(String name) throws FileNotFoundException; //read()方法 //逐个字节读取,返回值为读取的单个字节 public int read() throws IOException; //小数组读取,将结果存入数组,返回值为读取的字节个数 public int read(byte b[]) throws IOException; //小数组读取,存入数组指定位置,返回值为读取的字节个数 public int read(byte b[], int off, int len) throws IOException; //省略...}

注意,它们都会抛出异常,我们需要异常处理

下面示例为了可读性,我们选择继续抛出异常

①int read()

调用者:FileInputStream对象

参数:

返回值:读取的字节值(0-255),如果到达文件末尾返回 -1

作用:读取单个字节

注意事项:每次调用只能读取一个字节,效率较低

import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;public class Test { public static void main(String[] args) throws IOException, FileNotFoundException { String path = \"newFile.txt\"; File file = new File(path); //判断文件是否存在,不存在就新建一个 if(file.exists()){ System.out.println(\"文件存在\"); }else{ System.out.println(\"文件不存在\"); file.createNewFile(); } //通过文件对象创建流对象 InputStream is = new FileInputStream(file); //读取文件内容 int r; while((r = is.read()) != -1){ System.out.print((char)r); } is.close(); }}

注意:read()的调用需要放在循环里,否则只会读取一个字节

②int read(byte[] b)

调用者:FileInputStream对象

参数:byte[] b ——用于存储读取数据的字节数组

返回值:实际读取的字节个数,如果到达文件末尾返回 -1

作用:批量读取多个字节到数组中

public static void main(String[] args) throws IOException {    //1.创建流对象【IO流对象 跟 文件进行关联】    InputStream is = new FileInputStream(\"D:\\\\test\\\\a.txt\");    System.out.println(\"is: \" + is);    //2.读取文件内容    // int read(byte[] arr);    // 读多个字节,放入arr数组,返回成功读取字节数目,    // 如果到文件末尾,则返回-1    byte[] arr = new byte[10];    int len = is.read(arr);    System.out.println(\"成功读取字节数目:\" + len);    //遍历数组有效内容    for(int i = 0; i < len; i++)        System.out.println(arr[i]);    System.out.println(\"------------\");    //再次读取    len = is.read(arr);    System.out.println(\"第二次读取: \" + len); // -1    //3.关闭流对象,释放资源    is.close();}//输出结果:is: java.io.FileInputStream@7852e922成功读取字节数目:3979899------------第二次读取: -1

③int read(byte[] b, int off, int len)

调用者:FileInputStream对象

参数:byte[] b ——用于存储读取数据的字节数组

           int off    ——指定在字节数组中开始存储数据的位置

           int len   ——要读取的最大字节数

返回值:实际读取的字节个数,如果到达文件末尾返回 -1

作用:批量读取多个字节到数组中

public static void main(String[] args) throws Exception {    //1.创建流对象    InputStream is = new FileInputStream(\"D:\\\\test\\\\a.txt\");    System.out.println(\"is: \" + is);    //2.读取    // 读取5个字节往arr中 往后偏移3个位置 放入    // 如果读取成功,则返回实际读取长度    // 如果返回-1,则表示读取到文件末尾    byte[] arr = new byte[10];    int len = is.read(arr,3,5); //arr[ , , , a, b, c, ...]    System.out.println(\"成功读取: \" + len);    //遍历数组所有内容    for(int i = 0; i < arr.length; i++)        System.out.print(arr[i] + \" \");    //3.关闭资源    is.close();}//输出结果:is: java.io.FileInputStream@7852e922成功读取: 30  0  0  97  98  99  0  0  0  0

⑵FileOutputStream——文件输出流

用于写入字节数据到文件中

源码(构造方法 和 write()方法):

package java.io;public class FileOutputStream extends OutputStream{    //创建文件输出流以写入由指定的 File对象表示的文件。 public FileOutputStream(File file) throws FileNotFoundException;    //创建文件输出流以指定的名称写入文件 public FileOutputStream(String name) throws FileNotFoundException; //追加模式,如果第二个参数不写true默认为false,即写入的数据会覆盖原数据 //写上true之后,写入的数据会跟在原数据之后 public FileOutputStream(File file, boolean append) throws FileNotFoundException; public void write(int b) throws IOException; public void write(byte b[]) throws IOException; public void write(byte b[], int off, int len) throws IOException; //省略...}

注意:

  • 创建一个输出流对象时,传入的文件路径可以不存在,不会抛出异常,系统会自动创建这个文件,但是目录必须存在,系统不会自动创建
  • 如果有这个文件,系统默认会清空这个文件的数据

示例1

提前创建好目录 src/dir,用文件输出流写入字节到 src/dir/a.tx

public class Test_Write { public static void main(String[] args) throws Exception { //1.关联流对象和文件   // 实例化输出流时,目标文件a.txt不存在不会抛异常,系统会自动创建 // 但src/dir目录必须存在,系统不会自动创建目录 OutputStream os = new FileOutputStream(\"src/dir/a.txt\"); System.out.println(\"os: \" + os); //2.写数据 os.write(97); //a os.write(98); //b os.write(99); //c //3.关闭资源 os.close(); } public static void main02(String[] args) throws Exception { //1.关联流对象和文件   OutputStream os = new FileOutputStream(\"src/dir/a.txt\"); System.out.println(\"os: \" + os); //2.写数据        String str = \"abcd\"; byte[] arr = str.getBytes(); //将arr所有元素全部写入文件 //写入 会 覆盖 文件原有内容 os.write(arr); //3.关闭资源 os.close(); } public static void main03(String[] args) throws Exception { //1.关联流对象和文件   OutputStream os = new FileOutputStream(\"src/dir/a.txt\"); System.out.println(\"os: \" + os); //2.写数据       \'1\'\'2\'\'3\'\'4\'\'5\' byte[] arr = {49,50,51,52,53,54,55}; //os.write(arr,0,arr.length); //从arr[2]开始,获取arr数组的3个字节,即[51,52,53],然后写入a.txt //写入 会 覆盖 文件原有内容 os.write(arr,2,3);                // 写出一个换行, 换行符号转成数组写出        os.write(\"\\r\\n\".getBytes()); //3.关闭资源 os.close(); }}

示例2

将A.txt的内容拷贝到B.txt

public class Test_Copy { public static void main(String[] args) throws Exception { //1.关联文件和流对象 InputStream is = new FileInputStream(\"src/dir/A.txt\"); OutputStream os = new FileOutputStream(\"src/dir/B.txt\"); //2.拷贝 //2.1 逐个字节拷贝 // int r; // while((r = is.read()) != -1) { // os.write(r); // } //2.2 小数组拷贝,使用最多 byte[] arr = new byte[8]; int len; while((len = is.read(arr)) != -1) { //注意事项:读取多少个字节 就写出多少个字节 os.write(arr,0,len); } System.out.println(\"拷贝完成\"); //3.关闭资源   //注意:先关闭后打开的,后关闭先打开的 os.close(); is.close(); }}

⑶ByteArrayInputStream——内存输入流

使用文件流,我们可以操作文件中的数据

使用内存流,我们可以操作内存中字节数组中的数据

用于读取内存中的字节数组中的数据

源码:

package java.io;public class ByteArrayInputStream extends InputStream {    protected byte buf[];    protected int pos;    protected int count;    //关键构造器    public ByteArrayInputStream(byte buf[]) {        this.buf = buf;        this.pos = 0;        this.count = buf.length;   }    //省略...}

示例:

1.从键盘录入1行字符串,将其转换为byte[]

2.由byte[]构建一个内存输入流对象

3.从内存输入流中用小数组方式读取数据,并写入到 src\\dir\\b.txt 文件中

4.关闭流、释放资源

public class Test_ByteArrayInput { public static void main(String[] args) throws Exception { //1.实例化sc对象 并录入一行字符串 Scanner sc = new Scanner(System.in); System.out.println(\"input line:\"); String line = sc.nextLine(); //2.将字符串转换成字节数组 byte[] bytes = line.getBytes(); //由 字节数组 构建 内存输入流对象 InputStream is = new ByteArrayInputStream(bytes); //创建文件输出流对象 OutputStream os = new FileOutputStream(\"src/dir/b.txt\"); //3.小数组方式读取内存流数据 byte[] arr = new byte[5]; int len; while((len = is.read(arr)) != -1) { //4.写入b.txt文件中 os.write(arr, 0, len); } System.out.println(\"文件操作完成!\"); //5.关闭流、释放资源 os.close(); // 注意:内存流不需要关闭 }}

⑷ByteArrayOutputStream——内存输出流

用于把数据写入到内存中的字节数组中

源码:

package java.io;public class ByteArrayOutputStream extends OutputStream {    //存储数据的数组    protected byte buf[];    //存入字节数组的元素(字节)个数    protected int count;    //无参构造器创建的字节数组输出流,数组大小为32个字节    public ByteArrayOutputStream() {        this(32);   }        //关键方法:获取内存输出流中存储的数据,返回字节数组    public synchronized byte toByteArray()[] {        return Arrays.copyOf(buf, count);   }        //省略...}

示例:

        读取 src/dir/a.txt 文件中的所有内容,写入到字节数组输出流 中,然后从字节输出流中获取所有数据,最后转换成String字符串输出

public class Test_ByteArrayOutput { public static void main(String[] args) throws IOException { //1.关联流对象和文件 // 创建内存输出流对象[new byte[32]] InputStream is = new FileInputStream(\"src/dir/a.txt\"); ByteArrayOutputStream os = new ByteArrayOutputStream(); //2.读取文件内容 然后写入到 内存输出流中 byte[] arr = new byte[8]; int len; while((len = is.read(arr)) != -1) { //写入 内存输出流 os.write(arr, 0, len); } System.out.println(\"拷贝完成!\"); //3.关键方法:获取内存输出流中的数据 byte[] byteArray = os.toByteArray(); //4.将byte[] --> String 并输出 System.out.println(new String(byteArray));                //5.注意:内存流不需要close()释放资源 }}

再次强调,内存流使用完不需要 close() 释放资源

内存流的底层数据源是内存中的字节数组

会自动被垃圾回收器回收

4、字符流

本质上字符流底层借助字节流实现的

相比于字节流,字符流提供了更高效方便的字符处理方式

可以直接读写字符,无需进行字节与字符的转换

这使得字符流更适合处理文本数据

Reader Writer 同样有很多子类

我们依旧从最简单的开始:

⑴文件字符流

FileReader——用于读取字符文件

FileWriter——用于写出字符到文件

源码:

package java.io;public class FileReader extends InputStreamReader {    public FileReader(String fileName) throws FileNotFoundException;        public FileReader(File file) throws FileNotFoundException;        //省略...}public class FileWriter extends OutputStreamWriter {    public FileWriter(String fileName) throws IOException;     public FileWriter(String fileName, boolean append) throws IOException;     public FileWriter(File file) throws IOException;     public FileWriter(File file, boolean append) throws IOException;        //省略...}

示例:

使用文件字符流拷贝a.txt的文件内容到b.txt的文件末尾

public class Test025_FileReaderWriter { public static void main(String[] args) throws IOException { // 1.实例化流对象 File file1 = new File(\"src/dir/a.txt\");        File file2 = new File(\"src/dir/b.txt\"); Reader reader = new FileReader(file1); // 设置文件追加 Writer writer = new FileWriter(file2,true);         // 2.使用流进行文件拷贝        int len = -1;        char[] buf = new char[8];        while ((len = reader.read(buf)) != -1) {       writer.write(buf, 0, len);       }        //刷新流        writer.flush();         // 3.关闭流        writer.close();        reader.close(); }}

⑵操作字节文件?

示例:

使用文件字符流拷贝图片

仿照前面的案例即可,略微修改文件名即可

会发现拷贝的图片打不开

所以字符流只能操作文本文件

不能操作图片、视频等非文本文件

基础节点流差不多就介绍这些,我们下篇开始介绍增强流