Java的I/O技术可以将数据保存到文本文件、二进制文件甚至是ZIP压缩文件中,以达到永久性保存数据的要求。
1、流概述
流是一组有序的数据序列,根据操作的类型,可分为输入流和输出流两种。I/O(Input/Output)流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地。虽然I/O流经常与磁盘文件存取有关,但是程序的源和目的地也可以是键盘、鼠标、内存或显示器窗口等。
2、输入/输出流
Java语言定义了许多类专门负责各种方式的输入/输出,这些类都被放在java.io包中。其中,所有输入流类都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类;而所有输出流都是抽象类OutputStream(字节输入流)或抽象类Writer(字符输出流)的子类。
2.1 输入流
InputStream类是字节输入流的抽象类,是所有字节输入流的父类。该类中所有方法遇到错误时都会引发IOException异常。
InputStream类的具体层次结构如图所示:
Java中的字符是Unicode编码,是双字节的。InputStream是用来处理字节的,在处理字符文本时不是很方便。Java为字符文本的输入提供了专门一套单独的类Reader,但Reader类并不是InputStream类的替换者,只是在处理字符串时简化了编程。Reader类是字符输入流的抽象类,所有字符输入流的实现都是它的子类。
Reader类的具体层次结构如下图所示:
2.2 输出流
OutputStream类是字节输入流的抽象类,此抽象类是表示输出字节流的所有类的超类。OutputStream类中的所有方法均返回void,在遇到错误时会引发IOException异常。
OutputStream类的具体层次如图所示:
Writer类是字符输出流的抽象类,所有字符输出类的实现都是它的子类。
Writer类的层次结构如下图所示:
3、File类
File对象即可表示文件,也可表示目录,在程序中一个File对象可以代表一个文件或目录。利用它可用来对文件或目录及其属性进行基本操作。
3.1 文件的创建与删除
(1)引入File类。
import java.io.File;
(2)构建一个文件对象,通用以下3种构造方法来创建文件对象。
File(String pathname)
File(String parent , String child)
File(File f , String child)
示例:在项目中创建类FileTest,在主方法中判断文件是否存在,如果该文件存在则将其删除,不存在则创建该文件。
import java.io.*;
public class FileTest
{
// 创建类FileTest
public static void main(String[] args)
{
// 创建文件对象
File file = new File("word.txt");
if (file.exists())
{
// 如果该文件存在
file.delete(); // 将文件删除
System.out.println("文件已删除"); // 输出的提示信息
} else
{
// 如果文件不存在
try
{
// try语句块捕捉可能出现的异常
file.createNewFile(); // 创建该文件
System.out.println("文件已创建"); // 输出的提示信息
} catch (Exception e)
{
// catch处理该异常
e.printStackTrace(); // 输出异常信息
}
}
}
}
3.2 获取文件信息
File类提供了很多方法用于获取文件本身的一些信息,File类的常用方法如下表所示。
getName():获取文件的名称。
canRead():判断文件是否为可读的。
canWrite():判断文件是否可被写入。
exits():判断文件是否存在。
length():获取文件的长度(以字节为单位)。
getPath():获取文件路径。
getAbsolutePath():获取文件的绝对路径。
getParent():获取文件的父路径。
isFile():判断文件是否存在。
isDirectory():判断文件是否为一个目录。
isHidden():判断文件是否为隐藏文件。
lastModified():获取文件最后修改时间(返回值:long类型)。
示例:获取文件相关信息。
import java.io.*;
public class FileTest
{
public static void main(String[] args)
{
File file = new File("word.txt"); // 创建文件对象
if (file.exists())
{ // 如果文件存在
String name = file.getName(); // 获取文件名称
long length = file.length(); // 获取文件长度
String path = file.getPath();// 获取文件路径
String absolutePath = file.getAbsolutePath();// 获取文件绝对路径
boolean hidden = file.isHidden(); // 判断文件是否是隐藏文件
System.out.println("文件名称:" + name); // 输出信息
System.out.println("文件长度是:" + length);
System.out.println("文件路径是:" + path);
System.out.println("文件绝对路径是:" + absolutePath);
System.out.println("该文件是隐藏文件吗?" + hidden);
} else
{ // 如果文件不存在
System.out.println("该文件不存在"); // 输出信息
}
}
}
4、文件输入/输出流
4.1 FileInputStream与FileOutputStream类
FileInputStream类与FileOutputStream类都是用来操作磁盘文件。如果用户的文件读取需求比较简单,则可以使用FileInputStream类。该类继承自InputStream类。FileOutputStream类与FileInputStream类对应,提供了基本的文件写入能力。FileOutputStream类是OutoputStream类的子类。
示例:使用FileOutputStream类向文件中写入信息,然后通过FileInputStream类将文件中的数据读取到控制台上。
import java.io.*;
public class FileTest
{
public static void main(String[] args)
{
// 创建文件对象
File file = new File("word.txt");
// 文件写操作
try
{
// 创建FileOutputStream对象
FileOutputStream out = new FileOutputStream(file);
// 创建byte型数组
byte[] msgBytes = "我有一只小毛驴,我从来也不骑".getBytes();
// 将数组中的信息写入到文件中
out.write(msgBytes);
// 关闭流
out.close();
} catch (Exception e)
{
e.printStackTrace();
}
// 文件读操作
try
{
// 创建FileInputStream类对象
FileInputStream in = new FileInputStream(file);
// 创建Byte数组
byte[] byt = new byte[1024];
// 从文件中读取信息
int len = in.read(byt);
// 关闭流
in.close();
// 将文件中的信息输出
System.out.println("文件中的信息是:" + new String(byt, 0, len));
} catch (Exception e)
{
e.printStackTrace();
}
}
}
说明:虽然Java在程序结束时自动关闭所有打开的流,但是当使用完流后,显示地关闭所有打开的流仍是一个号习惯。一个被打开的流有可能会用尽系统资源,这取决于平台和实现。如果没有将打开的流关闭,当另一个程序试图打开另一个流时,可能会得不到需要的资源。
4.2 FileReader类和FileWriter类
使用FileOutputStream类向文件中写入数据与使用FileInputStream类从文件中将内容读出来,存在一点不足,即这两个类都只提供了对字节或字节数组的读取方法。由于汉字在文件中占用两个字节,如果使用字节流,读取不好可能会出现乱码现象。此时采用字符流Reader或Writer类即可避免这种现象。
FileReader、FileWriter字符流对应了FileInputStream、FileOutputStream类。FileReader流顺序地读取文件,只要不关闭流,每次调用read()方法就顺序地读取源中其余的内容,直到源的末尾或流被关闭。
示例:使用FileWriter类向文件中写入信息,然后通过FileReader类将文件中的数据读取到控制台上。
import java.io.*;
public class FileTest
{
public static void main(String[] args)
{
// 创建文件对象
File file = new File("word.txt");
// 文件写操作
try
{
// 创建FileWriter对象
FileWriter out = new FileWriter(file);
// 创建字符串内容
String msgStr = "我有一只小毛驴,我从来也不骑";
// 将数组中的信息写入到文件中
out.write(msgStr);
// 关闭流
out.close();
} catch (Exception e)
{
e.printStackTrace();
}
// 文件读操作
try
{
// 创建FileReader类对象
FileReader in = new FileReader(file);
// 创建char数组
char[] charArray = new char[1024];
// 从文件中读取信息
int len = in.read(charArray);
// 关闭流
in.close();
// 将文件中的信息输出
System.out.println("文件中的信息是:" + new String(charArray, 0, len));
} catch (Exception e)
{
e.printStackTrace();
}
}
}
5、带缓存的输入输出流
缓存是I/O的一种性能优化。缓存流为I/O流增加了内存缓存区。有了缓存区,使得在流上执行skip()、mark()和reset()方法都成为可能。
5.1 BufferedInputStream类与BufferedOutputStream类
BufferedInputStream类可以对任何的InputStream类进行带缓存区的包装以达到性能的优化。BufferedInputStream类有两个构造方法:
BufferedInputStream(InputStream in)。
BufferedInputStream(InputStream in, int size)。
使用BufferedOutputStream类输出信息和往OutputStream输出信息完全一样,只不过BufferedOutputStream有一个flush()方法用来将缓存区的数据强制输出完。BufferedOutputStream类也有两个构造方法:
BufferedOutputStream(OutputStream out)。
BufferedOutputStream(OutputStream out, int size)。
注意:flush()方法就是用于即使在缓存区没有满的情况下,也将缓存区的内容强制写入到外设,习惯上称这个过程为刷新。flush()方法只对使用缓存区的OutputStream类的子类有效。当调用close()方法时,系统在关闭流之前,也会将缓存区中的信息刷新到磁盘文件中。
5.2 BufferedReader与BufferedWriter类
BufferedReader类与BufferedWriter类分别继承Reader类与Writer类。这两个类同样具有内部缓存机制,并可以以行为单位进行输入输出。
BufferedReader类常用方法:
read():读取单个字符。
readLine():读取一个文本行,并将其返回为字符串。若无数据可读,则返回null。
在使用BufferedWriter类的Write()方法时,数据并没有立刻被写入至输出流中,而是首先进入缓存区中。如果想立刻将缓存区中的数据写入输出流中,一定要调用flush()方法。
BufferedWriter类中的方法都是返回void,常用方法:
write(String s, int off, int len):写入字符串的某一部分。
flush() :刷新该流的缓存。
newLine():写入一个行分隔符。
示例:向指定的文件写入数据,并通过BufferedReader类将文件中的信息分行显示。
import java.io.*;
public class FileTest
{
public static void main(String args[])
{
// 定义字符串数组
String content[] = { "好久不见", "最近好吗", "常联系" };
File file = new File("word.txt"); // 创建文件对象
try
{
FileWriter fw = new FileWriter(file); // 创建FileWriter类对象
// 创建BufferedWriter类对象
BufferedWriter bufw = new BufferedWriter(fw);
// 循环遍历数组
for (int k = 0; k < content.length; k++)
{
bufw.write(content[k]); // 将字符串数组中元素写入到磁盘文件中
bufw.newLine(); // 将数组中的单个元素以单行的形式写入文件
}
bufw.close(); // 将BufferedWriter流关闭
fw.close(); // 将FileWriter流关闭
} catch (Exception e)
{ // 处理异常
e.printStackTrace();
}
try
{
FileReader fr = new FileReader(file); // 创建FileReader类对象
// 创建BufferedReader类对象
BufferedReader bufr = new BufferedReader(fr);
String s = null; // 创建字符串对象
int i = 0; // 声明int型变量
// 如果文件的文本行数不为null,则进入循环
while ((s = bufr.readLine()) != null)
{
i++; // 将变量做自增运算
System.out.println("第" + i + "行:" + s); // 输出文件数据
}
bufr.close(); // 将FileReader流关闭
fr.close(); // 将FileReader流关闭
} catch (Exception e)
{ // 处理异常
e.printStackTrace();
}
}
}
运行结果:
第1行:好久不见
第2行:最近好吗
第3行:常联系
6、数据输入输出流
数据输入输出流(DataInputStream类与DataOutputStream类)允许应用程序以与机器无关的方式从底层输入流中读取基本Java数据类型。也就是说,当读取一个数据时,不必再关心这个数值应当是什么字节。
6.1 利用数据流类DataInputStream类读二进制文件
(1)引用相关类
import java.io.FileInputStream;
import java.io.DataInputStream;
(2)构造一个数据输入流对象
FileInputStream fis = new FileInputStream("D:\\MyImage.jpg");
DataInputStream dis = new DataInputStream(fis);
(3)利用数据输入流类的方法读取二进制文件的数据。
dis.readInt(); // 读取出来的是整型
dis.readByte(); // 读取出来的数据是Byte类型
(4)关闭数据输入流
dis.close(); // 关闭数据输入流
6.2 利用数据流类DataOutputStream类写二进制文件
(1)引用相关类
import java.io.DataOutputStream;
import java.io.FileOutputStream;
(2)构造一个数据输出流对象
FileOutputStream outFile = new FileOutputStream("E:\\MyImage2.jpg");
DataOutputStream out = new DataOutputStream(outFile);
(3)利用数据输出流类的方法写二进制文件的数据
out.write(1); //把数据写入二进制文件
(4)关闭数据输出流
out.close();
示例:将D盘下的MyImage.jpg文件复制到E盘下,并命名为MyImage2.jpg。
import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class ReadAndWriteBinaryFile
{
public static void main(String[] args)
{
try
{
FileInputStream fis = new FileInputStream("D:\\MyImage.jpg");
DataInputStream dis = new DataInputStream(fis);
FileOutputStream outFile = new FileOutputStream("E:\\MyImage2.jpg");
DataOutputStream out = new DataOutputStream(outFile);
int temp;
while ((temp = dis.read()) != -1)
{
out.write(temp);
}
dis.close();
out.close();
System.out.println("文件复制成功");
} catch (Exception e)
{
e.printStackTrace();
}
}
}
7、ZIP压缩输入/输出流
ZIP压缩管理文件(ZIP archive)是一种十分典型的文件压缩形式,使用它可以节省存储空间。关于ZIP压缩的I/O实现,在Java的内置类中提供了非常好用的相关类,所以其实实现方法非常简单。使用java.util.zip包的ZipInputStream类与ZipOutputStream类来实现文件的压缩/解压缩。利用ZipEntry、ZipInputStream和ZipOutputStream这3个Java类实现ZIP数据压缩方式的编程方法。
7.1 压缩文件
利用ZipOutputStream类对象,可将文件压缩为“.zip”文件。ZipOutputStream类的构造函数如下所示:
ZipOutputStream(OutputStream out);
ZipOutputStream类的常用方法:
putNextEntry(ZipEntry e):开始编写新的ZIP文件条目,并将流定位到条目数据的开头。
write(byte[] b, int off, int len):将一个字节数组写入当前的ZIP条目数据。
finish():完成编写ZIP输出流的内容,而不关闭底层流。
setComment(String comment):设置ZIP文件注释。
示例:使用ZipOutputStream类对文件夹进行压缩。
import java.io.*;
import java.util.zip.*;
public class MyZip { // 创建类
private void zip(String zipFileName, File inputFile) throws Exception {
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
zipFileName)); // 创建ZipOutputStream类对象
zip(out, inputFile, ""); // 调用方法
System.out.println("压缩中…"); // 输出信息
out.close(); // 将流关闭
}
private void zip(ZipOutputStream out, File f, String base)
throws Exception { // 方法重载
if (f.isDirectory()) { // 测试此抽象路径名表示的文件是否是一个目录
File[] fl = f.listFiles(); // 获取路径数组
out.putNextEntry(new ZipEntry(base + "/")); // 写入此目录的entry
base = base.length() == 0 ? "" : base + "/"; // 判断参数是否为空
for (int i = 0; i < fl.length; i++) { // 循环遍历数组中文件
zip(out, fl[i], base + fl[i]);
}
} else {
out.putNextEntry(new ZipEntry(base)); // 创建新的进入点
// 创建FileInputStream对象
FileInputStream in = new FileInputStream(f);
int b; // 定义int型变量
System.out.println(base);
while ((b = in.read()) != -1) { // 如果没有到达流的尾部
out.write(b); // 将字节写入当前ZIP条目
}
in.close(); // 关闭流
}
}
public static void main(String[] temp) { // 主方法
MyZip book = new MyZip(); // 创建本例对象
try {
// 调用方法,参数为压缩后文件与要压缩文件
book.zip("hello.zip", new File("src"));
System.out.println("压缩完成"); // 输出信息
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
7.2 解压ZIP文件
ZipInputStream类可读取ZIP压缩格式的文件,包括对已压缩和未压缩条目的支持(entry)。ZipInputStream类的构造函数如下所示:
ZipInputStream(InputStream in)
ZipInputStream的常用方法:
read(byte[] b, int off, int len):从当前ZIP条目读取到字节数组。
available():判断是否已读完目前entry所指定的数据。已读完成返回0,否则返回1。
closeEntry():关闭当前的ZIP条目,并定位流以读取下一个条目。
skip(long n):跳过当前ZIP条目中指定的字节数。
getNextEntry():读取下一个ZIP文件条目,并将流定位在条目数据的开头。
createZipEntry(String name):为指定的条目名称创建一个新的 ZipEntry对象。
示例:使用ZipInputStream类对文件进行解压缩。
import java.io.*;
import java.util.zip.*;
public class Decompressing { // 创建文件
public static void main(String[] temp) {
ZipInputStream zin; // 创建ZipInputStream对象
try { // try语句捕获可能发生的异常
zin = new ZipInputStream(new FileInputStream("hello.zip"));
// 实例化对象,指明要进行解压的文件
ZipEntry entry = zin.getNextEntry(); // 获取下一个ZipEntry
while (((entry = zin.getNextEntry()) != null)
&& !entry.isDirectory()) {
// 如果entry不为空,并不在同一目录下
File file = new File("d:\\" + entry.getName()); // 获取文件目录
System.out.println(file);
if (!file.exists()) { // 如果该文件不存在
file.mkdirs();// 创建文件所在文件夹
file.createNewFile(); // 创建文件
}
zin.closeEntry(); // 关闭当前entry
System.out.println(entry.getName() + "解压成功");
}
zin.close(); // 关闭流
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结:
1、流是指一连串流动的字符,是以先进先出的方式发送信息的通道。程序和数据源之间是通过流联系起来的。
2、流可以分为输入流和输出流,也可以分为字节流和字符流。
3、File类用于访问文件或目录的属性。
4、FileInputStream类和FileOutputStream类以字节流的方式读写文本文件。
5、BufferedReader类和BufferedWriter类以字符流的方式读写文本文件,而且效率更高。
6、DataInputStream类和DataOutputStream类可用于读写二进制文件。
7、ZipInputStream类和ZipOutputStream类可用于文件的压缩和解压缩。