Java I/O系统学习系列一:File和RandomAccessFile

  I/O系统即输入/输出系统,对于一门程序语言来讲,建立一个好的输入/输出系统并不是易事。由于不只存在各类I/O源端和想要与之通讯的接收端(文件、控制台、网络连接等),并且还须要支持多种不一样方式的通讯(顺序、随机存取、缓冲、二进制、按字符、按行、按字等)。java

  Java类库的设计者经过建立大量的类来解决这个难题,好比面向字节的类(字节流,InputStream、OutputStream)、面向字符和基于Unicode的类(字节流,Reader、Writer)、nio类(新I/O,为了改进性能及功能)等。因此,在充分理解Java I/O系统以便正确地运用以前,咱们须要学习至关数量的类。所以一开始可能会对Java I/O系统提供的如此多的类感到迷惑,不过在咱们系统地梳理完整个Java I/O系统并将这部分知识与融入到自个人整个知识体系中后,咱们就能很快消除这种迷惑。windows

  在I/O这个专题里面,我会总结Java 中涉及到的大多数I/O相关类的用法,从传统I/O诸如:File、字节流、字符流、序列化到新I/O:nio。在本节中我会先总结File和RandomAccessFile的相关知识,按照以下顺序:数组

  File网络

  RandomAccessFiledom

  总结ide

 

1. File

1.1 File简介经常使用方法

  根据官方文档的解释,Java中的File类是文件和目录路径的抽象,用户经过File直接执行与文件或目录相关的操做。个人理解就是File类的做用是用来指代文件或者目录的,经过File的抽象咱们能够很方便的操做文件或目录,无需关心操做系统的差别。官方文档是这样描述的:工具

An abstract representation of file and directory pathnames.性能

User interfaces and operating systems use system-dependent pathname strings to name files and directories.  This class presents an abstract, system-independent view of hierarchical pathnames. 学习

  用户接口和操做系统经过系统相关的路径名来命名文件和目录。而File类提供了一个抽象地、系统无关的视角来描述分层次路径名。File表明抽象路径名,有两个部分组成:  spa

  • 一个可选的系统相关的前缀,好比磁盘驱动器说明符(disk-drive specifier),unix系统中是“/”而windows系统中则是“\”;
  • 0或多个字符串名称组成的序列;

  关于File的用法,我以为直接经过示例来学习会比较高效:  

public class FileDemo {    
    public static void main(String[] args) throws IOException {
        File dir = new File("f:/dirDemo");
        System.out.println("dir exists: " + dir.exists());
        dir.mkdirs();
        System.out.println("dir exists: " + dir.exists());
        if(dir.isFile()) {
            System.out.println("dir is a file.");
        }else if(dir.isDirectory()) {
            System.out.println("dir is a directory");
        }
        
        File file = new File("f:/dirDemo/fileDemo");        
        System.out.println(
                "\n Absolute path: " + file.getAbsolutePath() +
                "\n Can read: " + file.canRead() + 
                "\n Can write: " + file.canWrite() +
                "\n getName: " + file.getName() +
                "\n getParent: " + file.getParent() +
                "\n getPath: " + file.getPath() +
                "\n length: " + file.length() +
                "\n lastModified: " + file.lastModified() +
                "\n isExist: " + file.exists());
        file.createNewFile();
        System.out.println("is file exist: " + file.exists());
        if(file.isFile()) {
            System.out.println("file is a file.");
        }else if(file.isDirectory()) {
            System.out.println("file is a directory");
        }

        System.out.println();
        for(String filename : dir.list()) {
            System.out.println(filename);
        }
    }    
}

  输出结果:

dir exists: false
dir exists: true
dir is a directory

 Absolute path: f:\dirDemo\fileDemo
 Can read: false
 Can write: false
 getName: fileDemo
 getParent: f:\dirDemo
 getPath: f:\dirDemo\fileDemo
 length: 0
 lastModified: 0
 isExist: false
is file exist: true
file is a file.

fileDemo

  在这个简单demo中咱们用到多种不一样的文件特征查询方法来显示文件或目录路径的信息:

  • getAbsolutePath(),获取文件或目录的绝对路径;
  • canRead()、canWrite(),文件是否可读/可写;
  • getName(),获取文件名;
  • getParent(),获取父一级的目录路径名;
  • getPath(),获取文件路径名;
  • length(),文件长度;
  • lastModified(),文件最后修改时间,返回时间戳;
  • exists(),文件是否存在;
  • isFile(),是不是文件;
  • isDirectory(),是不是目录;
  • mkdirs(),建立目录,会把不存在的目录一并建立出来;
  • createNewFile(),建立文件;
  • list(),能够返回目录下的全部File名,以字符数组的形式返回;

  exists()方法能够返回一个File实例是否存在,这里的存在是指是否在磁盘上存在,而不是指File实例存在于虚拟机堆内存中。一个File类的实例可能表示一个实际的文件系统如文件或目录,也可能没有实际意义,仅仅只是一个File类,并无关联实际文件,若是没有则exists()返回false。

1.2 File过滤器

  list()方法返回的数组中包含此File下的全部文件名,若是想要得到一个指定的列表,好比,但愿获得全部扩展名为.java的文件,可使用“目录过滤器”(实现了FilenameFilter接口),在这个类里面能够指定怎样显示符合条件的File对象。咱们把一个本身实现的FilenameFilter传入list(FilenameFilter filter)方法中,在这个被当作参数的FilenameFilter中重写其accept()方法,指定咱们本身想要的逻辑便可,这实际上是策略模式的体现。

  好比咱们只要获取当前项目跟目录下的xml文件:

public class XmlList {    
    public static void main(final String[] args) {
        File file = new File(".");
        String list;
        list = file.list(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name) {
                Pattern pattern = Pattern.compile("(.*)\\.xml");
                return pattern.matcher(name).matches();
            }
        });
        Arrays.sort(list,String.CASE_INSENSITIVE_ORDER);
        for(String dirItem : list)
            System.out.println(dirItem);
    }
}

  在这个例子中,咱们用匿名内部类的方式给list()传参,accept()方法内部咱们指定了正则过滤策略,在调用File的list()方法时会自动为此目录对象下的每一个文件名调用accept()方法,来判断是否要将该文件包含在内,判断结果由accept()返回的布尔值来表示。

  如上也只是罗列了一些我的认为File类较经常使用的方法,也只是一部分,若须要更详细信息请参考官方文档。

1.3 目录工具

  接下来咱们来看一个实用工具,能够得到指定目录下的全部或者符合要求的File集合:

public class Directory {
    // local方法能够得到指定目录下指定文件的集合
    public static File[] local(File dir,String regex) {
        
        return dir.listFiles(new FilenameFilter() {
            private Pattern pattern = Pattern.compile(regex);
            @Override
            public boolean accept(File dir, String name) {
                return pattern.matcher(new File(name).getName()).matches();
            }
        });
    }
    
    public static File[] local(String dir,String regex) {
        return local(new File(dir),regex);
    }
    
    // walk()方法能够得到指定目录下全部符合要求的文件或目录,包括子目录下
    public static TreeInfo walk(String start,String regex) {
        return recurseDirs(new File(start),regex);
    }
    
    public static TreeInfo walk(File start,String regex) {
        return recurseDirs(start,regex);
    }
    
    public static TreeInfo walk(String start) {
        return recurseDirs(new File(start),".*");
    }
    
    public static TreeInfo walk(File start) {
        return recurseDirs(start,".*");
    }
    
    static TreeInfo recurseDirs(File startDir,String regex) {
        TreeInfo treeInfo = new TreeInfo();
        for(File item : startDir.listFiles()) {
            if(item.isDirectory()) {
                treeInfo.dirs.add(item);
                treeInfo.addAll(recurseDirs(item,regex));
            }else {
                if(item.getName().matches(regex))
                    treeInfo.files.add(item);
            }
        }
        return treeInfo;
    }
    
    public static class TreeInfo implements Iterable<File>{

        public List<File> files = new ArrayList();
        public List<File> dirs = new ArrayList();
        
        @Override
        public Iterator<File> iterator() {
            return files.iterator();
        }
        
        void addAll(TreeInfo other) {
            files.addAll(other.files);
            dirs.addAll(other.dirs);
        }
    }
}

  经过工具中的local()方法,咱们能够得到指定目录下符合要求文件的集合,经过walk()方法能够得到指定目录下全部符合要求的文件或目录,包括其子目录下的文件,这个工具只是记录在这里以备不时之需。

 

 2. RandomAccessFile

  由于File类知识文件的抽象表示,并无指定信息怎样从文件读取或向文件存储,而向文件读取或存储信息主要有两种方式:

  • 经过输入输出流,即InputStream、OutputStream;
  • 经过RandomAccessFile;

  输入输出流的方式咱们后面会专门总结,这也是Java I/O系统中很大的一块,本文会讲一下RandomAccessFile,由于它比较独立,和流的相关性不大。

  RandomAccessFile是一个彻底独立的类,其拥有和咱们后面将总结的IO类型有本质不一样的行为,能够在一个文件内向前和向后移动。咱们来看一下其主要方法:

  • void write(int d) 向文件中写入1个字节,写入的是传入的int值对应二进制的低8位;
  • int read() 读取1个字节,并以int形式返回,若是返回-1则表明已到文件末尾;
  • int read(byte[] data) 一次性从文件中读取字节数组总长度的字节量,并存入到该字节数组中,返回的int值表明读入的总字节数,若是返回-1则表明未读取到任何数据。一般字节数组的长度能够指定为1024*10(大概10Kb的样子,效率比较好);
  • int read(byte[] data, int off, int len) 一次性从文件中读取最多len个字节,并存入到data数组中,从下标off处开始;
  • void write(int b) 往文件中写入1个字节的内容,所写的内容为传入的int值对应二进制的低8位;
  • write(byte b[]) 往文件中写入一个字节数组的内容;
  • write(byte b[], int off, int len) 往文件中写入从数组b的下标off开始len个字节的内容;
  • seek(long pos) 设置文件指针偏移量为指定值,即在文件内移动至新的位置;
  • long getFilePointer() 获取文件指针的当前位置;
  • void close() 关闭RandomAccessFile;

  上面只是一部分方法,更多请参考官方文档。咱们再来看一个简单demo学习一下:

public class RandomAccessFileDemo {  
  public static void main(String[] args) {
    File file = new File("./test.txt");
    if(!file.exists()) {
      try {
        file.createNewFile();
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    }
    RandomAccessFile raf = null;
    try {
      raf = new RandomAccessFile("./test.txt","rw");
      raf.write(1000);
      raf.seek(0);
      System.out.println(raf.read());
      raf.seek(0);      
      System.out.println(raf.readInt());
    } catch (FileNotFoundException e) {
      System.out.println("file not found");
    } catch (EOFException e) {
      System.out.println("reachs end before read enough bytes");
      e.printStackTrace();
    } catch(IOException e) {
      e.printStackTrace();
    }finally {
      try {
        raf.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

  输出结果:

232
reachs end before read enough bytes

  在RandomAccessFile的构造器中有两个参数,第一个是文件路径或者File,表明该RandomAccessFile要操做的文件,第二个是读写模式。若是操做的文件不存在,在模式为“rw”时会直接建立文件,若是是“r”则会抛出异常。

  这是一个简单的例子,首先建立文件test.txt,而后建立一个和该文件关联的RandomAccessFile,指定读写模式为读写,调用write()写入1000,这里只会写入一个字节,跳到文件头部,读取1个字节,输出232(正好是1000对应二进制的低8位),再跳到文件头部,调用readInt()读取1个整数,这时候由于文件中只有1个字节,因此抛出EOFException异常,最后关闭RandomAccessFile。

  如上例子咱们学习了RandomAccessFile的基本用法,这里有一点须要注意,RandomAccessFile是基于文件指针从当前位置来读写的,而且写入操做是直接将插入点后面的内容覆盖而不是插入。若是咱们想实现插入操做,则须要将插入点后面的内容先保存下来,再写入要插入的内容,最后将保存的内容添加进来,看下面的例子:

public class RandomAccessFileDemo {
    
    public static void main(String[] args) throws IOException {
        File file = new File("f:/test.txt");
        file.createNewFile();
        // 建立临时空文件用于缓冲,并指定在虚拟机中止时将其删除
        File temp = File.createTempFile("temp", null);
        temp.deleteOnExit();
        RandomAccessFile raf = null;
        try {
            // 首先往文件中写入下面的诗句,并读取出来在控制台打印
            raf = new RandomAccessFile(file,"rw");
            raf.write("明月几时有,把酒问青天".getBytes());
            raf.seek(0);            
            byte[] b = new byte[60];
            raf.read(b, 0, 30);
            System.out.println(new String(b));
            
            // 接下来在诗句中间再插入一句诗
            raf.seek(12);
            FileOutputStream fos = new FileOutputStream(temp);
            FileInputStream fis = new FileInputStream(temp);
            byte[] buffer = new byte[10];
            int num = 0;
            while(-1 != (num = raf.read(buffer))) {
                fos.write(buffer, 0, num);
            }
            raf.seek(12);
            raf.write("希望人长久,千里共婵娟。".getBytes());
            // 插入完成后将缓冲的后半部份内容添加进来
            while(-1 != (num = fis.read(buffer))) {
                raf.write(buffer, 0, num);
            }
            raf.seek(0);
            raf.read(b, 0, 60);
            System.out.println(new String(b));
            System.out.println();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            raf.close();
        }
    }
}

  输出结果,插入诗句成功:

明月几时有,把酒问青天
明月几时有,希望人长久,千里共婵娟。把酒问青天

 

3. 总结

  本文是Java I/O系统系列第一篇,主要总结了File和RandomAccessFile的一些知识。

  • File类是对文件和目录路径的抽象,用户经过File来直接执行与文件或目录相关的操做,无需关心操做系统的差别。
  • RandomAccessFile类能够写入和读取文件,其最大的特色就是能够在任意位置读取文件(random access的意思),是经过文件指针实现的。
相关文章
相关标签/搜索