目录
Java中针对文件内容的操作,主要是通过一组“流对象”来实现的。
“流”是一种很形象的比喻,将数据比喻成水流,来定义数据的传输方式。
一、IO流的分类
(一)字节流
字节流读写文件是以字节为单位,是针对二进制文件使用的。
针对读和写,有两个类:InputStream和OutputStream类。
1、InputStream
InputStream用于输入,从文件中读取数据。
但是InputStream 只是一个抽象类,要使用还需要具体的实现类。
InputStream类中的成员方法
read()返回的是字节数据。
read(byte[]b)是将读取到的字节数据读入到数组中,返回的是读取到的字节数。
关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream。
FileInputStream的构造方法
这里的创建对象操作一但成功,就相当于“打开文件”,打开文件进行读写操作之后就要手动释放,否则就会引起“文件资源泄露”。
public class Demo05 {
public static void main(String[] args) throws IOException {
File file=new File("./hello.txt");
file.createNewFile();
//创建流对象,成功就相当于打开文件
InputStream inputStream=new FileInputStream(file);
//没有手动释放
}
}
为了确保文件必须关闭,采用try+finally的写法:
public class Demo05 {
public static void main(String[] args) throws IOException {
File file=new File("./hello.txt");
file.createNewFile();
InputStream inputStream=null;
try{
inputStream=new FileInputStream(file);
}finally {
inputStream.close();
}
}
}
优化成try with resources的写法 :
public class Demo05 {
public static void main(String[] args) throws IOException {
File file=new File("./hello.txt");
file.createNewFile();
try(InputStream inputStream=new FileInputStream(file);){
}
}
read()和read(byte[]b)代码示例:
将文件完全读完的两种方式,第二种的IO次数更少性能更好。
在项目目录下准备好了一个hello.txt 的文件,里面填充 "Hello" 的内容。
public class Demo05 {
public static void main(String[] args) throws IOException {
try(InputStream inputStream=new FileInputStream("hello.txt");){
while(true){
int n=inputStream.read();
if(n==-1){
break;
}
//转换类型
System.out.print((char)n);
}
}
}
}
public class Demo05 {
public static void main(String[] args) throws IOException {
try(InputStream inputStream=new FileInputStream("hello.txt");){
byte[]buf=new byte[1024];
int len;
while(true){
len=inputStream.read(buf);
if(len==-1){
break;
}
for(int i=0;i<len;i++){
//转换类型
System.out.print((char)buf[i]);
}
}
}
}
}
对于第二种写法:
- byte[]buf=new byte[1024]作为缓冲区,提高数据读取或者写入的效率。
- 每次调用 inputStream.read(buf) 时,它会尝试从输入流中读取最多 buf.length 字节的数据到缓冲区 buf 中,并返回实际读取的字节数。
- 每次
read(buf)
执行时,会将新数据写入buf
的起始位置,覆盖之前的数据。
2、利用Scanner进行字符读取
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类。
构造方法 |
说明 |
Scanner(InputStream is, String charset) |
使用charset字符集进行is的扫描读取 |
代码示例:
public class Demo06 {
public static void main(String[] args) {
try(InputStream is=new FileInputStream("hello.txt");){
try(Scanner sc=new Scanner(is);){
while(sc.hasNext()){
System.out.println(sc.next());
}
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Scanner和read()的作用都是从输入流中获取数据。
3、OutputStream
对于OutputStream来说,默认情况下会尝试创建不存在的文件,而InputStream不会。
OutputStream中的成员方法
修饰符及返回值类型 | 方法签名 | 说明 |
void | write(int b) |
写入要给字节的数据 |
void | write(byte[] b) |
将b这个字符数组中的数据全部写入os 中 |
int | write(byte[]b, int off, int len) |
将b这个字符数组中从 off 开始的数据写入os 中,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() |
将可能遗留在缓冲区里的数据flush刷新到设备中 |
OutputStream同样只是一个抽象类,要使用还需要具体的实现类,我们现在只是关心写入文件中,所以使用FileOutputStream
FileOutputStream代码示例
示例一:
public class Demo07 {
public static void main(String[] args) throws IOException {
try(OutputStream os=new FileOutputStream("output.txt");) {
os.write('H');
os.write('e');
os.write('l');
os.write('l');
os.write('o');
//不要忘记flush
os.flush();
}
}
}
示例二:
public class Demo07 {
public static void main(String[] args) throws IOException {
try(OutputStream os=new FileOutputStream("output.txt");) {
byte[]b=new byte[]{
(byte)'H',(byte)'e',(byte)'l',(byte)'l',(byte)'o'
};
os.write(b);
//不要忘记flush
os.flush();
}
}
}
示例三:
public class Demo07 {
public static void main(String[] args) throws IOException {
try(OutputStream os=new FileOutputStream("output.txt");) {
byte[]b=new byte[]{
(byte)'H',(byte)'e',(byte)'l',(byte)'l',(byte)'o'
};
os.write(b,0,5);
//不要忘记flush
os.flush();
}
}
}
示例四:
public class Demo07 {
public static void main(String[] args) throws IOException {
try(OutputStream os=new FileOutputStream("output.txt");) {
String s="Hello";
byte[]b=s.getBytes();
os.write(b);
//不要忘记flush
os.flush();
}
}
}
示例五:
public class Demo07 {
public static void main(String[] args) throws IOException {
try(OutputStream os=new FileOutputStream("output.txt");) {
String s="我爱中国";
byte[]b=s.getBytes("UTF-8");
os.write(b);
//不要忘记flush
os.flush();
}
}
}
追加写:
在以上代码案例中,如果我们将代码多执行几遍,期望应该是每次执行的代码都会重复地将数据写入到文件中,实际上文件里只会存在一次写入的数据。Outputstream是会清除上次文件的内容的,打开文件的一瞬间,上次的文件就被清空了。
try(OutputStream os=new FileOutputStream("output.txt",true);) {
}
在创建对象时,加上一个参数“true”,就是追加写,能保证上次的数据不被清空。
(二)字符流
字符流读写文件是以字符为单位,是针对文本文件使用的。
针对读和写,有两个类:Reader和Writer类。
与字节流一样Reader和Writer都是抽象类,要实例化其子类对象FileReader和Filewriter。
字符流类的成员方法和构造方法与字节流基本一致,下面是代码案例。
1、Reader
read()和read(byte[]b)代码示例:
public class Demo08 {
public static void main(String[] args)throws IOException {
try(Reader reader=new FileReader("hello.txt")){
while(true){
int n=reader.read();
if(n==-1){
break;
}
//n要转换成char类型
System.out.print((char)n);
}
}
}
}
public class Demo08 {
public static void main(String[] args)throws IOException {
try(Reader reader=new FileReader("hello.txt")){
//建立缓冲区
//这里是字符流,要用char类型
char[]buf=new char[1024];
int len;
while(true){
len=reader.read(buf);
if(len==-1){
break;
}
for(int i=0;i<len;i++){
System.out.println(buf[i]);
}
}
}
}
}
2、Writer
public class Demo09 {
public static void main(String[] args) throws IOException {
try(Writer writer=new FileWriter("output.txt",true)){
String s="我爱中国";
writer.write(s);
writer.flush();
}
}
}
二、总结
不管是字节流还是字符流,在读写文件时,代码是差不多的。
对于读入文件,在while中主要有read()和read(byte/char[]b)两种方式:
- read()反复地读取文件,返回值n是读取到的字节数据,当n=-1时表示文件读取完成。
- read(byte/char[]b)在read()的基础上,使用了缓存区byte/char[]b,read(byte/char[]b)将读取到的字节数据存到缓存区中,返回值len为读取到的数据个数,当len=-1时表示文件读取完成。
对于写出文件,有writer()和writer(byte/char[]b)两种方式。