文件流
文件输入流
java.io.FileInputStream使用文件输入流向从文件中读取数据
构造器
FileInputStream(String path)
基于给定的路径对应的文件创建文件输入流
FileInputStream(File file)
基于给定的File对象所表示的文件创建文件输入流
例
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 文件输入流
* 用于从文件中读取字节的流
*/
public class FISDemo {
public static void main(String[] args) throws IOException {
/*
FileInputStream(String path)
FileInputStream(File file)
*/
FileInputStream fis = new FileInputStream("fos.dat");
/*
int read()
读取1个字节,以int形式返回该字节内容。int值只有"低八位"有数据,高24位
全部补0.
有一个特殊情况:如果返回的int值为整数-1,则表示EOF。
EOF:end of file 文件末尾
fos.dat文件数据
00000001 00000010
第一次调用:
int d = fis.read();
00000001 00000010
^^^^^^^^
读取的字节
返回值d的二进制样子:
00000000 00000000 00000000 00000001
|-----自动补充的24个0------| 读取的字节
第二次调用:
d = fis.read();
00000001 00000010
^^^^^^^^
读取的字节
返回值d的二进制样子:
00000000 00000000 00000000 00000010
|-----自动补充的24个0------| 读取的字节
第三次调用:
d = fis.read();
00000001 00000010
^^^^^^^^
文件末尾了
返回值d的二进制样子:
11111111 11111111 11111111 11111111
|----------32位2进制都是1-----------|
*/
int d = fis.read();
System.out.println(d);//1
d = fis.read();
System.out.println(d);//2
d = fis.read();//文件只有2个字节,因此第三次读取已经是文件末尾EOF
System.out.println(d);//-1
fis.close();
}
}
文件复制
复制文件的原理就是使用文件输入流从原文件中陆续读取出每一个字节,然后再使用文件输出流将字节陆续写入到另一个文件中完成的。
示例
第一次读取
第二次读取
第三次读取
循环进行上述操作,直到某次fis.read()方法返回值为-1,表示读取到了文件末尾,那么就不再写出即可。
例
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 文件的复制
*/
public class CopyDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("image.png");
FileOutputStream fos = new FileOutputStream("image_cp.png");
int d;
// while(true){
// d = fis.read();
// if(d==-1){
// break;
// }
// fos.write(d);
// }
while((d = fis.read())!= -1){
fos.write(d);
}
System.out.println("复制完毕!");
fis.close();
fos.close();
}
}
效率问题
上述案例在复制文件时的读写效率是很低的。因为硬盘的特性,决定着硬盘的读写效率差,而单字节读写就是在频繁调用硬盘的读写,从而产生了"木桶效应"。
为了解决这个问题,我们可以采取使用块读写的方式来复制文件,减少硬盘的实际读写的次数,从而提高效率。
块读写
-
块读:一次性读取一组字节的方式
InputStream中定义了块读的方法
int read(byte[] data) 一次性读取给定字节数组总长度的字节量并存入到该数组中。 返回值为实际读取到的字节数。如果返回值为-1表示本次没有读取到任何字节已经是流的末尾了
-
块写:一次性写出一组字节的方式
OutputStream中定义了块写的方法
void write(byte[] data) 一次性将给定数组中所有字节写出 void write(byte[] data,int offset,int len) 一次性将data数组中从下标offset处开始的连续len个字节一次性写出
例
改为块读写形式后,复制效率得到了明显的提升
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 提高每次读写的数据量减少读写次数,可以提高读写效率
*
* 块读取:一次性读取一组字节的方式
* 块写:一次性写出一组字节
*/
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("image.png");
FileOutputStream fos = new FileOutputStream("image_cp2.png");
/*
在InputStream中定义了块读取的方法
int read(byte[] data)
一次性读取给定字节数组总长度的字节量并存入到该数组中。
返回值为实际读取到的字节数。如果返回值为-1表示本次没有读取到任何字节已经是流的末尾了
文件内容(6字节):
00110011 11001100 10101010 01010101 11110000 00001111
byte[] data = new byte[4];//4个字节的数组
data:{00000000,00000000,00000000,00000000} 2进制表示
第一次调用:int d = fis.read(data);
一次性从文件中读取4(data数组的长度为4)个字节
00110011 11001100 10101010 01010101 11110000 00001111
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
|----------读取的数据---------------|
data:{00110011,11001100,10101010,01010101}
数组里的4个字节为本次读取到的全部数据
d:4 返回值d为整数4,表示本次实际读取到了4个字节
第二次调用:d = fis.read(data);
一次性从文件中读取4(data数组的长度为4)个字节
00110011 11001100 10101010 01010101 11110000 00001111 文件末尾了
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
|---读取的数据-----|
data:{11110000,00001111,10101010,01010101}
|-本次实际读取字节-| |---旧数据-------|
d:2 返回值d为整数2,表示本次实际读取到了2个字节
第二次调用:d = fis.read(data);
一次性从文件中读取4(data数组的长度为4)个字节
00110011 11001100 10101010 01010101 11110000 00001111 文件末尾
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
已经是文件末尾
data:{11110000,00001111,10101010,01010101}
|-------------旧数据-----------------|
d:-1 返回值d为整数-1,表示已经是末尾了,本次没有读取任何数据
OutputStream中定义了块写方法
void write(byte[] data)
一次性将给定数组中所有字节写出
*/
/*
00000000 1byte 1字节
1024byte 1kb
1024kb 1mb
1024mb 1gb
1024gb 1tb
1024tb 1pb
*/
byte[] data = new byte[1024*10];//10kb
int d;//记录每次实际读取的数据量
long start = System.currentTimeMillis();
while( (d = fis.read(data)) !=-1){
fos.write(data);
}
long end = System.currentTimeMillis();
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
fis.close();
fos.close();
}
}
问题
速度问题解决了,但是复制后的文件比原文件大一些。这是文件不一定是10240的倍数,这会导致最后一次读取时是读不够10240的字节数的,那么data数组中就不是所有数据都是新数据了。此时如果在写出时将data数组所有内容写出就会出现文件最后多出很多旧的数据。
示例
第一次操作
第二次操作
第三次读取操作
第四次操作
解决办法
使用OutputStream的另一个块写操作
void write(byte[] data,int offset,int len)
将给定数组data从offset处开始的连续len个字节一次性写出
例
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 提高每次读写的数据量减少读写次数,可以提高读写效率
*
* 块读取:一次性读取一组字节的方式
* 块写:一次性写出一组字节
*/
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("image.png");
FileOutputStream fos = new FileOutputStream("image_cp2.png");
byte[] data = new byte[1024*10];//10kb
int d;//记录每次实际读取的数据量
long start = System.currentTimeMillis();
while( (d = fis.read(data)) !=-1){
fos.write(data,0,d);
}
long end = System.currentTimeMillis();
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
fis.close();
fos.close();
}
}
写出文本数据
文件中只能保存2进制信息,因此我们要想写出文本数据,需要先将字符串转换为2进制。
文字编码
String提供了将字符串转换为一组字节的方法
byte[] getBytes(Charset cs)
将当前字符串按照指定的字符集转换为一组字节
例如:
String line = "和我在成都的街头走一走,哦哦哦哦~";
byte[] data = line.getBytes(StandardCharsets.UTF_8);//将字符串按照UTF-8编码转换为一组字节
写入文本文件
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 写出文本数据
*/
public class WriteStringDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("fos.txt");
String line = "和我在成都的街头走一走,哦哦哦哦~";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
fos.write(data);
fos.write("直到所有的灯都熄灭了也不停留.".getBytes(StandardCharsets.UTF_8));
System.out.println("写出完毕!");
fos.close();
}
}
追加模式
文件输出流有两种模式
- 覆盖模式:当文件流创建时发现指定的文件已经存在了,那么会将该文件内容清空。
- 追加模式:当文件流创建时发现指定的文件已经存在了,那么文件数据全部保留通过该流新写出的数据会被陆续的追加到文件中。
文件输出流的两种模式对应的构造器
-
覆盖模式
FileOutputStream(String path) FileOutputStream(File file)
-
追加模式
当append参数为true时,则开启追加模式 FileOutputStream(String path,boolean append) FileOutputStream(File file,boolean append)
例
package io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 写出文本数据
*/
public class WriteStringDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("fos.txt",true);
fos.write("成都~带不走的只有你.".getBytes(StandardCharsets.UTF_8));
System.out.println("写出完毕!");
fos.close();
}
}
执行两次上述程序会看到文件中的内容(每次执行程序写出的内容都会被保留):
读取文本数据
先将文件中的字节读取出来,然后再将这些字节按照对应的字符集转换为字符串即可
文本解码
String的构造器提供了对字节解码为字符串的操作
String(byte[] data,Charset cn)
将data数组中的所有字节按照指定的字符集转换为字符串
例
package io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 读取文本数据
*/
public class ReadStringDemo {
public static void main(String[] args) throws IOException {
/*
1:先从fos.txt中读取所有的字节
2:再将这些字节转换为字符串
*/
//1
File file = new File("fos.txt");
long len = file.length();//文件名长度
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int)len];//创建一个与文件长度等长的字节数组
fis.read(data);//一口气将文件所有字节读入到data数组中(块读)
//2将data数组中所有字节按照UTF-8编码还原为字符串
String line = new String(data, StandardCharsets.UTF_8);
System.out.println(line);
fis.close();
}
}