文章目录
一、文件
1. 创建文件
创建文件方式一:new File(String pathname) 根据文件全路径创建
public static void main(String[] args) {
// 文件路径也可以写成 d:/new.txt,第一个\是转义符
File file = new File("d:\\new.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
创建文件方式二:new File(File parent, String child) 根据父目录文件对象+子路径创建
public static void main(String[] args) {
File parentFile = new File("d:\\");
File file = new File(parentFile, "new.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
创建文件方式三:new File(String parent, String child) 根据父路径+子路径创建
public static void main(String[] args) {
File file = new File("d:\\", "new.txt");
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
注意:如果创建文件对象传入的路径名是多级目录,则必须确保每一级目录都存在,否则系统会报找不到指定的路径。
2. 文件相关操作
获取文件信息
public static void main(String[] args) {
// 创建文件对象
File file = new File("d:\\test\\new.txt");
// 创建文件
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
// 获取文件名
System.out.println(file.getName()); // new.txt
// 获取文件绝对路径
System.out.println(file.getAbsolutePath()); // d:\test\new.txt
// 获取文件父级目录
System.out.println(file.getParent()); // d:\test
// 获取文件大小(多少个字节)UTF-8编码:一个汉字占三个字节,一个英文字符占一个字节
// 例如:"hello你好"(总共占5+3*2=11个字节)
System.out.println(file.length()); // 0
// 判断文件是否存在
System.out.println(file.exists()); // true
// 判断是不是一个文件
System.out.println(file.isFile()); // true
// 判断是不是一个目录
System.out.println(file.isDirectory()); // false
}
删除文件
public static void main(String[] args) {
// 创建文件对象
File file = new File("d:\\new.txt");
// 判断文件是否存在,存在则删除
if (file.exists()) {
if (file.delete()) {
System.out.println("文件删除成功");
} else {
System.out.println("文件删除失败");
}
} else {
System.out.println("文件不存在");
}
}
3. 目录相关操作
public static void main(String[] args) {
// 目录也可以当做文件来操作
File file = new File("d:\\demo");
// 判断目录是否存在,存在则删除,不存在则创建
if (file.exists()) {
System.out.println("目录已存在");
// 只能删除空目录
if (file.delete()) {
System.out.println("目录已删除");
} else {
System.out.println("目录删除失败,目录为空才可以删除");
}
} else {
System.out.println("目录不存在");
// 创建多级目录需要使用 file.mkdirs()
if (file.mkdir()) {
System.out.println("目录创建成功");
} else {
System.out.println("目录创建失败");
}
}
}
二、IO流
I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输,如读写文件,网络通信等。Java程序中,对于数据的输入输出操作是以流的方式进行,例如最常见的要将Java内存中的数据输出到磁盘的文件中或者要将磁盘文件中的数据写入Java内存中都是以I/O流的方式。
1. IO流的分类
- 按操作数据单位不同分为:字节流、字符流,字节流通常用于处理二进制文件,字符流通常用于处理文本文件;因为文本文件中通常会包含中文字符,在UTF-8编码中,一个汉字占三个字节,如果我们以字节流去方式一个字节一个字节去读取,必然会出现乱码
- 按数据流的流向不同分为:输入流、输出流,对于Java程序来讲,输入流指的是外部数据源(例如磁盘上的文件)的数据读取到Java程序(内存)中,输出流指的是Java程序(内存)中的数据输出到外部数据源
2. IO流体系图
三、字节文件流
1. 字节文件输入流 FileInputStream
例子:通过FileInputStream读取磁盘文件中的内容到Java程序中,并打印到控制台
通过read()方法进行单个字节的读取,效率比较低
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
// 创建一个文件输入流,将给输入流和文件关联起来,就可以通过该输入流来操作文件了
FileInputStream fileInputStream = null;
int readData = 0;
try {
fileInputStream = new FileInputStream(filePath);
// fileInputStream.read()的返回值是一个int类型的值,正常情况下返回当前读取的字节的值,如果返回-1说明已到达文件末尾
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char)readData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过read(byte[] b)方法一次最多可以读取b.length个字节的数据到byte数组里,提高读取
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
// 创建一个文件输入流,将输入流和文件关联起来,就可以通过该输入流来操作文件了
FileInputStream fileInputStream = null;
// 字节数组,一次读取8个字节
byte[] bytes = new byte[8];
int readLen = 0;
try {
fileInputStream = new FileInputStream(filePath);
// fileInputStream.read()的返回值是一个int类型的值,正常情况下返回实际读取的字节数,如果返回-1说明已到达文件末尾
while ((readLen = fileInputStream.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 字节文件输出流 FileOutputStream
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
// 创建一个文件输出流,将输出流和文件关联起来,就可以通过该输出流来操作文件了
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath);
// 写入一个字节 write(int b)
fileOutputStream.write('a');
// 写入字符串 write(byte[] b)
String str = "hello,world";
fileOutputStream.write(str.getBytes());
// 从位移量off开始读取byte数组中的len个字节 write(byte[] b, int off, int len)
fileOutputStream.write(str.getBytes(), 0, 3);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例子:通过FileOutputStream将Java内存中的List的值写入磁盘的文件中,如果文件不存在,会自动创建文件(前提是目录都已存在)
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
// 创建一个文件输出流,将输出流和文件关联起来,就可以通过该输出流来操作文件了
FileOutputStream fileOutputStream = null;
try {
// fileOutputStream = new FileOutputStream(filePath, true); // 以追加的形式写入文件
fileOutputStream = new FileOutputStream(filePath); // 以覆盖的形式写入文件
List<String> list = new ArrayList<>();
list.add("abc");
list.add("def");
for (String s : list) {
fileOutputStream.write(s.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、字符文件流
1. 字符文件输入流 FileReader
FileReader继承于InputStreamReader,而InputStreamReader又继承于Reader
单个字符的读取
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
File file = new File(filePath);
// 创建一个字符文件输入流,将输入流和文件关联起来,就可以通过该输入流来操作文件了
FileReader fileReader = null;
int readData = 0;
try {
fileReader = new FileReader(file);
while ((readData = fileReader.read()) != -1) {
System.out.print((char)readData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符数组的读取
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
File file = new File(filePath);
// 创建一个字符文件输入流,将输入流和文件关联起来,就可以通过该输入流来操作文件了
FileReader fileReader = null;
int readLen = 0;
char[] chars = new char[8];
try {
fileReader = new FileReader(file);
while ((readLen = fileReader.read(chars)) != -1) {
System.out.print(new String(chars, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 字符文件输出流 FileWriter
FileWriter继承于OutputStreamWriter,而OutputStreamWriter又继承于Writer
FileWriter使用后,必须要关闭(close)或者刷新(flush),否则写入不到指定的文件
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
// 创建一个字符文件输出流,将输出流和文件关联起来,就可以通过该输出流来操作文件了
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(filePath); // 默认是覆盖的方式
// 写入一个字符 write(int)
fileWriter.write('a');
// 写入字符数组 write(char[])
char[] ch = {'b', 'c', '你', '好'};
fileWriter.write(ch);
// 写入字符数组的指定部分 write(char[], off, len)
fileWriter.write(ch, 0, 2);
// 写入字符串 write(String)
fileWriter.write("nihao");
// 写入字符串 write(String, off, len)
fileWriter.write("我爱中国", 0, 2);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
五、节点流和处理流
- 节点流可以从一个特定的数据源读写数据,例如FileInputStream、FileOutputStream、FileReader、FileWriter就属于节点流,只能读取文件或者写入文件,无法操作其它数据源。
- 处理流也叫包装流,是可以对节点流进行包装,增强节点流的功能,为程序提供更为强大的读写功能,例如BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter是最常用的包装流,包装流可以处理多种数据源,只需要在创建包装流对象的时候动态传递对应的参数即可。
一般来说,我们都会使用包装流,而不会直接使用节点流,因为使用包装流性能可以得以提高,例如缓冲流BufferedReader/BufferedWriter就以增加缓冲的方式来提高输入输出的效率,并且包装流也会提供一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便。
包装流的设计就使用了装饰者模式,节点流和包装流共同继承了父类,例如InputStream,并且包装流内部将InputStream父类聚合进来,相当于把节点流聚合到内部,这样一来,可以在不改变原有对象的基础上动态增强节点流的功能,例如原来的节点流FileInputStream只能文件一行行读取,通过包装流包装之后,可以实现对文件多行读取,底层调用的还是节点流的方法,只是对原来的方法做了增强,同时,包装流也提供了一些更为丰富的方法,增强了节点流的读写功能。
关于装饰者模式,可以参考文章:https://blog.youkuaiyun.com/can_chen/article/details/105786680
六、字符缓冲流
1. 字符输入缓冲流 BufferedReader
是一个包装流,从字符输入流读取文本,缓冲字符,以提供字符、数组和行的高效读取
public static void main(String[] args) {
String filePath = "d:\\hello.txt";
// 创建一个节点流
FileReader fileReader = null;
// 创建一个包装流
BufferedReader bufferedReader = null;
String line = null;
try {
fileReader = new FileReader(filePath);
bufferedReader = new BufferedReader(fileReader);
// 一行一行读取数据,效率高
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流资源
try {
// 关闭最外层的流就可以,底层实际去关闭的也是节点流
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 字符输出缓冲流 BufferedWriter
public static void main(String[] args) throws IOException {
String filePath = "d:\\hello.txt";
// 以追加的方式写入
// BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath, true));
// 以覆盖的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello,world");
// 插入一个和系统相关的换行符
bufferedWriter.newLine();
bufferedWriter.write("我是程序猿");
bufferedWriter.close();
}
3. 通过BufferedReader和BufferedWriter完成文件拷贝
public static void main(String[] args) {
String srcFilePath = "d:\\hello.txt";
String destFilePath = "e:\\hello.txt";
BufferedWriter bw = null;
BufferedReader br = null;
String line = null;
try {
// 注意:BufferedReader和BufferedWriter是按照字符操作,应该用于操作文本文件
// 不要用来操作二进制文件(图片、视频、doc、pdf)会造成文件损坏
br = new BufferedReader(new FileReader(srcFilePath));
bw = new BufferedWriter(new FileWriter(destFilePath));
// readLine读取一行内容,但是没有换行,需要手动加上
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
七、标准输入输出流
- System.in 代表标准输入,默认设备是键盘,in是System类的一个变量
public final static InputStream in
,该变量的编译类型是InputStream,运行类型是BufferedInputStream
应用:Scanner sc = new Scanner(System.in);
Scanner 是从标准输入键盘接收数据 - System.out 代表标准输出,默认设备是显示器,out是System类的一个变量
public final static PrintStream out
,该变量的编译类型是PrintStream,运行类型也是PrintStream
应用:System.out.println("");是使用out对象将数据输出到显示器
八、转换流InputStreamReader和OutputStreamWriter
转换流的意思是可以将字节流转换为字符流,使用转换流的作用是解决乱码问题;因为一般来说,如果我们读取文件时,没有指定文件的编码格式,那么默认会以UTF-8的编码格式读取文件,那如果我们的文件不是以UTF-8编码的格式保存的,例如gbk编码(ANSI),那么读取该文件时就会出现乱码。
一般来说,读取文本文件,会使用字符流读取,效率较高,如果读取的文件编码格式不是默认的UTF-8,那么需要使用转换流InputStreamReader指定具体编码格式,同时,会传入一个字节流进行转换,最终使用字符流去读取文件,可以避免乱码问题。
public static void main(String[] args) throws IOException {
String filePath = "d:\\hello.txt";
// 创建一个转换流,该转换流将字节流转换成字符流,并指定读取的文件编码格式为gbk
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "gbk");
// 创建一个字符包装流,将转换流作为包装的对象,提高转换流的读取效率
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line = null;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
// 关闭最外层的流即可
bufferedReader.close();
}
同样,当我们使用输出流往文件写入数据时,默认该文件也是以utf8编码的格式保存,如果我们想以指定的编码格式保存,可以使用转换流OutputStreamWriter
public static void main(String[] args) throws IOException {
String filePath = "d:\\hello.txt";
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(filePath), "gbk");
outputStreamWriter.write("以gbk编码格式保存文件");
outputStreamWriter.close();
}
}
九、Properties类
位于java.util包下,它的父类是Hashtable,是一个专门用于读取配置文件的集合类,它要求配置文件的格式为:键=值,并且键值对不需要有空格,值不需要用引号引起来,默认类型是String,例如:user=root
以下是传统方式读取配置文件,这种方式不够灵活,如果我们只想读取配置文件中某个键的值,这种方式不方便
public static void main(String[] args) throws IOException {
// 传统方式读取配置文件
BufferedReader bufferedReader = new BufferedReader(new FileReader("src\\mysql.properties"));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close();
}
通过Properties类来读取配置文件
public static void main(String[] args) throws IOException {
// 创建Properties对象
Properties properties = new Properties();
// 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
// 把所有kv键值对打印到控制台
properties.list(System.out);
// 根据key获取指定的值
String user = properties.getProperty("user");
System.out.println(user);
}
通过Properties类来创建配置文件/修改配置文件内容
public static void main(String[] args) throws IOException {
// 创建Properties对象
Properties properties = new Properties();
// 设置属性(如果key存在,则是修改,不存在则是创建)
properties.setProperty("charset", "utf8");
properties.setProperty("name", "小明"); // 中文保存的是Unicode码值
// 保存配置文件(第二个参数comments指的是配置文件的注释内容,设置之后会显示在配置文件第一行,不需要注释则设置为null)
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
}
十、获取文件编码
通常,在使用IO流读取一个文件时,如果不知道文件编码格式的前提下,可能会出现中文乱码问题;以下代码可以判断出大部分情况下的常见文件编码,拿到文件编码之后,使用输入流读取文件时指定编码,就可以避免乱码问题。
public String getFileEncode(String path) {
String charset = "GBK";
byte[] head = new byte[3];
BufferedInputStream bis = null;
// 代码扫描修改
int firstKey = 0;
int secondKey = 1;
int thirdKey = 2;
int firstValue = -17;
int secondValue = -69;
int thirdValue = -65;
try {
boolean checked = false;
bis = new BufferedInputStream(new FileInputStream(path));
bis.mark(0);
int read = bis.read(head, 0, 3);
if (read == -1) {
// 文件内容为空,返回默认编码
return charset;
}
if (head[firstKey] == -1 && head[secondKey] == -2) {
// UTF-16LE
charset = "Unicode";
checked = true;
} else if (head[firstKey] == -2 && head[secondKey] == -1) {
// UTF-16BE
charset = "Unicode";
checked = true;
} else if (head[firstKey] == firstValue && head[secondKey] == secondValue && head[thirdKey] == thirdValue) {
// UTF8带BOM
charset = "UTF-8";
checked = true;
}
bis.reset();
if (!checked) {
int len = 0;
int loc = 0;
while ((read = bis.read()) != -1) {
loc++;
if (read >= 0xF0) {
break;
}
if (0x80 <= read && read <= 0xBF) {
// 单独出现BF以下的,也算是GBK
break;
}
if (0xC0 <= read && read <= 0xDF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
// 双字节 (0xC0 - 0xDF) (0x80 - 0xBF), 也可能在GBK编码内
continue;
} else {
break;
}
} else if (0xE0 <= read && read <= 0xEF) {
// 也有可能出错,但是几率较小
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
read = bis.read();
if (0x80 <= read && read <= 0xBF) {
charset = "UTF-8";
break;
} else {
break;
}
} else {
break;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException ex) {
}
}
}
return charset;
}
PS:读取UTF-8带BOM文件时,可能会出现读取到的字符串多了一个额外字符’\uFEFF’,这个字符位于字符串的第一个字符,过滤掉即可。
String firstSql = "文件读取出来的字符串";
if ("\uFEFF".equals(firstSql.substring(0,1))) {
firstSql = firstSql.substring(1);
}