I/O系统即输入/输出系统,对于一门程序语言来说,创建一个好的输入/输出系统并非易事。因为不仅存在各种I/O源端和想要与之通信的接收端(文件、控制台、网络链接等),而且还需要支持多种不同方式的通信(顺序、随机存取、缓冲、二进制、按字符、按行、按字等)。

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

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

  File

  RandomAccessFile

  总结

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代表抽象路径名,有两个部分组成:  

  • 一个可选的系统相关的前缀,比如磁盘驱动器说明符(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 directoryAbsolute path: f:\dirDemo\fileDemoCan read: falseCan write: falsegetName: fileDemogetParent: f:\dirDemogetPath: f:\dirDemo\fileDemolength: 0lastModified: 0isExist: 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(){@Overridepublic 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);@Overridepublic 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();@Overridepublic 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的意思),是通过文件指针实现的。

Java I/O系统学习系列一:File和RandomAccessFile相关推荐

  1. Java I/O系统学习系列二:输入和输出

    编程语言的I/O类库中常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象."流"屏蔽了实际的I/O设备中处理数据的细节. 在这个系列的第一篇 ...

  2. Java I/O系统学习系列三:I/O流的典型使用方式

    尽管可以通过不同的方式组合IO流类,但我们可能也就只用到其中的几种组合.下面的例子可以作为典型的IO用法的基本参考.在这些示例中,异常处理都被简化为将异常传递给控制台,但是这只有在小型示例和工具中才适 ...

  3. 零基础参加java培训的系统学习路线

    ​ 零基础想要学习java技术,那么最好的选择就是参加java培训,进行系统的学习,以下就是小编为大家整理的零基础参加java培训的系统学习路线,希望能够帮助到正在学习java技术的零基础同学. 零基 ...

  4. QT系统学习系列:1.2 ToolBar(工具栏)

    文章目录 ToolBar 基础 ToolBar介绍 movable 属性 allowedAreas 属性 orientation 属性 iconSize 属性 toolButtonStyle 属性 f ...

  5. QT系统学习系列:1.2 PushButton(常规按钮)

    文章目录 一.PushButton 基础 PushButton介绍 给PushButton添加快捷键 PushButton构造函数 PushButton添加/更改(图标,文本,父类) PushButt ...

  6. Java基础知识(重点)总结(Java学习方法、系统学习路线)

    参考:https://www.cnblogs.com/schiller-hu/p/10662429.html Java自学网:https://www.51zxw.net/list.aspx?cid=3 ...

  7. java php mysql_系统学习javaweb13----MYSQL学习(使用PHP、SQL)1

    系统学习javaweb13----MYSQL学习(使用PHP.SQL.mysqladmin)1 (本随笔是自学笔记,我学习的教程来自"菜鸟教程|MYSQL教程",十分感谢!) 目录 ...

  8. 【高级Java架构师系统学习】java如何开发安卓软件

    认识HTTP 什么是超文本 什么是传输 什么是协议 HTTP相关组件 网络模型 OSI 模型 浏览器 Web服务器 CDN WAF WebService HTML Web页面构成 与 HTTP 有关的 ...

  9. Java 7 源码学习系列(一)——String

    String表示字符串,Java中所有字符串的字面值都是String类的实例,例如"ABC".字符串是常量,在定义之后不能被改变,字符串缓冲区支持可变的字符串.因为 String ...

最新文章

  1. 台式电脑计算机无法启动 启动修复,Win10启动修复无法修复你的电脑解决方法
  2. Linux内存管理 (2)页表的映射过程
  3. optee中的密码学算法注册模型
  4. python如何打印时间,在python2.7中,如何提取和打印日期、时间和m
  5. go get报错:unrecognized import path “golang.org/x/net/context”…
  6. java调用ole ie_ActiveX (.ocx)的写法,及在IE里调用
  7. 如何完善自己的知识结构
  8. tomcat(2)一个简单的servlet容器
  9. 线程可警告状态以及APC队列
  10. php免费根据ip查城市,根据ip获取城市的方法
  11. 回顾 - 判断质数精简算法
  12. Python中list(列表)、tuple(元组)、dict(字典)的基本操作快速入门
  13. 解读SQL Server 2012中的最新BI功能
  14. IDF 实验室 初探乾坤
  15. webuploader
  16. 谭浩强C语言练习题及详细答案
  17. SATA工作模式咋选?揭秘AHCI和IDE区别(全文)
  18. haosou属于搜索引擎的_中国的搜索引擎有哪些?
  19. 数据库事务(Transaction)详解
  20. XMPP即时通讯协议使用(六)——开发Openfire聊天记录插件

热门文章

  1. 在Mybatis-spring上基于注解的数据源实现方案
  2. ibatis annotations 注解方式返回刚插入的自增长主键ID的值--转
  3. Catch Me If You ... Can't Do Otherwise--转载
  4. 机器学习算法基础——朴素贝叶斯算法
  5. [深度应用]·实战掌握Dlib人脸识别开发教程
  6. 计算机视觉工具包Luminoth
  7. php屏蔽行,PHP屏蔽过滤指定关键字的方法
  8. Docker Review - 图形化工具 Portainer
  9. Tomcat - Tomcat套娃式架构与配置文件的对应关系解读
  10. 白话Elasticsearch12-深度探秘搜索技术之基于multi_match + best fields语法实现dis_max+tie_breaker