文件IO之 File 类和 InputStream, OutputStream 的用法

目录

  • 如何对文件操作
  • File 类
    • 构造方法
    • 普通方法
  • 文件内容的读写
    • InputStream 的使用
    • 读操作
    • OutputStream 的使用
  • 文件操作案例

如何对文件操作

文件是存储在硬盘上的, 直接通过代码操作硬盘不方便, 就在内存中创建一个对应的对象, 操作这个对象就可以间接的影响到硬盘中的文件了.

Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不代表真实存在该文件。

File 类

构造方法

文件IO之 File 类和 InputStream, OutputStream 的用法_第1张图片

普通方法

文件IO之 File 类和 InputStream, OutputStream 的用法_第2张图片

代码1(文件获取):

public class Test {
    public static void main(String[] args) throws IOException {
        File file = new File("./hello.txt");
        System.out.println(file.getParent());  // 获取File对象父目录文件路径
        System.out.println(file.getName());    //获取File对象名称
        System.out.println(file.getPath());    //获取File对象文件路径
        System.out.println(file.getAbsolutePath());  //获取File对象绝对路径
        System.out.println(file.getCanonicalPath()); //获取File对象修饰过的绝对路径
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第3张图片
代码2(文件的删除创建):

public class Test {
    public static void main(String[] args) throws IOException, InterruptedException {
        File file = new File("hello.txt");
        System.out.println("1 :" + file.exists());     //判断File对象代表文件是否存在
        System.out.println("2 :" + file.isDirectory()); //判断File对象代表文件是否是目录
        System.out.println("3 :" + file.isFile());    //判断File对象代表文件是否是一个普通文件
        System.out.println("4 :" + file.createNewFile());  //创建File对象代表文件, 成功返回true
        System.out.println("5 :" + file.createNewFile());
        System.out.println("6 :" + file.exists());
        System.out.println("7 :" + file.isDirectory());
        System.out.println("8 :" + file.isFile());
        Thread.sleep(5000);   //睡眠5s 便于观察文件是否存在
        System.out.println("9 :" + file.delete());  //删除该文件, 成功返回true
        System.out.println("10 :" + file.exists());
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第4张图片
在在睡眠 5s 时, 我们观察到, 文件确实被创建出来了.
文件IO之 File 类和 InputStream, OutputStream 的用法_第5张图片
代码3(另一种文件删除:deleteOnExit) :

public class Test {
    public static void main(String[] args) throws IOException, InterruptedException {
        File file = new File("hello.next");
        System.out.println(file.createNewFile());
        System.out.println(file.exists());
        file.deleteOnExit();
        System.out.println(file.exists());
        Thread.sleep(5000);
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第6张图片
观察文件目录发现, hello.txt 文件在程序运行结束时被删除了.

代码4(目录创建) :

public class Test {
    public static void main(String[] args) {
        File file = new File("目录");
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
        System.out.println(file.mkdir());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第7张图片
成功创建目录 :
文件IO之 File 类和 InputStream, OutputStream 的用法_第8张图片
创建子目录

public class Test {
    public static void main(String[] args) {
        File file = new File("a/b");  //要求两个目录不存在
        System.out.println(file.mkdir());  //没有中间目录不能创建
        System.out.println(file.mkdirs());  //没有中间目录 就会创建中间目录
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第9张图片
文件IO之 File 类和 InputStream, OutputStream 的用法_第10张图片
代码4(文件重命名) :

public class Test {
    public static void main(String[] args) throws IOException {
        File file1 = new File("hello.txt");  //该文件得存在
        file1.createNewFile();
        File file2 = new File("world.txt");  //要求该文件不存在
        System.out.println(file1.renameTo(file2));
        System.out.println(file1.exists());
        System.out.println(file2.exists());
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第11张图片
文件IO之 File 类和 InputStream, OutputStream 的用法_第12张图片
可以理解为 hello.txt 的数据换了个名字继续存在.

文件内容的读写

针对文本文件, 提供了一组类, 统称为 “字符流”, 典型代表 : Reader, Writer. (读写的基本单位是字符)
针对二进制文件, 提供了一组类, 统称为 “字节流”, 典型代表 : InputStream, OutputStream (读写的基本单位是字节)

所谓的 “流”, 就相当于水流, 打开水龙头有水流出来, 就可以通过水流来接水.
同样的如果我们不用了, 也要给它关闭上, 不然会造成资源的浪费.

每种流对象又分为两种 :
输入流 : Reader, InputStream
输出流 : Writer, OutputStream
(注: 这里输入输出, 针对的是CPU来讨论的, 输出就是从硬盘输出到内存, 输入就是从内存写入硬盘)

InputStream 的使用

在这里插入图片描述
可以看到, InputStream 是个抽象类, 不能直接实例化, 要使用还需要具体的实现类.
因为 InputStream 是进行 IO 访问的类, 而 IO 不仅仅是读写硬盘的文件, 在网络编程里还可以用来读写网卡.
所以 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,因为我们现在只关心从文件中读取,所以使用 FileInputStream.
文件IO之 File 类和 InputStream, OutputStream 的用法_第13张图片

        InputStream inputStream = new FileInputStream("test.txt");

意思是 : 打开 hello.txt 文件, 让变量 inputStream 能够和硬盘上的此文件关联起来, 就像有了遥控器, 以后我们要对该文件进行操作, 就可以通过该变量, 间接操作了.
当然, 如果该文件不存在, 就会抛异常 : FileNotFoundException

注意 :

当我们不需要对这个文件操作时, 就要把这个文件进行关闭, 就像打开水流接水, 不用了也得关.

关闭文件用到了 close 方法.

	inputStream.close();

补充 :

为啥不关就会造成为文件资源泄漏呢 ?
这里的资源主要就是指 : 文件描述符表, 它是进程的一个结构.
进程的结构是使用 PCB 来表示的.
包括了 : 1.pid, 2.内存指针, 3.文件描述符表
文件描述符表就是记载了当前进程都打开了哪些文件, 每打开一个文件, 就会在表里申请一个位置.
这个表就可以当成一个数组, 数组下标就是文件描述符, 数组元素就是这个文件在内核中的结构体表
示.
遗憾的是, 这个表长度有限, 不能无休止的打开且不释放, 一旦满了 就会打开失败. 这就是文件资源泄漏.

    public static void main(String[] args) throws IOException {
        InputStream inputStream = new FileInputStream("d:/test.txt");
        //...代码
        inputStream.close();
    }

其实在中间代码执行过程中, 还可能出现问题, 比如中间就 return 了, 这样同样会导致文件资源泄漏.
我们可以利用 try :

    public static void main(String[] args) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream("d:/test.txt");
        }finally {
            inputStream.close();  //这里的代码一定会被执行到
        }
    }

上述代码还可以进行优化, 变得更简洁 :

    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("d:/test.txt")) {           
        }
    }

这个操作叫 : try with resources, 也就是带有资源的 try 操作, 资源会在 try 代码块结束时, 自动执行 close 操作.

为啥它能写进 try 里面呢?
其实 InputStream 实现一个特定接口 : Closeable, 实现了该接口的类, 就可以写成这种语法.

读操作

文件IO之 File 类和 InputStream, OutputStream 的用法_第14张图片
以 read 来说明 : 从输入流读取数据的下一个字节。 读到的字节被作为 int 返回, 因为是字节, 所以范围就是 0~255 。 如果没有读到字节,可能已经到达流的末尾,则返回值-1 .

现在我们来读取数据, 首先在 d 盘创建个文件 :
文件IO之 File 类和 InputStream, OutputStream 的用法_第15张图片

public class Test {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("d:/test.txt")) {
            while(true) {
                int a = inputStream.read();  //为什么不用byte接收, 就是因为它的表示范围是 0 ~ 255,不能表示-1
                if(a == -1) {   //读到末尾结束循环
                    break;
                }
                System.out.printf("%d\n", a);  //以十进制打印每个字符
            }
        }
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第16张图片

java默认使用UTF-8 来进行编码, 我们可以通过查看字符编码网站 来检验 :文件IO之 File 类和 InputStream, OutputStream 的用法_第17张图片
网址链接

换个内容试试 :
文件IO之 File 类和 InputStream, OutputStream 的用法_第18张图片
输出 :

文件IO之 File 类和 InputStream, OutputStream 的用法_第19张图片
对应的检验一下 :
文件IO之 File 类和 InputStream, OutputStream 的用法_第20张图片
为啥结果不同呢?
因为一个汉字是由三个字符组成的, 而之前是打印每个字符的10进制, 我们可以换成 16 进制打印每个字符 :

public class Test {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("d:/test.txt")) {
            while(true) {
                int a = inputStream.read();
                if(a == -1) {
                    break;
                }
                System.out.printf("%x\n", a);  //以 16 进制打印每个字符
            }
        }
    }
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第21张图片
这样就对应上了.

OutputStream 的使用

文件IO之 File 类和 InputStream, OutputStream 的用法_第22张图片
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream.

flush 的作用 :

我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。

public class Main {
	public static void main(String[] args) throws IOException {
		try (OutputStream os = new FileOutputStream("output.txt")) {
			os.write('H');
			os.write('e');
			os.write('l');
			os.write('l');
			os.write('o');
			// 不要忘记 flush
			os.flush();
		}
	}
}

文件IO之 File 类和 InputStream, OutputStream 的用法_第23张图片

文件操作案例

扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)

我们可以遍历目录, 遍历过程中查找文件内容, 类似于检索功能.
目录结构, 是 “N” 叉树, 数本身就是递归定义的, 通过递归来查找比较合理.

具体步骤 :

  1. 先递归遍历目录, 递归的把所给定目录下的所有文件都列举出来.
  2. 每次都打开一个文件, 并读取文件内容 (转化为String)
  3. 判定要查找的内容, 是否在上述文件内容中存在, 如果存在 即为所求.

这种查找方式是比较低效的, 小规模搜索还行, 如果要大规模搜索还得依赖 “倒排索引” 这样的数据结构.

public class IODemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //先让用户指定一个要搜索的根目录
        System.out.printf("请输入要扫描的根目录: ");
        File file = new File(scanner.next());
        //判断该目录是否存在
        if(!file.isDirectory()) {
            return;
        }
        //让用户输入要查找的词
        System.out.printf("请输入要查找的词: ");
        String str = scanner.next();
        //递归进行文件内容/目录的遍历
        FileTraversal(file,str);
    }

    private static void FileTraversal(File file, String str) {
        //列出当前 file 的内容
        File[] files = file.listFiles();
        //如果目录为空. 就遍历结束
        if(files == null) {
            return;
        }
        //遍历目录中的元素
        for (File f : files) {
            System.out.println("当前搜索到: " + f.getAbsolutePath());
            if(f.isFile()) {
                //如果是普通文件, 则读取内容进行比较
                String s = readFile(f);  //将内容读取出来
                if(s.contains(str) || f.getName().contains(str)) {  //比较
                    System.out.println(f.getAbsolutePath() + " 包含要查找的关键字!");
                }
            }else if(f.isDirectory()) {
                //如果是目录, 则进行递归操作
                FileTraversal(f,str);
            }else {
                //不是普通文件也不是目录, 不做处理
            }
        }
    }
    private static String readFile(File file) {
        //读取文件的整个内容, 并进行返回
        StringBuilder stringBuilder = new StringBuilder();
        try(Reader reader = new FileReader(file)) {
            while(true) {
                int c = reader.read();
                if(c == -1) {
                    break;
                }
                //强制转换为字符型, 进行拼接
                stringBuilder.append((char)c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }
}

输出 :
文件IO之 File 类和 InputStream, OutputStream 的用法_第24张图片

你可能感兴趣的:(JavaEE,java,文件系统常识,文件操作,文件IO)