疯狂java15

输入输出

​ java的IO通过java.io包下的类和接口来支持,该包下主要包括输入,输出两种IO流,每种输出,输出流又可以分为字节流和字符流两大类。
​ java7在java.nio及其子包下提供了全新的API,对原有的新IO的升级,被称NIO2,NIO2程序可以更高效的进行输入输出。


File类

​ File类是java.io包下代表与平台无关的文件和目录,如果希望程序中操作文件和目录,都可以通过Flie类来完成。不管是文件还是目录都可以使用File来操作,File能建立,删除,重命名文件和目录,File不能访问文件内容本身。如果需要访问则需要使用输出输出流。


访问文件和目录

​ Flie类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。一旦创建了File对象后,就可以调用File对象的方法来访问文件和目录。

  1. 访问文件名相关的方法:

    在这里插入图片描述

  2. 文件监测相关的方法:

    在这里插入图片描述

    在这里插入图片描述

  3. 获取常规文件信息:

    在这里插入图片描述

  4. 文件操作相关的方法:

在这里插入图片描述

  1. 目录操作相关的方法:

    在这里插入图片描述

//常用实例
public class FileTest {
    public static void main(String[] args) throws IOException {
        //以当前路径创建一个File对象
        File file=new File(".");
        //直接获取文件名,输出一点
        System.out.println(file.getName());
        //获取相对路径的父路径可能出错,下面代码输出null
        System.out.println(file.getParent());
        //获取绝对路径
        System.out.println(file.getAbsoluteFile());
        //获取上一级路径
        System.out.println(file.getAbsoluteFile().getParent());
        //在当前路径下创建一个临时文件
        File tmpFile=File.createTempFile("aaa",".txt",file);
        //指定当JVM退出时删除该文件
        tmpFile.deleteOnExit();
        //以系统当前时间作为新文件名来创建文件
        File newFile=new File(System.currentTimeMillis()+"");
        System.out.println("newFile对象是否存在:"+newFile.exists());
        //以指定newFile对象来创建一个文件
        newFile.createNewFile();
        //以newFile对象来创建一个目录,因为newFile已经存在
        //所以下面方法会返回false,即无法创建该目录
        newFile.mkdir();
        //使用list()方法列出当前路径下的所有文件和路径
        String[] fileList=file.list();
        System.out.println("====当前路径下所有的文件和路径如下====");
        for(String fileName:fileList){
            System.out.println(fileName);
        }
        //listRoots()静态方法列出所有的磁盘根路径
        File[] roots=File.listRoots();
        System.out.println("====系统所有根路径如下====");
        for(File root:roots){
            System.out.println(root);
        }
    }

文件过滤器

​ 在File类的list()方法中可以接收一个FlienameFilter参数,通过该参数可以只列出符合条件的文件

​ FilenameFilter接口里包含一个accept(File dir,String name)方法,该方法将依次对指定的File的所有子目录或文件进行迭代,如果返回true,则list()方法会列出该子目录或者文件。

public class FilenameFilterTest {
    public static void main(String[] args) {
        File file=new File(".");
        //使用Lambda表达式实现文件过滤器
        //如果文件名以.java结尾,或者文件对应一个路径,则返回true
        String[] nameList=file.list(((dir, name) -> name.endsWith(".java")||new File(name).isDirectory()));
        for (String name:nameList){
            System.out.println(name);
        }
    }
}

FilenameFilter接口内只有一个抽象方法,因此该接口也是一个函数式接口,可使用lambda表达式创建实现该接口的对象


理解java的IO

​ java的IO流是实现输入输出的基础,他可以方便实现数据的输入输出操作,在java中把不同的输入输出源抽象表述为 流,通过流的方式允许java程序使用相同的方式来访问不同的输入输出源


流的分类

  1. 输入流和输出流

    • 输入流:只能从中读取数据,而不能向其写入数据
    • 输出流:只能从中写入数据,不能从中读取数据

    java的输入流主要由InputStream和Reader作为基类,而输出流主要由OutputStream和Writer作为基类,都是抽象类,无法创建实例。

  2. 字节流和字符流

    不同点:字节流和字符流所操作的数据单元不同----字节流操作的数据单元是8个字节,而字符流操作的数据单元是16个字符

    字节流主要由InputStream和OutputStream作为基类,而字符流主要由Reader和Writer作为基类

  3. 节点流和处理流

    • 节点流:可以从/向一个特定的IO设备读/写数据的流
    • 处理流:用于对一个已存在的流在进行连接或封装,通过封装后的流来实现数据读/写功能

在这里插入图片描述

在这里插入图片描述


流的概念模型

​ java把所有设备里有序数据抽象成流模型,简化输入输出处理。

​ java的IO流的40多个类都是从如下4个抽象基类派生的:

  • InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
  • OutputStream/Writer:所有输出流的基类,前者是字节输入流,后者是字符输入流

在这里插入图片描述

在这里插入图片描述


字节流和字符流

​ 字节流和字符流区别在于操作的数据单元不同,字节流操作的数据单元是字节,字符流操作的数据单元是字符


InputStream和Reader

在这里插入图片描述

InputStream和Reader都是抽象类,本身是不能创建实例的,但他们分别有一个用于读取文件的输入流:FileInputStream和FileReader,它们都是节点流,会直接和指定文件关联

//实例FileInputStream
public class FileInputStreamTest {
    public static void main(String[] args) throws IOException {
        //创建字节输入流
        FileInputStream fis=new FileInputStream("FileInputStreamTest.java");
        //创建一个长度为1024的数组
        byte[] bbuf=new byte[1024];
        //用于保存实际读取的字节数
        int hasRead=0;
        //使用循环重复 取出数据的过程
        while((hasRead=fis.read(bbuf))>0){
            //取出数据中字节,将字节数组转换成字符串输入
            System.out.print(new String(bbuf,0,hasRead));
        }
        fis.close();
    }
}

使用FileReader

public class FileReaderTest {
    public static void main(String[] args) throws IOException {
        try(//创建字符流
            FileReader fr=new FileReader("FileReaderTest.java")){
            //创建长度为32的数组
            char[] cbuf=new char[32];
            //用于保存实际读取的字符串
            int hasRead=0;
            //使用循环重复输入数据的过程
            while((hasRead=fr.read(cbuf))>0){
                //取出数据中字符,将字符数组转换成字符串输入
                System.out.println(new String(cbuf,0,hasRead));
            }
        }catch (IOException ex){
            ex.printStackTrace();
        }
    }
}

在这里插入图片描述


OutputStream和Writer

​ 这两个流提供了如下方法:

在这里插入图片描述

字符流直接以字符为操作单位,所以Writer可以用字符串来代替字符数组,可以用String对象作为参数。Writer还包含两个方法:

在这里插入图片描述

//使用FileInputStream执行输入,使用FileOutputStream执行输出,实现复制java文件的功能 
public class FileOutputStreamTest {
    public static void main(String[] args) {

        try(//创建字节输入流
            FileInputStream fis=new FileInputStream("src\\test\\java\\test001\\FileOutputStreamTest.java");
            //创建字节输出
            FileOutputStream fos=new FileOutputStream("newFile.txt")){
            byte[] bbuf=new byte[32];
            int hasRead=0;
            //循环从输入流中读取数据
            while((hasRead=fis.read(bbuf))>0){
                //每读取一行,即写入文件输出流,读多少,写多少
                fos.write(bbuf,0,hasRead);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

使用java的IO流执行输出时,不要忘记关闭输出流和输入流,除了可以保证流的物理资源被回收之外,可能还可以将输出流缓存区中的数据flush到节点里。

如果希望直接输出字符串内容,使用Writer效果会更好

//实例
public class FileWriterTest {
    public static void main(String[] args) {
        try(FileWriter fw=new FileWriter("poem.txt")){
        fw.write("java学习\t\n");
        fw.write("我爱java");

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

输入\输出流体系

​ 输入\输出的4个基类使用起来有些繁琐,可以借助处理流。


处理流的用法

​ 使用处理流时的典型思路,使用处理流来包装节点流,程序通过处理流来执行输入\输出功能,让节点流与底层的I\O设备,文件交互。

使用处理流的优势:

  1. 对开发人员来说,使用处理流进行输入\输出操作更简单
  2. 使用处理流的执行效率更高
//使用PrintStream处理流来包装OutputStream
public class PrintStreamTest {
    public static void main(String[] args) {
        try(//定义一个输出流
            FileOutputStream fos=new FileOutputStream("test.txt");
            //定义一个处理流,并且包装fos输出流
            PrintStream ps=new PrintStream(fos)){
            //使用printStream执行输出
            ps.println("普通字符窜");
            //直接使用PrintStream输出对象
            ps.println(new PrintStreamTest());

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

由于PrintStream类的输出功能十分强大,通常如果需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。

​ 在使用处理流包装了底层节点流后,关闭资源时,只要关闭最上层的处理流即可,系统会自动的关闭该处理流包装的节点流。


输入\输出流体系

在这里插入图片描述

​ 通常,字节流的功能比字符流的功能强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件,但问题是,如果使用字节流来处理文本文件,则需要使用适合的方式把字节转换成字符,这样增加了编程复杂度。

​ 所以有一个规则:如果进行输入\输出的内容是文本内容,则考虑使用字符流;如果进行输入\输出的内容是二进制内容,则考虑使用字节流。


转换流

​ 输入\输出流体系提供两个转换流:

​ InputStreamReader:将字节输入流转换成字符输入流

​ OutputStreamWriter:将字节输出流转换成字符输出流

//转换流的用法实例
public class KeyinTest {
    public static void main(String[] args) {
        try(//将System.in对象转换成Reader对象
            InputStreamReader reader=new InputStreamReader(System.in);
            //将普通的Reader包装成BufferedReader
            BufferedReader br=new BufferedReader(reader)){
            String line=null;
            //才用循环方式逐行地读取
            while((line=br.readLine())!=null){
                //如果读取的字符串为 “exit”则程序退出
                if(line.equals("exit")){
                    System.exit(1);
                }
                //打印读取的内容
                System.out.println("输入的内容:"+line);
            }

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

在这里插入图片描述

由于BufferedReader具有一个readLine()方法,可以很方便地一次读取一行内容,所以经常把读取的内容的输入流包装成BufferedReader,用来方便地读取输入流的文本内容。


推回输入流

​ 在输入输出体系中,有两个特殊的流与众不同,PushbackInputStream和PushbackReader,他们提供了三个方法:

这两个推回输入流都带有一个推回缓冲区,当程序调用这个两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组时才从原输入流中读取。

在这里插入图片描述

使用这两个推回需要指定推回缓冲区的大小,默认的推回缓存区的长度是1,如果程序中推回到推回缓冲区的内容超过了推回缓存区大小,将会引发Pushback bufer overflow的IOExcetion异常

//实例
package T1;

import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.io.PushbackReader;

public class PushbackTest {
    public static void main(String[] args) {
        try(//创建一个PushbackReader对象,指出推回缓冲区的长度是64
            PushbackReader pr=new PushbackReader(new FileReader("src\\test\\java\\T1\\PushbackTest.java"),64)){
            char[] buf=new char[32];
            //用以保存上次读取字符串内容
            String lastConter="";
            int hasRead=0;
            //循环读取文件内容
            while((hasRead=pr.read(buf))>0){
                //将读取的内容转换成字符串
                String content=new String(buf,0,hasRead);
                int targetIndex=0;
                //将上次读取的字符串和本次读取的字符串拼接
                //查看是否包含目标字符串,如果包含目标字符串
                if((targetIndex=(lastConter+content).indexOf("new PushbackReader"))>0){
                    //将本次内容与上次内容一起推回缓冲区
                    pr.unread((lastConter+content).toCharArray());
                    //重新定义一个长度为targetIndex的char数组
                    if(targetIndex>32){
                        buf=new char[targetIndex];
                    }
                    //再次读取指定长度的内容
                    pr.read(buf,0,hasRead);
                    //打印读取内容
                    System.out.println(new String(buf,0,targetIndex));
                    System.exit(0);
                }else{
                    //打印上次读取的内容
                    System.out.println(lastConter);
                    //将本次内容设为上次读取内容
                    lastConter=content;
                }

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

重定向标准输入\输出

​ 在System类里提供了如下三个重定向标准输入输出的方法

在这里插入图片描述

//将System.out的重定向到文件输出,而不是屏幕上输出
public class RedirectOut {
    public static void main(String[] args) {
        try(//一次性创建PrintStream输出流
            PrintStream ps=new PrintStream(new FileOutputStream("out.txt"))){
            //将标准输出重定向到ps输出流
            System.setOut(ps);
            //向标准输出输出一个字符串
            System.out.println("普通字符串");
            //向标准输出输出一个对象
            System.out.println(new RedirectOut());
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
//将System.in重定向到指定文件,而不是键盘输入
public class RedirecIn {
    public static void main(String[] args) {
        try(//
            FileInputStream fis=new FileInputStream("src\\test\\java\\T1\\RedirecIn.java")){
            //将标准输入重定向到fis输入流
            System.setIn(fis);
            //使用System.in创建Scanner对象,用于获取标准输入
            Scanner sc=new Scanner(System.in);
            //增加下面一行只把回车作为分隔符
            sc.useDelimiter("\n");
            //判断是否还有下一个输入项
            while(sc.hasNext()){
                //输出输入项
                System.out.println("输入键盘内容:"+sc.next());
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

java虚拟机读写其他进程的数据

​ 使用Runitme对象的exec()方法可以运行平台上的其他程序,该方法产生一个Process对象,Process对象代表由该java程序启动的子进程。Process对象提供三个方法,让程序和其子程序进行通信。

在这里插入图片描述

在这里插入图片描述

//读取其他进程输出信息
public class ReadFromProcess {
    public static void main(String[] args) throws IOException {
        //运行javac命令,返回运行该命令的子进程
        Process p=Runtime.getRuntime().exec("javac");
        try(
            //以p进程的错误流创建BufferedReader对象
            //这个错误流对本程序是输入流,对p进程是输出流
            BufferedReader br=new BufferedReader(new InputStreamReader(p.getErrorStream()))
            ){
            String buff=null;
            //才用循环方式来读取p进程的错误输出
            while((buff=br.readLine())!=null){
                System.out.println(buff);
            }
        }
    }
}
//实现了java程序中启动java虚拟机运行另一个java程序,并向另外一个java程序中输入数据
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Scanner;

public class WriteToProcess {
    public static void main(String[] args) throws IOException {
        //运行java ReadStandard命令,返回运行该程序的子进程
        Process p=Runtime.getRuntime().exec("java ReadStandard");
        try(//以p进程的输出流创建PrintStream对象
            //这个输出流对本程序是输出流,对p进程是输入流
            PrintStream ps=new PrintStream(p.getOutputStream())){
            //向ReadStandard程序写入内容,这些内容将被ReadStandard读取
            ps.println("普通字符串");
            ps.println(new WriteToProcess());

        }
        System.out.println("j");
    }

}
//定义一个ReadStandard类,该类可以接收标准输入
//并将标准输入写入out.txt文件
class ReadStandard{
    public static void main(String[] args) {
        try(//使用System.in创建Scanner对象,用于获取标准输入
            Scanner sc=new Scanner(System.in);
            PrintStream ps=new PrintStream(new FileOutputStream("out.txt"))){
            //增加下面一行只把回车作为分隔符
            sc.useDelimiter("\n");
            //判断是否有下一个输入项
            while(sc.hasNext()){
                //输出输入项
                ps.println("键盘输入:"+sc.next());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


RandomAccessFile

​ RandomAccessFile是java输入输出流体中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可读取文件内容,也可以向文件输出数据。

与普通输入输出流不同:

  1. 支持随机访问,可以跳转到文件任意位置读取数据。
  2. 允许自由定义文件记录指针,可以向已存在的文件后追加内容。
  3. 有一个局限性,只能读写文件,不能读写其他IO节点

操作文件记录指针的方法:

在这里插入图片描述

​ RandomAccessFile还包含了一系列的ReadXxx()方法和WriterXxx()方法来完成输入,输出。

​ RandomAccessFile类有两个构造器,两个构造器基本相同,只是指定文件形式不同,一个是使用String参数来指定文件名,另一个是使用File参数来指定文件本身。
​ 创建该对象时还需要指定一个mode参数,该参数指定了RandomAccessFile执行写入方法,该参数有如下4个值:

在这里插入图片描述

//使用RandomAccessFile来访问指定的中间部分数据
public class RandomAccessFileTest {
    public static void main(String[] args) {
        try(//
            RandomAccessFile raf=new RandomAccessFile("src\\test\\java\\T1\\RandomAccessFileTest.java","r")){
            //获取RandomAccessFile对象的文件指针位置,初始化为0
            System.out.println("文件指针位置:"+raf.getFilePointer());
            //移动raf的文件指针的位置
            raf.seek(300);
            byte[] bbuf=new byte[1024];
            //用于保存实际读取的字节数
            int hasRead=0;
            //使用循环来重复取出数据
            while((hasRead=raf.read(bbuf))>0){
                //取出数据,将字节数组转换成字符串输入
                System.out.println(new String(bbuf,0,hasRead));

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

    }
}
//在文件最后追加内容
public class AppendContent {
    public static void main(String[] args) {
        try(//以读,写的方式打开一个RandomAccessFile对象
            RandomAccessFile raf=new RandomAccessFile("out.txt","rw")
             ){
            //将记录指针移动到out.txt文件的最后
            raf.seek(raf.length());
            raf.write("追加内容:\t\n".getBytes());

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

在这里插入图片描述

//指定文件,指定位置插入内容的功能
public class InsertContent {
    public static void insert(String fileName,long pos,String insertContent) throws IOException {
        File tmp=File.createTempFile("tmp",null);
        tmp.deleteOnExit();
        try(
            RandomAccessFile raf=new RandomAccessFile(fileName,"rw");
            //使用临时文件保存插入点后的数据
            FileOutputStream tmpOut=new FileOutputStream(tmp);
            FileInputStream tmpIn=new FileInputStream(tmp);
            ){
            raf.seek(pos);
            //下面代码讲插入点后的内容读入临时文件中保存
            byte[] bbuf=new byte[64];
            //用于保存实际读取的字节数
            int hasRead=0;
            //使用循环方式读取插入点后的数据
            while ((hasRead=raf.read(bbuf))>0){
                //将读取的数据写入临时文件
                tmpOut.write(bbuf,0,hasRead);
            }

            //下面代码用于插入内容
            //把文件记录指针重新定位到pos位置
            raf.seek(pos);
            //追加需要插入内容
            raf.write(insertContent.getBytes());
            //追加临时文件中的内容
            while((hasRead=tmpIn.read(bbuf))>0){
                raf.write(bbuf,0,hasRead);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        insert("src\\test\\java\\T1\\InsertContent.java",45,"插入的内容\r\n");
    }
}


对象序列化

​ 对象序列化的目标是将对象保存在磁盘中,或允许在网络中直接输出对象。


序列化的含义和意义

​ 序列化机制是允许将实现序列化的java对象转换成字节序列,这些字节序列可以保存在磁盘中,或通过网络传输,以备以后重新恢复成原来的对象。这个机制使得对象可以脱离程序的运行而独立存在

​ 对象的序列化:指将一个java对象写入IO流中,与此对应,对象的反序列化则是指IO流中恢复该java对象

需要让某个对象支持序列化机制,则必须让它的类是可序列化,为了让某个类是可序列化,该类必须实现如下两个接口之一:

  1. Serializable
  2. Externalizable

所有可能在网络上传输对象的类都应该是可序列化的,否则程序将会出现异常。


使用对象流实现序列化

​ 如果需要将某个对象保存到磁盘或者通过网络传输,那么这个类应该实现 Serializable接口或 Externalizable接口

对象序列化步骤:

  1. 让目标类实现Serializable接口
  2. 创建一个ObjectOutputStream,这个输出流是一个处理流。
  3. 调用ObjectOutputStream对象的writerObject()方法输出可序列化对象
//定义一个类,实现Serializable接口
import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    //此处没有无参构造器
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

}
//通过ObjectOutputStream输出流将一个Person对象写入磁盘中
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class WriterObject {
    public static void main(String[] args) {
        try(//创建一个ObjectOutputStream输出流
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("object.txt"))){
            Person per=new Person("悟空",100);
            //将per对象写入输出流
            oos.writeObject(per);

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

如果需要从二进制流中恢复java对象,则需要反序列化

反序列化步骤:

  1. 创建ObjectInputStream输入流,这是输入流的处理流
  2. 调用ObjectInputStream输入流的readObject()方法读取流中的对象,该方法返回一个Object类型的java对象,如果程序知道该java对象的类型,则可以用强制类型转换成真实的类型
public class ReadObject {
    public static void main(String[] args) {
        try(//创建一个ObjectInputStream输入流
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream("object.txt"))){
            //从输入流中读取一个java对象,并将其强制类型转换成Person类
            Person p=(Person) ois.readObject();
            System.out.println("名字为:"+p.getName()+"\n年龄:"+p.getAge());

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

​ 反序列化读取仅仅是java对象的数据,而不是java类,因此才用反序列化恢复java对象时,必须提供java对象所属类的class文件,否则会报错。

当一个可序列化类有多个父类时(包括直接父类和间接父类),这些父类要么有无参构造器,要么也是可序列化的–否则反序列时将抛出InvalidClassException异常。如果父类时不可序列化的,只是带有无参数的构造器,则父类中定义的成员变量值不会序列化到二进制流。


对象引用的序列化

​ 如果某类的成员变量是引用类型变量,那么这个引用类必须是可序列化的,否则拥有该类类型成员的类是不可序列化的。

​ java序列化机制采用了一种特殊的序列化算法:

  1. 所有保存到磁盘中的对象都有一个序列化编号。
  2. 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)被序列化过,系统才会将该对象转换成字节序列并输出
  3. 如果某对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象。

在这里插入图片描述

//两个Teacher对象都同时持有引用同一个Person对象的引用,程序调用两次writeObject()方法输出同一个Teacher对象
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class WriteTeacher {
    public static void main(String[] args) {
        try(//创建一个ObjectOutputStream输出流
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("teacher.txt"))){
            Person per=new Person("孙悟空",500);
            Teacher t1=new Teacher("唐僧",per);
            Teacher t2=new Teacher("八戒",per);
            //依次将4个对象写入输出流
            oos.writeObject(t1);
            oos.writeObject(t2);
            oos.writeObject(per);
            oos.writeObject(t2);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
//代码4次调用writerObject()方法来输出对象,实际上只序列化了三个对象
//而且序列的两个Teacher对象的student引用实际是同一个Person对象
//读取序列化文件的对象证明上面结论
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ReadTeacher {
    public static void main(String[] args) {
        try(//创建一个ObjiceInputStream输入流
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream("teacher.txt"))){
            //依次读取ObjectInputStream输入流中的4个对象
            Teacher t1=(Teacher) ois.readObject();
            Teacher t2=(Teacher) ois.readObject();
            Person p=(Person) ois.readObject();
            Teacher t3=(Teacher) ois.readObject();
            //输出true
            System.out.println(t1.getStudent()==p);
            //输出true
            System.out.println(t2.getStudent()==p);
            //输出true
            System.out.println(t3.getStudent()==t2.getStudent());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
//通过后面的判断,发现t2和t3是同一个java对象,t1是student引用的,t2的student引用和p引用变量引用也是同一个java对象

​ 由于java序列化机制使然,要是多次序列化同一个java对象时,只有一次序列化时才会把该java对象转换成字节序列并输出,这潜在一个问题,当程序序列化一个可变对象时,只有第一次writerObject()方法输出时才会将该对象转换成字节序列输出,当再次调用writerObject()方法时,程序只是输出前面的序列化编号,即使后面该对象的实例变量值已经被改变,改变的实例变量值也不会被输出。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializeMutable {
    public static void main(String[] args) {
        try(//创建一个ObjectOutputStream输出流
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("mutable.txt"));
            //创建一个ObjectInputStream输入流
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream("mutable.txt"));
        ){
            Person per=new Person("悟空",500);
            //系统将per对象转换成字节序列并输出
            oos.writeObject(per);
            //改变per对象的name实例变量的值
            per.setName("猪八戒");
            //系统只是输出序列化编号,所以改变后的name不会被序列化
            oos.writeObject(per);
            Person p1=(Person) ois.readObject();
            Person p2=(Person) ois.readObject();
            //下面输出true,即反序列化后p1等于p2
            System.out.println(p1==p2);
            //下面看到依旧输出悟空,即改变后的实例变量没有被序列化
            System.out.println(p2.getName());

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

在这里插入图片描述


自定义序列化

​ 通过在实例变量前使用transient关键字修饰,可以指定java序列化无须理会该实例变量。

//实例
public class Person implements Serializable {
    private String name;
    private transient int age;

    //此处没有无参构造器
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
}

transient关键字只能用于修饰实例变量,不可以修饰java程序中的其他部分

//下面程序先序列化一个Person对象,然后在反序列化该Person对象,得到反序列化的Person对象后程序输出该对象的age实例的变量值
public class TransientTest {
    public static void main(String[] args) {
        try(//创建一个ObjectOutputStream输出流
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("transient.txt"));
            //创建一个ObjectInputStream输入流
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream("transient.txt"))
            ){
            Person per=new Person("孙悟空",500);
            //系统将per对象转换成字节序列并输出
            oos.writeObject(per);
            Person p= (Person) ois.readObject();
            System.out.println(p.getAge());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
//输出结果为:0

在这里插入图片描述

/*
为Person提供writeObject()和readObject()方法
writeObject():在保存Person对象时将其name包装成StringBuffer,并将其字符串序列反转后写入;
readObject():在处理name的政策与此对应,先将读取的数据强制类型转换成StringBuffer,再将其反转赋给name实例变量
*/
public class Person implements Serializable {
    private String name;
    private transient int age;

    //此处没有无参构造器
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    
    private void writeObject()(ObjectOutputStream out) throws IOException {
        //将name实例变量值反转后写入二进制流
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        //将读取的字符串反转后赋值给name实例变量
        this.name=((StringBuffer)in.readObject()).reverse().toString();
        this.age=in.readInt();
    }
}
//上面程序提高了序列化的安全性

还有一种更彻底的自定义机制,它可以在序列化对象时将该对象替换成其他对象。如果需要实现序列化某个对象时替换该对象,则应为序列化类提供如下特殊方法:

ANY-ACCESS-MODIFIER Object writerReplace() throws ObjectStreamExection;

​ 此方法将有序列化机制调用,只要该方法存在。因为该方法可以拥有private,protected,package-private 等访问权限,所以其子类有可能获得该方法。
​ 例如Person类提供了writerReplace()方法,这样可以在写入Person对象时将该对象替换成ArrayList

在这里插入图片描述

在这里插入图片描述

​ 与writerReplace()方法相对的是,序列化机制里还有一个特殊的方法,它可以实现保护性复制整个对象

ANY-ACCESS-MODIFIER Object writerReplace() throws ObjectStreamExection;

​ 这个方法紧贴接着readObject()之后被调用,该方法的返回值将会代替原来反序列化的对象,而原来readObject()反序列化的对象将会被立即丢弃。

​ readResolve()方法在序列化单列类,枚举类尤其有用。readResolve()方法的返回值将会替换原来反序列化的对象,也就是反序列化得到Orientation对象被直接丢弃。

在这里插入图片描述


另一种自定义序列化机制

​ 这种序列化机制完全由程序员决定存储和恢复的对象数据,要实现该目标,java类必须实现Externalizable接口,该接口有两个方法:
在这里插入图片描述

public class Person implements Externalizable {
    private String name;
    private transient int age;

    //此处没有无参构造器
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        //将name实例变量值反转后写入二进制流
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }
    

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //将读取的字符串反转后赋给name实例变量
        this.name=((StringBuffer)in.readObject()).reverse().toString();
        this.age=in.readInt();
    }
}

​ 如果程序需要实例化实现Externalizable接口的对象,一样调用ObjectOutputStream的writeObject()方法输出该对象即可;反序列化该对象,则调用ObjectInputStream的readObject()方法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


版本

​ java序列化机制允许为序列化提供一个private static final的serialVersionUID值,该类变量的值用于标识java类的序列化版本,也就是说,如果一个类升级后, 只要它的serialVersionUID类变量值保存不变,序列化机制也会把她们当成同一个序列化版本。

//分配serialVersionUID类变量的值
public class Test{
    //为该类指定一个serialVersionUID类变量值
    private static final long serialVersionUID
}

​ 如果不显式定义serialVersionUID值,该类变量的值将由JVM根据类的香港信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而构造对象的反序列化因类的版本不兼容而失败。

在这里插入图片描述

在这里插入图片描述


NIO

​ 从JDK1.4开始,java提供了一系列改进的输入/输出处理的新功能,这些功能被统称为新IO,新增了许多用于处理输出输出的类,这些类都被放在java.nio包以及子包下,并且对员又的java.io包中的很多类都以NIO的基础进行改写,新增了满足NIO的功能。


java新IO概述

​ 新IO使用了不同的方式处理输入输出,新IO才用内存映射文件的方式来处理输入输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件。

​ java中与新IO相关的包:

在这里插入图片描述

Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象

  • Channel:是对传统输入输出系统的模拟,在新IO系统中所有的数据都需要通过通道传输;与传统inputStream,OutputStream最大的区别在于它提供了一个map()方法,通过该方法可以将一块数据映射到内存中。
  • Buffer:可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先放到Buffer中,而从Channel中读取数据也必须先放到Buffer中。

新IO还提供了将 Unicode字符串映射成字节序列以及逆映射操作的Charset类,也提供了用于支持非阻塞式输入输出的Selector类


使用Buffer

​ 从内部结构看,Buffer就像一个数组,它可以保存多个类型相同的数据。子类ByteBuffer,他可以在底层字节数组上进行get/set操作。除了ByteBuffer之外,对应与其他数据类型(booleam除外)都有相对于的Buffer类:CharBuffer,IntBuffer等

​ 这些Buffer类都没有提供构造器,通过如下方法得到一个Buffer对象:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

​ Buffer的主要作用就是装入数据,然后输出数据,开始时Buffer的position为0,limit为capacity,程序可通过put()方法向Buffer中放入一些数据,每放入一些数据,Buffer的position相应的向后移动一些位置。
​ 当Buffer装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position所在位置,并将position设置为0,这使得Buffer的读写指针又移到了开始位置。

Buffer包含两个重要方法,即flip()和clear(),flip()为从Buffer中获取出数据做好准备,而clear()为再次向Buffer中装入数据做好准备。

在这里插入图片描述

​ Buffer的所有子类还提供了put()和get()方法,用于向Buffer中放入数据和取出数据。

​ 当使用put()和get()来访问Buffer中的数据,分为相对和绝对两种

相对:从Buffer的当前position从开始读取或写入数据,然后将位置的值按处理元素的个数增加

绝对:直接根据索引向Buffer中读取或写入数据,使用绝对的方式访问Buffer里的数据时,不会影响位置的值

public class BufferTest {
    public static void main(String[] args) {
        //创建Buffer
        CharBuffer buff=CharBuffer.allocate(8);//1
        System.out.println("capacity:"+buff.capacity());
        System.out.println("limit:"+buff.limit());
        System.out.println("position:"+buff.position());
        //放入元素
        buff.put('A');
        buff.put('B');
        buff.put('C');//2
        System.out.println("加入三个元素后,position="+buff.position());
        //调用flip()方法
        buff.flip();//3
        System.out.println("执行flip后,limit()="+buff.limit());
        System.out.println("position="+buff.position());
        //取出第一个元素
        System.out.println("第一个元素:"+buff.get());//4
        System.out.println("取出一个元素后,position="+buff.position());
        //调用clear()方法
        buff.clear();//5
        System.out.println("执行clear()后,limit="+buff.limit());
        System.out.println("执行clear()后,position="+buff.position());
        System.out.println("执行clear()后,缓冲区内容没有被清楚,第三个元素为:"+buff.get(2));//6
        System.out.println("执行绝对后,position="+buff.position());

    }
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

​ 通过allocate()方法创建Buffer对象是普通Buffer,ByteBuffer 提供了一个allocateDirect()方法来创建直接Buffer。直接Buffer的创建成本比普通Buffer创建成本要高,但直接Buffer的读取效率更高。

在这里插入图片描述


使用Channel

​ Channel类似于传统输入流,但是与传统输入了主要的区别:

  1. Channel可以直接将指定文件的部分或全部直接映射成Buffer
  2. 程序不能直接访问Channel中的数据,包括读取,写入都不行,Channel只能与Buffer进行交互。如果从Channel中取出数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer冲取出这些数据;如果要将程序中的数据写入Channel中,一样先让程序将数据放入Buffer中,程序在将Buffer里的数据写入Channel中。

​ Channel通过传统节点InputStream,OutputStream的getChannel()方法来返回对应Channel,不同的节点流获取的Channel不一样。

​ Channel常用的三个方法map(),read(),write(),其中map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;而read()和write()方法都有一系列重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据。

public class FileChannelTest {
    public static void main(String[] args) {
        File f=new File("src\\test\\java\\T1\\FileChannelTest.java");
        try(//创建FileInputStream,以该文件输入流创建FileChannel
            FileChannel inChannel=new FileInputStream(f).getChannel();
            //以文件输出流创建FileChannel,用以控制输出
            FileChannel outChannel=new FileOutputStream("a.txt").getChannel();
        ){
            //将FileChannel里的全部数据映射成ByteBuffer
            MappedByteBuffer buffer=inChannel.map(FileChannel.MapMode.READ_ONLY,0,f.length());
            //使用GBK的字符集来创建解码器
            Charset charset=Charset.forName("UTF-8");
            //直接将buffer里的数据全部输出
            outChannel.write(buffer);
            //再次调用buffer的clear()方法,复原limit,position的位置
            buffer.clear();
            //创界解码器(CharsetDecoder)对象
            CharsetDecoder decoder=charset.newDecoder();
            //使用解码器将ByteBuffer转换成CharBuffer
            CharBuffer charBuffer=decoder.decode(buffer);
            //CharBuffer的toString方法可以获取对应的字符串
            System.out.println(charBuffer);
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

​ 在RandomAccessFile中也包含了一个getChannel()方法,RandomAccessFile返回的FileChannel()是只读还是只写,取决于RandomAccessFile打开文件的模式

//对a.txt文件内容进行复制,追加在该文件后面
public class RandomFileChannelTest {
    public static void main(String[] args) {
        File f=new File("a.txt");
        try(//创建一个RandomAccessFile对象
            RandomAccessFile raf=new RandomAccessFile(f,"rw");
            //获取RanmdomAccessFile对应的Channel
            FileChannel randomChannel=raf.getChannel();
             ){
            //将Channel中的所有数据映射成ByteBuffer
            ByteBuffer buffer=randomChannel.map(FileChannel.MapMode.READ_ONLY,0,f.length());
            //把Channel的记录指针一道最后
            randomChannel.position(f.length());
            //将buffer中的所有数据输出
            randomChannel.write(buffer);

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

字符集和Charset

​ 所有的文件在底层都是二进制文件,即全部都是字节码,之所以能看到一个个的字符,这完全是因为系统将底层的二进制序列转换成字符了。在这个过程中设计两个概念:
​ 编码和解码,把文明字符序列转换成二进制序列称为编码;把二进制序列转换成普通人能看懂的文明字符串称为解码

在这里插入图片描述

​ JDK1.4提供Charset来处理字节序列和字符序列之间的转换,该类包含了用于创建解码器和编码器的方法,还提供了获取Charset所支持的字符集的方法,Charset类是不可变的。

​ Charset类提供了一个acailableCharset()静态方法来获取当前jdk所支持的所有字符集,如:

public class CharsetTest {
    public static void main(String[] args) {
        //获取java支持的全部字符集
        SortedMap<String, Charset> map=Charset.availableCharsets();
        for (String alias : map.keySet()) {
            System.out.println(alias+"----->"+map.get(alias));
        }
    }
}

​ 对中国程序员而言,下面几个字符串别名是常用的

在这里插入图片描述

​ 程序可以 Charset的forName()方法来创建对应的Charset对象,forName()方法的参数就是相应字符集的别名

Charset cs=Charset.forName("UTF-8");
Charset csCn=Charset.forName("GBK");

​ 获取Charset对象后,就可以通过该对象的newDecoder(),newEncoder()这两个方法返回CharsetDecoder和CharsetEncoder对象,代表该Charset的解码器和编码器。调用CharsetDecoder的decode()方法就可以将ByteBuffer(字节序列)转换成CharBuffer(字符序列);调用CharsetEncoder的encode()方法就可以将ByteBuffer或String(字符序列)转换成ByteBuffer(字节序列).

在这里插入图片描述

public class CharsetTransfrom {
    public static void main(String[] args) throws Exception {
        //创建简体中文对应的Charset
        Charset cn=Charset.forName("GBK");
        //获取cn对象对应的编码器和解码器
        CharsetEncoder cnEncoder=cn.newEncoder();
        CharsetDecoder cnDecoder=cn.newDecoder();
        //创建一个CharBuffer对象
        CharBuffer cbuff=CharBuffer.allocate(8);
        cbuff.put('孙');
        cbuff.put('悟');
        cbuff.put('空');
        cbuff.flip();
        //将CharBuffer中的字符序列转换成字节序列
        ByteBuffer bbuer=cnEncoder.encode(cbuff);
        //循环访问ByteBuffer中的每个字节
        for(int i=0;i<bbuer.capacity();i++){
            System.out.println(bbuer.get(i)+" ");
        }
        //将ByteBuffer的数据解码成字符序列
        System.out.println("\n"+cnDecoder.decode(bbuer));
    }
}

​ Charset类也提供如下三个方法:

在这里插入图片描述


文件锁

​ 在NIO中,java提供了FileLock来支持文件锁定功能,在FileChannel中提供了lock()/tryLock()方法可以获取文件锁FileLock对象,从而锁定文件。

​ lock():试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞。
​ tryLock():尝试锁定文件,将它直接返回而不是阻塞,如果获取了文件锁,该方法则返回该文件锁,否则将返回null

在这里插入图片描述

直接使用lock()或tryLock()方法获取的文件锁是排它锁

public class FileLockTest {
    public static void main(String[] args) throws Exception {
        try(//使用FileOutputStream获取FileChanel
            FileChannel channel=new FileOutputStream("a.txt").getChannel()){
            //使用非阻塞方式对指定文件加锁
            FileLock lock=channel.tryLock();
            //程序暂停10秒
            Thread.sleep(10000);
            //释放锁
            lock.release();
        }
    }
}
//程序中调用了Thread.sleep(10000);暂停了10秒后才释放文件锁,因此在10秒内,其他程序无法修改a.txt文件

在这里插入图片描述

在这里插入图片描述


java7的NIO.2

​ java7对原有NIO进行了改进。

  1. 提供了全面的文件IO和文件系统访问支持。
  2. 基于异步Channel的IO

Path,Paths,和Files核心API

​ Path接口代表一个平台无关的平台路径。除此之外,NIO.2还提供了Files和Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths包含了两个返回Path的静态工厂方法。

//实例,Path功能和用法
public class PathTest {
    public static void main(String[] args)throws Exception {
        //以当前路径创建Path对象
        Path path=Paths.get(".");
        System.out.println("path里包含的路径数量:"+path.getNameCount());
        System.out.println("path的根路径:"+path.getRoot());
        //获取path对应的绝对路径
        Path absolutePath=path.toAbsolutePath();
        System.out.println(absolutePath);
        //获取绝对路径的根路径
        System.out.println("absolutePath的根路径"+absolutePath.getRoot());
        //获取绝对路径所包含的路径数量
        System.out.println("absolutePath里包含的路径数:"+absolutePath.getNameCount());
        System.out.println(absolutePath.getName(1));
        //以多个String来构建Path对象
        Path path2=Paths.get("g:","publish","codes");
        System.out.println(path2);
    }
}

​ Files是一个操作文件的工具类,它提供了大量 便捷的工具方法。

public class FilesTest {
    public static void main(String[] args) throws Exception {
        //复制文件
        Files.copy(Paths.get("src\\test\\java\\T1\\FilesTest.java"),new FileOutputStream("a.txt"));
        //判断FilesTest.java文件是否为隐藏文件
        System.out.println("FilesTest.java是否为隐藏文件:"+Files.isHidden(Paths.get("src\\test\\java\\T1\\FilesTest.java")));
        //一次性读取FilesTest.java文件的所有行
        List<String> lines=Files.readAllLines(Paths.get("src\\test\\java\\T1\\FilesTest.java"), Charset.forName("UTF-8"));
        System.out.println(lines);
        //判断指定文件的大小
        System.out.println("FilesTest.java的大小为:"+Files.size(Paths.get("src\\test\\java\\T1\\FilesTest.java")));
        List<String> poem=new ArrayList<>();
        poem.add("水晶");
        poem.add("裕丰");
        //直接将多个字符串内容写入指定文件中
        Files.write(Paths.get("pome.txt"),poem,Charset.forName("UTF-8"));
        //使用java8新增Stream API列出当前目录下所有文件和子目录
        Files.list(Paths.get(".")).forEach(path -> System.out.println(path));
        //使用java8新增Stream API读取文件内容
        Files.lines(Paths.get("src\\test\\java\\T1\\FilesTest.java"),Charset.forName("UTF-8")).forEach(line-> System.out.println(line));
        FileStore cStore=Files.getFileStore(Paths.get("C:"));
        //判断C盘的总空间,可用空间
        System.out.println("C盘共有空间:"+cStore.getTotalSpace());
        System.out.println("C盘可用空间:"+cStore.getUsableSpace());
    }
}

熟练掌握Files工具类的用法,可以大大简化文件IO


使用FileVisitor遍历文件和目录

​ Files工具类提供了两个方法来遍历文件和子目录:

在这里插入图片描述

​ 这两个方法都需要FileVisitor参数,FileVisitor代表一个文件访问器,walkFileTree()方法会自动变量start路径下的所有文件和子目录,遍历文件和子目录都会触发FileVisitor中相应的方法,FileVisitor中定义的四个方法:

在这里插入图片描述

​ 这四个方法都返回一个FileVisitResult对象,他是一个枚举类,代表访问之后的后续行为。FileVisitorResult定义的几种后续行为:

在这里插入图片描述

​ 实际编程没有必要为四个方法提供实现,所以可以通过继承SimpleFileVisit(FileVisitor的实现类)来实现自己的文件访问器。

//使用FileVisitor遍历文件和子目录
public class FileVisitorTest {
    public static void main(String[] args) throws IOException {
        //遍历C盘目录下的所有文件和子文件
        Files.walkFileTree(Paths.get("C:","Temp"),new SimpleFileVisitor<Path>(){
            //访问文件时触发该方法
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println("正在访问"+file+"文件");
                //找到了FileVisit.java文件
                if(file.endsWith("FileVisit.java")){
                    System.out.println("--已经找到目标--");
                    return FileVisitResult.TERMINATE;
                }
                return FileVisitResult.CONTINUE;
            }
            //开始访问目录时触发该方法
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                System.out.println("正在访问:"+dir+"路径");
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

​ 这就实现了对指定目录进行搜索,直到找到文件为止。


使用WatchService监控文件变化

​ NIO.2的Path类提供了如下一个方法来监听文件系统变化:

在这里插入图片描述

​ 这个方法中WatchService代表了一个文件监听服务,他负责监听Path代表的目录下的文件变化。一旦使用register()方法完成注册之后,接下来就可调用WatchService的如下三个方法来获取被监听目录的文件变化:

在这里插入图片描述

​ 需要一直监控,可以选择take();方法,如果程序只需要指定时间,则考虑poll()方法

//实现监听C盘目录变化
public class WatchServiceTest {
    public static void main(String[] args) throws Exception {
        //获取文件系统的WatchService对象
        WatchService watchService= FileSystems.getDefault().newWatchService();
        //为C盘根路径注册监听
        Paths.get("C:/").register(watchService,
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);
        while(true){
            //获取下一个文件变化事件
            WatchKey key=watchService.take();
            for (WatchEvent<?> pollEvent : key.pollEvents()) {
                System.out.println(pollEvent.context()+"文件发生了"+pollEvent.kind()+"事件");
            }
            //重设WatchKey
            boolean valid=key.reset();
            //如果重设失败,退出监听
            if(!valid){
                break;
            }
        }
    }
}


访问文件属性

​ java7的NIO.2在java.nio.file.attribute包下提供了大量的工具类,通过这些工具类,开发者可以非常简单的读取,修改文件属性,工具类分如下两类:

在这里插入图片描述

在这里插入图片描述

//示范读取,修改文件的属性
public class AttributeViewTest {
    public static void main(String[] args) throws Exception {
        //获取将要操作的文件
        Path testPath= Paths.get("src\\test\\java\\T1\\AttributeViewTest.java");
        //获取访问基本属性的BasicFileAttributeView
        BasicFileAttributeView basicView= Files.getFileAttributeView(testPath,BasicFileAttributeView.class);
        //获取访问基本属性的BasicFileAttributes
        BasicFileAttributes basicAttribs=basicView.readAttributes();
        //访问文件的基本属性
        System.out.println("创建事件:"+new Date(basicAttribs.creationTime().toMillis()));
        System.out.println("最后访问时间:"+new Date(basicAttribs.lastAccessTime().toMillis()));
        System.out.println("最后修改时间"+new Date(basicAttribs.lastModifiedTime().toMillis()));
        System.out.println("文件大小:"+basicAttribs.size());
        //获取访问文件属主信息的FileOwnerAttributeView
        FileOwnerAttributeView ownerView=Files.getFileAttributeView(testPath,FileOwnerAttributeView.class);
        //获取该文件所属的用户
        System.out.println(ownerView.getOwner());
        //获取系统中guest对应的用户
        UserPrincipal user= FileSystems.getDefault().getUserPrincipalLookupService().lookupPrincipalByName("guest");
        //修改用户
        ownerView.setOwner(user);
        //获取访问自定义属性的FileOwnerAttributeView
        UserDefinedFileAttributeView userView=Files.getFileAttributeView(testPath,UserDefinedFileAttributeView.class);
        List<String> attrName=userView.list();
        //遍历所有的自定义属性
        for (String name : attrName) {
            ByteBuffer buf=ByteBuffer.allocate(userView.size(name));
            userView.read(name,buf);
            buf.flip();
            String value= Charset.defaultCharset().decode(buf).toString();
            System.out.println(name+"--->"+value);
        }
        //添加一个自定义属性
        userView.write("发行者",Charset.defaultCharset().encode("疯狂java联盟"));
        //获取访问DOS属性的DosFileAttributeView
        DosFileAttributeView dosView=Files.getFileAttributeView(testPath,DosFileAttributeView.class);
        //将文件设置隐藏,只读
        dosView.setHidden(true);
        dosView.setReadOnly(true);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值