文件相关的概念
本节讨论的文件是指硬盘上的文件,操作系统管理文件,引入了专门的模块:文件系统。每个文件都有一个“路径”描述文件所在位置。
绝对路径:从盘符出发,到文件名结束。
相对路径:有一个基准路径,以这个基准做为参考,.表示当前目录,..表示当前目录的上一级目录。
文本文件:存储的内容虽然是二进制数据,但是这些二进制能从对应的字符集码表中查找出来翻译成对应的合法字符。
二进制文件:存储的是二进制数据,在字符集码表中查不出对应字符。
文件操作
1.文件系统的操作
文件操作包括:创建文件、删除文件、创建目录、重命名,在Java中提供了File类,来进行文件操作。
File类的方法:
构造方法
File(File parent,String child) | 根据父目录+子文件路径创建File对象 |
File(String pathname) | 根据文件路径创建File对象,可以是相对路径或绝对路径 |
File(String parent,String child) | 根据父目录和子文件路径创建File对象 |
方法
返回值 | 方法名 | 作用功能 |
---|---|---|
String | getParent() | 返回File对象的父目录文件路径 |
String | getName() | 返回File对象的文件名称 |
String | getPath() | 返回File对象的文件路径 |
String | getAbsolute() | 返回File对象的绝对路径 |
String | getCanonicalPath() | 返回File对象的修饰过的绝对路径 |
boolean | exists() | 判断File对象描述的文件是否存在 |
boolean | isDirectory() | 判断File对象描述的文件是否是一个目录 |
boolean | isFile() | 判断File对象描述的文件是否是一个普通文件 |
boolean | createNewFile() | 根据File对象,自动创建空文件,创建成功返回true |
boolean | delete() | 根据File对象,删除文件,删除成功返回true |
void | deleteOnExit() | 根据File对象,删除文件,在程序运行结束才会删除 |
String[] | list() | 返回File对象代表的目录下的所有的文件名 |
File[] | listFiles() | 返回File对象代表的目录下的所有的文件,以File对象的形式返回 |
boolean | mkdir() | 创建File对象代表的目录 |
boolean | mkdirs() | 创建File对象代表的目录,必要时会创建中间目录 |
boolean | renameTo(File dest) | 进行文件名修改 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
代码案例:
案例1:getParent()、getName()、getPath()、getAbsolutePath()、getCanonicalPath()的使用
public class demo1 {
public static void main1(String[] args) throws IOException {
File file = new File("C:\\Vampire\\Directory1\\test.txt");
System.out.println("File对象的父目录文件路径: " + file.getParent());
System.out.println("File对象的文件名称: " + file.getName());
System.out.println("File对象的文件路径: " + file.getPath());
System.out.println("File对象的绝对路径: " + file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
案例2:createNewFile()的使用、exists()、isFile()、isDirectory()的使用
public class demo2 {
/**
* 创建文件
*/
public static void main1(String[] args) throws IOException {
File file = new File("./test.txt");
boolean ok = file.createNewFile();//创建File对象指定的文件
System.out.println(file.exists());//文件是否存在
System.out.println(file.isFile());//是否是普通文件
System.out.println(file.isDirectory());//是否是目录
}
//删除文件
public static void main2(String[] args) {
File file = new File("./test.txt");
boolean ok = file.delete();//删除File对象所指定的文件
System.out.println(ok);
}
//删除文件,程序退出后才删除
public static void main(String[] args) {
File file = new File("./test.txt");
file.deleteOnExit();//程序退出时才删除文件
System.out.println("删除完毕");
Scanner scanner = new Scanner(System.in);
scanner.next();
}
}
案例3:list的使用
public class demo3 {
public static void main(String[] args) {
File file = new File(".");
System.out.println(Arrays.toString(file.list()));
//返回File对象代表的目录下的所有的文件名,返回值类型String[]
}
}
案例4:递归遍历目录,打印出所有的目录
public class demo4 {
private static void scan(File cur) {
//1.判断是否是目录
if (!cur.isDirectory()) {
return;//不是目录,返回
}
//2.列出当前目录中所有的内容
File[] files = cur.listFiles();
if (files == null || files.length == 0) {
//目录不存在或者目录中没有元素
return;
}
//3.打印当前目录
System.out.println(cur.getAbsolutePath());
//4.遍历所有的内容
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
//是普通文件
System.out.println(files[i].getAbsolutePath());
} else {
//是目录,进行递归
scan(files[i]);
}
}
}
public static void main(String[] args) {
File file = new File("./");
scan(file);
}
}
案例5:mkdir和mkdirs的使用
public class demo5 {
public static void main(String[] args) {
File f1 = new File("./abc");
File f2 = new File("./abc/def/ghi");
// boolean ok = f1.mkdir();//创建目录,只能创建一个
boolean ok = f2.mkdirs();//创建目录,可以创建多个子目录
System.out.println(ok);
}
}
案例6:renameTo的使用,renameTo既可以修改文件名,也可以移动目录
public class demo6 {
public static void main1(String[] args) {
File srcFile = new File("./abc");
File destFile = new File("./abc11");
boolean ok = srcFile.renameTo(destFile);//修改srcFile的文件名,修改成destFile
System.out.println(ok);
}
public static void main(String[] args) {
File srcFile = new File("./abc/def");
File destFile = new File("./def");
//相当于移动文件,把当前目录下的abc目录下的def,移动到当前目录下
boolean ok = srcFile.renameTo(destFile);//修改srcFile的文件名,修改成destFile
System.out.println(ok);
}
}
2. 文件内容的操作
文件内容操作主要包括读文件和写文件,Java中对这些操作进行了封装,叫:文件流/IO流,这个叫法也是跟随操作系统的。
Java中将IO流分成两个:
1、字节流:
读写数据的基本单位是字节,一次只能读写一个字节。主要有InputStream、OutputStream这两个类,分别是输入、输出的字节流。
2、字符流:
读写数据的基本单位是字符,一次只能读写一个字符(一个字符是多少个字节不确定,取决于编码方式,字符流内部会自动查询码表,把二进制转换成对应的字符)。主要有Reader、Writer这两个类,分别是输入、输出的字符流。
我们谈的输入、输出是针对cpu来说的,数据远离cpu就是输出、数据靠近cpu就是输入。
InputStream、OutputStream、Reader、Writer都是抽象类,如果要使用,就得使用实现了上述4个抽象类的类,主要了解:FileInputStream、FileOutputStream、FileReader、FileWriter 这4个类即可。下面我会给出使用案例
小知识:
文件资源泄漏:打开文件,其实是在该进程中的"文件描述表"中,创建了一个新的表项,文件描述表描述了该进程需要操作哪些文件,文件描述表可以认为是一个数组,数组的每个元素包含了操作的文件的信息,而数组的下标就称为文件描述符,每次打开一个文件就相当于在数组中占据一个位置,数组长度是固定的,必须调用close才能释放空间,否则当数组满了,后面再打开文件就会失败。
为了避免上述问题,防止忘记关闭文件,我们可以将使用try catch和finally,将close操作放在finally中,另外还需要注意的一点是,在创建InputStream对象的同时,会有一个隐藏操作:打开文件,所以并不需要我们手动打开文件(请看案例1~)。
案例1:
public class demo1 {
public static void main1(String[] args) throws IOException {
InputStream inputStream = null;
try {
inputStream = new FileInputStream("./test.txt");//创建对象,创建对象的同时会有一个隐含的操作:打开文件
} catch (IOException e) {
e.printStackTrace();
} finally {
inputStream.close();//关闭文件
}
}
}
案例1虽然可以解决文件资源泄漏的问题,但是看起来非常繁琐,非常麻烦,我们还有更简单的方法,请看案例2~
案例2:文件读取,使用FileInputStream的read方法
使用下面这种方法,可以不用写close方法,try里面的{}执行完毕最终会执行close
try(new 对象) {
} catch(异常) {
}
读取文件内容,使用read方法,read有下面几种参数版本
read();一次读取一个字节,会有多次IO操作,才能读取完毕
read(byte[] b);进行一次IO,把byte数组尽可能填满
read(byte[] b,int off,int len);进行一次IO,读取文件中的内容,这些内容会把byte数组尽可能填满,从数组中[off,off+len)的范围内进行填充
上面三种形式,返回值都是int,表示实际读取到的字节数,文件读取完毕就返回-1,不使用byte是因为byte只有0~255,不能表示-1
使用无参数版本:
public class demo2 {
public static void main(String[] args) {
//这种方法,不用写close,try里面的{}执行完毕最终会执行close
try (InputStream inputStream = new FileInputStream("./test.txt")) {
while (true) {
int b = inputStream.read();//读取内容
if (b == -1) {
break;//读取完毕
}
System.out.printf("0x%x\n", b);//16进制打印b
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用有参数版本:
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("./test.txt")) {
while (true) {
byte[] buffer = new byte[1024];
int n = inputStream.read(buffer);//一次IO把byte数组尽可能填满,n表示read操作实际读取到多少字节
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.printf("0x%x\n", buffer[i]);//16进制打印n
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
案例3:文件写入,使用FileOutputStream的write方法
创建对象FileOutputStream时,如果不加参数true,写操作会把文件之前的内容清空,不是追加,如果需要追加,并且每次操作后程序关闭都能保存之前的内容,可以添加参数true
public class demo3 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("./test.txt", true)) {
outputStream.write(97);
outputStream.write(98);
} catch (IOException e) {
e.printStackTrace();
}
}
}
write也有三种版本:
write(byte[] b,int off,int len);将字节数组b的某一空间,[off,off+len)的范围写入文件
write(int b);写入1个字节到文件中
write(byte[] b);将一个字节数组的内容写入到文件中
案例4:使用FileReader,读取文件
public class demo4 {
public static void main1(String[] args) {
try (Reader reader = new FileReader("./test.txt")) {
while (true) {
int n = reader.read();
if (n == -1) {
break;
}
char ch = (char) n;
System.out.println(ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Reader的read方法,也有3个版本
read();一次读取一个字符,返回值int
read(char[] cbuf);一次IO把读取的字符填充到字符数组中,返回实际读取到的个数(int)
read(char[] cbuf,int off,int len);把读取的字符填充到数组的[off,off+len)范围
案例5:FileWriter写入文件
public class demo5 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("./test.txt", true)) {
// writer.write("你好");
String s = new String("世界第一可爱");
writer.write(s, 0, 2);//写入的是世界
} catch (IOException e) {
e.printStackTrace();
}
}
}
write有4个版本
write(int c);写一个字符到文件
write(String str);写一个字符串到文件
write(String str,int off,int len);将字符串的[off,off+len)的范围的内容写入文件中
write(char[] cbuf);写入字符数组中的内容到文件中
write(char[] cbuf,int off,int len);写入字符数组中[off,off+len)的范围的内容到文件中
案例6:InputStream对象也能作为Scanner的参数
public class demo6 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("./test.txt")) {
Scanner scanner = new Scanner(inputStream);//Scanner也能传InputStream
while (scanner.hasNext()) {
System.out.println(scanner.next());//通过Scanner读取文件内容
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
综合案例
案例1:扫描指定目录,找到名称包含指定字符的所有普通文件(不包括目录),并且询问是否要删除该文件
public class demo1 {
private static void scan(File cur, String key) {
if (!cur.isDirectory()) {
return;
}
File[] files = cur.listFiles();//列出所有内容
if (files.length == 0 || files == null) {
return;
}
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
//普通文件,删除操作
delete(files[i], key);
} else {
scan(files[i], key);//进行递归遍历
}
}
}
private static void delete(File file, String key) {
if (!file.getName().contains(key)) {
return;//不包含关键字
}
Scanner scanner = new Scanner(System.in);
System.out.println("是否要进行删除文件:" + file.getAbsolutePath() + " Y表示是");
String input = scanner.next();
if (input.equals("Y")) {
file.delete();
}
}
public static void main(String[] args) {
System.out.println("输入搜索的路径");
Scanner scanner = new Scanner(System.in);
String rootPath = scanner.next();
File file = new File(rootPath);
if (!file.isDirectory()) {
System.out.println("路径不存在");
return;
}
System.out.println("输入关键字");
String key = scanner.next();
scan(file, key);
}
}
案例2:实现文件复制,也就是把文件中每个字节读取,写入另一个文件中
public class demo12 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入源文件路径");
String srcPath = scanner.next();
File srcFile = new File(srcPath);
if (!srcFile.isFile()) {
System.out.println("源文件不存在");
return;
}
System.out.println("输入目标文件路径");
String destPath = scanner.next();
File destFile = new File(destPath);
if (!destFile.getParentFile().isDirectory()) {
System.out.println("目标文件的路径不存在");
return;
}
//开始复制
try (InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)) {
while (true) {
//读取源文件的内容
byte[] buffer = new byte[1024];
int n = inputStream.read(buffer);
if (n == -1) {
break;
}
// buffer写入outputStream
outputStream.write(buffer, 0, n);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例3:递归遍历目录,查找文件,按照内容查找
public class demo13 {
private static void search(File f, String key) {
StringBuilder stringBuilder = new StringBuilder();
try (Reader reader = new FileReader(f)) {
char[] chars = new char[1024];
while (true) {
int n = reader.read(chars);
if (n == -1) {
break;
}
String s = new String(chars, 0, n);//0~n的字符拼成字符串
stringBuilder.append(s);
}
} catch (IOException e) {
e.printStackTrace();
}
if (stringBuilder.indexOf(key) == -1) {
//没找到
return;
}
System.out.println("文件: " + f.getAbsolutePath());
}
private static void scan(File cur, String key) {
if (!cur.isDirectory()) {
return;//不是目录
}
File[] files = cur.listFiles();
if (files.length == 0 || files == null) {
return;
}
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
//是文件
search(files[i], key);
} else {
//是目录,递归
scan(files[i], key);
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要搜索的路径");
String rootPath = scanner.next();
File rootFile = new File(rootPath);
if (!rootFile.isDirectory()) {
System.out.println("路径不存在");
return;
}
System.out.println("输入关键字");
String key = scanner.next();
scan(rootFile, key);
}
}