Java I/O深入学习之File和RandomAccessFile

前言

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的相关知识,按照如下顺序:

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 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的意思),是通过文件指针实现的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java使用RandomAccessFile类对文件进行读写

    1. RandomAccessFile类简介 前面一篇随笔<File类遍历目录及文件>中有说到,File类只能用于表示文件或目录的名称.大小等信息,而不能用于文件内容的访问.而当需要访问文件内容时,就可以用RandomAccessFile类了. RandomAccessFile是Java提供用来访问一些保存数据记录的文件的类,可以进行读取操作,也可以进行写入操作,写入的数据则以byte的形式存储:支持随机访问,也就是可以访问文件的任意位置(通过文件指针实现). 2. 构造函数 RandomAc

  • java使用randomaccessfile在文件任意位置写入数据

    复制代码 代码如下: import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.RandomAccessFile; public class InsertContent {    public static void insert(String fileName, long pos, String inse

  • Java核心编程之文件随机读写类RandomAccessFile详解

    本文实例为大家分享了Android九宫格图片展示的具体代码,供大家参考,具体内容如下 1.RandomAccessFile RandomAccessFile主要用于文件内容的读写访问 2.访问模式 "r":只读方式. "rw":打开以便读取和访问,如果文件不存在则创建文件. "rws": 除了'rw'功能以外,文件内容或者元数据更新时一同写入. "rwd":除了'rw'功能以外,文件内容更新时一同写入. 3.使用案例 pack

  • RandomAccessFile简介_动力节点Java学院整理

    RandomAccessFile RandomAccessFile 是随机访问文件(包括读/写)的类.它支持对文件随机访问的读取和写入,即我们可以从指定的位置读取/写入文件数据. 需要注意的是,RandomAccessFile 虽然属于java.io包,但它不是InputStream或者OutputStream的子类:它也不同于FileInputStream和FileOutputStream. FileInputStream 只能对文件进行读操作,而FileOutputStream 只能对文件进

  • Java RandomAccessFile 指定位置实现文件读取与写入

    Java RandomAccessFile 指定位置实现文件读取与写入 RandomAccessFile是属于随机读取类,是可以对文件本身的内容直接随机进行操作的,可以在文件的指定位置的读取和写入内容,这在很多时候都是很方便的. RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的.但是该类仅限于操作文件. RandomAccessFile不属于InputStream和Out

  • Java中IO流 RandomAccessFile类实例详解

    Java中IO流 RandomAccessFile类实例详解 RandomAccessFile java提供的对文件内容的访问,既可以读文件,也可以写文件. 支持随机访问文件,可以访问文件的任意位置. java文件模型,在硬盘上的文件是byte byte byte存储的,是数据的集合 打开文件,有两种模式,"rw"读写."r"只读:RandomAccessFile raf = new RandomAccessFile(file, "rw");,文

  • Java RandomAccessFile的用法详解

    RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的.但是该类仅限于操作文件. RandomAccessFile不属于InputStream和OutputStream类系的.实际上,除了实现DataInput和 DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫

  • Java I/O深入学习之File和RandomAccessFile

    前言 I/O系统即输入/输出系统,对于一门程序语言来说,创建一个好的输入/输出系统并非易事.因为不仅存在各种I/O源端和想要与之通信的接收端(文件.控制台.网络链接等),而且还需要支持多种不同方式的通信(顺序.随机存取.缓冲.二进制.按字符.按行.按字等). Java类库的设计者通过创建大量的类来解决这个难题,比如面向字节的类(字节流,InputStream.OutputStream).面向字符和基于Unicode的类(字节流,Reader.Writer).nio类(新I/O,为了改进性能及功能

  • java中表示一个文件的File类型详解

    前言 从本篇文章开始,我们将开启对 Java IO 系统的学习,本质上就是对文件的读写操作,听上去简单,其实并不容易.Java 的 IO 系统一直在完善和改进,设计了大量的类,也只有理解了这些类型被设计出来的意义以及各自的应用场景,才能提升文件 IO 的理解. 那么,第一步就是要解决如何表示一个文件的问题,Java 世界中「万物皆对象」,如何将一个实际磁盘文件或目录对应到一个 Java 对象则是我们首要的问题. Java 中使用 File 来抽象一个文件,无论是普通文件或是目录,都可对应于一个

  • Java文件(io)编程_基于File类的基本用法(必看篇)

    1.首先了解文件流的相关概念: 2.文件File类的基本用法 public class Demo_1 { public static void main(String[] args) { //创建一个文件对象 File f=new File("e:\\aa.txt"); //得到文件的路径 System.out.println("文件路径:"+f.getAbsolutePath()); //得到文件的大小,字节数 System.out.println("文

  • Java的Synchronized关键字学习指南(全面 & 详细)

    前言 在Java中,有一个常被忽略 但 非常重要的关键字Synchronized今天,我将详细讲解 Java关键字Synchronized的所有知识,希望你们会喜欢 目录 1. 定义 Java中的1个关键字 2. 作用 保证同一时刻最多只有1个线程执行 被Synchronized修饰的方法 / 代码 其他线程 必须等待当前线程执行完该方法 / 代码块后才能执行该方法 / 代码块 3. 应用场景 保证线程安全,解决多线程中的并发同步问题(实现的是阻塞型并发),具体场景如下: 修饰 实例方法 / 代

  • Java文件操作之IO流 File类的使用详解

    File类概述 File类能新建.删除.重命名文件和目录,但不能访问文件内容本身,如果需要访问文件内容本身,则需要使用后续的输入/输出流. 要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录. File对象可以作为参数传递给流的构造器. 常用构造器 ①public File(String pathname) 以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果是相对路径,则默认相

  • Java数据结构与算法学习之双向链表

    目录 双向链表的储存结构示意图 双向链表的初始化结构 1.双向链表的结点 2.双向链表的头结点 3.总代码 双向链表中的指定文件插入元素  1.插入的为第一个位置 2.其他位置插入 总代码 双向链表的删除 1.删除第一个元素 2.删除其他位置元素 总代码 双向链表的储存结构示意图 双向链表的初始化结构 1.双向链表的结点 代码实现 //双向链表的结点,包含一个数据域,两个指针域 typedef struct DoublyNode { ElementType date; //数据域 struct

  • Java数据结构与算法学习之循环链表

    目录 存储结构示意图 初始化循环链表  循环链表的插入 首位置 代码实现 其他位置 代码实现(总) 循环链表的删除 1.操作的为第一个元素 2.操作元素不为第一个元素 代码实现(总) 循环链表的常见操作  存储结构示意图 优点 : 能够通过任意结点遍历整个链表结构 初始化循环链表  1.循环链表的结点 typedef struct CircularNode { ElementType date; //数据域 struct CircularNode* next; //指向下一个结点的指针域 }Ci

  • Java中反射的学习笔记分享

    目录 简介 一个简单的例子 设置使用反射 模拟instanceof运算 了解类的方法 获取有关构造函数的信息 查找类字段 按名称调用方法 创建新对象 更改字段的值 使用数组 总结 简介 反射是Java编程语言中的一个特性.它允许执行的Java程序检查或 操作 自身,并操作程序的内部属性.例如,Java类可以获取其所有成员的名称并显示它们. 从程序内部检查和操作Java类的能力听起来可能不太显示,但是在其他编程语言中,这个特性根本不存在.例如,在C或C ++ 程序中无法获取有关该程序中定义的函数的

  • Java多线程之锁学习(增强版)

    目录 阻塞锁 非阻塞锁 锁的四种状态 无锁状态 偏向锁 轻量级锁 重量级锁 可重入锁 自旋锁 读写锁 互斥锁 悲观锁 乐观锁 公平锁 非公平锁 显示锁和内置锁 轮询锁和定时锁 对象锁和类锁 锁粗化 锁消除 信号量 独享锁 共享锁 分段锁 死锁案例和排查 阻塞锁 含义:多个线程同时调用一个方法的时候,所有的线程都被排队处理了,让线程进入阻塞状态进行等待,当获得相应的信号(唤醒.时间)时,才能进入线程的准备就绪的状态.通过竞争.进入运行状态. Java中,能够进入\退出.阻塞状态或包含阻塞锁的方法有

  • JSON for java快速入门总结学习

    一.JSON介绍 JSON(JavaScript Object Notation),类似于XML,是一种数据交换格式,比如Java产生了一个数据想要给JavaScript,则除了利用XML外,还可以利用JSON; JSON相比XML的优势是表达起来很简单:官网:http://www.json.org/ JSON是AJAX中的X(就是可以取代XML);          ------出自JSON创始人; 注意:JSON并不是一个文档格式,没有*.json的文档,一般JSON格式的文档存在txt中,

随机推荐