1、操作系统会把很多硬件设备和软件资源都抽象成“文件”进行统一管理。但大部分情况下我们说的文件指的是硬盘中数据的一种抽象。
2、计算机上通过“文件系统”(操作系统提供的模块)来组织文件,“文件系统”使用“目录”这样的结构(树形结构)来组织文件。
此电脑是目录树的根节点,路径即使用目录来描述文件所在的位置。
绝对路径:以C:、D:盘符开头的路径就是“绝对路径”,Windows上盘符不区分大小写。
相对路径:先指定一个目录作为基准目录,从基准目录出发,考虑沿着什么样的路线能找到指定的文件,此时涉及到的路径就是“相对路径”,“相对路径”往往以.(当前目录)或者…(当前目录的上一级目录)开头,.这种情况可以省略。
①假定当前的基准目录是D:\JAVACODE
则“相对路径”为.\the-second
②假设当前的基准目录是D:\JAVACODE\101
则“相对路径”为…\the-second
在编程上文件类型主要有两大类:
①文本文件,文件中保存的数据是字符串,保存的内容都是合法的字符。
②二进制文件,文件中保存的数据是二进制数据,保存的内容不需要都是合法的字符。
记事本会尝试按照字符的方式来展示文件内容,用记事本打开文件如果打开之后是乱码就是二进制文件否则为文本文件。
static String pathSeparator和static char pathSeparator表示分割目录的符号。Windows上\和/都可以作为分割符,Linux和Mac上/作为分割符,一般我们用/。
File(File parent, String child)
File(String pathname)//参数传入文件的绝对/相对路径
File(String parent, String child)
getParent()、getName())、getPath())、getAbsolutePath())、getCanonicalPath()):
class test1 {
public static void main(String[] args) throws IOException {
File f = new File("./test.txt");
System.out.println(f.getParent());//输出.
System.out.println(f.getName());//输出test.txt
System.out.println(f.getPath());//输出.\test.txt
System.out.println(f.getAbsolutePath());//输出D:\project\io\.\test.txt,即把当前的相对路径拼接到基准目录上
System.out.println(f.getCanonicalPath());//输出D:\project\io\test.txt,即针对getAbsolutePath()获得的绝对路径进行简化
}
}
exists()、isDirectory()、isFile())、createNewFile():
class Test2 {
public static void main(String[] args) throws IOException {
File file = new File("d:/test.txt");
System.out.println(file.exists());//输出false
System.out.println(file.isDirectory());//输出false
System.out.println(file.isFile());//输出false
// 创建文件
boolean ret = file.createNewFile();
System.out.println("ret = " + ret);
System.out.println(file.exists());//输出true
System.out.println(file.isDirectory());//输出false
System.out.println(file.isFile());//输出true
}
}
delete()、deleteOnExit():
class Test3 {
public static void main(String[] args) throws InterruptedException {
File f = new File("d:/test.txt");
//boolean ret = f.delete();
//System.out.println("ret = " + ret);
f.deleteOnExit();//进程结束之后才会删除文件
Thread.sleep(5000);
System.out.println("进程结束!");
}
}
list()、listFiles()(返回File对象目录下的所有内容,包括系统自带的在此电脑里看不见的特殊目录):
class Test4 {
public static void main(String[] args) {
File f = new File("d:/");//用list()时File构造方法的参数中不能传入具体的文件名,不然list()会返回null
String[] files = f.list();//返回值为String[];listFiles()的返回值为File[]
System.out.println(Arrays.toString(files));
}
}
mkdir()、mkdirs()(创建目录):
class Test5 {
public static void main(String[] args) {
File f = new File("d:/project/a/b/c");
// boolean ret = f.mkdir();//如果File构造方法的参数中传入d:/project,则可以用mkdir();如果传入的是d:/project/a/b/c这样多个层次的目录,则要用mkdirs()
boolean ret = f.mkdirs();
System.out.println("ret = " + ret);
}
}
renameTo(File dest)(文件重命名):
class Test6 {
public static void main(String[] args) {
File srcFile = new File("d:/test.txt");
File destFile = new File("d:/test2.txt");
boolean ret = srcFile.renameTo(destFile);//将srcFile重命名为destFile
System.out.println("ret = " + ret);
}
}
文件内容操作即读写文件,写代码时要close()对应的文件资源。
关于流对象Java标准库中提供了很多类,这些类可以归结到两个大类中。
1、字节流:每次读/写的最小单位为字节,对应着二进制文件。字节流延伸出的父类有InputStream、OutputStream,这两个父类延伸出很多子类。
2、字符流:每次读/写的最小单位为字符,一个字符可能对应多个字节,看当前的字符集是哪种,GBK一个中文字符对应两个字节,UTF-8一个中文字符对应三个字节。字符流延伸出的父类有Reader、Writer,这两个父类延伸出很多子类。
把数据保存到硬盘中,我们以cpu为视角,这个过程叫输出,相反从硬盘中读取数据到内存这个过程叫输入。
①read()一次读取一个字符。
②read(char[] cbuf)一次读取多个字符,会把参数指定的cbuf数组给填充满(即输出型参数),read(char[] cbuf)会尽可能把cbuf这个数组给填充满。
③read(char[] cbuf, int off, int len)一次读取多个字符,会把参数指定的cbuf数组从off这个位置往后数len这么长的位置给填充满。
read()的返回值是int,代表读到的字符,这样做的目的是当文件读完时要返回-1,而char无法返回-1。如果读到了字符int会返回的范围是0~65535(即一个char能表示的范围,即两个字节)。
read()读取到的字符如果是中文字符每个字符对应的是两个字节是unicode编码,但如果是utf-8编码每个中文字符应该对应的是三个字节,这不就混乱了吗?
实际上并不会,Java标准库对字符编码进行了一系列的处理。如果使用char,使用的字符集就是unicode;如果是使用String,使用的字符集就是utf-8。
char[] arr里面的每个字符都是以unicode编码,
但一旦使用这个字符数组构造成String,比如String s=new String(arr) 就会把字符数组里的每个字符转换为utf-8编码,
s.charAt(i)又会把utf-8编码的字符转换为unicode编码。
字符是unicode编码,而String大概率是utf8编码(用了大概率这个词是因为是否是utf8编码是可以配置的)。
read()涉及到的是字符所以用的是unicode编码,每个中文字符只是两个字节。
read(char[] cbuf)、read(char[] cbuf, int off, int len)的返回值为int表示实际读取到的字符的个数,如果读到文件末尾返回-1。
使用close()来关闭对应的文件资源的主要目的是为了释放文件描述符表里的元素。
文件描述符表是一个顺序表,一个进程每打开一个文件就需要在这个表里分配一个元素,而这个表的长度是有上限的,如果代码一直是打开文件而不去关闭文件,就会使这个表里的元素越来越多直到把表占满,后续再尝试打开文件就会报错,这个问题叫“文件资源泄露”。
class Test7 {
public static void main(String[] args) throws IOException {
/* Reader reader = new FileReader("d:/test.txt");//reader是抽象类不能通过new关键字创建对象,只能通过Reader子类用new关键字来创建子类对象赋值给Reader引用。创建对象的过程就是打开d:/test.txt文件的过程,如果文件不存在则打开失败抛出FileNotFoundException异常
//1.用无参数的read()
while (true) {
int c = reader.read();
if (c == -1) {
break;
}
char ch = (char)c;System.out.println(ch);
}
//2.用有参数的read(cbuf)
try {
while (true) {//加while循环是因为,如果文件内容特别长,超出了cbuf数组的范围,这时就可以循环多次,保证最终能把文件读完。
char[] cbuf = new char[3];
int n = reader.read(cbuf);//n表示当前读到的字符的个数
if (n == -1) {
break;
}
System.out.println("n = " + n);
for (int i = 0; i < n; i++) {
System.out.println(cbuf[i]);
}
}
} finally {
//文件使用完了,要记得close()
reader.close();//close()要写在finally里保证try里的代码无论是正常执行完毕还是抛出异常执行完毕close()都一定会被执行到。如果close()不写在finally里,如果上面的read(cbuf)抛出异常方法会立刻结束此时后面的close()就执行不到了。
} */
//3.对2的写法进行优化,写成try with resources的形式
try (Reader reader1 = new FileReader("d:/test.txt")) {//推荐写法,这个语法叫try with resources,在try的()中定义的变量会在try代码块结束的时候(无论是正常结束还是抛出异常结束)自动调用其中的close()方法。在try的()中的对象必须实现Closeable接口,流对象都可以写入try的()中。
while (true) {
char[] cbuf = new char[3];
int n = reader1.read(cbuf);
if (n == -1) {
break;
}
System.out.println("n = " + n);
for (int i = 0; i < n; i++) {
System.out.println(cbuf[i]);
}
}
}
}
}
new FileReader(“d:/test.txt”)会抛出FileNotFoundException,read()会抛出IOException,IOException是FileNotFoundException的父类。
①write(int c)一次写一个字符。
②write(String str)一次写一个字符串。
③write(char[] cbuf)一次写多个字符(字符数组)。
④write(String str, int off, int len)
⑤write(char[] cbuf, int off, int len)
write()的一系列方法写入文件时在默认情况下会把原有文件的内容清空掉再写入。
如果不想把把原有文件的内容清空,则要在FileWriter的构造方法中多传入第二个参数true(第二个参数代表是否append追加),此时就不会清空原来文件的内容而是写入到原来文件内容的末尾。
class Test7 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("d:/test.txt", true)) {
writer.write("我正在学习");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
①read(byte[] b, int off, int len)
②read(byte[] b)
③read()
read()的返回值是int,如果读到了字节int会返回的范围是0~255(即一个无符号byte能表示的范围,即一个字节),如果文件读完时则返回-1。
class Test9 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("d:/test.txt")) {
byte[] buffer = new byte[1024];
int n = inputStream.read(buffer);
System.out.println("n = " + n);
for (int i = 0; i < n; i++) {
System.out.printf("%x\n", buffer[i]);//%x是打印16进制,4个比特位(4个二进制位)对应一个16进制位
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
①write(byte[] b, int off, int len)
②write(int b)
③write(byte[] b)
class Test10 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("d:/test.txt", true)) {//不想清空文件里的原有内容则FileOutputStream的构造方法中要传入一个true
String s = "你好世界";
outputStream.write(s.getBytes());//将字符串里面的内容转成字节数组
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果别人给我们提供的是字节流对象,但是我们知道实际的数据内容是文本数据,这时我们可以把字节流转换成字符流(有很多种转换的办法)。
读时字节流转换为字符流:
class Test11 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("d:/test.txt")) {
//此时 scanner从文件中读取内容
try (Scanner scanner = new Scanner(inputStream)) {//inputStream即System.in,在实际开发中Scanner这一行要写入try后面的()中
String s = scanner.next();
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
写时字节流转换为字符流:
class Test12 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("d:/test.txt")) {
//这相当于把字节流转成字符流
try(PrintWriter writer = new PrintWriter(outputStream)) {
writer.println("hello");
writer.flush();//这里的flush()一定要加
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
writer.flush()一定要加,因为PrintWriter这样的类在进行写入的时候,不一定是直接把数据写入硬盘,而是先把数据写入到一个内存构成的“缓冲区”(buffer)中。引入“缓冲区”的概念是为了提高程序执行效率。比如把数据写入内存非常快,而把数据写入硬盘非常慢,为了提升效率先把数据写入到内存中等数据积攒到一定程度时再把数据写入硬盘,这样减少了写入硬盘的次数提升了效率。但这样有一个问题,当数据写入到缓冲区之后,如果缓冲区的数据还没来得及写入硬盘进程就结束了,此时缓冲区的数据也随之丢失。为了避免这个问题,应该在合适的时机使用flush()手动刷新缓冲区。