要把程序所处理的数据在不同的内存容器(内存或外存)进行传输,例如将内存的数据写到外存(某个文件中),就要用到I/O(输入/输出)技术。Java提供的I/O操作可以把数据保存到多种类型的文件中。
大多数的应用程序都需要与外部的输入/输出设备I/O(Input/Output)进行数据交换。在Java中,所有的I/O机制都是基于数据“流”方式进行输入/输出。
这些“数据流”可视为同一台计算机不同设备或网络中不同计算机之间流动的数据序列。如同水管里的水流一样,在水管的一端一点一滴地供水,而在水管的另一端看到的是一股连续不断的水流。
Java把这些不同来源和目标的数据统一抽象为 “数据流” 。
当Java程序需要读取数据时,就会开启一个通向数据源的流,这个数据源可以是文件、内存,也可以是网络连接。
当Java程序需要写入数据时,也会开启一个通向目的地的流。
流为操作各种物理设备提供了一致的接口。通过打开操作将流关联到文件,通过关闭流操作将流和文件解除关联。
这些流序列中的数据通常有两种形式:文本流和二进制流。
文本流每一个字节存放一个ASCII码,代表一个字符(而对于Unicode编码来说,每两个字节表示一个字符)。
使用文本流时,可能会发生一些字符转换。例如,在Windows操作系统中,当输出换行字符的时候,它可以被转换为回车和换行序列。
二进制流,也称字节流,它是把数据按其内存中存储的以字节形式“原封不动”地输出或存储。
两者的区别与联系可以用下面的例子(以ASCII码为例)来说明。例如,有一个整型数12345,其在内存当中仅需要2个字节,由于系统为整型数据分配4个字节,所以其高位两个字节均为0,而按文本流形式输出则占用5个字节,分别是“12345”这5个字符对应的ASCII码,如图下所示:
文本流形式与字符一一对应,因而便于对字符进行逐个处理,也便于输出显示,但一般占用较多的内存空间,且花费较多的转化时间(二进制形式与编码之间的转换)。
需要注意的是,在Java中使用的是Unicode编码,这是一种定长编码,每个字符都是2字节,因此在存储ASCII码时会额外浪费一个字节的空间。
二进制形式输出数值,可以节省外存空间和转化时间,但一个字节并不对应一个字符,不能直接输出字符形式。
一般来讲,对于纯文本信息(比如说字符串),以文本形式存储较佳;而对于数值信息,则用二进制形式较好。
I/O流的优势在于简单易用,缺点是效率较低。
Java的I/O流提供了读写数据的标准方法。Java语言中定义了许多类专门负责各种方式的输入/输出,这些类都被放在java.io包中。
在Java类库中,有关I/O操作的内容非常庞大:有标准输入/输出、文件的操作、网络上的数据流、字符串流和对象流等。
一、文件操作类——File
包java.io中定义的大多数类是对数据实施流式操作的,但File类例外,它用于处理文件和文件系统。也就是说,File类没有指定数据怎样从文件读取或向文件存储,它仅仅描述了文件本身的属性。
在java.io包之中,File类是唯一一个与文件本身有关的操作类。 它定义了一些与平台无关的方法来操作文件,通过调用File类提供的各种方法,能够完成创建、删除文件,重命名文件,判断文件的读写权限及文件是否存在,设置和查询文件创建时间、权限等操作。File类除了对文件操作外,还可以将目录当作文件进行处理——Java中的目录当成File对象对待。
如果要想使用File类进行操作,那么就必须设置一个要操作文件的路径。 下面的三个构造方法可以用来生成File对象:
在这里,“directoryPath”表示的是文件的路径名,filename 是文件名,而dirObj 是一个指定目录的File对象。
//创建指定文件或目录路径的File对象
File(String directoryPath)
//创建由File对象和指定文件名的File对象
File(String directoryPath,String filename)
//创建指定文件目录路径和文件名的File对象
File(File dirObj,String filename)
下面的例子分别用上面的3个构造方法创建了三个文件对象:F1,F2和F3。
F1是由仅有一个目录路径参数的构造方法生成的。
F2是由两个参数——路径和文件名的构造方法生成的。
F3的参数包括指向文件F1的路径及文件名。事实上,F3和F2指向相同的文件——在根目录(/)下的文件abc.txt。
File F1 = new File("/");
File F2 = new File("/","abc.txt");
File F3 = new File(F1," abc.txt");
Java 能正确处理UNIX和Windows/DOS约定路径分隔符。如果在Windows版本的Java下用斜线(/),路径处理依然正确。请注意:如果在Windows/DOS下使用反斜线(\)来作为路径分隔符,那么就需要在字符串内使用它的转义序列(即两个反斜线“\”)。Java约定是用UNIX和URL风格的斜线“/”来作路径分隔符。
File类中定义了很多获取File对象标准属性的方法。例如getName( )用于返回文件名,getParent( )返回父目录名;exists( )方法在文件存在的情况下返回true,反之返回false。但File类的方法是不对称的,意思是说虽然存在可以验证一个简单文件对象属性的很多方法,但是没有相应的方法来改变这些属性。下表给出了部分常用的File类方法:
方法 | 功能 |
---|---|
boolean canRead() | 测试应用程序是否能从指定的文件中进行读取 |
boolean canWrite() | 测试应用程序是否能写当前文件 |
boolean delete() | 删除当前对象指定的文件 |
boolean equals(Object obj) | 比较该对象和指定对象 |
boolean exists() | 测试当前File是否存在 |
String getAbsolutePath() | 返回由该对象表示的文件的绝对路径名 |
String getCanonicalPath() | 返回当前File对象的路径名的规范格式 |
String getName() | 返回表示当前对象的文件名 |
String getParent() | 返回当前File对象路径名的父路径名,如果此名没有父路径则为null |
String getPath | 返回当前对象的路径名 |
boolean isAbsolute() | 测试当前File对象表示 的文件是否是一个绝对路径名 |
boolean isDirectory() | 测试当前File对象表示的文件是否是一个路径 |
boolean isFile() | 测试当前File对象表示的文件是否是一个“普通”文件 |
boolean lastModified() | 返回当前File对象表示的文件最后修改的时间 |
long length() | 返回当前File对象表示的文件长度 |
String list() | 返回当前File对象指定的路径文件列表 |
String list(Filename Filter) | 返回当前File对象指定的目录中满足指定过滤器的文件列表 |
boolean mkdir() | 创建一个目录,它的路径名由当前File对象指定 |
boolean mkdirs() | 创建一个目录,它的路径由当前File对象指定,包括任一必须的父路径 |
boolean renameTo(File file) | 将当前File对象指定的文件更名为给定参数File指定的路径名 |
下面看个示例:
package com.xy.io;
import java.io.File;
public class FileDemo1 {
public static void main(String[] args) {
File f = new File("C:\\Users\\XY\\Desktop\\FileDemo1.txt");
if(f.exists()) {
f.delete();
}
else {
try {
f.createNewFile();
}
catch(Exception e) {
e.printStackTrace();
}
}
// getName()方法,获得文件名
System.out.println("文件名:" + f.getName());
// getPath()方法,获得文件路径
System.out.println("文件路径:" + f.getPath());
// getAbsolutePath()方法,
System.out.println("绝对路径:" + f.getAbsolutePath());
// getParent()
System.out.println("父文件夹名:" + f.getParent());
// exists()
System.out.println(f.exists() ? "文件存在" : "文件不存在");
// canRead()
System.out.println(f.canRead() ? "文件可读" : "文件不可读");
// canWrite()
System.out.println(f.canWrite() ? "文件可写" : "文件不可写");
// isDirectory()
System.out.println((f.isDirectory() ? "是" : "不是") + "目录");
// isFile()
System.out.println(f.isFile() ? "是文件" : "不是文件");
// isAbsolute()
System.out.println((f.isAbsolute() ? "是" : "不是") + "绝对路径");
// lastModified()
System.out.println("文件最后修改时间:" + f.lastModified());
// length()
System.out.println("文件大小:" + f.length() + "Bytes");
}
}
【结果】
路径的分隔符用两个“\”表示转义字符,这一句完全可用下面的语句代替。
File f = new File("c:/1.txt") ;
在File类中还有许多的方法,没有必要去死记这些用法,只要记住在需要的时候去查Java的API手册就可以了。
File类只能对文件进行一些简单操作,如读取文件的属性以及创建、删除和更名等,但并不支持文件内容的读/写。如果想对文件进行实施读写操作,就必须通过输入/输出流来达到这一目的。
以上的程序完成了文件的基本操作,但是在本操作之中可以发现如下的问题:
问题一:在进行操作的时候出现了延迟,因为文件的管理肯定还是由操作系统完成的,那么程序通过JVM(Java虚拟机)与操作系统进行操作,多了一层操作,所以势必会产生一定的延迟。
问题二:在Windows之中路径的分隔符使用“\”,而在Linux中分隔符使用“/”,而现在Java程序如果要想让其具备可移植性,就必须考虑分隔符的问题,所以为了解决这样的困难,在File类中提供了一个常量:
public static final String separator。
File file = new File("c:" + File.separator + "1.txt"); // 要定义的操作文件路径
在日后的开发之中,只要遇见路径分隔符的问题,都可用separator常量来解决。
问题三:以上的程序是直接在d盘的根路径下创建的新文件,如果说现在有目录的时候就发现无法直接创建文件了,因为文件目录不存在,要想创建文件之前首先要先创建目录。
创建一级目录:public boolean mkdir();
创建多级目录:public boolean mkdirs();
而如果要想创建目录应该是根据给定路径的父路径才可以创建,所以要想取得父路径可以使用如下方法。
取得父路径:public File getParentFile();
代码如下所示:创建文件
package com.xy.io;
import java.io.File;
public class FileDemo2 {
public static void main(String[] args) throws Exception {
File file = new File("d:" + File.separator +
"mytest" + File.separator +
"demo" + File.separator +
"mldn.txt"); // 要操作文件的路径
if(!file.getParentFile().exists()) { // 父路径不存在
file.getParentFile().mkdirs(); // 创建目录
}
System.out.println(file.createNewFile());
}
}
【结果】