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    其中./表示为当前的目录 

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

我们同样希望定位到  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 StringpathSeparator依赖于系统路径分隔符,String类型的表示
static charpathSeparator依赖于系统的路径分隔符,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方法【不需要背】

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

getCanonicalPath()

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

renameTo(File dest)

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

canRead()

判断用户是否对文件有可读权限
booleancanWrite()判断用户是否对文件有可写权限
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());
    }
}

运行输出结果:

(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());

    }
}

运行输出结果:

(3)普通文件的删除
public class IOD3 {
    public static void main(String[] args) {
        File file = new File("d:/hello.txt");
        System.out.println(file.delete());
    }
}

运行输出结果:

延申:关于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();
    }
}

运行后的文件夹显示:

(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使用字节流来读取文件

(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();
    }
}

输出结果:

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使用字节流来写文件

写文件有三个版本:

完整代码实现:
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文件夹中的内容是:

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

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

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

4.2.3怎样理解这里说的读与写?【约定】
4.2.4关于close操作
(1)理解

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

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

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

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

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

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

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

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

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

可以但是不够美观,更推荐的一种写法是将变量的定义写到try( )里面,后面将不需要写close语句。

try with resources

没有显式的写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();
        }
    }
}

输出结果:

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

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操作还可以继续使用。

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("取消删除操作");
                    }

                }
            }
        }

    }
}

控制台输出结果:

需要注意的是:

 

这里使用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部分就大体结束啦!下一部分将开启网络部分的内容~

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西西¥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值