文章目录
06 IO流
6-1 File类的使用
java.io.File类是文件和文件目录的抽象表现形式,与平台无关。
File能新建、删除、重命名文件和目录,但File不能访问文件内容本身。如果需要访问文件内容本身,需要使用输入输出流。
如果要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象;反之Java程序中的一个File对象不一定是真实存在的。
File构造器
常用构造器:
- public File(String pathname)
- 以pathname为路径创建File对象,可以是绝对路径或相对路径。
- 如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储
- public File(String parent, String child)
- 以parent为父路径,child为子路径创建File对象
- public File(File parent, String child)
- 根据一个父File对象和子文件路径创建File对象
路径分隔符相关细节:
- 路径分隔符和系统有关:
- windows和Dos系统默认使用"\"来表示
- UNIX和URL使用"/"来表示
- 为了解决不同系统分隔符问题,File类提供了一个常量public static final String separator,动态提供分隔符。
File类的实例化:
public class FileTest {
@Test
public void test() {
//路径分隔符常量
System.out.println(File.separator);
//1. public File(String pathname)
//绝对路径
File file = new File("F:\\Java\\ABCs\\hello.txt");
System.out.println(file);
//相对路径:当前module
File file1 = new File("hello.txt");
System.out.println(file1);
//2. public File(String parent, String child)
File file2 = new File("F:\\Java\\ABCs", "JavaSenior");
System.out.println(file2);
//3. public File(File parent, String child)
File file3 = new File(file2, "hi.text");
System.out.println(file3);
}
}
File常用方法
- File类的获取功能
method | description |
---|---|
文件方法: | |
public String getAbsolutePath() | 获取绝对路径 |
public String getPath() | 获取路径 |
public String getName() | 获取名称 |
public String getParent() | 获取上层文件目录 |
public long length() | 获取文件字节数 |
public long lastModified() | 获取最后一次的修改时间,毫秒值 |
文件目录方法: | |
public String[] list() | 获取指定目录下所有文件或文件目录的名称数组 |
public File[] listFiles | 获取指定目录下所有文件或文件目录的File数组 |
- File类的重命名功能
method | description |
---|---|
public boolean renameTo(File dest) | file1.renameTo(file2):将file1重命名为file2的路径(file1需要存在,file2不能存在) |
- File类的创建功能
method | description |
---|---|
public boolean createNewFile() | 创建文件。若文件存在则不创建并返回false |
public boolean mkdir() | 创建文件目录。如果上层文件目录不存在,则不创建 |
public boolean mkdirs() | 创建文件目录。如果上层文件目录不存在,则一并创建 |
使用示例:
- 使用递归打印出所有文件名称
- 使用递归计算所有文件大小
public class FileTest {
@Test
public void test() {
//1. 使用递归打印出所有文件名称
File file = new File("F:\\Java\\ABCs");
printFiles(file);
//2. 使用递归计算所有文件大小
long length = findLength(file);
System.out.println("该目录下所有文件大小为\t" + length + " 字节");
}
public void printFiles(File file) {
if (file.isFile()) {
System.out.println(file.getName());
} else {
File[] subDir = file.listFiles();
for (File f: subDir) {
printFiles(f);
}
}
}
public long findLength(File file) {
if (file.isFile()) {
return file.length();
} else {
File[] subDir = file.listFiles();
long l = 0L;
for (File f: subDir) {
l += findLength(f);
}
return l;
}
}
}
6-2 IO流原理及分类
I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输,如读写文件、网络通讯等。
Java程序中,对于数据的输入输出操作以“流(Stream)”的方式进行。
java.io包下提供了各种“流”类和接口,用于获取不同种类的数据,并通过标准的方法输入或输出数据。
流的分类:
- 按操作数据单位划分:
- 字节流(8 bit)
- 字符流(16 bit)
- 按数据流的流向不同划分:
- 输入流
- 输出流
- 按流的角色不同划分:
- 节点流
- 处理流
(抽象基类) | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
IO流体系:
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
Filter | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackInputReader | ||
特殊流 | DataInputStream | DataOutputStream |
节点流(或文件流)
- 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt…):
- FileInputStream
- FileOutputStream
- 对于文本文件(.txt,.java,.c,.cpp…):
- FileReader
- FileWriter
FileReader和FileWriter示例:
public class FileReaderWriterTest {
@Test
public void testFileReader() {
//1.实例化File类对象,指明要操作的文件
File file = new File("hello.txt");
System.out.println(file.getAbsolutePath());
FileReader fr = null;
try {
//2.提供具体的流
fr = new FileReader(file);
//3.数据的读入
//read():返回读入的一个字符,如果达到文件末尾,返回-1
int data;
while ((data = fr.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭操作
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//对read()操作升级:使用read的重载方法
@Test
public void testFileReader1() {
//1.File类的实例化
File file = new File("hello.txt");
FileReader fr = null;
try {
//2.FileReader流的实例化
fr = new FileReader(file);
//3.读入操作
//read(char[] cbuf): 返回每次读入cbuf数组中字符的个数
char[] cbuf = new char[10];
int len;
while ((len = fr.read(cbuf)) != -1) {
//方式一:循环打印char字符
//错误示范:
// for (int i = 0; i < cbuf.length; i++) {
// System.out.print(cbuf[i]);
// }
//正确写法
for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]);
}
//方式二:使用String(char[], int offset, int count)构造器
String str = new String(cbuf, 0, len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//从内存中写出数据到硬盘文件里
@Test
public void testFileWriter() throws IOException {
//1.File类的实例化
File file = new File("dream.txt");
//2.提供FileWriter对象,用于数据的写出
FileWriter fw = new FileWriter(file, true);
//3.写出的操作
fw.write("I have a dream.\n");
fw.write("You have to dream about a future with hope.\n");
//4.流资源的关闭
fw.close();
}
}
注意点:
- read()在读到文件末尾时返回-1
- 处理IO异常:为了保证流资源一定可以执行关闭操作,需要使用try-catch-finally处理
- 读入的文件一定要存在,否则就会报FileNotFoundException
- 输出操作write():
- 对应的File可以不存在,如果不存在将自动创建文件
- 如果文件存在,当FileWriter的append参数为true时进行追加写入,false时对原文件进行覆盖。
FileInputStream和FileOutputStream示例:
public class FileInputOutputStreamTest {
//实现对图片的复制操作:
@Test
public void testFileInputOutputStream() {
File file1 = new File("IshibaraSatomi.jpg");
File file2 = new File("SatomiChan.jpg");
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(file1);
fos = new FileOutputStream(file2);
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲流
缓冲流是处理流的一种,相比于节点流可以提高文件读写的效率。
- 处理非文本文件:
- BufferedInputStream --> FileInputStream的包装
- BufferedOutputStream --> FileOutputStream的包装
- 处理文本文件
- BufferedReader --> FileReader的包装
- BufferedWriter --> FileWriter的包装
使用缓冲流与节点流的效率对比(以30M音频文件为例):
public class BufferedTest {
//实现非文本文件的复制操作
@Test
public void BufferedStreamTest() {
String src = "D:\\CloudMusic\\Storefront Church - The Gift.ncm";
String dest = "music1.ncm";
long start = System.currentTimeMillis();
copyFile(src, dest);
long end = System.currentTimeMillis();
System.out.println("使用字节流复制文件操作花费时间为" + (end - start) + "ms");
System.out.println("*********************************");
dest = "music2.ncm";
start = System.currentTimeMillis();
copyFileBuffered(src, dest);
end = System.currentTimeMillis();
System.out.println("使用缓冲流复制文件操作花费时间为" + (end - start) + "ms");
}
public void copyFileBuffered(String srcPath, String destPath) {
//1. 创建File对象
File file1 = new File(srcPath);
System.out.println("原始文件大小为:\t" + file1.length() + "字节");
File file2 = new File(destPath);
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//2.1 创建输入输出流对象
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
//2.1 创建缓冲流对象
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3. 复制操作
byte[] buffer = new byte[256];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
System.out.println("新文件大小为:\t" + file2.length() + "字节");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源
//要求:先关闭外层的流,再关闭内层的流
try {
if (bos != null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
//关闭外层流时,内层流也会自动关闭;因此可以省略内层流的关闭
// fos.close();
// fis.close();
}
}
//指定路径下文件的复制
public void copyFile(String srcPath, String destPath) {
File file1 = new File(srcPath);
System.out.println("原始文件大小为:\t" + file1.length() + "字节");
File file2 = new File(destPath);
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(file1);
fos = new FileOutputStream(file2);
byte[] bytes = new byte[256];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
System.out.println("新文件大小为:\t" + file2.length() + "字节");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
原始文件大小为: 38123009字节
新文件大小为: 38123009字节
使用字节流复制文件操作花费时间为570ms
*********************************
原始文件大小为: 38123009字节
新文件大小为: 38117376字节
使缓冲流复制文件操作花费时间为56ms
Process finished with exit code 0
缓冲流提高读写速度的原因:
- 内部提供了一个缓冲区,读到一定大小时会刷新缓冲区
转换流
转换流也是处理流的一种,它提供了在字节流和字符流之间的转换。
Java API提供了两个转换流:
- InputStreamReader: 将InputStream转换为Reader
- OutputStreamWriter:将Writer转换为OutputStream
转换流可以用于处理不同编码集的文本文件,示例如下:
public class InputStreamReaderTest {
//1. 读取文本示例
@Test
public void test1() {
InputStreamReader isr = null;//使用系统默认的字符集
try {
FileInputStream fis = new FileInputStream("dream.txt");
isr = new InputStreamReader(fis);
// isr = new InputStreamReader(fis, StandardCharsets.UTF_8);//第二个参数指定了字符集
char[] buffer = new char[20];
int len;
while ((len = isr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (isr != null)
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//2. 使用转换流进行编码格式的转换
@Test
public void test2() {
File file1 = new File("dream.txt");
File file2 = new File("dream_utf8.txt");
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
isr = new InputStreamReader(fis);
osw = new OutputStreamWriter(fos, "UTF-8");//此处指定写入格式
char[] buffer = new char[20];
int len;
while ((len = isr.read(buffer)) != -1) {
osw.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (osw != null)
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (isr != null)
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//3. 读取以utf-8编码的文本文件(默认编码集为gbk)
@Test
public void test3() throws IOException {
//使用默认的FileReader读取文本文件
FileReader fr = new FileReader("dream_utf8.txt");
char[] buffer = new char[20];
int len;
while ((len = fr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
fr.close();
System.out.println("\n******************");
//使用InputStreamReader,指定编码格式读取文本文件
FileInputStream fis = new FileInputStream("dream_utf8.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
while ((len = isr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
isr.close();
}
}
示例3输出结果:
鏄庡ぉ鑷細鍚规槑澶╃殑椋?
******************
明天自会吹明天的风
Process finished with exit code 0
其他流的使用
1. 标准的输入、输出流
- System.in: 标准的输入流,默认从键盘输入
- System.out: 标准的输出流,默认从控制台输出
- System类的setIn()和setOut()方法用以重新指定输入和输出的流
2. 打印流
- PrintStream和PrintWriter有自动flush功能
- PrintStream打印的所有字符都使用平台默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,需要使用PrintWriter类。
- System.out返回的是PrintStream实例
3. 数据流
- 为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流
- DataInputStream和DataOutputStream,分别套接在InputStream和OutputStream子类的流上
数据流使用示例:
public class OtherStreamTest {
@Test
public void dataOutputStreamTest() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("Thomas Wong");
dos.flush();
dos.writeInt(24);
dos.flush();
dos.writeDouble(5487.45);
dos.flush();
dos.close();
}
//注意点:读取数据类型的顺序要与当初写入时的顺序一致
@Test
public void dataInputStreamTest() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
String name = dis.readUTF();
int age = dis.readInt();
double bonus = dis.readDouble();
System.out.println("name:\t" + name);
System.out.println("age:\t" + age);
System.out.println("bonus:\t" + bonus);
dis.close();
}
}
4. 对象流
- 用于存储和读取基本数据类型数据或对象的处理流。
- 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
- 反序列化:用ObjectInputStream类读取基本数据类型或对象的机制
- ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
5. 随机存取文件流
- RandomAccessFile类同时实现了DataInput和DataOutput接口,既可以作为输入流也可以作为输出流
- 作为输出流时,如果写到的文件不存在则自动创建,如果文件存在,则对原有文件内容从头覆盖。
- RandomAccessFile类支持“随机访问”方式,程序可以直接跳到文件的任意地方来读写文件
- RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置。
- long getFilePointer(): 获取文件记录指针的当前位置
- void seek(long pos): 将文件记录指针定位到pos位置
随机存取文件流示例:
public class RandomAccessFileTest {
@Test
public void test1() {
RandomAccessFile rafIn = null;
RandomAccessFile rafOut = null;
try {
rafIn = new RandomAccessFile(new File("OurSatomi.jpg"), "rw");
rafOut = new RandomAccessFile(new File("SatomiMyWife.jpg"), "rw");
byte[] buffer = new byte[1024];
int len;
while ((len = rafIn.read(buffer)) != -1) {
rafOut.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (rafOut != null) {
try {
rafOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rafIn != null) {
try {
rafIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test2() throws IOException {
//原文件字符:abcdefghijklmn
RandomAccessFile raf = new RandomAccessFile(new File("day26\\hello.txt"), "rw");
raf.seek(3);//指针调到脚标为3的位置
raf.write("XYZ".getBytes());//aXYZefghijklmn
raf.close();
}
}
补充知识:对象的序列化
对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其他程序获取了这种二进制流,就可以恢复成原来的Java对象。
序列化的好处在于可以将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,即:
- 该类必须实现如下两个接口之一:
- Serializable
- Externalizable
- 该类必须提供一个全局常量:
- public static final long serialVersionUID
- 该类的所有属性必须是可序列化的(默认情况下,基本数据类型是可序列化的)