目录
File及IO概述
在电子设备的日常使用中,我们无时不刻在对各个应用的数据进行存储和读取,正因为有了这个技术,对我们的信息存储带来了极大便利。
在Java中,IO流就是用于读写文件中的数据(读写文件,或网络中的数据)。而IO流的使用,离不开文件操作,它们间存在密切的联系,通常一起用来实现文件的读取、写入和处理。
总之,文件管理提供了对文件系统的操作能力,而I/O流则提供了对文件内容的读取和写入能力,二者结合使用可以实现对文件的全面管理和处理。在Java中,java.io
和java.io.file
包提供了丰富的类和方法,支持文件管理和I/O流的操作。
一、File-文件管理
1. File概念及构造方法
重要概念:
路径(Path)是用于描述文件或文件夹在文件系统中位置的字符串。路径可以指定文件或文件夹的唯一位置,以便在计算机系统中进行访问和操作。
在计算机中,有两种常见的路径表示方式:
-
相对路径(Relative Path):相对路径是相对于当前工作目录(Working Directory)的路径。它描述了从当前位置到目标文件或文件夹的相对位置。相对路径通常使用一些特殊符号来表示位置关系,例如
..
表示上级目录,.
表示当前目录。相对路径适用于在当前工作目录下进行文件操作。 -
绝对路径(Absolute Path):绝对路径是完整的路径,从根目录开始一直到目标文件或文件夹的路径。它描述了文件或文件夹在文件系统中的确切位置。绝对路径在不同的操作系统和文件系统中可能存在差异,例如Windows系统中的绝对路径以盘符开头(如
C:\folder\file.txt
),而Unix/Linux系统中的绝对路径以斜杠/
开头(如/home/user/file.txt
)。绝对路径适用于指定文件系统中任意位置的文件或文件夹。
在使用路径时,需要注意以下几点:
- 在Windows系统中,路径分隔符为反斜杠
\
,而在Unix/Linux系统中,路径分隔符为正斜杠/
。 - 对于包含空格或特殊字符的路径,可以使用引号或转义字符来处理。
- 在编程语言中,通常会提供相关的API或函数来操作和处理路径,以简化路径的使用和转换。
构造方法:
这些构造方法允许我们根据不同的情况来创建File
对象,可以根据路径字符串、父目录和子目录/文件名字符串、父目录File
对象等来表示文件或目录。
在Java中,路径的分隔符反斜杠\是一个转义字符,所以在填写路径时,要用\\转义成一个\
2. 常见的成员方法
(1)判断、获取:
(2)创建、删除:
(3)获取并遍历
3. 综合练习
下面,通过一些练习介绍一些方法的细节。
需求: 在当前模块下的aaa文件夹中创建一个a.txt文件
public class FileTest1 {
public static void main(String[] args) throws IOException {
/*
需求:
在当前模块下的aaa文件夹中创建一个a.txt文件
*/
//1.创建父级目录
File file = new File(".\\aaa");
file.mkdirs();
//2.创建子级目录并拼接
File src = new File(file, "a.txt");
boolean b = src.createNewFile();
if (b) {
System.out.println("创建成功");
} else {
System.out.println("创建失败");
}
}
}
createNewFile(); 这个方法的细节:已有该文件时,返回false。
需求: 定义一个方法找某一个文件夹中,是否有以avi结尾的电影。 (暂时不需要考虑子文件夹)
public class FileTest2 {
public static void main(String[] args) {
/*
需求:
定义一个方法找某一个文件夹中,是否有以avi结尾的电影。
(暂时不需要考虑子文件夹)
*/
File file = new File("aaa");
boolean b = FileTest2.haveAVI(file);
System.out.println(b);
}
//找某一个文件夹中,是否有以avi结尾的电影
public static boolean haveAVI(File file) {
//1.获取所有文件夹
File[] files = file.listFiles();
//2.遍历
if (files != null) {
for (File f : files) {
//f依次表示File里面的文件或者文件夹
if (f.isFile() && f.getName().endsWith(".avi")) {
return true;
}
}
}
return false;
}
}
listFiles(); 这个方法的细节,如果当前文件夹没有子文件,返回的File[]数组为null
需求: 删除一个多级文件夹
public class FileTest4 {
public static void main(String[] args) {
/*
需求:
删除一个多级文件夹
*/
File file = new File("aaa\\src");
delete(file);
}
/*
参数src表示要删除的文件夹
*/
public static void delete(File src) {
//1.获取所有文件夹
File[] files = src.listFiles();
//2.遍历
if (files != null) {
for (File file : files) {
if (file.isFile()) { //1.如果要删除的是文件,直接删除
file.delete();
} else { //2.如果要删除的是文件夹,继续执行该方法(递归)
delete(file);
}
}
}
//3.删除自己
src.delete();
}
}
delete(); 这个方法的返回值和createNewFile()很相似,没有该文件时会返回false
如果要删除的是文件夹,需要删除该文件夹中的所有文件。
二、IO流
1. IO流概述及体系
IO流就是用于读写文件中的数据(读写文件,或网络中的数据)。IO流又分基本流和高级流两种类型。高级流又是基于基本流之上的一层封装,提供了更丰富、更方便的功能。高级流通常用于处理特定格式的数据、实现数据缓冲、提供对象序列化等高级操作。
常见的高级流有:缓冲流、对象流、转换流、数据流、打印流、压缩流
这次我们主要介绍基本流:
流的方向:
操作文件类型:
体系:
2. 字节输出流和字节输入流
FileOutputStream:操作本地文件的字节输出流,可以把程序中的数组写到本地文件中。
三个主要部分:
1.字节输出流的三大步骤
2.字节输出流写出文件的三种方式
3.换行(\r\n)和续写
public class FileOutput { //字节输出流
public static void main(String[] args) throws IOException {
//1.创建对象 实参是文件路径(没有则创建 有则清空原文件)
FileOutputStream fos = new FileOutputStream("File\\aaa");
//2.写出数据
fos.write(97);
//3.释放资源
fos.close();
/*
字节输出流写出文件的三种方式
*/
FileOutputStream fos1 = new FileOutputStream("File\\bbb");
//1.单字符写入
fos1.write(97);
//2.byte数组写入
byte[] bytes = {97, 98, 99, 100, 101};
fos1.write(bytes);
//3.byte数组指定位置和长度写入
fos1.write(bytes, 1, 2);
fos1.close();
/*
换行和续写
*/
FileOutputStream fos2 = new FileOutputStream("File\\ccc");
//换行
String str = "woleigedou";
byte[] bs = str.getBytes();
fos2.write(bs);
fos2.write("\r\n".getBytes()); //换行
fos2.write("666".getBytes());
fos2.close();
//续写
FileOutputStream fos3 = new FileOutputStream("File\\aaa", true);
fos3.write("xuxie\r\n".getBytes());
fos3.write("666".getBytes());
fos3.close();
}
}
FileInputStream:操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来。
注意字节输入流读取数据的细节:
public class FileInput { //字节输入流
public static void main(String[] args) throws IOException {
//读取数据
//如果文件不存在,直接报错
FileInputStream fis = new FileInputStream("File\\bbb");
int b1 = fis.read();
System.out.println((char) b1);
int b2 = fis.read();
System.out.println((char) b2);
int b3 = fis.read();
System.out.println((char) b3);
fis.close();
System.out.println("===============");
//循环读取
FileInputStream fis1 = new FileInputStream("File\\bbb");
//read方法,一次读取一个字节,并移动指针
//文件数据:aabcdebc
//错误示例:
while (fis1.read() != -1) {
System.out.print((char) fis1.read());
}
fis1.close();
System.out.println();
System.out.println("===============");
//文件数据:aabcdebc
//正确示例:
FileInputStream fis2 = new FileInputStream("File\\bbb");
int b;
while ((b = fis2.read()) != -1) {
System.out.print((char) b);
}
fis2.close();
}
}
字节输入流每调用一次read()方法,文件中的指针就会移动一次,因此在错误示例中,会导致读一个字节漏一个字节。
知道字节输入流读取文件的方式后,我们就可以读取文件的数据,并且结合字节输出流边读边写完成文件拷贝的操作。
而读一个字节写一个字节的效率非常低,因此通常一次读取一个字节数组的内容并一次性写出,大大提高了读写效率。
int len;
byte[] bytes = new byte[1024 * 500]; //这里是500KB 通常是1024的整数倍
while ((len = fis.read(bytes)) != -1) {
//写上面读到的数据长度
fos.write(bytes, 0, len);
}
3. 了解字符集及Java编码解码的方法
了解完字节流后,我们就可以想想为什么还要有字符流呢?
这时候就要知道,为什么会产生乱码?
乱码是因为字符编码和解码不一致,导致字符集无法正确转换所致。
在计算机中,字符在存储和传输时都需要进行编码和解码。字符编码是将字符映射为数字(二进制)的过程,而字符解码则是将数字(二进制)转换为字符的过程。在进行编码和解码时,如果使用不同的字符集或编码方式,就会导致字符集转换错误,从而产生乱码。
所以,为了能正确转换文本文件,就有了字符流。
字符流是对字节流的一层封装,它提供了一种更高级、更方便的方式来处理文本数据。在Java中,使用字符流可以方便地读写文本文件或网络数据,而不需要关心具体的字符编码和转换。
下面列举一些使用字符流的优点:
高效性:与字节流相比,字符流可以快速地处理大量的字符数据,因为它们以字符为单位进行读取和写入,避免了对单个字节进行频繁的读写操作。
地域性:不同的国家和地区使用不同的字符集和编码方式,字符流可以根据指定的字符编码自动进行字符集转换,从而确保读写操作的正确性和一致性。
处理文本数据:字节流只能读写二进制数据,无法直接处理文本数据。而字符流提供了处理文本数据的高级抽象,可以方便地读写文本文件、HTML页面等。
最常见的字符集有GBK,UTF-8,Unicode等,常用的IDEA编译器使用的就是UTF-8字符集,Eclipse使用的是GBK字符集。
如何不产生乱码? 1. 不要使用字节流读取文本文件 2. 编码解码时使用同一个码表,同一个编码方式Java中编码的方法 public byte[] getBytes() 使用默认方式进行编码 public byte[] getBytes(String charsetName) 使用指定方式进行编码 Java中解码的方法 String(byte[] bytes) 使用默认方式进行解码 String(byte[] bytes, String charsetName) 使用指定方式进行解码
public class Test { //java中编码和解码的代码实现
public static void main(String[] args) throws UnsupportedEncodingException {
//1.编码
String str = "奥里给!";
byte[] bytes1 = str.getBytes(); //使用默认方式进行编码
System.out.println(Arrays.toString(bytes1));
byte[] bytes2 = str.getBytes("GBK"); //使用指定方式进行编码
System.out.println(Arrays.toString(bytes2));
//2.解码
String str1 = new String(bytes1); //使用默认方式进行解码
System.out.println(str1);
String str2 = new String(bytes2, "GBK"); //使用指定方式进行解码
System.out.println(str2);
//3.编码解码使用不同的编码方式
String s = new String(bytes1, "GBK");
System.out.println(s);
String s1 = new String(bytes1, StandardCharsets.US_ASCII);
System.out.println(s1);
}
}
4. 字符输入流和字符输出流
字符输入流:
字符流的底层也是字节流,默认也是一个字节一个字节进行读取的。
如果遇到中文一次读取多个,GBK编码一次读取2个字节,UTF-8一次读取3个字节。
public class FileReaderDemo { //字符输入流
public static void main(String[] args) throws IOException {
//1.空参read()方法 -- 返回一个十进制数字
// public int read() 读取数据,读到末尾返回-1
//1.1 创建对象
FileReader fr = new FileReader("File\\test.txt");
//1.2 读取数据
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
//1.3 释放资源
fr.close();
System.out.println();
System.out.println("=======================");
//2.带参read()方法 -- 读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中
// public int read(char[] buffer) 读取多个数据,读到末尾返回-1
//2.1 创建对象
FileReader fr1 = new FileReader("File\\test.txt");
//2.2 读取数据
char[] chars = new char[2];
int len;
while ((len = fr1.read(chars)) != -1) {
//把数组中的数据变成字符串再进行打印
System.out.print(new String(chars, 0, len));
}
//2.3 释放资源
fr1.close();
}
}
字符输出流:
void write(int c) //写出一个字符
void write(String str) //写出一个字符串
void write(String str, int off, int len) //写出一个字符串的一部分
void write(char[] cbuf) //写出一个字符数组
void write(char[] cbuf, int off, int len) //写出一个字符数组的一部分
public class FileWriterDemo { //字符输出流
public static void main(String[] args) throws IOException {
//1.创建对象
FileWriter fw = new FileWriter("File\\test1.txt");
//2.写入数据
//fw.write("奥利奥"); //写出一个字符串
char[] chars = {'6', '6', '6', '牛'}; //写出一个字符数组
fw.write(chars);
//续写 -- 构造方法中打开续写开关
//FileWriter fw = new FileWriter("File\\test1.txt", true);
//3.释放资源
fw.close();
}
}
5. 综合练习
深入学习字节流和字符流后,就可以解决一些不太复杂的问题了。
拷贝文件夹
需求:
拷贝一个文件夹,考虑子文件夹。
public class Test1 {
public static void main(String[] args) throws IOException {
/*
拷贝文件夹
需求:
拷贝一个文件夹,考虑子文件夹
*/
//1.创建对象表示数据源
File src = new File("File\\src");
//2.创建对象表示目的地
File dest = new File("File\\dest");
//3.调用方法开始拷贝
copydir(src, dest);
}
private static void copydir(File src, File dest) throws IOException {
dest.mkdirs();
//1.进入数据源
File[] files = src.listFiles();
//2.遍历数组
for (File file : files) {
if (file.isFile()) {
//3.文件 拷贝
FileInputStream fis = new FileInputStream(file); //要拷贝的文件
FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
} else {
//4.文件夹 递归
copydir(file, new File(dest, file.getName()));
}
}
}
}
修改文件中的数据
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数组进行排序,变成以下的数据:
1-2-4-7-8-9
public class Test3_1 {
public static void main(String[] args) throws IOException {
//1.读取数据
FileReader fr = new FileReader("File\\a.txt");
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = fr.read()) != -1) {
sb.append((char) ch);
}
fr.close();
//2.排序
String str = sb.toString();
String[] split = str.split("-");
ArrayList<Integer> list = new ArrayList<>(); //将数据转换成整型
for (String s : split) {
list.add(Integer.parseInt(s));
}
System.out.println(list);
Collections.sort(list);
//3.写出数据
FileWriter fw = new FileWriter("File\\a.txt");
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) {
fw.write(list.get(i) + ""); //将list转成字符写入
} else {
fw.write(list.get(i) + "-");
}
}
fw.close();
}
}