一、 I/O流概述
输入流就是指某数据源到程序的流
输出流则是程序中的数据传输到了某个目的地
其中,字节流和字符流都是抽象类,我们要详细介绍的是它们的子类
这些类的命名有一个特点:字节流以Stream结尾,字符流都是以Reader或Writer结尾的
二、File类(文件的操作)
在创建File对象时,有三种构造方法可以选择
绝对路径:是一个文件的具体位置
第二种构造方法:文件夹和文件名是分开的
package file_file;
import java.io.File;
public class Demo {
public static void main(String[] args) {
/**
* 项目下的路径(默认路径):word.txt
* 包中的文件路径:src/file_file/word.txt
* 注意: /表示文件夹
* \\表示文件夹(转义字符)
* 绝对路径:D:\\test\\word.txt
*/
File f1 = new File("D:\\test\\word.txt\\"); //第一种构造方法
File f2 = new File("D:\\test\\", "word.txt"); //第二种构造方法
File dir = new File("D:\\test\\"); //文件夹
File f3 = new File(dir, "word.txt"); //第三种构造方法
System.out.println(f1.getAbsolutePath()); //输出文件的绝对路径
System.out.println(f2.getAbsolutePath()); //输出文件的绝对路径
System.out.println(f3.getAbsolutePath()); //输出文件的绝对路径
System.out.println(f1 == f2);
System.out.println(f1.equals(f2));
}
}
注意:
f1 == f2(结果为false,因为f1和 f2是两个独立的File对象)
f1.equals(f2) (结果为true,因为f1和 f2指向的是同一个文件)
package file_file;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Demo {
public static void main(String[] args) {
/**
* 项目下的路径(默认路径):word.txt
* 包中的文件路径:src/file_file/word.txt
* 注意: /表示文件夹
* \\表示文件夹(转义字符)
* 绝对路径:D:\\test\\word.txt
*/
File f1 = new File("D:\\test\\word.txt\\"); //第一种构造方法
// File f2 = new File("D:\\test\\", "word.txt"); //第二种构造方法
//
// File dir = new File("D:\\test\\"); //文件夹
// File f3 = new File(dir, "word.txt"); //第三种构造方法
//
// System.out.println(f1.getAbsolutePath()); //输出文件的绝对路径
// System.out.println(f2.getAbsolutePath()); //输出文件的绝对路径
// System.out.println(f3.getAbsolutePath()); //输出文件的绝对路径
//
// System.out.println(f1 == f2);
// System.out.println(f1.equals(f2));
System.out.println("文件是否存在:" + f1.exists()); //判断文件是否存在
System.out.println("文件名:" + f1.getName()); //输出文件名
System.out.println("文件的绝对路径:" + f1.getAbsolutePath()); //输出文件的绝对路径
System.out.println("是否是隐藏文件:" + f1.isHidden()); //是否是隐藏文件
System.out.println("文件的字节数:" + f1.length()); //输出文件大小,单位:字节
Date date = new Date(f1.lastModified()); //通过毫秒值 创建日期类
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println("文件的最后修改时间:" + sdf.format(date)); //文件最后的修改时间
// boolean del = f1.delete(); //删除文件
// System.out.println("删除文件是否成功:" + del);
try {
//已存在的文件,不能重新创建(不能覆盖已有文件)
boolean creat = f1.createNewFile(); //创建新空的文件
System.out.println("创建文件是否成功:" + creat);
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果我们获取的文件是不存在的,我们发现程序仍然可以运行(但是输出的值都是默认值),这是不对的
所以我们应该在一开始就判断文件是否存在
三、File类(文件夹的操作)
package file_directory;
import java.io.File;
public class Demo {
public static void main(String[] args) {
File dir = new File("dir/dir2/dir3/dir4/");
boolean flag = dir.mkdir(); //创建文件夹
boolean flag2 = dir.mkdirs(); //创建文件夹及其父文件夹(创建很多文件夹)
System.out.println("创建文件夹是否成功:" + flag);
System.out.println("创建多层文件夹是否成功:" + flag2);
// //删除文件路径最后一个文件夹
// boolean del = dir.delete(); //删除文件夹
// System.out.println("删除文件夹是否成功:" + del);
File f = new File("C:\\Windows\\"); //C盘Windows文件夹
File files[] = f.listFiles(); //返回文件夹下 所有的子文件及子文件夹
for(File tmp: files) {
if(tmp.isFile()) {
System.out.println("文件:" + tmp.getName());
}else if(tmp.isDirectory()) {
System.out.println("文件夹:" + tmp.getName());
}
}
}
}
mkdir()这个方法只能创建一层文件夹,不能创建多层文件夹
创建文件夹及其父文件夹(多层文件夹)用mkdirs()这个方法
listfiles()方法返回的是我文件夹下所有的子文件及子文件夹(返回的值是文件数组,所以可以通过遍历数组来获取所有的子文件)
四、文件字节流
电脑中的文件都是用二进制来储存的,读取文件时就是在读取这些二进制字节码,java提供了读取这些字节码的文件字节流
输入流用来读文件,输出流用来写文件
注意:在使用完字节流之后,一定要将其关闭
package file_IO_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("word.txt");
FileOutputStream out = null;
try {
out = new FileOutputStream(f, true); //文件输出流,替换文件内容//在文件末尾追加内容
String str = "你见过洛杉矶,凌晨四点的样子吗?";
byte b[] = str.getBytes(); //字符串转换为字节数组
out.write(b); //将字节数组中数据写入到文件中
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream in = null;
try {
in = new FileInputStream(f); //输入流读文件
byte b2[] = new byte[200]; //缓冲区
int len = in.read(b2); //读入缓冲区的总字节数
System.out.println("文件中的数据是:" + new String(b2, 0, len)); //去掉空格
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
当我们反复向这个文件中写值时,会覆盖原来的内容(可以再添加一个参数true,这样就是在文件末尾追加内容,其默认值为false(替换文件内容))
在文件之后添加内容还是替换内容用一个布尔值来控制
如何将byte数组里面的东西变成我们能看到的内容:用String的构造方法,java会自动将这个字节数组转换成对应的字符串
**注意:**滚动条能拉到很往后(因为字节数组长度为1024,所以字符串后面全是空格)
不显示它后面的这些空格的方法:记录读入缓冲区的总字节数,在String的构造方法中添加两个参数
五、文件字符流
字符流能避免读取数据不全而造成的乱码问题
java中的文件字符流有两个:FileReader 文件字符输入流 FileWriter文件字符输出流
package file_char_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("word.txt");
FileWriter fw = null;
try {
fw = new FileWriter(f, true); //在源文件后追加新内容
String str = "天行健,自强不息;地势坤,厚德载物。";
fw.write(str); //将字符串写入到文本文档
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader fr = null;
try {
fr = new FileReader(f);
char ch[] = new char[1024]; //缓冲区
int count; //已经读出的字符数
while( (count = fr.read(ch)) != -1) { //循环读取文件中的数据,直到所有字符都读完
System.out.println("文件中的内容为:" + new String(ch, 0, count));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
六、缓冲字节流
在流中创造一片缓冲区,让数据一车一车运输(而不是一点一点运输),大大降低数据源和目的地的交互次数,效益会明显提高
缓冲字节流的使用方法和其他字节流一样,但是它需要用在其他字节流之上
这是没有缓冲字节输入流的情况:
package buffered_IO_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("C:\\Program Files\\dotnet\\ThirdPartyNotices.txt\\");
FileInputStream in = null;
long start = System.currentTimeMillis(); //获取流开始时毫秒值
try {
in = new FileInputStream(f);
byte b[] = new byte[1024]; //缓冲区字节数组(这个缓冲区与Buffered不同)
while(in.read(b) != -1) {
}
long end = System.currentTimeMillis(); //获取流结束时毫秒值
System.out.println("运行经历的毫秒数:" + (end - start));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这是有缓冲字节输入流的情况:
package buffered_IO_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("C:\\Program Files\\dotnet\\ThirdPartyNotices.txt\\");
BufferedInputStream bi = null;
FileInputStream in = null;
long start = System.currentTimeMillis(); //获取流开始时毫秒值
try {
in = new FileInputStream(f);
bi = new BufferedInputStream(in); //将文件字节流包装成缓冲字节流
byte b[] = new byte[1024]; //缓冲区字节数组(这个缓冲区与Buffered不同)
while(bi.read(b) != -1) { //使用缓冲流读取数据
}
long end = System.currentTimeMillis(); //获取流结束时毫秒值
System.out.println("运行经历的毫秒数:" + (end - start));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bi != null) {
try {
bi.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
将文件字节流包装成缓冲字节流,所以在读数据时就不能用文件字节流读了,而是要用缓冲字节流来读数据
现在,这里运行经历的毫秒数大大减少了
这就是使用缓冲字节流的效果:大大提高运行效率
package buffered_IO_stream;
import java.io.*;
public class Demo2 {
public static void main(String[] args) {
File f = new File("word.txt");
BufferedOutputStream bo = null; // 大大提高了运行效率
FileOutputStream out = null;
try {
out = new FileOutputStream(f);
bo = new BufferedOutputStream(out); // 将文件字节流包装成缓冲字节流
String str = "天生我材必有用,千金散尽还复来。";
byte b[] = str.getBytes();
bo.write(b);
bo.flush(); //刷新。强制将缓冲区数据写入文件,即使缓冲区没有被写满
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bo != null) {
try {
bo.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
其实这样看除了效率以外,没有什么变化
但是缓冲输出流提供了这样一个方法:flush()(这个方法的意思是刷新,可以强制将缓冲区数据写入文件中,即使缓冲区没有被写满,这其实也是提高效率的一个动作)
使用缓冲字节输出流时,要多进行刷新操作(否则缓冲区会等待 里面被写满之后,才写入文件)
总结:缓冲字节流可以提高其它流的运行效率(将其它流包装起来)
七、缓冲字符流
缓冲字符流有个最大的特点:可以以行为单位进行输入输出(之前介绍的都是以字节或字符为单位的)
要注意流的关闭规则:先创建的,后关闭
package buffered_char_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("word.txt");
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw = new FileWriter(f);
bw = new BufferedWriter(fw); //将文件字符输出流包装成缓冲字符流
String str1 = "世界这么大";
String str2 = "我想去看看";
bw.write(str1); //第一行是数据
bw.newLine(); //创建一个新行
bw.write(str2); //第二行的数据
} catch (IOException e) {
e.printStackTrace();
}finally { //要注意流的关闭顺序,先创建的后关闭
if(bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader(f);
br = new BufferedReader(fr); //将文件字符输入流包装成缓冲字符输入流
String tmp = null;
int i = 1; //计数器
while((tmp = br.readLine()) != null) { //循环读取文件中的内容
System.out.println("第" + i + "行:" + tmp);
i++;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
八、数据流
数据流可以从流中读取或写入java的基本数据类型
DataInputStream数据输入流
DataOutputStream数据输出流
package data_IO_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("word.txt");
FileOutputStream out = null;
DataOutputStream dos = null;
try {
out = new FileOutputStream(f);
dos = new DataOutputStream(out); //将文件流包装成数据流
dos.writeUTF("这是写入字符串数据"); //写入字符串数据
dos.writeDouble(3.14); //写入浮点型数据
dos.writeBoolean(true); //写入布尔类型的数据
dos.writeInt(123); //写入整型数据
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream in = null;
DataInputStream di = null;
try {
in = new FileInputStream(f);
di = new DataInputStream(in);
System.out.println("readUTF()读取数据:" + di.readUTF()); //读文件中的String类型数据
System.out.println("readDouble()读取数据:" + di.readDouble()); //读文件中的Double类型数据
System.out.println("readBoolean()读取数据:" + di.readBoolean());
System.out.println("readInt()读取数据:" + di.readInt());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(di != null) {
try {
di.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
将一些内容写入文本文档时,里面显示的都是乱码(因为写入的是字节码),那如何去读我们写的数据呢? 要解析字节码, 用到数据输入流,将这个文件的字节码读出来,通过相应的方法将这些字节码解析成我们对应的数据类型
注意:当写入数值类型的值时(比如int和double),尽量不要写在一起
九、字符流转为字节流
java提供了两种流来处理数据,分别是字节流和字符流。
写代码时可以发现,这两种流的底层机制不同,这就导致字节流和字符流不能互相调用(字节流更偏向底层,而字符流的功能更强大)
java提供了两个用来将字节流封装成字符流的类
InputStreamReader和OutputStreamWriter,这样就可以把字节流变成字符流了
这两个包装类还提供了一个很重要的功能:字符编码的转换
java能够跨平台,所以能兼容不同的字符集
package byte_transform_char;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("word.txt");
FileOutputStream fos = null;
BufferedWriter bw = null;
OutputStreamWriter osw = null;
try {
fos = new FileOutputStream(f);
osw = new OutputStreamWriter(fos, "GBK"); //字节流转为字符流的桥梁,将数据按照GBK字符集写入
bw = new BufferedWriter(osw);
bw.write("你见过洛杉矶凌晨四点的样子吗?");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(osw != null) {
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream fis = null;
BufferedReader br = null;
InputStreamReader isr = null;
try {
fis = new FileInputStream(f);
isr = new InputStreamReader(fis, "GBK");
br = new BufferedReader(isr);
String str = br.readLine();
System.out.println("读出的内容为:" + str);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
若写入的时候用GBK字符集,读出的时候用UTF-8的字符集,这两个字符集解码的方式是不一样的(虽然在文本文档中看到的是正常的汉字,因为这个文本文档是自动按照GBK来解码的,但是这里是通过代码底层解码,如果它俩不匹配,就会出现乱码)
十、流的两种关闭方式
若不关闭数据流,会造成资源无法释放的问题。若访问文件的数据流不关闭,其他数据流就无法访问这个文件了,这是一个非常大的问题(但是我现在把close()写在try…catch语句里也不好,一旦发生异常,关闭的这行代码就执行不到了,同样会造成资源无法释放的问题)
java为了简化这种繁琐的代码,又提供了另外一种关闭方式:使用try语句自动关闭流
try…catch结束后,流会自动关闭(即使执行过程发生了异常,流也会被关闭)
package way_of_close_of_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
File f = new File("word.txt");
FileInputStream fis = null;
BufferedReader br = null;
InputStreamReader isr = null;
try {
fis = new FileInputStream(f);
isr = new InputStreamReader(fis, "GBK");
br = new BufferedReader(isr);
String str = br.readLine();
System.out.println("读出的内容为:" + str);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这种写法很繁琐,我们创建的所有流都要显式关闭(而且不能把关闭语句写在一起,这样的话出现异常后面的关闭操作都不会执行),这是数据流的一个弊端。
package way_of_close_of_stream;
import java.io.*;
public class Demo {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream("word.txt");
InputStreamReader isr = new InputStreamReader(fis, "GBK");
BufferedReader br = new BufferedReader(isr);){
String str = br.readLine();
System.out.println("读出的内容为:" + str);
} catch(Exception e) {
e.printStackTrace();
}
}
}
在try语句这里添加一个圆括号,在里面创建流的对象(注意创建顺序),这里finally就不需要写了,因为try…catch结束后这三个流会自动关闭