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对象。 pathname:指定路径名称(包含文件名),例如:D:/doc/letter.txt |
File(String parent , String child) | 该构造方法根据定义的父路径和子路径字符串(包含文件名)创建一个新的File对象。 parent:父路径字符串,例如:D:/ 或 D:/doc child:子路径字符串,例如:letter.txt |
File(File f , String child) | 该构造方法根据父路径对象和child路径名字符串创建一个新的File对象。 f:父路径对象。 child:子路径字符串。例如:letter.txt |
示例:在项目中创建类FileTest,在主方法中判断文件是否存在,如果该文件存在则将其删除,不存在则创建该文件。
import java.io.File;
/**
* 判断文件是否存在
*
* @author pan_junbiao
*
*/
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类型)。 |
File[] listFiles() | 获取该目录下的所有子目录和文件,返回File类数组。 |
示例:获取文件相关信息。
import java.io.File;
/**
* 获取文件相关信息
*
* @author pan_junbiao
*
*/
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("该文件不存在"); // 输出信息
}
}
}
执行结果:
3.3 获取目录下的所有目录和文件
示例:假设目录“D:\TestDir1”下有两个文件夹(dir1 和 dir2)和一个文件 file1.txt 。
File[] listFiles()方法:获取该目录下的所有子目录和文件,返回File类数组。
import java.io.File;
/**
* 获取目录下的所有目录和文件
* @author pan_junbiao
**/
public class DirFileTest
{
public static void main(String[] args)
{
File file = new File("D:\\TestDir1");
//判断目录是否存在
if (!file.exists())
{
System.out.println("目录不存在");
return;
}
//获取文件数组
File[] fileList = file.listFiles();
for (int i = 0; i < fileList.length; i++)
{
//判断是否为目录
if (fileList[i].isDirectory())
{
System.out.println("目录:" + fileList[i].getName());
}
else
{
System.out.println("文件:" + fileList[i].getName());
}
}
}
}
执行结果:
4、文件输入/输出流
4.1 FileInputStream与FileOutputStream类
FileInputStream类与FileOutputStream类都是用来操作磁盘文件。如果用户的文件读取需求比较简单,则可以使用FileInputStream类。该类继承自InputStream类。FileOutputStream类与FileInputStream类对应,提供了基本的文件写入能力。FileOutputStream类是OutoputStream类的子类。
示例:使用FileOutputStream类向文件中写入信息,然后通过FileInputStream类将文件中的数据读取到控制台上。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 使用FileOutputStream类向文件中写入信息,<br/>
* 然后通过FileInputStream类将文件中的数据读取到控制台上
*
* @author pan_junbiao
*
*/
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 = "您好,欢迎访问 pan_junbiao的博客".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("使用FileInputStream与FileOutputStream类:");
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.File;
import java.io.FileReader;
import java.io.FileWriter;
/**
* 使用FileWriter类向文件中写入信息<br/>
* 然后通过FileReader类将文件中的数据读取到控制台上。
*
* @author pan_junbiao
*
*/
public class FileTest
{
public static void main(String[] args)
{
// 创建文件对象
File file = new File("word.txt");
// 文件写操作
try
{
// 创建FileWriter对象
FileWriter out = new FileWriter(file);
// 创建字符串内容
String msgStr = "您好,欢迎访问 pan_junbiao的博客";
// 将数组中的信息写入到文件中
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("使用FileReader类和FileWriter类:");
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.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
/**
* 向指定的文件写入数据<br />
* 并通过BufferedReader类将文件中的信息分行显示
*
* @author pan_junbiao
*
*/
public class FileTest
{
public static void main(String args[])
{
// 定义字符串数组
String content[] = { "好久不见", "最近好吗", "欢迎访问 pan_junbiao的博客", "博客地址:https://blog.csdn.net/pan_junbiao",
"常联系,祝您身体健康" };
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();
}
}
}
运行结果:
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.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 将D盘下的MyImage.jpg文件复制到E盘下 <br />
* 并命名为MyImage2.jpg
*
* @author pan_junbiao
*
*/
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.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 使用ZipOutputStream类对文件夹进行压缩
*
* @author pan_junbiao
*
*/
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.File;
import java.io.FileInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* 使用ZipInputStream类对文件进行解压缩
*
* @author pan_junbiao
*
*/
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类可用于文件的压缩和解压缩。