文件操作—IO

目录

一、关于IO

1.学习IO目的

2.什么是I/O

二、文件(File)

文件系统(FileSystem)

1.硬盘存储和内存存储的区别【硬盘的特点】

1)非易失性存储与低速存储

2)各种的读写速度:

3)对低速的应对方案

4)对于低速的出现硬盘中数据的读取

5)由于低速性—提出缓冲区(buffer)技术

2.文件的数据组成

1)对数据的认识

2)文件本身的数据组成

3)总结文件的分类

3.文件的位置(location)/路径(Path)⭐⭐

1)绝对路径(absolute path)

2)相对路径(relative path)

4.File对象

1)创建File对象

2)Java程序运行时的“我们”身处何处?

3)File类的常见方法

拓展:endsWith()方法:用来检测字符串是否以指定后缀结束

4)文件创建与删除

a.普通文件的创建

b.目录文件的创建

c.删除文件

5.普通文件的内容读取

 1)InputStream(抽象类)

2)读方法:

6.输出

1)OutPutStream

2)写的方法

7.练习

1)文本替换工具

2)文件的复制(任意类型的文件)

3)文件夹的复制


一、关于IO

1.学习IO目的

      Java比较少用于系统开发(开发系统软件),比较多的是用于业务开发,所以不太多直接去用IO进行操作,一般是在上层封装上做一些工作,但是还是应该简单学习IO。

1)学习基本理论(对基本常识要有基本认识),尤其是关于网络(对于网卡中数据的读写问题)、文件IO(硬盘中的数据)

2)网络是我们学习的重点,会应用到IO编写一些实例程序,所以需要了解一定的IO开发流程

2.什么是I/O

      计算机硬件中,非内存数据的读写动作(表现为输入设备和输出设备的读写过程),当下以文件(硬盘中的数据)IO为主。【ps:我们之前接触过的IO->System.in、System.out、System.err】

二、文件(File)

      狭义的文件是关于硬盘中数据的抽象。一种抽象的概念,表示硬盘中的数据块。【变量、对象是对内存中数据的抽象,文件是对硬盘中数据的抽象】

文件系统(FileSystem)

      对硬盘上的数据——文件进行直接组织和管理的一个模块。作为应用开发者很少关注文件系统本身,只需要关注到文件这个层级即可。

1.硬盘存储和内存存储的区别【硬盘的特点】

1)非易失性存储与低速存储

      硬盘存储是非易失性存储(non-volatile storage):可以在断电的情况下进行数据的保存,是低速存储:读写速度远低于内存存储。

      L1-L4都是易失性存储(volatile storage),L5及往后都是非易失性存储。越往上读写速度快,存储容量小,越往下读写速度慢,存储容量大。硬盘:固态硬盘(SSD-Solid State Drive)、磁盘(HDD-Hard Disk Drive)

2)各种的读写速度:

表格解释:

墙上时间(真实时间)作为对比的时间
CPU cycle:CPU周期->CPU执行一条指令的时间(最快的)CPU执行一条指令0.3ns将CPU执行一条指令的时间假设成1s
CPU L1 Cache:读取CPU一级缓存读取L1缓存的时间是1ns
...
Main Mermory(RAM):读取内存,内存又称RAM(Randon Access Memory—支持随机访问的存储【正式因为这个特性,数组下标访问是O(1)】)读取内存时间120ns
SSD:固态硬盘读取固态硬盘150微秒
HDD:磁盘读取硬盘10ms
SF to NYC:SF:旧金山;NYC:纽约跨越m国整个东西部的网络读取时间40ms

      作为对比的时间是鉴于真实时间不好观察与对比所以写出来让我们观察的,如假设了CPU执行一条指令时间假设成1s,那么RAM执行时间是相当于6min的,从对比可以观察到,相对于CPU执行周期,内存执行特别慢...

3)对低速的应对方案

      为了应对低速,弥合速度差,所以针对硬盘数据的读写提出了各种各样的数据结构,算法来解决,尽可能的减少读写频次

4)对于低速的出现硬盘中数据的读取

硬盘中的数据更适合:

a.块状读取(block)而非字节读取(char)

      对于硬盘数据一下子读一整块进来,而非频繁进行读写。【单纯从IO来讲,就可以分为块状设备和字符设备(以字节为单位进行读取,不能一次读一大块)】

b.连续的读写而非随机的读写

      对数据每次进行读取是连续的(磁盘寻道的一些算法—电梯算法等,就是为了解决怎么尽可能让他连续读写而非随即读写)

      而内存中数据在哪个位置读写的性能都一样,可以随机读写

5)由于低速性—提出缓冲区(buffer)技术

      与缓存不一样,缓存更多是针对读的,缓冲区是针对写的。低速性的核心要点就是减少硬盘数据的读写频次,但是不可控,所以,以操作系统为代表,一般会在内存中开辟一段空间,这段空间被称为缓冲区,这时,次次都读写就被消化为先往内存的缓冲区中写(从内存写到内存),等达到一定的条件,再统一从内存中写回到硬盘中,从而有效减少了硬盘的读写频次。

1.一定条件是什么条件?

a.应用程序可以主动要求:冲刷缓冲区(flush操作),把缓冲区数据冲刷到硬盘中

b.定时冲刷:每隔一定的间隔时间冲刷

c.定量冲刷:满了之后必须写到硬盘

2.IO中flush操作必不可少,否则可能数据会丢失(遗留在缓冲区中的数据没被及时冲刷,造成数据丢失)

2.文件的数据组成

1)对数据的认识

      我们一般把数据抽象为数字(什么进制、什么宽度);如何解释这些数据->文本、像素、波。我们现在理解,数据可分为:

a.文本数据:可以按照一定的字符集编码解释称文本的数据

b.二进制数据:不可以直接解释成文本

从内容角度文件分为文本文件或二进制文件

2)文件本身的数据组成

a.逻辑上,文件由两块数据组成:内容数据+元数据(管理数据)

b.文件系统是按照树形结构进行文件的组织

      目录/文件夹(directory/dir)本身也是一类文件,只是一种特殊的文件。从这个角度将,又可以把文件分为两类:文件=普通文件(文件)或文件夹文件

3)总结文件的分类

a.普通文件 vs 目录文件(也叫文件夹文件)

b.二进制文件 vs 文本文件

3.文件的位置(location)/路径(Path)⭐⭐

      用于定位到是哪个文件的(用树的时间看,就是树上的一个节点)。文件按树形结构组织,通过路径(path)描述节点的方式,来唯一的确定一个文件

1)绝对路径(absolute path)

      从一棵树的根节点出发,到达对应节点的完整路径。一个唯一的路径一定可以描述一个唯一的节点

      eg:描述a.txt:C:Programs Files->Java->a.txt。windows上使用反斜杠\作为分隔符path separator,所以描述就是:C:\Programs Files\Java\a.txt,而写在程序上的话,反斜杠有转义的意思,所以应该对反斜杠先转义,写成:"C:\\Program Files\\Java\\a.txt"。

      路径存在,并不代表文件一定存在,路径是描述树中一个可能的位置,但并不代表这个位置一定有节点,如C:Windows\c.txt,路径合法,但文件c.txt并不存在。

2)相对路径(relative path)

      相对...而言的路径,不是从根出发,相对“我们”当前所在位置/当前工作目录current work directory cwd。从当前位置出发进行节点描述的路径,路径不变的情况,当前位置变化了,描述的节点也跟着变化。“.”代表当前位置,“..”代表上一层位置。

eg:找a.txt:若我们现在在Windows目录情况下和若我们在MySQL目录下:

      一般用两个点(..)来代表回到上一层,用一个点(.)代表在当前路径下,所以当前在Windows目录下的写成相对路径就是:..\Programs Files\Java\a.txt,MySQL目录下的:..\Java\a.txt。

a.从几个角度理解什么叫:我们身处何处?(什么叫当前目录)

1>从文件系统角度:我们当下所处的界面

2>从cmd(命令行)角度

 cd:change directory->修改目录

4.File对象

java提供了java.io.File类,File对象就是对文件的管理数据的抽象

1)创建File对象

File(File parent,String child)通过一个父目录创建
File(String pathname)通过一个绝对或相对路径,来创建File对象。File对象被创建,并不一定代表文件存在
File(String parent,String child)
package com.wy.file_demo;
//File(String pathname)
import java.io.File;
public class Main {
    public static void main(String[] args) {
        File file=new File("D:\\Users\\Dell\\Desktop\\学校相关作业\\wy.txt");
        System.out.println(file);
        //对象存在,文件wy.txt并不存在
    }
}
package com.wy.file_demo;

import java.io.File;

/**
 *File(File parent,String child)
 **/
public class Main {
    public static void main(String[] args) {
        {
            File file = new File("D:\\Users\\Dell\\Desktop\\学校相关作业\\wy.txt");
            System.out.println(file);
            //对象存在,文件wy.txt并不存在
        }
        {
            //父节点,一般都是目录
            File parent = new File("D:\\Users\\Dell\\Desktop\\学校相关作业");
            File file = new File(parent, "wy.txt");
            System.out.println(file);
            //结果和上面一样
        }
        {
            File file = new File("D:\\Users\\Dell\\Desktop\\学校相关作业\\", "wy.txt");//最后作业后面可以带斜杠也可以不带斜杠
            System.out.println(file);
        }
    }
}

2)Java程序运行时的“我们”身处何处?

      我们身处何处,是一个运行期间的概念,和代码编写的时候是没有关系的,每次运行就有自己唯一的身在何处。【如写了一个代码,交给A去用,A用户在哪个目录下启动,完全A决定】

      工作目录,在IDEA中是配置出来的(进程的启动目录)

      进程在运行过程中身处D:\Users\Dell\IdeaProjects\java_classcode\位置,但是不一定会处在这个位置,这个位置是配置来的,可以在IDEA中配置当前工作目录:

      默认是工程所在目录。这个可以修改,假若改成C:\,就是C:\\world.txt,可以看出,代码不变的情况下,外围的配置在变化,代码的运行结果可能会变。

3)File类的常见方法

返回值类型方法解释
booleancanExecute()可执行权限文件中是被OS进行了权限管理的,以用户为单位进行管理(普通用户/管理员用户)
booleancanRead()可读权限
booleancanWrite()可写权限
FilegetAbsoluteFile()AbsolutePath(绝对路径:D\User\...\a.txt);权威路径(D:\a.txt)
StringgetAbsolutePath()绝对路径
FilegetCanonicalFile()
StringgetCanonicalPath()权威路径
booleanisAbsolute()是否用绝对路径描述
booleanisDirectory()是否存在并且是目录
booleanisFile()是否存在并且是文件
booleanisHidden()是否存在并且是隐藏文件
booleanexists()判断文件是否存在
StringgetName()文件名
StringgetParent()父路径
longlength()文件大小
booleanrenameTo()通过重命名形式把一个文件移动到另一个节点
File[]listFiles()列出目录下的所有文件
listFiles()文件是目录时可以调用listFiles()查看这个目录下有哪些下一级的孩子节点

eg1:文件是否存在

eg2:是否是目录或普通文件

package com.wy.file_demo;

import java.io.File;
import java.io.IOException;

public class Main3 {
    public static void main(String[] args) throws IOException {
        File file=new File("..\\hello.txt");
        System.out.println(file.getAbsolutePath());//获取绝对路径——D:\Users\Dell\IdeaProjects\java_classcode\..\hello.txt
        System.out.println(file.getCanonicalPath());//权威路径—可以直接得到处理后的路径结果——D:\Users\Dell\IdeaProjects\hello.txt
        System.out.println(file.exists());//是否存在-hello.txt存在——true【常见】
        System.out.println(file.isDirectory());//是一个目录吗——false【常见】
        System.out.println(file.isFile());//是一个普通文件吗——true【常见】

        System.out.println(file.canExecute());//是否可执行——true
        System.out.println(file.canRead());//是否可读——true
        System.out.println(file.canWrite());//是否可写——true
    }
}

eg3:

public class Main4 {
    public static void main(String[] args) throws IOException {
        File file=new File("../hello.txt");//反斜杠/正斜杠\都可以,java可以自行处理
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());
        System.out.println(file.length());
        System.out.println(file.getName());
        System.out.println(file.getParent());
    }
}

结果:

D:\Users\Dell\IdeaProjects\java_classcode\..\hello.txt
D:\Users\Dell\IdeaProjects\hello.txt
0
hello.txt
..

eg4:

package com.wy.file_demo;

import java.io.File;

/**
 * renameTo()方法的使用
 * 把hello目录下的a.txt文件移动到world目录下【把目录的管理关系做了修改】
 **/
public class Main8 {
    public static void main(String[] args) {
        File src=new File("../hello/a.txt");
        File dest=new File("../world/a.txt");
        System.out.println(src.renameTo(dest));
    }
}

eg5:列出目录下的所有文件【普通文件且名字为xxx】

      本质上,就是进行一棵树的遍历,遍历时对所有节点做判断(深度优先方式进行遍历(递归))【用List<String>保存所有路径】

package com.wy.file_demo;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 找到目录下所有普通文件且文件名是图书的
 **/
public class 找到所有文件名为图书的文件 {
    static List<String> 所有图书=new ArrayList<>();
    private static void traversal(File dir) throws IOException {//传进来一个目录
        //遍历这个目录得到它的所有目录结构
        File[] files=dir.listFiles();//该目录下的所有文件/孩子
        if(files==null){
            return;
        }
        for (File file:files){
            if (file.isDirectory()) {//是目录吗
                traversal(file);
            }else if(file.isFile()){//是文件吗
                if(file.getName().equals("图书.txt")){
                    所有图书.add(file.getCanonicalPath());
                }
            }
        }

    }
    public static void main(String[] args) throws IOException {
       File starDir=new File("D:/Users/文件学习部分练习内容");//从D:/Users/文件学习部分练习内容目录开始遍历
        traversal(starDir);
        for (String path:所有图书){
            System.out.println(path);
        }
    }
}
//结果
//D:\Users\文件学习部分练习内容\a\图书.txt
//D:\Users\文件学习部分练习内容\b\图书.txt
//D:\Users\文件学习部分练习内容\c\图书.txt

拓展:endsWith()方法:用来检测字符串是否以指定后缀结束

4)文件创建与删除

a.普通文件的创建

前提:文件父目录存在并且文件不存在,才能创建成功

import java.io.File;
import java.io.IOException;

public class Main5 {
    public static void main(String[] args) throws IOException {
        File file=new File("../world.txt");
        System.out.println(file.createNewFile());//还没有这个文件时,创建成功,返回true
                                                 //若文件存在创建,就会返回false
    }
}

eg:

b.目录文件的创建

前提:要创建目录的父目录存在并且要创建的目录不存在(mkdir)

注意:如果想连同父目录一起创建,要使用到mkdirs()

public class Main6 {
    public static void main(String[] args) {
        //一级一级目录建
//        File f1=new File("../a");
//        f1.mkdir();//创建完a目录有了
//        File f2=new File("../a/b");
//        f2.mkdir();//a目录已经准备好了,所以再创建b目录就可以成功了
//        File f3=new File("../a/b/c");
//        f3.mkdir();
//        File f4=new File("../a/b/c/d");
//        f4.mkdir();

        //等价于下面:直接一起创建好几个目录

        //前提,目录全部不存在,若存在,还会报错
        File file=new File("../a/b/c");//a->b->c
        file=new File("../a/b/d");//a->b->d
        System.out.println(file.mkdirs());//true

    }
}

c.删除文件

前提:普通文件是正常删除,目录文件删除时要求目录必须是空目录

      windows上的回收站只是一个特殊的目录,回收站的删除其实不是删除,只是把文件移动到对应的目录。delete是立即删除,deleteOnExit是进程结束时删除。

eg1:找到给定目录下的所有符合条件的文件普通文件然后将文件删除

package com.wy.file_demo;
import java.io.File;

/**
 * 找到给定目录下所有符合条件的文件,然后删除
 **/
public class 找符合条件的文件并删除 {
    public static void main(String[] args) {
        File file=new File("D:/Users/文件学习部分练习内容");//文件起始所在目录
        //找到此目录下的所有world文件,打印路径然后删除
        findWorld(file);//当前目录传入
    }
    /**
     * 找到file目录下所有world文件并删除
     * @param file
     */
    private static void findWorld(File file) {
        File[] files=file.listFiles();//该目录下的所有文件
        if(files==null){
            return;
        }
        //不为空,判断是普通文件还是目录
        for (File file1:files){
            if(file1.isDirectory()){
                //是目录-在这个目录下继续找所找文件
                findWorld(file1);
            }else if(file1.isFile()){
                //是普通文件
                //判断是否是world文件
                if(file1.getName().equals("world.txt")){
                    //是world文件——执行删除
                    System.out.println(file1.delete());
                }
            }
        }
    }
}

上面集中在管理数据上进行了操作分析

5.普通文件的内容读取

 1)InputStream(抽象类)

      本身有Closeable,AutoCloseable这两个接口,表示水龙头可以打开也可以关闭。

AudioInputStream读音频的
FileInputStream读文件的,从文件中往出读
.................................

2)读方法:

abstract intread()一字节一字节读取,即一次读取一滴水【返回获取到的一字节的数据】读完标志:返回-1表示数据读完(EOS)
intread(byte[] b)一次读多个字节,即一次读一桶水,byte传进去的字节个数就是水桶水桶b有多大最多能读多少水,但不一定有b个数据返回值<=b,-1代表数据读完【返回数据大小】
intread(byte[] b,int off,int len)从off开始,有len字节供使用-1代表数据全部读完

a.读方法的使用

package com.wy.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

/**
  *   hello.txt中内容:123456空格hello【空格占两个字节:\r\n】
 **/
public class ReadFile1 {
    public static void main(String[] args) throws IOException {
//        FileInputStream is=new FileInputStream("hello.txt");
//        //等价于
//        File file=new File("hello.txt");
//        FileInputStream is1=new FileInputStream(file);
        //一般不去写实现类FileInputStream,写接口,因为用到的是接口中的方法
        //关注接口不要关注实现类
        InputStream is=new FileInputStream("hello.txt");
        byte[] b=new byte[20];
        int n=is.read(b);//读取文件内容到数组,返回字节大小
        System.out.println(n);//13,因为文件内容只有13个字节
        System.out.println(Arrays.toString(b));
        n=is.read();//因为内容只有13字节,上一步已经全部读取完毕,此时读取下一个字节会返回-1
        System.out.println(n);
        is.close();//记得关闭文件
    }
}
/**
 * 结果:13
 * [49, 50, 51, 52, 53, 54, 13, 10, 72, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0]
 * -1
 */

      假设new byte[10],第二次n读取的就是108,因为第一次is.read(b)只能读取10字节,所以is.read()就可以读取到第11个字节,拿到108。

注意:为了避免忘记关文件,我们可使用try(){},出了大括号文件会自动关闭

public static void main(String[] args) throws IOException {
    try(InputStream is=new FileInputStream("hello.txt")){
        byte[] b=new byte[20];
        int n=is.read(b);//读取文件内容到数组,返回大小
        System.out.println(n);//13,因为文件内容只有13个字节
        System.out.println(Arrays.toString(b));
        n=is.read();//因为内容只有13字节,上一步已经全部读取完毕,此时读取下一个字节会返回-1
        System.out.println(n);
    }//出了这个大括号自动会关闭文件,不需要手动close了
}
//注意,read(b)返回字节的数据大小(接了多少水),read()是返回下一个字节的数据(把下一滴水返回)
/**
 * 10
 * [49, 50, 51, 52, 53, 54, 13, 10, 72, 101]
 * 108
 */

b.不知道多少数据的情况下,如何is.read()确保读完所有数据

public class ReadFile2 {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            while(true){
                int b = is.read();
                if (b == -1) {//读到数据为-1时读取完毕
                    break;
                }
                System.out.println(b);
            }
        }
    }
}

c.read(byte[] b)形式读完所有数据【等价于c】

public class ReadFile3 {
    public static void main(String[] args) throws IOException {
        try (InputStream is = new FileInputStream("hello.txt")) {
            byte[] buf = new byte[10];
            while (true) {
                int n = is.read(buf);
                if (n == -1) {//读到的字节长度等于-1时,读取完毕
                    break;
                }
                System.out.println(Arrays.toString(Arrays.copyOf(buf, n)));//使用copyOf,读取都是有效内容(0不会出现)
            }
        }
    }
}
/**结果:
 * [49, 50, 51, 52, 53, 54, 13, 10, 72, 101]
 * [108, 108, 111]
 */

硬盘的读写频次越少越好,所以new byte[n]的n越大越好,一般是1024或者它的倍数

d.read()和read(byte[] b)读取效率

      调用System.currentTimeMillis()获取读取前后时刻计算时间,会发现,读大量数据时,read()的效率比read(byte[] b)慢得多,这也就佐证了硬盘的读写频次越少越好这个观点,读的次数越少,执行效率就越高。

e.hello.txt读取中文时读法

上面hello.txt文本中是字符+数字,读取到的是对应ascii码值,而当hello.txt的内容是中文时,会存在问题。假如hello.txt内容是你 好 中 国回车中 国 你 好:

读到的结果是12个字节:[-28, -67, -96, -27, -91, -67, -28, -72, -83, -27, -101, -67]

解释:UTF-8是一种变长编码,每个字符占用的字节长度是不相等的,最典型的就是在ascii编码中,数字、英文占一个字节,中文大部分占3个字节,emoji(这种表情包:♥)一般就会占四个字节。使用纯二进制形式读取文本很不方便,我们需要查表来对应出来文字,很不方便。

其他:new Scanner(System.in)的System.in是InputStream类型,所以可以直接给Scanner传入InputStream。请按照utf-8的方式把输入进行字符转换,new Scanner(is,"UTF-8")代表按照utf-8形式把输入字符进行转换。

public class ReadText {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            Scanner scanner=new Scanner(is,"UTF-8");//将文本内容按utf-8形式读取出来
            //neextLine()是读到回车换行,next()是读到空格或回车换行
            while (scanner.hasNextLine()){
                System.out.println(scanner.nextLine());
            }
        }
    }
}
/**结果:(nextLine时)
 * 你 好 中 国
 * 中 国 你 好
 * (next()时):
 * 你
 * 好
 * 中
 * 国
 * 中
 * 国
 * 你
 * 好
 */
//站在进程的主体上,in是外部输入设备中把数据读进来

6.输出

out站在进程的主体上就是把进程中的数据经过外部设备输出出去,表现在磁盘上就是写文件

1)OutPutStream

常见实现类:

FileOutPutStream向某一个文件中输出
......

2)写的方法

abstract voidwrite(int b)给一个字节,把字节写入【虽然传的是int,但是是以字节形()式往文件里面写的】
void()write(byte[] b)与read(byte[] b)一样,传给一个水桶,水桶水全部倒入
flush()如果缓冲区还有数据,全部加载到硬盘中去

a.write的使用(下面方法写中文不行,会出错)

public class WriteFile1 {
    public static void main(String[] args) throws IOException {
        try(OutputStream os=new FileOutputStream("world.txt")){
            //输出是覆盖式输出,如果原来文件有内容,直接把原来内容全部清空,填充新内容
            os.write('H');//此时就把H的ascii码写入文件中了
            os.write('e');
            os.write('l');
            os.write('l');
            os.write('o');
            os.write('\r');
            os.write('\n');
            os.write('1');
            os.write('2');
            os.flush();//如果缓冲区还有数据,全部冲到硬盘中去
        }
    }
}
//此时生成了world.txt文件并且文件中写入了hello回车12
byte[] b={'H','e','l','l','o','\r','\n'};
os.write(b,0,3);//从数组的第0个字节写,一共写三个字节

b.中文字符写入到文件的写法

      SyStem.out是PrintStream类型,所以去构造一个PrintStream类型即可,PrintStream是一个早期类型,现在我们构造的一般是PrintWriter类型。

      如何构造:首先需要一个OutPutStream对象,这个对象是字节类型的,即处理的是二进制数据,先把它处理转化成字符(字符集编码)类型Writer【抽象类,一般直接用它的实现类OutPutStreamWriter】,有了OutPutStreamWriter就可以书写汉字了,再转为PrintWriter类型就可以书写了。

public class WriteText {
    public static void main(String[] args) throws IOException {
        try(OutputStream os=new FileOutputStream("world2.txt")){
            try(Writer writer=new OutputStreamWriter(os,"utf-8")){
                try(PrintWriter printWriter=new PrintWriter(writer)){
                    printWriter.println("你好世界");
                    printWriter.print("中国");
                    printWriter.printf("%s %d\r\n","走起来",123);
                    printWriter.flush();//调最底层这个flush就会把数据都刷新一次
                }
            }
        }
    }
}

7.练习

1)文本替换工具

文本中的“你好”全部替换改为“我好”,替换前在一个文件,替换后在新的文件里·

思路:读—替换—写

public class ReplaceTool {
    public static void main(String[] args) throws IOException {
        //搞定输入的对象
        try(InputStream is=new FileInputStream("src.txt")){//从src中读
            try(Scanner scanner=new Scanner(is,"utf-8")){//按utf-8读取到内容
                //搞定输出的对象
                //改后的值需要放入这个新文件中
                try(OutputStream os=new FileOutputStream("dest.txt")){
                    try(Writer writer=new OutputStreamWriter(os,"UTF-8")){
                        try(PrintWriter printWriter=new PrintWriter(writer)){
                            while(scanner.hasNextLine()){//只要scanner还有下一行
                                String line= scanner.nextLine();//就一直读
                                //读到数据,开始进行替换
                                String replace=line.replace("你好","我好");//字符串是不可变的,所以得到一个新的字符串才有意义,光调用没有用
                                //把新的字符串repalce再写到某个新的文件中即可,这里是dest
                                //调用printerWriter.println()
                                printWriter.println(replace);
                            }
                            //循环完后刷新,保证都被写进去dest了,缓存区没有东西遗留
                            printWriter.flush();
                        }
                    }
                }
            }
        }
    }
}

2)文件的复制(任意类型的文件)

文件的复制就是把一棵树的节点再复制出来一颗节点

//文件复制-InputStream和OutputStream
public class CpoyFile {
    public static void main(String[] args) throws IOException {
        //源文件:src.png
        //目标文件:dest.png
        try(InputStream is=new FileInputStream("src.png")){
            try(OutputStream os=new FileOutputStream("dest.png")){
                byte[] buf=new byte[1024];
                while(true){
                    int n= is.read(buf);//不断从src.png中读取出来n个数据
                    if(n==-1){
                        //数据读取完毕
                        break;
                    }
                    //从0到n就是有效数据,往buf写的时候写入即可
                    os.write(buf,0,n);//从0下标开始,写n个数据
                }//不用管数据内容,就一桶水一桶水接,直到数据读取并写入完毕
                os.flush();
            }
        }
    }
}

      对上述过程简单处理可以做到一个简单的加密,如在往dest写时,给buf中的内容都加一,此时最后得到一个损坏的文件(加密后的),解密就是给buf的内容减一即可,dest是损坏文件,读dest文件,再进行文件复制中途进行buf内数据的减一操作放到dest2中,此时dest2中就得到了和src一样的图片,如下:

 public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("dest.png")){
            try(OutputStream os=new FileOutputStream("dest2.png")){
                byte[] buf=new byte[1024];
                while(true){
                    int n= is.read(buf);
                    if(n==-1){
                        break;
                    }
                    for (int i = 0; i < n; i++) {
                        buf[i]-=1;
                    }
                    os.write(buf,0,n);//从0下标开始,写n个数据
                }
                os.flush();
            }
        }
    }

3)文件夹的复制

文件夹的复制就是一棵树的复制,遍历原来树上的每一个节点,放到另一个树上

eg:将source源当前目录下的文件复制到dest目标目录下

    static String source="D:\\Users\\文件学习部分练习内容";
    static String dest="D:\\Users\\文件学习部分\\目标";
    public static void main(String[] args) throws IOException{
        File srcFile=new File(source);
        //看srcFile是否是目录
        if(!srcFile.isDirectory()){
            System.out.println("源不是目录");
            return;
        }
        File destFile=new File(dest);
        if(destFile.exists()){
            System.out.println("目标路径已经存在");
            return;
        }
        //开始复制
        //复制实际上就是个遍历过程
        traversal(srcFile,destFile);
    }

    private static void traversal (File dir, File destFile) throws IOException {
        File[] files=dir.listFiles();//得到dir中所有文件
        if(files==null){
            return;
        }
        for (File file:files){
            //遍历files
            if(file.isDirectory()){
                //是目录——给目标的相对应位置建一个一样的目录
                System.out.println(file.getCanonicalPath());//绝对路径
                System.out.println(source);
                System.out.println(dest);
                System.out.println("-----------------------");
                traversal(file,destFile);
            }else if(file.isFile()){
                //是文件
            }
        }
    }
}


/**第一行是绝对路径,第二个是源的路径,第三行是目标路径。我们是要把源的这部分切掉,剩余拼接到目标目录后面,即a,b,c,文件代码测试内容拼接到目标这个目录后面
 * D:\Users\文件学习部分练习内容\a
 * D:\Users\文件学习部分练习内容
 * D:\Users\文件学习部分\目标
 * -----------------------
 * D:\Users\文件学习部分练习内容\b
 * D:\Users\文件学习部分练习内容
 * D:\Users\文件学习部分\目标
 * -----------------------
 * D:\Users\文件学习部分练习内容\c
 * D:\Users\文件学习部分练习内容
 * D:\Users\文件学习部分\目标
 * -----------------------
 * D:\Users\文件学习部分练习内容\文件测试代码内容
 * D:\Users\文件学习部分练习内容
 * D:\Users\文件学习部分\目标
 */
public class CopyDierctory {
    static String source="D:\\Users\\文件学习部分练习内容";
    static String dest="D:\\Users\\文件学习部分\\目标";
    public static void main(String[] args) throws IOException{
        File srcFile=new File(source);
        //看srcFile是否是目录
        if(!srcFile.isDirectory()){
            System.out.println("源不是目录");
            return;
        }
        File destFile=new File(dest);
        if(destFile.exists()){
            System.out.println("目标路径已经存在");
            return;
        }
        //开始复制
        //复制实际上就是个遍历过程
        traversal(srcFile,destFile);
    }

    private static void traversal (File dir, File destFile) throws IOException {
        File[] files=dir.listFiles();//得到dir中所有文件
        if(files==null){
            return;
        }
        for (File file:files){
            //遍历files
            if(file.isDirectory()){
                //是目录——给目标的相对应位置建一个一样的目录
                String path1 = file.getCanonicalPath();//当前源文件所在完整的的绝对路径
                //path1中把源的那一部分\Users\文件学习部分练习内容切掉
                String relative = path1.substring(source.length());//传入的是起始切割位置
                //拼接到目标路径上,得到最终目标路径
                String path2=dest+relative;
                //拿着path2创建File
                File file2=new File(path2);//路径是不存在的,开始建目录
                file2.mkdirs();
                traversal(file,destFile);
            }else if(file.isFile()){
                //是文件
            }
        }
    }
}

此时,目录全部复制了过来,但是文件内容还没复制过来,接下来完善内容复制(2)中有介绍):

package com.wy.io.OutputStream;

import java.io.*;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class CopyDirectory {
    // 当前已经复制了多少文件
    static AtomicInteger count = new AtomicInteger(0);

    static class MyThread extends Thread {
        @Override
        public void run() {
            while (true) {
                int currentCount = count.get(); // 不会有线程安全问题
                System.out.print("\r已经复制了 " + currentCount + " 个文件或目录");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            int currentCount = count.get(); // 不会有线程安全问题
            System.out.print("\r已经复制了 " + currentCount + " 个文件或目录");
        }
    }

    // 1. static 是为了遍历的时候随时可以使用这两个值
    // 2. 定义 File,一会要使用 File 中的某些方法
    static File srcDir = new File("D:\\Users\\文件学习部分练习内容");
    static File destDir = new File("D:\\Users\\文件学习部分\\目标");

    private static File getDestFile(File file) throws IOException {
        String srcPath = srcDir.getCanonicalPath();
        String filePath = file.getCanonicalPath();
        String subPath = filePath.substring(srcPath.length());
        String destPath = destDir.getCanonicalPath();
        String destFilePath = destPath + subPath;

        return new File(destFilePath);
    }

    private static void traversal(File dir) throws IOException {
        // 1. 获取目录下的所有孩子
        File[] files = dir.listFiles();
        if (files == null) {
            return;
        }

        // 2. 遍历目录下的每个孩子(文件)
        for (File file : files) {
            File destFile = getDestFile(file);

            if (file.isDirectory()) {
                // 按照相同的结构,创建出目录  mkdirs()
                destFile.mkdirs();
                count.incrementAndGet();

                traversal(file);
            } else if (file.isFile()) {
                copyFile(file, destFile);
                count.incrementAndGet();
            }
        }
    }

    private static void copyFile(File srcFile, File destFile) throws IOException {
        byte[] b = new byte[1024];
        try (InputStream is = new FileInputStream(srcFile)) {
            try (OutputStream os = new FileOutputStream(destFile)) {
                while (true) {
                    int n = is.read(b);
                    if (n == -1) {
                        break;
                    }

                    os.write(b, 0, n);
                }

                os.flush();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        // 基本条件检查
        if (!srcDir.isDirectory()) {
            System.out.println("源不是目录");
            return;
        }

        if (destDir.exists()) {
            System.out.println("目标目录已经存在");
            return;
        }

//        MyThread t = new MyThread();
//        t.start();
        Timer timer = new Timer();
        MyTimerTask task = new MyTimerTask();
        timer.scheduleAtFixedRate(task, 0, 1000);

        traversal(srcDir);
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值