目录
文件路径
文件类型
File类
文件的创建
文件流
File类文件读写操作
通过PrintWriter写或Scanner读
PrintWriter写
Scanner读
计算机中的文件是以树形结构进行存储的。要找一个文件,需要通过根目录一层一层找,直到找到,这个路径是唯一确定的,因此这整个查找路径称为绝对路径。如果在任意层(非根目录)目录下开始寻找,那么这个路径称为相对路径。
例如要查找office这个文件:
绝对路径:C:\Program Files (x86)\Microsoft Office\Office
相对路径(从Microsoft Office开始):.\Office
.\代表从当前目录开始查找,..\代表从当前目录的上一层开始查找。
在编译器上输入路径时,可以使用'/'代替'\',系统可以自动识别。'\'与其他字符搭配会成为转义字符,如果要用'\'输入路径,那么每次输入'\'都要输入两次,譬如上述绝对路径应改为:C:\\Program Files (x86)\\Microsoft Office\\Office
文件通常简单的分为文本文件和二进制文件。
文本文件是基于字符编码的文件,常见的编码有ASCII编码,UNICODE编码等等。
二进制文件是基于值编码的文件,你可以根据具体应用,指定某个值是什么意思,也就是可以自定义编码。
要区分文本文件和二进制文件,只需要把内容拷贝到记事本上,通过观察是否出现乱码进行判断。如果没有乱码则是文本文件,乱码则代表是二进制文件。
例如把一个新建的word文件拖到记事本中,就会出现乱码:
由此可见word是二进制文件,记事本无法翻译,要想翻译这样的文件,首先要知道它的编码格式,而记事本只会按照特定的编码格式进行翻译,翻译不出来的就会用某些符号代替——这也是出现乱码的原因。
File类有三种构造方法:
File(File parent,String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
File(String pathname) | 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径 |
File(String parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示 |
new出来的文件并不一定实际存在,只是获得了一个实例对象。可以通过exists方法进行判断文件是否存在。
File file = new File("./hello-world.txt");//实际上没有这个文件
System.out.println(file.exists());//输出false
File类的方法比较简单,基本可以通过方法名直接推断出其功能,例如getName(获取文件名),getPath(得到相对路径),getAbsolutePath(得到绝对路径),getCanonicalPath(得到绝对路径),isFile(是否为文件),isDirectory(是否为目录),createNewFile(文件不存在时,以当前路径创建一个文件)等等……
文件的输入输出操作是通过文件流实现的。按照文件的内容,大体分为了两类读取方式:一类使用字节流,用于二进制文件读写,一类使用字符流,用于文本文件的读写。
Filie文件的读写,不能直接使用这些抽象类,需要使用继承于他们的FileInputStream、FileOutputStream、FileRead和FileWriter类
字节流读:FileInputStream
通过read方法进行读数据,read方法有三个版本:
无参版本表示读取一个字节,有一个参数表示把读取的数据放到该数组中,三个参数的版本是从文件第off位置读取len长度个字节,读取结果放到目标数组中。
标准的读操作:
public static void main(String[] args) throws IOException {
File file = new File("hello.txt");
InputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
while (true) {
//每次读取一个字节
int elem = inputStream.read();//返回的是ASCII码值
if (elem == -1) {
//读完了
break;
}
System.out.println(elem);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
一般来说,read方法较为低效,因此要尽可能地降低读取次数,那么就需要每次读取尽可能多的字节数,所以更常见的read使用带一个参数的版本:
byte[] buf = new byte[1024];
int len = inputStream.read(buf);//读取的数据放入ret数组当中,返回读取字节长度
if (len == -1) {
//读完了
break;
}
for (int i = 0; i < len; i++) {
System.out.println(buf[i]);
}
这里的操作需要经过这样的流程:打开文件流->文件操作->关闭文件流!
这里的new操作就是打开文件流,执行完操作后一定不能忘记close,否则可能造成内存泄露!
当然,在Java中实现了一种自动关闭功能——try with resources功能:
try (InputStream inputStream = new FileInputStream(file)) {
while (true) {
//每次读取一个字节
byte[] buf = new byte[1024];
int len = inputStream.read(buf);//读取的数据放入ret数组当中,返回读取字节长度
if (len == -1) {
//读完了
break;
}
for (int i = 0; i < len; i++) {
System.out.println(buf[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
也就是把InputStream放在try后的()中,后续就不用再写close了。这里的()中并不是什么都可以放的,只有实现了Closeable接口的才可以放,当然,Java中所有的流对象都实现了Closeable接口。如果要放我们自己实现的对象或流,就需要先实现Closeable接口。
字节流写:FileOutputStream
与读类似:通过writer方法将内容写到目标文件中
File file = new File("hello.txt");
try (OutputStream outputStream = new FileOutputStream(file)) {//每次都会覆盖原有内容
//try (OutputStream outputStream = new FileOutputStream(file, true)) {//每次从文件末尾开始写
byte[] buf = { 1, 2, 3, 4 };
outputStream.write(buf);//把buf中的内容写入文件file,也可指定单个字符写
} catch (IOException e) {
e.printStackTrace();
}
字符流的读和写类似,通过FileReader的read方法读,FileWriter的writer方法写。只不过读取和写入的数据由byte类型变为了char类型,其余则完全相同。
字符流读:FileReader
try (Reader reader = new FileReader(file)){
char[] buf = new char[1024];
int len = reader.read(buf);
for (int i = 0; i < len; i++) {
System.out.println(buf[i]);
}
} catch (IOException e) {
e.printStackTrace();
}
PrintWriter类提供了我们熟悉的print/println/printf 方法,把FileOutputStream的实例对象作为参数构造PrintWriter的实例对象,就可以通过PrintWriter中的println方法把内容写入目标文件:
File file = new File("hello.txt");
System.out.println(file.exists());
System.out.println(file.getCanonicalPath());
try (OutputStream outputStream = new FileOutputStream(file)) {
PrintWriter printWriter = new PrintWriter(outputStream);//传入要写入的流对象
printWriter.println("hello world");//写入文件的内容
printWriter.flush();//刷新才能写入成功
} catch (IOException e) {
e.printStackTrace();
}
这里println函数打印的内容首先会放到缓冲区,正常情况下缓冲区满了以后才会一起提交到目的位置。如果使用flush函数,就会刷新缓冲区,并直接把内容提交,不需要等缓冲区放满。
与从键盘上读类似,只不过参数部分不再是System.in,而是FileInputStream实例对象。
try (InputStream inputStream = new FileInputStream(file)) {
Scanner scanner = new Scanner(inputStream);
String str = scanner.nextLine();//从文件中读
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}