文件操作和IO

文件系统操作

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

File概述

我们先来看看 File 类中的常见属性、构造方法和方法


属性
构造方法

方法

修饰符及返回
值类型
方法签名说明
StringgetParent()返回 File 对象的父目录文件路径
StringgetName()返回 FIle 对象的纯文件名称
StringgetPath()返回 File 对象的文件路径
StringgetAbsolutePath()返回 File 对象的绝对路径
StringgetCanonicalPath()返回 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 对象代表的目录,如果必要,会创建中间目
booleanrenameTo(File
dest)
进行文件改名,也可以视为我们平时的剪切、粘贴操
booleancanRead()判断用户是否对文件有可读权限
booleancanWrite()判断用户是否对文件有可写权限

文件内容操作(读写--数据流)

在标准库中,提供的读写文件的流对象,归结到两个大类中

1.字节流(对应二进制文件)

每次读写的最小单位是"字节"

InputStream

返回值看起来是int,实际上也是byte.0-255之间的数据,使用-1表示读到文件的末尾

public class Demo10 {
    public static void main(String[] args) {
        try(InputStream inputStream = new FileInputStream ( "D:/test.txt" )) {
            byte[] buffer = new byte[1024];
            int n = inputStream.read(buffer);
            System.out.println ("n="+n);
            for (int i = 0; i < n; i++) {
                System.out.printf ("%x\n",buffer[i]);
            }
        } catch (IOException e) {
            e.printStackTrace ();
        }
    }
}

OutputStream

public class Demo11 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream ( "d:/test.txt",true )){//和Writer类似,OutputStream打开一个文件,就会清空文件原有的内容,如果不想清空,就写入追加写方式(在构造方法中,第二个参数传入true)
            String str = "你好世界";
            outputStream.write(str.getBytes( StandardCharsets.UTF_8 ));
        } catch (IOException e) {
            e.printStackTrace ();
        }
    }
}

2.字符流(对应文本文件)

每次读写的最小单位是"字符"

本质上是针对字节流进行有一层封装

字符流,就能自动把文件中几个相邻的字节,转换成一个字符

GBK,一个中文字符 两个字节

UTF8,一个中文字符 三个字节

Reader

无参read:一次读一个字符

一个参数read:一次读取若干个字符,会把参数指定的cbuf数组给填充满

三个参数read:一次读取若干个字符,会把参数指定的cbuf数组,中的off这个位置开始,到len这么长的范围填满

cbuf是"输出型参数":read里转入的是一个空的字符数组,然后read方法内部,对这个数组进行填充

在java标准库内部,对于字符编码进行了很多的处理工作

如果只使用char,此时使用的是字符集,固定是unicode

如果使用String,此时就会自动的把每个字符的unicode转为utf8

public class Demo7 {
    public static void main(String[] args) throws IOException {
        Reader reader = new FileReader ( "D:/test.txt" );

        //1.一次read读一个字符
        while (true) {
            int c = reader.read ();
            if (c == -1) {
                break;
            }
            char ch = (char) c;
            System.out.println ( "c=" + c );
            System.out.println ( ch );
        }

        //2.一次read读多个字符
        try {
            while (true) {
                char[] cbuf = new char[1];
                int n = reader.read ( cbuf );
                if (n == -1) {
                    break;
                }
                System.out.println ( "n=" + n );
                for (int i = 0; i < n; i++) {
                    System.out.println ( cbuf[i] );
                }
            }
        } finally {
            reader.close();
        }

        //3.一个文件使用完后,记得要close
        //reader.close();//一旦上面的逻辑抛出异常,就会导致close执行不到

        //使用close方法,最主要的目的是为了释放文件描述符

        //PCB包含很多属性
        //1.pid
        //2.内存指针
        //3.文件描述符表 顺序表(数组) 一个进程每次打开一个文件,就需要在这个表里分配一个元素,这个数组的长度是存在上限的
        //这就是文件资源泄露问提
        //java有GC,不需要手动释放
        //但是文件还是要手动释放

    }
}
public class Demo8 {
    public static void main(String[] args) throws IOException {
        try(Reader reader = new FileReader ( "D:/test.txt" )) {//try with recourses  这个语法目的就是()定义的变量会在try代码块结束时(正常结束,还是抛出异常)自动调用其中的close方法
                                                                       //要求写到()里的对象必须要实现Closeable接口
            while (true) {
                char[] cbuf = new char[1];
                int n = reader.read ( cbuf );
                if (n == -1) {
                    break;
                }
                System.out.println ( "n=" + n );
                for (int i = 0; i < n; i++) {
                    System.out.println ( cbuf[i] );
                }
            }
        }
    }
}

Writer

一次写一个字符

一次写一个字符串

一次写多个字符(字符数组)

public class Demo9 {
    public static void main(String[] args) throws IOException {
        try(Writer writer = new FileWriter ( "d:/test.txt",true )){
            writer.write("hello world");//Writer写入文件,默认会把原有的文件内容清空掉,如果不想清空就要在构造方法中多加个参数
        }

    }
}

java标准库中支持的流流对象,种类很多,功能也非常丰富,上述介绍的,只是基础的四个流对象的使用 

字节流代码可以转成字符流

利用 Scanner 进行字符读取

构造方法说明
Scanner(InputStream is, String charset)使用 charset 字符集进行 is 的扫描读取


 

public class Demo12 {
    public static void main(String[] args) {
        try(InputStream inputStream =new FileInputStream ( "D:/test.txt" )) {
            //此时scanner就是从文件读取了
            Scanner scanner = new Scanner ( inputStream );
            String s = scanner.next();
            System.out.println (s);
        } catch (IOException e) {
            e.printStackTrace ();
        }
    }
}

 

PrintWriter

上述,我们其实已经完成输出工作,但总是有所不方便,我们接来下将 OutputStream 处理下,使用PrintWriter 类来完成输出,因为PrintWriter 类中提供了我们熟悉的 print/println/printf 方法

public class Demo13 {
    public static void main(String[] args) {
        try(OutputStream outputStream =new FileOutputStream ( "d:/test.txt" )) {
            //这相当于把字节流转换为字符流了
            PrintWriter writer = new PrintWriter ( outputStream );
            writer.println ("你好");
            
        } catch (IOException e) {
            e.printStackTrace ();
        }

    }
}

程序已经写入字符串,但文件还是空着的,为什么呢?

这涉及到"缓冲区"

缓冲区

PrintWriter这样的类,在进行写入的时候,不一定是直接写入硬盘,而是先把数据写入到一个内存构成的"缓冲区"(buffer)中

引入缓冲区是为了提高效率

相当于垃圾堆成一堆再一起去楼下扔掉

当我们把数据写入缓冲区后,如果还没来得及把缓冲区的数据写入硬盘,进程就结束了,此时数据就丢失了.

flush方法

为了能够确保数据确实被写入硬盘

就应该在合适的时机使用flush方法,手动刷新缓冲区

public class Demo13 {
    public static void main(String[] args) {
        try(OutputStream outputStream =new FileOutputStream ( "d:/test.txt" )) {
            //这相当于把字节流转换为字符流了
            PrintWriter writer = new PrintWriter ( outputStream );
            writer.println ("你好");
            writer.flush ();
        } catch (IOException e) {
            e.printStackTrace ();
        }

    }
}

需要手动使用flush方法确保数据确实被写入硬盘--"刷新缓冲区"

练习

例1

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

public class Demo14 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner ( System.in );
        //1.先让用户输入一个要扫描的目录
        System.out.println ("请输入要扫描的根目录:");
        String path = scanner.next ();
        File rootPath = new File ( path );
        if (!rootPath.isDirectory ()){
            System.out.println ("输入的文件路径有误");
            return;
        }

        //2.再让用户输入一个要查询的关键词
        System.out.println ("请输入要找的文件名中的字符:");
        String word = scanner.next ();



        //3.可以进行递归扫描
        //通过这个方法进行递归
        scanDir(rootPath,word);



    }

    private static void scanDir(File rootPath , String word) {

        //1.先列出rootPath中所有的文件和目录
        File[] files = rootPath.listFiles ();
        if(files == null){
            //当前目录为null,就可以直接返回了
            return;
        }else{
            for(File file:files){
                System.out.println (file.getAbsolutePath ());
                if(file.isDirectory ()){
                    scanDir ( file,word );
                }else{
                    checkDelete(file,word);
                }
            }
        }

    }

    private static void checkDelete(File file , String word) {
        if(file.getName ().contains ( word )){
            System.out.println ("当前文件为"+file.getAbsolutePath ());
            System.out.println ("请确认是否要删除(y/n):");
            Scanner scanner = new Scanner ( System.in );
            String choice = scanner.next ();
            if (choice.equals ( "Y" )||choice.equals ( "y" )){
                file.delete ();
                System.out.println ("删除完毕");
            }else{
                System.out.println ("取消删除");
            }


        }else{
            return;
        }
    }
}

例2

进行普通文件的复制
 

public class Demo15 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner ( System.in );

        System.out.println ("请输入要复制的文件路径:");
        String srcPath = scanner.next ();
        File srcFile = new File ( srcPath );

        if(!srcFile.exists ()){
            System.out.println ("文件不存在,请确认路径是否正确");
            return;
        }

        if(!srcFile.isFile ()){
            System.out.println ("文件不是普通文件,请确认路径是否正确");
            return;
        }

        System.out.println ("请输入要复制到的目标路径:");
        String destPath = scanner.next ();
        File destFile = new File ( destPath );
        if(destFile.exists ()){
            if(destFile.isDirectory ()){
                System.out.println ("目标路径已经存在,并且是一个目录,请确认路径是否正确");
                return;
            }

            if(destFile.isFile ()){
                System.out.println ("目标路径已经存在,是否进行覆盖?y/n");
                String choice = scanner.next ();
                if(!choice.toLowerCase ().equals ( "y" )){
                    System.out.println ("停止复制");
                    return;
                }
            }
        }

        try(InputStream inputStream = new FileInputStream ( srcFile )){
            try(OutputStream outputStream = new FileOutputStream ( destFile )){
                byte[] buf = new byte[1024];
                int len;
                while(true){
                    len = inputStream.read (buf);
                    if (len==-1){
                        break;
                    }

                    outputStream.write ( buf ,0,len);
                }
                outputStream.flush ();
            }
        }
        System.out.println ("复制完成");
    }
}

例3

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

public class Demo17 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner ( System.in );
        System.out.print ( "请输入要扫描的根目录(绝对路径 OR 相对路径): " );
        String rootDirPath = scanner.next ();
        File rootDir = new File ( rootDirPath );
        if (!rootDir.isDirectory ()) {
            System.out.println ( "您输入的根目录不存在或者不是目录,退出" );
            return;
        }
        System.out.print ( "请输入要找出的文件名中的字符: " );
        String token = scanner.next ();
        List<File> result = new ArrayList<> ();
        // 因为文件系统是树形结构,所以我们使用深度优先遍历(递归)完成遍历
        scanDir ( rootDir , token , result );
        System.out.println ( "共找到了符合条件的文件 " + result.size () + " 个,它们分别 是" );
        for (File file : result) {
            System.out.println ( file.getCanonicalPath () );

        }
    }

    private static void scanDir(File rootDir , String token , List<File> result) {
        File[] files = rootDir.listFiles ();
        if (files == null || files.length == 0) {
            return;
        }
        for (File file : files) {
            if (file.isDirectory ()) {
                scanDir ( file , token , result );
            } else {
                if (file.getName ().contains ( token )) {
                    result.add ( file.getAbsoluteFile () );
                }
            }
        }
    }
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值