Java的File类和IO流

File

Filejava.io.包下的类, File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)

注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据

File类创建对象

注意:

1.File对象既可以代表文件、也可以代表文件夹。

2.File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。

1
2
3
4
5
6
7
8
9
//下面3种方法都能创建File对象,第3种可以跨平台使用
//File f1 = new File("E:\\2_学习\\Java\\test.txt");
//File f1 = new File("E:/2_学习/Java/test.txt");
File f1 = new File("E:" + File.separator + "2_学习" + File.separator + "Java" + File.separator + "test.txt");
System.out.println(f1.length());//文件的大小
System.out.println(f1.exists());//文件是否存在

File f2 = new File("E:/你好");//File可以指代不存在的文件路径
File f3 = new File("helloworld-app/src/itheima.txt");//相对路径

绝对路径:从盘符开始。

相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。

File类判断文件类型、获取文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
File f1 = new File("E:" + File.separator + "2_学习" + File.separator + "Java" + File.separator + "test.txt");
System.out.println(f1.length());//文件的大小
System.out.println(f1.exists());//文件是否存在
System.out.println(f1.isFile());//判断是否是文件
System.out.println(f1.isDirectory());//判断是否是文件夹
System.out.println(f1.getName());//获取文件名称(包含后缀)
long time = f1.lastModified();//获取文件的最后修改时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(time));

File f3 = new File("helloworld-app/src/itheima.txt");//相对路径
System.out.println(f3.getPath());//获取创建文件对象时,使用的路径:helloworld-app\src\itheima.txt
System.out.println(f3.getAbsolutePath());//获取绝对路径:E:\2_学习\Java\code\javasepro\helloworld-app\src\itheima.txt

File类创建文件和删除文件

注意:delete方法默认只能删除文件和空文件夹,删除后的文件不会进入回收站delete方法不能删除非空文件夹

1
2
3
4
5
6
7
8
9
10
File fi1 = new File("E:/hello.txt");
System.out.println(fi1.createNewFile());//必须抛出异常:throws IOException,因为有可能电脑中无E盘

File fi2 = new File("E:/aaa");//只能创建一级文件夹
System.out.println(fi2.mkdir());

File fi3 = new File("E:/aaa/bbb/ccc");//可以创建多级文件夹
System.out.println(fi3.mkdirs());

System.out.println(fi2.delete());//删除文件、空文件夹,不能删除非空文件夹

File类遍历文件夹

使用listFiles方法时的注意事项:

当主调是文件,或者路径不存在时,返回null

当主调是空文件夹时,返回一个长度为0的数组。

当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回。

当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件

当主调是一个文件夹,但是没有权限访问该文件夹时,返回null

1
2
3
4
5
6
7
8
9
10
11
File file1 = new File("E:\\2_学习\\Java");
String[] names = file1.list();//获取当前目录下的一级文件名称
for (String name : names) {
System.out.println(name);
}

File file2 = new File("E:\\2_学习\\Java");
File[] files = file2.listFiles();//获取当前目录下的一级文件对象
for (File file : files) {
System.out.println(file.getAbsolutePath());
}

案例

猴子吃桃问题(递归)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个;
第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个;
以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个;
等到第10天的时候发现桃子只有1个了。
需求:请问猴子第一天摘了多少个桃子?
*/
public class eat {
public static void main(String[] args) {
//f(x) - f(x)/2 - 1 = f(x+1)
//f(x) = 2f(x+1) + 2
System.out.println(f(1));//1534
}
public static int f(int i) {
if( i == 10 ) {
return 1;
}else {
return 2 * f(i+1) + 2;
}
}
}
文件搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Test {
public static void main(String[] args) throws IOException {//或throws Exception
searchFile(new File("D:"), "ToDesk.exe");
}

public static void searchFile(File dir, String fileName) throws IOException {
// 1、把非法的情况都拦截住
if(dir == null || !dir.exists() || dir.isFile()){
return; // 代表无法搜索
}

// 2、dir不是null,存在,且不是文件,一定是目录对象。
// 获取当前目录下的全部一级文件对象。
File[] files = dir.listFiles();

// 3、判断当前目录下是否存在一级文件对象,以及是否可以拿到一级文件对象。
if(files != null && files.length > 0){ //判断是否有一级文件对象
// 4、遍历全部一级文件对象。
for (File f : files) {
// 5、判断文件是否是文件,还是文件夹
if(f.isFile()){
// 是文件,判断这个文件名是否是我们要找的
if(f.getName().contains(fileName)){
System.out.println("找到了:" + f.getAbsolutePath());
Runtime runtime = Runtime.getRuntime();
runtime.exec(f.getAbsolutePath());//可以启动该程序
}
}else {
// 是文件夹,继续重复这个过程(递归)
searchFile(f, fileName);
}
}
}
}
}
删除非空文件夹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
public static void main(String[] args) throws IOException {//或throws Exception
deteteDir(new File("E:\\aaa"));
}

private static void deteteDir(File dir) {
if (dir==null || !dir.exists()) {
return;
}
if (dir.isFile()) {
dir.delete();
return;
}
// 1.dir存在且是文件夹,拿出里面的一级文件对象
File[] files = dir.listFiles();
if (files == null) {
return;
}
// 2.有内容的文件夹,先删除内容,最后删除自己
for (File file : files) {
if (file.isFile()){
file.delete();
}else{
deteteDir(file);
}
}
dir.delete();
}
}
啤酒问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶。请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
public class Test1 {
public static int totalNumber;//总酒数
public static int lastBottleNumber;//上一次剩的瓶子数
public static int lastCoverNumber;//上一次剩的盖子数
public static void main(String[] args) {
//啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶。10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
buy(10);
System.out.println("总数:" + totalNumber);//总数:15
System.out.println("剩余瓶子数:" + lastBottleNumber);//剩余瓶子数:1
System.out.println("剩余盖子数:" + lastCoverNumber);//剩余盖子数:3
}
public static void buy(int money) {
int buyNumber = money / 2;
totalNumber += buyNumber;

//把盖子和瓶子换算成钱继续买
//计算本轮总的盖子和瓶子数
int allBottleNumber = lastBottleNumber + buyNumber;
int allCoverNumber = lastCoverNumber + buyNumber;

int allMoney = 0;
//allMoney += money - buyNumber * 2; //代码里无这句,钱不能和酒瓶酒盖一起换
allMoney += allBottleNumber / 2 * 2;
allMoney += allCoverNumber / 4 * 2;

lastBottleNumber = allBottleNumber % 2;
lastCoverNumber = allCoverNumber % 4;

if(allMoney >= 2){
buy(allMoney);
}
}
}

IO流

字符

标准ASCII字符集

ASCII(American Standard Code for Information Interchange): 美国信息交换标准代码,包括了英文、符号等。

标准ASCII使用1个字节存储一个字符首位是0,总共可表示128个字符,对英语完全够用。

GBK(汉字内码扩展规范,国标)

汉字编码字符集,包含了2万多个汉字等字符,GBK一个中文字符编码成两个字节的形式存储。

注意:

1.GBK兼容了ASCII字符集。在GBK中,ASCII字符同样用一个字节存储,首位是0。

2.GBK规定汉字的第一个字节的第一位必须是1

3.GBK通过首位是0或者1判断是中文字符还是ASCII字符。

Unicode字符集(统一码、万国码)

Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集。

Unicode提供了很多编码方案,其中UTF-32使用4个字节表示一个字符。但是这占用存储空间,通信效率变低。

UTF-8

UTF-8Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节。

英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节

UTF-8编码方式(二进制)
0xxxxxxx (ASCII码)
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

总结:

  • ASCII字符集:只有英文、数字、符号等,占1个字节。
  • GBK字符集:汉字占2个字节,英文、数字占1个字节。
  • UTF-8字符集:汉字占3个字节,英文、数字占1个字节。

注意:

1.字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码。

2.英文,数字一般不会乱码,因为很多字符集都兼容了ASCII编码。

字符的编码和解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1.编码
String data = "a我b";
byte[] byte1 = data.getBytes();//按照平台默认字符集(UTF-8)编码
System.out.println(Arrays.toString(byte1));

byte[] byte2 = data.getBytes("GBK");
//需要throws UnsupportedEncodingException,因为编码可能会写错
System.out.println(Arrays.toString(byte2));

//2.解码
String s1 = new String(byte1);//按照平台默认字符集(UTF-8)解码
System.out.println(s1);

String s2 = new String(byte2, "GBK");
System.out.println(s2);

IO流的体系

字节流

FileInputStream(文件字节输入流)

作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去。

注意:

1.使用FileInputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码。

1
2
3
4
5
6
7
8
//两种方法都可以创建流,建议使用字符串
//InputStream is = new FileInputStream(new File("helloworld-app\\src\\itheima.txt"));
InputStream is = new FileInputStream("helloworld-app\\src\\itheima.txt");
int b;
while( ( b = is.read() ) != -1){//每次读取一个字节,没有数据返回-1
System.out.print((char)b);//不能解决汉字乱码
}
is.close();//关闭流

2.使用FileInputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
InputStream is = new FileInputStream("helloworld-app\\src\\itheima.txt");
//文件内容:abc666

/*
byte[] buffer = new byte[3];
int len = is.read(buffer);
System.out.println(new String(buffer) + ", 读取字节长度:" + len);
//abc, 读取字节长度:3
int len2 = is.read(buffer);
System.out.println(new String(buffer) + ", 读取字节长度:" + len2);
//66c, 读取字节长度:2
//因为第二次只读取了2个字节就结束了,所以第三个字节c是上一次读取留下的。
*/

byte[] buffer = new byte[3];
int len;
while ( (len = is.read(buffer)) != -1) {//每次读取多个字节,由buffer的大小确定
System.out.print(new String(buffer, 0, len));//不能解决汉字乱码
}
is.close();//关闭流

3.使用FileInputStream读取中文,保证输出不乱码:定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。直接把文件数据全部读取到一个字节数组可以避免乱码,但是如果文件过大,创建的字节数组也会过大,可能引起内存溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
InputStream is = new FileInputStream("helloworld-app\\src\\itheima.txt");

//方式1
/*
File file = new File("helloworld-app\\\\src\\\\itheima.txt");
long size = file.length();
byte[] buffer = new byte[(int) size];//size是long类型,这里数组大小是int类型,需要强转;
int len = is.read(buffer);
System.out.println(new String(buffer));
*/

//方式2
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
FileOutputStream(文件字节输出流)

作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//覆盖管道:每次覆盖之前的数据
//OutputStream os = new FileOutputStream("helloworld-app\\src\\itheima.txt");
//追加数据管道:
OutputStream os = new FileOutputStream("helloworld-app\\src\\itheima1.txt", true);
os.write(97);//'a'
os.write('b');
os.write('苏');//不会报错,但是只能写入一个字节,汉字有3个字节,会乱码

byte[] buffer = "我爱你中国888".getBytes();
os.write(buffer);
os.write(buffer, 0, 15);
os.write("\r\n".getBytes());//"\n"只是Windows系统,使用"\r\n"兼容所有系统,代表换行

os.close();//关闭流
文件复制

任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class copy {//复制什么类型的文件都可以
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("helloworld-app/src/com/itheima/IO_/Test.java");
OutputStream os = new FileOutputStream("helloworld-app\\src\\Test.txt");
byte[] buffer = new byte[1024];
int len;
while( (len = is.read(buffer)) > 0 ) {
os.write(buffer, 0, len);
}
os.close();
is.close();
}
}

释放资源的方式

try-catch-finally

finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。

作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//try-catch-finally的使用
public class Test5 {
public static void main(String[] args) {
try {
System.out.println(10/2);
//return; //使用return跳出方法的执行,还会执行finally
//System.exit(0); //退出虚拟机,则不会执行finally
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("finally");
}
System.out.println(test(10, 2));//111
}

public static int test(int a, int b) {
try {
return a / b;
} catch (Exception e) {
e.printStackTrace();
return -1;
} finally {
return 111;//不能在finally中返回数据
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//InputStream和OutputStream使用try-catch-finally安全释放资源的方式
public static void main(String[] args) throws Exception {
InputStream is = null;
OutputStream os = null;
try {
System.out.println(10 / 0);
is = new FileInputStream("helloworld-app/src/com/itheima/IO_/Test.java");
os = new FileOutputStream("helloworld-app\\src\\Test.txt");
System.out.println(10 / 0);
byte[] buffer = new byte[1024];
int len;
while( (len = is.read(buffer)) > 0 ) {
os.write(buffer, 0, len);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (os != null) { os.close(); }
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (is != null) { is.close(); }
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
try-with-resource

JDK7开始提供了更简单的资源释放方案:try-with-resource

1
2
3
4
5
try(定义资源1;定义资源2;…){//()中只能放置资源,否则报错。该资源使用完毕后,会自动调用其close()方法,完成对资源的释放。
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}

资源一般指的是最终实现了AutoCloseable接口。

1
2
3
public abstract class InputStream implements Closeable{ }
public abstract class OutputStream implements Closeable, Flushable { }
public interface Closeable extends AutoCloseable { }
1
2
3
4
5
6
public class MyConnection implements AutoCloseable {//自定义资源
@Override
public void close() throws Exception {
System.out.println("释放了与某个硬件的链接资源");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test6 {
public static void main(String[] args) throws Exception {
try (
InputStream is = new FileInputStream("helloworld-app/src/com/itheima/IO_/Test.java");
OutputStream os = new FileOutputStream("helloworld-app\\src\\Test.txt");
// 注意:这里只能放置资源对象(流对象)
// int age = 12;//不能放置
// 资源都是会实现AutoCloseable接口的。资源都有close方法,并且资源放到这里后,使用完之后,会被自动调用其close方法完成资源的释放操作
MyConnection conn = new MyConnection();//使用自定义资源
){
byte[] buffer = new byte[1024];
int len;
while( (len = is.read(buffer)) > 0 ) {
os.write(buffer, 0, len);
}
System.out.println(conn);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

字符流

FileReader(文件字符输入流)

作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
try(
Reader fr1 = new FileReader("helloworld-app\\src\\itheima1.txt");
Reader fr2 = new FileReader("helloworld-app\\src\\itheima1.txt");
){
//方式1:一次读取一个字符
int c;
while((c = fr1.read()) != -1){
System.out.print( (char)c );
}
//方式2:一次读取多个字符
char[] buffer = new char[3];
int len;
while((len = fr2.read(buffer)) != -1){
System.out.print(new String(buffer, 0, len));
}
}catch(Exception e) {
e.printStackTrace();
}
}
FileWriter(文件字符输出流)

作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
try (
Writer fw = new FileWriter("helloworld-app\\src\\Test1.txt")
) {
fw.write('a');
fw.write(98);
fw.write('苏');
fw.write("\r\n");//换行
fw.write("我爱你中国abc");
fw.write("我爱你中国abc", 0 ,5);
char[] buffer = {'我', '喜', '欢', '学', '习'};
fw.write(buffer);
fw.write(buffer, 0, 2);
} catch (Exception e) {
e.printStackTrace();
}
}

注意:字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效。

1
2
public void flush() throws IOException	//刷新流,就是将内存中缓存的数据立即写到文件中去生效!
public void close() throws IOException //关闭流的操作,包含了刷新!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {
Writer fw = new FileWriter("helloworld-app\\src\\Test2.txt");

fw.write('a');
fw.write(98);
fw.write('苏');
fw.write("\r\n");//换行
fw.write("我爱你中国abc");
fw.write("我爱你中国abc", 0 ,5);
char[] buffer = {'我', '喜', '欢', '学', '习'};
fw.write(buffer);
fw.write(buffer, 0, 2);

fw.flush();//刷新流
fw.close();//关闭流,关闭流包含刷新操作。
//注意:如果不进行flush或者close操作,则无法写入文件,因为此时字符还在缓冲区中,必须得通过刷新才能够写入文件。
//上述使用了try-with-resource方法没有直接调用flush或close方法,是因为try-with-resource已经自动调用close方法,所以能写入成功。
}

字节流、字符流的使用场景小结:

字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出。

字符流适合做文本文件的操作(读,写)。

缓冲流

BufferedInputStream(字节缓冲输入流)、BufferedOutputStream(字节缓冲输出流)

原理:字节缓冲输入流自带了8KB缓冲池字节缓冲输出流也自带了8KB缓冲池。提高字节流读写的性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
try (
InputStream is = new FileInputStream("helloworld-app/src/com/itheima/IO_/Test.java");
BufferedInputStream bis = new BufferedInputStream(is, 8192*2);//字节缓冲输入流,可以设置缓冲池大小
OutputStream os = new FileOutputStream("helloworld-app\\src\\Test.txt");
BufferedOutputStream bos = new BufferedOutputStream(os, 8192);//字节缓冲输出流,可以设置缓冲池大小
) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
}catch (IOException e) {
e.printStackTrace();
}
}
BufferedReader(字符缓冲输入流)、BufferedWriter(字符缓冲输出流)

作用:字符缓冲输入流和字符缓冲输出流都自带8K(8192)的字符缓冲池,可以提高字符流读写的性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
try(
Reader fr = new FileReader("helloworld-app\\src\\itheima1.txt");
BufferedReader br = new BufferedReader(fr);
){
String line;
while((line = br.readLine()) != null) {//一次读取一行
System.out.println(line);
}
//也可以使用字符输入流的另外两种方式:一个字符或者多个字符进行读取
}catch(Exception e) {
e.printStackTrace();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
try (
Writer fw = new FileWriter("helloworld-app\\src\\Test1.txt");
BufferedWriter bw = new BufferedWriter(fw);
) {
bw.write('a');
bw.write(98);
bw.write('苏');
bw.newLine();//换行
bw.write("我爱你中国abc");
} catch (Exception e) {
e.printStackTrace();
}
}

提高字节流读写数据的性能:建议使用字节缓冲输入流字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。

转换流

注意:

  • 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码
  • 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码
InputStreamReader(字符输入转换流)

InputStreamReader解决不同编码时,字符流读取文本内容乱码的问题。

解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//字符输入转换流InputStreamReader
try (
//1.得到文件的原始字节流(GBK字节流)
InputStream is = new FileInputStream("helloworld-app/src/file_GBK.txt");
//2.把原始字节输入流按照指定的字符集编码(GBK)转换成字符输入流
Reader isr = new InputStreamReader(is, "GBK");
//3.把字符输入流包装成字符缓冲输入流
BufferedReader br = new BufferedReader(isr);
){
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
OutputStreamWriter(字符输出转换流)

控制写出的字符使用指定的字符集编码:

1.调用String提供的getBytes方法:

1
2
String data = "我爱你中国abc";
byte[] bytes = data.getBytes("GBK");//throws UnsupportedEncodingException

2.OutputStreamWriter可以控制写出去的字符使用什么字符集编码。

解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//字符输出转换流OutputStreamWriter
try (
//1.创建文件字节输出流
OutputStream os = new FileOutputStream("helloworld-app/src/file_GBK_w.txt");
//2.把原始的字节输出流按照指定的字符集编码(GBK)转换成字符输出转换流
Writer osw = new OutputStreamWriter(os, "GBK");
//3.把字符输出流包装成字符缓冲输出流
BufferedWriter bw = new BufferedWriter(osw);
){
bw.write("我是中国人srr");
bw.write("我爱你中国999");
} catch (Exception e) {
e.printStackTrace();
}

打印流

PrintStreamPrintWriter都是打印流,打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去

PrintStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//打印流PrintStream
try (
PrintStream ps = new PrintStream("helloworld-app/src/PrintStream.txt", Charset.forName("GBK"));
//PrintStream内部包装了BufferedWriter,自带缓冲池
){
ps.println('a');
ps.println("我爱你中国");
ps.println(true);
ps.println(99.8);
ps.println(97);//print打印什么就是什么,这里就是97

ps.write(97);//write的时候这里就是'a'
} catch (Exception e) {
e.printStackTrace();
}
PrintWriter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//打印流PrintWriter
try (
PrintWriter pw =
new PrintWriter(new FileOutputStream("helloworld-app/src/PrintWriter.txt", true));
//PrintWriter是高级流,不能构造时加true代表追加文件,需要通向低级流管道,在低级流里面加true代表追加文件
){
pw.println('a');
pw.println("我爱你中国");
pw.println(true);
pw.println(99.8);
pw.println(97);//print打印什么就是什么,这里就是97

pw.write(97);//write的时候这里就是'a'
} catch (Exception e) {
e.printStackTrace();
}
PrintStream和PrintWriter的区别

1.打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)。

2.PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。

3.PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。

打印流的应用:输出语句的重定向

打印流可以把输出语句的打印位置改到某个文件中去。

1
2
3
4
5
6
7
8
9
10
//out是PrintStream的打印流对象,默认创造的打印流对象是通向控制台的
System.out.println("第一行");
System.out.println("第二行");
try (PrintStream printStream = new PrintStream("helloworld-app/src/PrintStream2.txt");){
System.setOut(printStream);//把系统默认的打印流对象改成自己设置的打印流
System.out.println("第三行");
System.out.println("第四行");
} catch (FileNotFoundException e) {
e.printStackTrace();
}

数据流

DataOutputStream(数据输出流)

允许把数据和其类型一并写出去。

1
2
3
4
5
6
7
8
9
10
11
12
//数据输出流DataOutputStream
try ( //创建一个数据输出流包装低级的字节输出流
DataOutputStream dos =
new DataOutputStream(new FileOutputStream("helloworld-app/src/DataOutputStream.txt"));){
dos.writeInt(97);
dos.writeChar('R');
dos.writeDouble(888.888);
dos.writeBoolean(true);
dos.writeUTF("你好我是中国人");
} catch (Exception e) {
e.printStackTrace();
}
DataInputStream(数据输入流)

用于读取数据输出流写出去的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//数据输入流DataInputStream
try (
DataInputStream dis =
new DataInputStream(new FileInputStream("helloworld-app/src/DataOutputStream.txt"));
){
//读的时候要和写的时候一一对应,写的是什么类型,读的时候就是什么类型,否则会出错
System.out.println(dis.readInt());
System.out.println(dis.readChar());
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
System.out.println(dis.readUTF());
} catch (Exception e) {
e.printStackTrace();
}

序列号流

ObjectOutputStream(对象字节输出流)

可以把Java对象进行序列化:把Java对象存入到文件中去。

注意:

1.对象如果要参与序列化,必须实现序列化接口java.io.Serializable。使用transient修饰成员变量,表示这个成员变量不参与序列化

2.ArrayList集合已经实现了序列化接口。若一次性序列化多个对象,用一个ArrayList集合存储多个对象,然后直接对集合进行序列化即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//User类
public class User implements Serializable {//对象序列化必须要实现Serializable序列化接口
private String name;
private int age;
private transient String password;//transient表示这个成员变量不参与序列化

public User() {}
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
//对象字节输出流ObjectOutputStream
try ( //创建对象字节输出流包装原始的字节输出流
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("helloworld-app/src/ObjectOutputStream.txt"));
){
User u = new User("admin", 18, "888888");
oos.writeObject(u);//序列化对象到文件中去
} catch (Exception e) {
e.printStackTrace();
}
ObjectInputStream(对象字节输入流)

可以把Java对象进行反序列化:把存储在文件中的Java对象读入到内存中来。

1
2
3
4
5
6
7
8
9
10
11
12
//对象字节输入流ObjectInputStream
try ( //创建对象字节输入流管道,包装低级字节输入流与源文件接通
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("helloworld-app/src/ObjectOutputStream.txt"));
){
User u = (User) ois.readObject();//返回Object类型,需要强转
System.out.println(u);
//User{name='admin', age=18, password='888888'} (password参与序列化)
//User{name='admin', age=18, password='null'} (password不参与序列化)
} catch (Exception e) {
e.printStackTrace();
}

IO框架

框架:解决某类问题,编写的一套类、接口等,可以理解成一个半成品,大多框架都是第三方研发的。

好处:在框架的基础上开发,可以得到优秀的软件架构,并能提高开发效率。

框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。

IO框架:封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。

导入commons-io-2.11.0.jar框架到项目中去:

  1. 官网:Commons IO – Download Apache Commons IO,下载Apache Commons IO 2.17.0 (requires Java 8)->Binaries->commons-io-2.17.0-bin.zip
  2. 在项目中创建一个文件夹:lib
  3. commons-io-2.6.jar文件复制到lib文件夹。
  4. jar文件上点右键,选择 Add as Library ->点击OK。
  5. 在类中导包使用。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception{
//IO框架
FileUtils.copyFile(new File("helloworld-app/src/Test.txt"), new File("helloworld-app/src/a.txt"));//复制文件
FileUtils.copyDirectory(new File("helloworld-app/src"), new File("helloworld-app/src2"));//复制文件夹
FileUtils.deleteDirectory(new File("helloworld-app/src2"));//删除文件夹

//Java提供的原生的一行代码搞定很多事情
Files.copy(Path.of("helloworld-app/src/Test2.txt"), Path.of("helloworld-app/src/b.txt"));//复制文件
System.out.println(Files.readString(Path.of("helloworld-app/src/Test.txt")));//读取文件
}

特殊文件

Properties属性文件

Properties是一个Map集合(键值对集合),但是一般不会当集合使用。

核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容。

1
2
3
4
5
6
# users.properties
# 以下内容都是用户名和密码。
admin=123456
张无忌=minmin
周芷若=wuji
赵敏=wuji
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws Exception {
// 1、创建一个Properties的对象出来(键值对集合,空容器)
Properties properties = new Properties();

// 2、开始加载属性文件中的键值对数据到properties对象中去
properties.load(new FileReader("helloworld-app/src/users.properties"));
System.out.println(properties);//{admin=123456, 周芷若=wuji, 赵敏=wuji, 张无忌=minmin}

// 3、根据键取值
System.out.println(properties.getProperty("赵敏"));
System.out.println(properties.getProperty("张无忌"));

// 4、遍历全部的键和值。
Set<String> keys = properties.stringPropertyNames();
for (String key : keys) {
String value = properties.getProperty(key);
System.out.println(key + "---->" + value);
}

properties.forEach((k, v) -> {//Lambda表达式遍历全部的键和值
System.out.println(k + "---->" + v);
});
}

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws Exception {
// 1、创建Properties对象出来,先用它存储一些键值对数据
Properties properties = new Properties();
properties.setProperty("张无忌", "minmin");
properties.setProperty("殷素素", "cuishan");
properties.setProperty("张翠山", "susu");

// 2、把properties对象中的键值对数据存入到属性文件中去
properties.store(new FileWriter("helloworld-app/src/users2.properties")
, "i saved many users!");
}
XML文件

XML( 全称EXtensible Markup Language, 可扩展标记语言 )。本质是一种数据的格式,可以用来存储复杂的数据结构,和数据关系。

应用场景:经常用来做为系统的配置文件;或者作为一种特殊的数据结构,在网络中进行传输。

XML的特点:

  1. XML中的<标签名> 称为一个标签或一个元素,一般是成对出现的。
  2. XML中的标签名可以自己定义(可扩展),但必须要正确的嵌套。
  3. XML中只能有一个根标签
  4. XML中的标签可以有属性。
  5. 如果一个文件中放置的是XML格式的数据,这个文件就是XML文件,后缀一般要写成.xml

XML的语法规则:

1.XML文件的后缀名为:xml,文档声明必须是第一行。

1
2
3
<?xml version="1.0" encoding="UTF-8" ?>
version:XML默认的版本号码、该属性是必须存在的
encoding:本XML文件的编码

2.XML中可以定义注释信息:

1
<!--- 注释内容 -->

3.XML中书写”<”、“&”等,可能会出现冲突,导致报错,此时可以用如下特殊字符替代。

1
2
3
4
5
&lt;    <  小于
&gt; > 大于
&amp; & 和号
&apos; ' 单引号
&quot; " 引号

4.XML中可以写一个叫CDATA的数据区:<![CDATA[ …内容… ]]>,里面的内容可以随便写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 注释:以上抬头声明必须放在第一行,必须有 -->
<!-- 根标签只能有一个 -->
<users>
<user id="1" desc="第一个用户">
<name>张无忌</name>
<sex></sex>
<地址>光明顶</地址>
<password>minmin</password>
<data> 3 &lt; 2 &amp;&amp; 5 > 4 </data>
<data1>
<![CDATA[
3 < 2 && 5 > 4
]]>
</data1>
</user>
<people>很多人</people>
<user id="2">
<name>敏敏</name>
<sex></sex>
<地址>光明顶</地址>
<password>wuji</password>
<data> 3 &lt; 2 &amp;&amp; 5 > 4 </data>
<data1>
<![CDATA[
3 < 2 && 5 > 4
]]>
</data1>
</user>
</users>
使用Dom4J解析XML文件

DOM4J解析XML文件的思想:文档对象模型

Dom4j解析XML得到Document对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static void main(String[] args) throws Exception {
// 1、创建一个Dom4J框架提供的解析器对象
SAXReader saxReader = new SAXReader();

// 2、使用saxReader对象把需要解析的XML文件读成一个Document对象。
Document document =
saxReader.read("helloworld-app/src/helloworld.xml");

// 3、从文档对象中解析XML文件的全部数据了
Element root = document.getRootElement();
System.out.println(root.getName());

// 4、获取根元素下的全部一级子元素。
// List<Element> elements = root.elements();
List<Element> elements = root.elements("user");
for (Element element : elements) {
System.out.println(element.getName());
}

// 5、获取当前元素下的某个子元素。
Element people = root.element("people");
System.out.println(people.getText());

// 如果下面有很多子元素user,默认获取第一个。
Element user = root.element("user");
System.out.println(user.elementText("name"));

// 6、获取元素的属性信息
System.out.println(user.attributeValue("id"));
Attribute id = user.attribute("id");
System.out.println(id.getName());
System.out.println(id.getValue());

List<Attribute> attributes = user.attributes();
for (Attribute attribute : attributes) {
System.out.println(attribute.getName() + "=" + attribute.getValue());
}

// 7、如何获取全部的文本内容:获取当前元素下的子元素文本值
System.out.println(user.elementText("name"));
System.out.println(user.elementText("地址"));
System.out.println(user.elementTextTrim("地址")); // 取出文本去除前后空格
System.out.println(user.elementText("password"));

Element data = user.element("data");
System.out.println(data.getText());
System.out.println(data.getTextTrim()); // 取出文本去除前后空格
}

把数据写出到XML文件:不建议用Dom4J做,直接把数据拼写成XML格式,然后用IO流写出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
// 1、使用一个StringBuilder对象来拼接XML格式的数据。
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n");
sb.append("<book>\r\n");
sb.append("\t<name>").append("从入门到跑路").append("</name>\r\n");
sb.append("\t<author>").append("dlei").append("</author>\r\n");
sb.append("\t<price>").append(999.99).append("</price>\r\n");
sb.append("</book>");
try (
BufferedWriter bw = new BufferedWriter(new FileWriter("helloworld-app/src/book.xml"));
){
bw.write(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
约束XML文件的书写

约束文档:专门用来限制xml书写格式的文档,比如:限制标签、属性应该怎么写。

约束文档的分类:

1.DTD文档

  • DTD约束文档,后缀必须是.dtd
  • 在需要编写的XML文件中导入该DTD约束文档。
  • 然后XML文件,就必须按照DTD约束文档指定的格式进行编写,否则报错。
  • DTD可以约束XML文件的编写,但是不能约束具体的数据类型
1
2
3
4
5
6
7
8
9
10
11
<!ELEMENT 书架 (书+)>
<!ELEMENT 书 (书名,作者,售价)>
<!ELEMENT 书名 (#PCDATA)>
<!ELEMENT 作者 (#PCDATA)>
<!ELEMENT 售价 (#PCDATA)>

<!-- <!ELEMENT 书架(书+)> 表示根标签是<书架>,并且书架中有子标签<书> -->
<!-- <!ELEMENT 书(书名、作者、售价)> 表示书是一个标签,且书中有子标签<书名>、<作者>、<售价> -->
<!-- <!ELEMENT 书名(#PCDATA)> 表示<书名>是一个标签,且<书名>里面是普通文本 -->
<!-- <!ELEMENT 作者(#PCDATA)> 表示<作者>是一个标签,且<作者>里面是普通文本 -->
<!-- <!ELEMENT 售价(#PCDATA)> 表示<售价>是一个标签,且<售价>里面是普通文本 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE 书架 SYSTEM "data.dtd">
<书架>
<>
<书名>从入门到删库</书名>
<作者>小猫</作者>
<售价>很便宜</售价>
</>
<>
<书名>从入门到删库</书名>
<作者>小猫</作者>
<售价>9.9</售价>
</>
<>
<书名>从入门到删库</书名>
<作者>小猫</作者>
<售价>9.9</售价>
</>
</书架>

2.Schema文档

  • 编写schema约束文档,后缀必须是.xsd
  • 在需要编写的XML文件中导入该schema约束文档。
  • 按照约束内容编写XML文件的标签。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itcast.cn"
elementFormDefault="qualified" >
<!-- targetNamespace:申明约束文档的地址(命名空间)-->
<element name='书架'>
<!-- 写子元素 -->
<complexType>
<!-- maxOccurs='unbounded': 书架下的子元素可以有任意多个!-->
<sequence maxOccurs='unbounded'>
<element name='书'>
<!-- 写子元素 -->
<complexType>
<sequence>
<element name='书名' type='string'/>
<element name='作者' type='string'/>
<element name='售价' type='double'/>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<书架 xmlns="http://www.itcast.cn"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itcast.cn data.xsd">
<!-- xmlns="http://www.itcast.cn" 基本位置
xsi:schemaLocation="http://www.itcast.cn books02.xsd" 具体的位置 -->
<>
<书名>从入门到删除</书名>
<作者>dlei</作者>
<售价>9.9</售价>
</>
<>
<书名>从入门到删除</书名>
<作者>dlei</作者>
<售价>0.9</售价>
</>
</书架>

日志技术

日志技术可以将系统执行的信息,方便的记录到指定的位置(控制台、文件中、数据库中)。

日志技术可以随时以开关的形式控制日志的启停,无需侵入到源代码中去进行修改。

日志框架:牛人或者第三方公司已经做好的实现代码,后来者直接可以拿去使用。

日志接口:设计日志框架的一套标准,日志框架需要实现这些接口。

注意:

1.因为对Commons Logging接口不满意,有人就搞了SLF4J;因为对Log4j的性能不满意,有人就搞了Logback

2.Logback是基于slf4j的日志规范实现的框架。

Logback日志框架

官方网站:https://logback.qos.ch/index.html

Logback日志框架有以下模块:

1.logback-core:基础模块,是其他两个模块依赖的基础(必须有)。

2.logback-classic:完整实现了slf4jAPI的模块(必须有)。

3.logback-access:与 TomcatJettyServlet容器集成,以提供HTTP访问日志的功能(可选,以后再接触)。

想使用Logback日志框架,至少需要在项目中整合如下三个模块:

1.slf4j-api:日志接口。

2.logback-core:基础模块。

3.logback-classic:功能模块,它完整实现了slf4jAPI。

使用Logback日志框架

1.导入Logback框架到项目中去:slf4j-apilogback-corelogback-classic

2.将Logback框架的核心配置文件logback.xml直接拷贝到src目录下(必须是src下)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
</encoder>
</appender>

<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>E:/2_学习/Java/code/log/itheima-data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式:当日志文件大小超过1MB时,就压缩文件-->
<fileNamePattern>E:/2_学习/Java/code/log/itheima-data-%i-%d{yyyy-MM-dd}-.log.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>

<!--
1、控制日志的输出情况:如,开启日志level="ALL",取消日志level="OFF"
-->
<root level="debug">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>

3.创建Logback框架提供的Logger对象,然后用Logger对象调用其提供的方法就可以记录系统的日志信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LogBackTest {
// 创建一个Logger日志对象
public static final Logger LOGGER = LoggerFactory.getLogger("LogBackTest");

public static void main(String[] args) {
//while (true) {
try {
LOGGER.info("chu法方法开始执行~~~");
chu(10, 0);
LOGGER.info("chu法方法执行成功~~~");
} catch (Exception e) {
LOGGER.error("chu法方法执行失败了,出现了bug~~~");
}
//}
}

public static void chu(int a, int b){
LOGGER.debug("参数a:" + a);
LOGGER.debug("参数b:" + b);
int c = a / b;
LOGGER.info("结果是:" + c);
}
}
日志级别

日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下(优先级依次升高):

只有日志的级别是大于或等于核心配置文件配置的日志级别,才会被记录,否则不记录。

1
2
3
4
<root level="info">  <!-- 只有info以上才会被记录 -->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>

注意:

  • ALL OFF分别是打开全部日志和关闭全部日志。
  • 级别程度依次是:TRACE< DEBUG< INFO<WARN<ERROR
  • 默认级别是debug(忽略大小写),只输出当前级别及高于该级别的日志。

Java的File类和IO流
http://surourou8.github.io/2024/09/28/Java的File类和IO流/
作者
Su Rourou
发布于
2024年9月28日
许可协议