JAVAEE初阶相关内容第十三弹--文件操作 IO

写在前

终于完成了!!!!内容不多·就是本人太拖拉!

这里主要介绍文件input,output操作。File类,流对象(分为字节流、字符流)

需要掌握每个流对象的使用方式:打开、读写、关闭

熟练使用API并知道这些API起到的效果

这里涉及到的经典面试题需要注意:

抽象类和接口的区别[4.2]

还需要重点去看的一个是4.2.8中的案例一,重点体会其中的递归·操作!!!

目录

文件操作--IO

1.认识文件

1.1狭义上的文件

1.2广义上的文件

2.文件路径

2.1绝对路径

2.2相对路径

3.文件类型

3.1文本文件

3.2二进制文件

4.Java中操作文件

4.1File类

4.1.1属性

4.1.2构造方法

4.1.3代码示例

(1)获取相应的路径

(2)普通文件的创建

​(3)普通文件的删除

(4)创建目录

(5)文件重命名

4.2文件内容操作

经典面试题--- 抽象类和接口什么区别?

4.2.1使用字节流来读取文件

(1)完整代码展示:

(2)buffer版本代码:

4.2.2使用字节流来写文件

完整代码实现:

4.2.3怎样理解这里说的读与写?【约定】

​4.2.4关于close操作

(1)理解

​(2)如果close操作没写会如何?

(3)确保close被执行?

try-ctach

try with resources

4.2.5使用字符流读文件

完整代码展示:

4.2.6使用字符流写文件

(1)完整代码展示:

(2)flush-冲刷

​4.2.7Scanner

4.2.8两个小案例的实现

(1)扫描指定目录

(2)文件拷贝


文件操作--IO

1.认识文件

1.1狭义上的文件

硬盘上的文件和目录(文件夹)  本篇博客记录的就是狭义上的文件。

1.2广义上的文件

泛指计算机中的很多软硬件资源。操作系统中,把很多的硬件设备和软件资源抽象成文件,按照文件的方式统一管理。

2.文件路径

2.1绝对路径

以c:  d:  盘符开头的路径

2.2相对路径

以当前所在的目录为基准,以 . 或者 .. 开头,(.有时可以省略),找到指定的路径。【当前所在的目录称为是工作目录,每个程序运行的时候,都有一个工作目录,,在控制台里通过命令操作的时候是特别明显的,后期进化到图形化界面,工作目录就不那么直观了。

举个例子:如下图,假定当前的工作目录是 f:/Code,定位到111这个目录上就可以表示为

./111    其中./表示为当前的目录 

JAVAEE初阶相关内容第十三弹--文件操作 IO_第1张图片

如果工作目录不同,定位到同一个文件,相对路径的写法是不同的。

我们同样希望定位到  111 这里

几种相对路径的写法
工作目录 相对路径
f:/ ./Code/111
f:/Code ./111
f:/Code/222 ../111
f:/Code/222/bbb ../../111

../表示当前目录的上级目录。

3.文件类型

word;exe;图片;视频;音频;源代码;动态库等等,这些不同的文件整体可以归纳为两类

3.1文本文件

存的是文本,字符串【字符串,由字符构成,每个字符都是通过一个数字来表示的。这个文本文件里存储的数据一定是合法的,都是在指定字符编码的码表之内的数据。】

3.2二进制文件

存的是二进制数据,不一定是字符串【没有任何限制,可以存储任何想要的数据。】

小思考?  当我们有一个文件,如何区分是文本文件还是二进制文件?

直接使用记事本打开,如果乱码了就说明是二进制文件,如果没有乱码就是文本。

4.Java中操作文件

主要有两类操作,第一类是针对文件系统的操作(文件的创建、删除、重命名);第二类是针对文件内容操作(文件的读和写)。

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

4.1File类

4.1.1属性
修饰符及类型 属性 说明
static String pathSeparator 依赖于系统路径分隔符,String类型的表示
static char pathSeparator 依赖于系统的路径分隔符,char类型的表示
4.1.2构造方法
签名 说明
File(File parent,String child) 根据父目录+孩子文件路径,创建一个新的File实例
File(String pathname) 根据文件路径创建一个新的File实例,路径可以是绝对路径或者是相对路径
File(String parent,String child)

根据父目录+孩子文件路径,创建一个新的File实例,父目录用路径表示

parent表示当前文件所在的目录,child是自身文件名。

在new File对象的时候,构造方法参数中,可以指定一个路径,此时File对象就代表这个路径对应的文件。

4.1.3方法【不需要背】

返回修饰符及返回值类型 方法签名 说明
String getparent() 返回File对象父目录文件路径
String getName() 返回File对象的纯文件名称
String getPath() 返回File对象的文件路径
String getAbsolutePath() 返回File对象的绝对路径
String

getCanonicalPath()

返回File对象的修饰过的绝对路径【绝对路径更加化简的表示方式】
boolean exists() 判断File对象描述的文件是否真实存在
boolean isDirectory() 判断File对象代表的文件是否是一个目录
boolean isFile() 判断File对象代表的文件是否是一个普通文件
boolean createNewFile() 根据File对象,自动创建一个空文件。成功创建后返回true
boolean delete() 根据File对象,删除该文件。成功删除后返回true
void deleteOnExit() 根据File对象,标注文件将被删除,删除动作会到JVM运行结束后才会进行
String[] list() 返回File对象代表的目录下的所有文件名
File[] listFiles() 返回File对象代表目录下的所有文件,以File对象表示
boolean mkdir() 创建File对象代表的目录
boolean mkdirs() 创建File对象代表的目录,如果必要,会创建中间目录
boolean

renameTo(File dest)

进行文件改名,也可以视为我们平时的剪切、粘贴操作
boolean

canRead()

判断用户是否对文件有可读权限
boolean canWrite() 判断用户是否对文件有可写权限
4.1.3代码示例
(1)获取相应的路径
public class IOD1 {
    public static void main(String[] args) throws IOException {
        File file = new File("d:/test.txt");//这里的路径不需要真实的存在、
        System.out.println(file.getPath());
        System.out.println(file.getName());
        System.out.println(file.getParent());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());
    }
}

运行输出结果:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第2张图片

(2)普通文件的创建
public class IOD2 {
    public static void main(String[] args) throws IOException {
        File file = new File("d:/hello.txt");
        System.out.println(file.exists());
        System.out.println(file.isFile());
        System.out.println(file.createNewFile());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());

    }
}

运行输出结果:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第3张图片(3)普通文件的删除
public class IOD3 {
    public static void main(String[] args) {
        File file = new File("d:/hello.txt");
        System.out.println(file.delete());
    }
}

运行输出结果:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第4张图片延申:关于deleteOnExit 不会立即删除,是在程序运行完关闭之后才进行的删除。

(4)创建目录

需要注意的是:mkdir()  mk是make的缩写!

public class IOD4 {
    public static void main(String[] args) {
        File dir = new File("d:/study");
        dir.mkdir();
    }
}

运行程序后就会在d盘生成一个名字为study的文件夹。

如果我们想创建多级目录。例如创建"d:/test/aaa/bbb",需要使用makedirs。

public class IOD4 {
    public static void main(String[] args) {
        File d = new File("d:/aaa/ddd/m");
        d.mkdirs();
    }
}

运行后的文件夹显示:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第5张图片

(5)文件重命名
public class IOD5 {
    public static void main(String[] args) {
        File dest = new File("./testAAA");
        File file = new File("./test");

        dest.renameTo(file);
    }
}

创建一个名为testAAA的文件和一个名为test的文件,将testAAA改名为test;

4.2文件内容操作

针对文件内容,使用流对象进行操作。[流对象 可以类比于 水流]

Java标准库的流对象,从类型上,可以分为两个大类,一类是字节流,一类是字符流。

字节流[操作二进制文件]:InputStream   OutputStream  (FileInputStream FileOutputStream)    

字符流[操作文本数据]:Reader Writer(FileReader FileWriter )

这些类的使用方式是非常固定的,核心操作就是四个:

(1)打开文件[构造对象]

(2)关闭文件[close]

(3)读文件[read针对InputStream 和 Reader]

(4)写文件[write针对OutputStream和Writer]

需要注意的是:InputStream   OutputStream  Reader Writer 都是抽象类,不能直接new

经典面试题--- 抽象类和接口什么区别?

抽象类比接口更抽象,抽象类的功能比接口更多。抽象类和普通的类相比差别不大,只不过抽象类不能new实例,带有抽象方法。抽象类可以有普通方法,也可以有抽象的方法。大部分的东西都是确定的有几个属性有几个方法,大部分都是明确的,只有一小部分是抽象方法。

接口里面都是抽象方法,接口中大部分都是不确定的,有什么属性都不知道,方法也都是抽象方法(不考虑default的情况)

因此,接口提供的信息更少,视为接口比抽象类更抽象。

4.2.1使用字节流来读取文件

JAVAEE初阶相关内容第十三弹--文件操作 IO_第6张图片

(1)完整代码展示:
public class IOD6 {
    //使用字节流来读取文件
    public static void main(String[] args) throws IOException {
        InputStream inputStream = new FileInputStream("f:/test.txt");

        while(true){
            int b = inputStream.read();
            if(b == -1){
                break;
            }
            System.out.println(""+(byte)b);

        }
        inputStream.close();
    }
}

输出结果:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第7张图片

read() 一次读一个字节,但是这里使用的是int类型,原因:因为要表述”读取完毕“这样的情况,看起来是int,其实是byte,把int转为byte进行使用,另外需要判断当前的值是否是-1,是-1说明读取完毕,也就可以结束循环了。

(2)buffer版本代码:
public class IOD6 {
    //使用字节流来读取文件
    public static void main(String[] args) throws IOException {
        InputStream inputStream = new FileInputStream("f:/test.txt");

        while (true) {
            byte[] buffer = new byte[1024];
            int len = inputStream.read(buffer);
            if (len == -1) {
                break;
            }
            for (int i = 0; i < len; i++) {
                System.out.printf("%x\n", buffer[i]);
            }
        }
        inputStream.close();
    }
}

一次读1024个字节,并使用十六进制形式打印出来。

buffer理解:

缓冲区,缓和冲突,减少冲击的次数

存在的意义:为了提高IO的操作效率,单词的IO操作,就是要访问硬盘/IO设备,单次操作是比较消耗时间的,如果频繁的进行这样的IO操作,耗时肯定就更多了。

单次IO时间是一定的,如果缩短IO的次数,此时就可以提高程序整体的效率了。

以上第一个版本的代码是一次读一个字节,循环次数就比较高,read次数也很高

第二个版本的代码是一次读1024个字节,循环次数就降低了很多,read次数变少了。

4.2.2使用字节流来写文件

写文件有三个版本:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第8张图片

完整代码实现:
public class IOD7 {
    //进行写文件
    public static void main(String[] args) throws IOException {
        OutputStream outputStream = new FileOutputStream("f:/test.txt");
        outputStream.write(97);
        outputStream.write(98);
        outputStream.write(97);
        outputStream.write(98);

        outputStream.close();
    }
}

现在在我们的f盘txt文件夹中的内容是:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第9张图片

当我们运行程序后再次打开txt文件,看到运行结果:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第10张图片

可以看到的是记事本中的内容发生了改变,输入的内容分别对应a和b的ASCII码值。

需要注意的是在outputStream进行写操作的时候总是将文件中的内容进行清空,再进行写入,如果不想清空,需要进行“追加写”操作。

JAVAEE初阶相关内容第十三弹--文件操作 IO_第11张图片

4.2.3怎样理解这里说的读与写?【约定】
JAVAEE初阶相关内容第十三弹--文件操作 IO_第12张图片4.2.4关于close操作
(1)理解

close,关闭文件,释放资源。

进程在内核里,使用PCB这样的数据结构来表示进程。

一个线程可以对应一个PCB,一个进程可以对应一个PCB也可以对应多个。

PCB有一个重要的属性是文件描述符表。(相当于一个数组,记录了该进程打开了哪些文件)即使一个进程里面有多个PCB也没关系,这些PCB共用同一个文件描述符表。

JAVAEE初阶相关内容第十三弹--文件操作 IO_第13张图片(2)如果close操作没写会如何?

如果没有close,对应的表项没有及时释放,虽然Java有GC,GC操作会在回收这个outputStream对象的时候去完成释放操作,但是GC不一定及时,如果不手动释放会导致文件描述符表可能很快被占满(文件描述符表不能自动扩容,是存在上线的),再次打开文件就会打开失败。

close一般来说是要执行的,但是如果程序要自始至终的使用,不关闭的问题也是不大的,随着进程结束,PCB销毁,文件描述符表也被销毁了,对应的资源操作系统就自动回收。如果close后程序立刻结束不执行close也没关系,但是在写代码的过程中还是需要注意这个close,如何确保close被执行到?

(3)确保close被执行?
try-ctach

我们首先可以想到的是使用try-catch执行:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第14张图片可以但是不够美观,更推荐的一种写法是将变量的定义写到try( )里面,后面将不需要写close语句。

try with resources

JAVAEE初阶相关内容第十三弹--文件操作 IO_第15张图片

没有显式的写close,但是实际上是会执行close的,只要try语句块执行完毕,就可以自动的执行到close,这个语法在Java中被称为是try with resources。

注意:不是随便拿一个对象放到try里面就可以自动释放的,需要满足的要求是实现Closeable接口,才可以。这个接口提供的方法就是close方法。

4.2.5使用字符流读文件
完整代码展示:
public class IOD8 {
    public static void main(String[] args) {
        try(Reader reader = new FileReader("f:/test.txt")) {
            while(true){
                int ch =reader.read();
                if(ch == -1){
                    break;
                }
                System.out.println("" +(char)ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第16张图片

如果输入汉字不能正确读取的话可能是编码的问题,需要改为utf-8编码。

JAVAEE初阶相关内容第十三弹--文件操作 IO_第17张图片

4.2.6使用字符流写文件
(1)完整代码展示:
public class IOD9 {
    public static void main(String[] args) {
        //使用字符流写一个字符串
       try(Writer writer = new FileWriter("f:/text.txt")){
           writer.write("hello123");
       } catch (IOException e) {
           e.printStackTrace();
       }
    }
}

有的时候可以发现写的文件内容没有真的在文件中出现,很大可能是缓冲区的问题。

类似于这样的操作,其实是先写到缓冲区中(缓冲区有很多种形态,自己写的代码中有缓冲区,标准库中也可以有缓冲区,操作系统内核中也存在缓冲区),在写操作执行完后,内容可能在缓冲区中,还没真正的进入硬盘。

close操作,会触发缓冲区的“冲刷”(冲刷操作,就是把缓冲区里的内容写到硬盘中)

(2)flush-冲刷

除了close之外,也可以通过flush方法,也可以起到刷新缓冲区的效果。close操作后就关闭用不了了,flush操作还可以继续使用。

JAVAEE初阶相关内容第十三弹--文件操作 IO_第18张图片4.2.7Scanner

点进去可以发现:inputStream,输入流对象【字节流】

Scanner不仅仅可以针对键盘的输入进行读取。

public class IOD10 {
    public static void main(String[] args) {
        //Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = new FileInputStream("f:/test.txt")){
            Scanner scanner = new Scanner(inputStream);
            scanner.next();
        }catch (IOException e){
            e.printStackTrace();
        }
    
    }
}

Scanner的close本质上是要关闭内部包含的这个流对象,此时内部的inputStream对象已经被try()关闭了,里面的这个Scanner不关闭也是没问题的。

4.2.8两个小案例的实现
(1)扫描指定目录

找到名称中包含指定字符的所有普通文件(不包含目录),并且询问是否要删除该文件

 完整代码实现:

public class IOD11 {
    private static Scanner scanner = new Scanner(System.in);
    public static void main(String[] args) {
        //首先让用户输入一个指定搜索的目录
       // Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要搜索的路径");
        String basePath = scanner.next();
        //针对用户的输入进行一个简单的判定
        File root = new File(basePath);
        //判断是不是目录
        if(!root.isDirectory()){
            //路径不存在或者只是一个普通文件。无法进行搜索
            System.out.println("当前输入的目录错误!");
            return;
        }
        //让用户再输入一个要删除的文件名
        System.out.println("请输入要删除的文件名");
        //此处使用的是next来读字符串而不是nextln!!
        //next是读到空白符就算是读完了。空白符包括换行,也可以是制表符,回车符
        //nextln仅仅是去找换行符
        //在控制台中,nextln是很难控制的
        String nameToDelete = scanner.next();

        //针对指定的路径进行扫描【递归操作】
        //从根目录出发(root)
        //先判断一下,当前的这个目录中是否包含要删除的文件,存在即删除,否则继续下一个
        //如果当前目录中包含了一些目录,则针对子目录进行递归

        scanDir(root,nameToDelete);
    }

    private static void scanDir(File root, String nameToDelete) {
        System.out.println("[scanDir]" +root.getAbsolutePath());
        //1.首先列出当前目录下包含的内容
        File[] files = root.listFiles();  //看一下当前的目录中有啥,好比文件资源管理器双击目录打开
        if(files == null){
            //当前root目录下没东西,是一个空目录
            //结束继续递归
            return;
        }
        //2.遍历当前的列出结果
        for(File f:files){
            if(f.isDirectory()){
                //如果是目录就进一步递归
                scanDir(f,nameToDelete);

            }else{
                //如果是普通文件判定是否要删除
                if(f.getName().contains(nameToDelete)){
                    System.out.println("确认删除?" +f.getAbsolutePath());
                    String choice = scanner.next();
                    if(choice.equals("y") || choice.equals("Y")){
                        f.delete();
                        System.out.println("删除成功");
                    }else{
                        System.out.println("取消删除操作");
                    }

                }
            }
        }

    }
}

控制台输出结果:

JAVAEE初阶相关内容第十三弹--文件操作 IO_第19张图片

需要注意的是:

 

这里使用File对象表示列出的结果,结果有多个,使用数组表示。相当于看一下当前的目录中有什么,打开文件资源管理器,文件资源管理器中显示的这些结果,就相当于是listFiles得到的内容。

list方法也有类似的功效,但是list得到的只是String类型的文件名。

(2)文件拷贝

把第一个文件按照字节依次读取,把结果写入到另一个文件中。

需要构建“源路径”和”目标路径“两个路径,对于源路径来说要看源是不是存在,对于目标路径来说要看目标路径是不是不存在。

需要注意一下代码中读一个字节写一个字节的部分

完整代码实现:

public class IOD12 {
    public static void main(String[] args) {
        //输入两个路径
        //第一个路径表示拷贝谁(源-src),第二个路径表示需要拷贝到哪里去(目的-dest)
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要拷贝哪个文件?");
        String srcPath = scanner.next();
        System.out.println("请输入要拷贝到哪个地方?");
        String destPath = scanner.next();

        //针对路径进行判定
        File srcFile = new File(srcPath);
        if(!srcFile.isFile()){
            //如果源不是一个文件(是个目录或者不存在)
            //此时就不做任何操作
            System.out.println("当前输入的源路径错误!");
            return;
        }
        File destFile = new File(destPath);
        if(destFile.isFile()){
            //如果已经存在了,认为也不能拷贝
            System.out.println("当前输入的目标路径错误!");
            return;
        }
        //进行拷贝操作
        //try()语法支持多个流对象,写几个都可以进行关闭
        //多个流对象之间使用分号进行分割就可以
        try(InputStream inputStream = new FileInputStream(srcFile);
            OutputStream outputStream = new FileOutputStream(destFile)) {
            //进行读文件操作

            while(true){
                //读一个字节,写一个字节!!
                int b = inputStream.read();
                if(b == -1){
                    break;
                }
                outputStream.write(b);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

OutputStream在进行写文件的时候,文件不存在就会自动创建。

InputStream不行,文件不存在就会抛异常了。

到这里文件IO部分就大体结束啦!下一部分将开启网络部分的内容~

JAVAEE初阶相关内容第十三弹--文件操作 IO_第20张图片

你可能感兴趣的:(Javaee,java-ee,java)