文章目录
前言
Java进阶部分内容,包含IO流,File类,特殊文件和日志,以及properties集合的使用等内容
一、IO流
1. 流的分类
流按照方向分为输入流和输出流
按照读取数据的方式分为字节流(一次读取一个字节,万能流什么文件都可以读)和字符流(一次读取一个字 符,只能读普通的文本文件—可以用记事本编辑的与后缀名无关)
字符流为字节流加编码的组合
eg:
a赵c(一个英文字符一个字节,大部分汉字字符占3个字节———window操作系统中)
字节流:
第一次读:a
第二次读:赵前一半
第三次读:赵的中间一个
第四次读:赵的最后一半
第四次读:c
字符流:
第一次读:a
第二次读:赵
第三次读:c
JAVA中流的详细分类
Java IO流这块有四大家族:
java.io.InputStream字节输入流
java.io.OutputStream字节输出流
java.io.Reader字符输入流
java.io.Writer字符输出流
四大家族的首领都是抽象类(abstract class)
所有的流都实现了java.io.Closeable接口,都是可关闭的,都有close() 方法。流是一个管道,是内存和硬盘之间的通道,用完后必须关闭否则会会耗费(占用)大量资源。
所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要调用flush()进行刷新,将通道/管道当中剩余未输出的数据强行输出完( 清空管道! )避免丢失数据。输出流关闭的时候会制动调用flush方法。
注意:在java中只要类名以stream结尾的都是字节流,以Reader/Writer"结尾的都是字符流
InputStream抽象
InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能。继承自InputStream 的流都是字节输入流,用于读文件的,读取单位为字节(8bit)InputStream类提供了 3种重载的read方法。
public abstract int read( ):读取一个byte的数据,返回值对应ASCII类型值。若返回为-1说明没有读取到任何字节读取工作结束。
public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数,若返回为-1说明没有读取到任何字节读取工作结束。该方法实际上是调用下一个方法实现的 。
public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量(下标)为off的b数组中。
public int available( ):返回输入流中可以读取的字节数。注意:若读取阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。
public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取 。
public int close( ) :我们在使用完后,必须对我们打开的流进行关闭。
主要子类:以InputStream结尾
OutputStream抽象类
OutputStream提供了3个write方法来做数据的输出,这个是和InputStream是相对应的。
public void write(byte b[ ]):将参数b中的字节写到输出流。
public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。
public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。
public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。
public void close( ) : 关闭输出流并释放与流相关的系统资源。
主要的子类:以OutputStream结尾
Reader抽象类
底层依靠InputStream字符流实现,用于读取字符流的抽象类。
提供了 read(char[], int, int) 和 close()。
Writer抽象类
底层依靠OutputStream字符流实现,用于写入字符流的抽象类。
提供了 write(char[], int, int)、flush() , close()以及write(String s)。
提供了write(String s)方法可以直接写入字符串
字符流的所有的写的方法都不是直接写到硬盘中,而且写到底层的字符数组中(字符缓冲区中),当字符缓冲区满了,或者手动调用flush() 才会参照码表编码成字节,然后通过字节流输出到文件中。缓冲区数组中存储的为字节。
在调用close方法关闭字符输出流时,会先flush在关闭流。
java.io包下需要掌握的流有16个
文件流专属: (重点)
java.io.FileInputstream 重点
java.io.FileOutputstream 重点
java.io.FileReader
java.io.FileWriter
转换流:(了解)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属: (字符缓冲流掌握)
java.io.BufferedReader 重点
java.io.BufferedWriter 重点
java.io.BufferedInputStrean
java.io.BufferedOutputStream
数据流:(重点)
java.io.DataInputStream
java.io.DataOutputStream
标准输出流:(了解)
java.io.PrintWriter
java.io.PrintStream
对象专属流:
java.io.ObjectInputStream
java.io.ObjectOutputStream
继承关系图
2. FileInputStream—文件字节输入流
package IOFlow;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileFlow {
public static void main(String[] args) {
//定义到try{}块外,使整个mian方法中都可以使用 --- 新特性定义到try的()内效果一样
FileInputStream fileInputStream = null; //下列文件内容均为"fristabcd"
/* 该类参数为File/String的构造方法都抛出了异常
public FileInputStream(File file)
throws FileNotFoundException
public FileInputStream(String name)
throws FileNotFoundException
所以创建对象时需要进行上抛或捕捉
*/
try {
//'\'转义字符,'\\'这为普通'\'字符
fileInputStream = new FileInputStream("D:\\文件\\FileInputStream1.txt");
//public int read() throws IOException 每次调用时均(接着上一个位置)读取一个字节返回该字节的ASCII值,读完后返回-1,
System.out.println(fileInputStream.read());//102
//循环读取
int intReader = 0;
while((intReader=fileInputStream.read())!=-1){
System.out.print(intReader+" ");//105 114 115 116 97 98 99 100
}
fileInputStream = new FileInputStream("D:\\文件\\FlieInputStream2.txt");
/*public int read(Byte[] b) throws IOException (与read()读取原理相同)读取
最多b.length个字节到b中,返回读取个数每次读取都是由byte[0]开始赋值,读取完毕后
放回-1,未覆盖调的byte保持原值*/
byte[] bytes = new byte[5];//(0~4)
fileInputStream.read(bytes);
System.out.println(new String(bytes));//frist
fileInputStream.read(bytes);//abcdt----第二次读取时字节不满5个,所以byte[4](t)未被覆盖调
System.out.println(new String(bytes));
/*int intReader2 = 0;
循环输出指定文件内容(不多输出t)
while((intReader2=fileInputStream.read(bytes))!=-1){
System.out.print(new String(bytes,0,intReader2));//fristabcd
}
*/
fileInputStream = new FileInputStream("D:\\文件\\FileInputStream3.txt");
// public int available() 返回未读取的字节数;
byte[] bytes1 = new byte[fileInputStream.available()];
fileInputStream.read(bytes1);
System.out.println(new String(bytes1));//fristabcd
}catch(FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException(e);
} finally{
//流使用后必须关闭,放入finally中来保证该方法必定执行
// public void close() throws IOException 关闭指定流
try {
fileInputStream.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
3. FileOutputStream—文件字节输出流
package IOFlow;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileFlow_FileOutputStream {
/*构造方法:
FileOutputStream(File/String file/name) 创建一个向 指定File对象表示/具有指定name 的文件中写入数据的数据流
每次运行程序调用第一个write时清空原内容(每次开启流时)
FileOutputStream(File/String file/name,boolen append) 创建一个向 指定File对象表示/具有指定name 的文件中
写入数据的数据流 当append为true时 每次运行调用第一个write时将不在清空原内容
注意:如果文件不存在底层会调用CreatNewFile创建文件(所以要保证文件所在的文件夹存在)
*/
public static void main(String[] args) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream("D:\\文件\\FileOutputStream1.txt");
// public void write(int b) throws IOException 将指定int数据写入输出流
fileOutputStream.write(99);
byte[] bytes = {101,102,103,104,105};
//public void write(byte[] b)将指定byte数组写入输出流 void write(byte[] b,int off,int,len) off初始位置,len长度
fileOutputStream.write(bytes);
String s = "我好难过啊";
fileOutputStream.write(s.getBytes());
// 输入流使用完毕必须刷新
fileOutputStream.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally{
try {
fileOutputStream.close();//必须关闭
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
4. FileReader,FileWriter
依靠字节流实现读取文件,所以方法与字节流一致,因为底层调用的还是字节流(包括构造方法的格式)
字符输入流:底层将文件按字节读入字符缓冲区,再根据编码转为字符。再读入程序
字符输入流:将字符读入字符缓冲区,在根据编码转为字节流再放入文件中
文件字符输入流
与FileInputStream方法一致只是read方法的参数由byte(字节)数组变成了char(字符)数组
文件字符输出流
与FileOutputStream方法一致只是writer方法的参数由byte(字节)数组变成了char(字符)数组
注意:FileWriter的write方法参数可以是String类型即可以写入字符串
字节流的底层还是字符流
文件字符流只能拷贝普通文本(可以用记事本打开的.txt文件)
示例
package IOFlow;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileTer {
public static void main(String[] args) {
FileReader reader = null;
FileWriter writer = null;
try {
//创建流
reader = new FileReader("MyFristJava\\src\\IOFlow\\input");
writer = new FileWriter("MyFristJava\\src\\IOFlow\\outputTer");
char[] chars = new char[1024*1024];
int i = -1;
//读入文件并写入另一个文件
while((i=reader.read(chars))!=-1){
writer.write(chars);
}
//手动刷新输出(缓冲区没满的时候不会自动写到文件中)
/*
字符流的所有的写的方法都不是直接写到硬盘中,而且写到底层的字符数组中(字符缓冲
区中),当字符缓冲区满了,或者手动调用flush() 才会参照码表编码成字节,然后通过字节
流输出到文件中。缓冲区数组中存储的为字节。
*/
//这里不flush也可以,因为在调用close方法关闭字符输出流时,会先flush在关闭流。
/*
所以即使不手动flush,在关闭流的时候如果缓冲区还没满也一定会写到文件中,如果在
读的过程中缓冲区满了,会自动刷进去,在继续往文件中读
*/
writer.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
finally{
if(reader != null) {
try {
//一定要关闭流
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
5. 缓冲流
-
缓冲流字节没有读写能力,需要传入字节/字符流,缓冲流也称为高效流,或者高级流,字节流可以称为原始流。
-
缓冲流自带缓冲区,可以提高原始字节流和字符流读写数据的性能。缓冲流的原理就是,创建流对象时,会创建一个内置的默认大小的缓冲区数组(相当于上面我门拷贝文件的时候创建的数组)长度为1024*8,通过缓冲区进行读写,减少系统的IO次数,提高读写效率。
-
通过缓冲区进行读写:将文件读写到缓存中,然后在对该缓存内容进行读写,就是将我门经常创建的数组的操作给包装了一下,效率还是不如我们自己创建数组。但字符缓冲流提供了readLine(BufferedRead)和newLine(BufferedWrite)方法。所以字节缓冲流死了,字符缓冲流还活着
- readLine和newLine方法可以根据操作系统识别和生成换行符,需要注意的是readLine但并没有读入换行符,所以不会自动换行。
- readLine和newLine方法可以根据操作系统识别和生成换行符,需要注意的是readLine但并没有读入换行符,所以不会自动换行。
6. 转换流(了解即可)
可以指定文件编码格式
InputStreamReader(InputStream in,String s) s为编码
OutputStreamWriter(OutputStream in)
就是利用其构造方法将字节流转换为字符流
7. 数据流
构造方法
DataInputStream(InputStream in)
DataOutputStream(OutputStream in)
可以让数据一帧一帧的写, 每个数据默认都有帧的间隔符, 不用我们自己操作数据数据间隔的问题
间隔符是给数据输出流看的
示例
public class Test {
public static void main(String[] args) throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("day10_api/b.txt"));
dos.writeUTF("aaa");
dos.writeUTF("bbb");
dos.writeUTF("ccc");
dos.close();
//写入的目的是为了读出
DataInputStream dis = new DataInputStream(new FileInputStream("day10_api/b.txt"));
String s = dis.readUTF();
System.out.println(s);
s = dis.readUTF();
System.out.println(s);
s = dis.readUTF();
System.out.println(s);
dis.close();
}
}
//有自己的缓冲区
结果
8. 打印流(了解)
打印流只有输出流,为字节/字符输出流的子类,所以字节打印流用于字节输出流的方法,字符打印流用于字符输出流的方法
package IOFlow;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
public class PrintFlow {
/*
构造方法
PrintStream(OutputStream/File/String) 关联字节输出流/文件/文件路径
PrintStream(String fileName, Charset charset) 指定字符编码
PrintStream(OutputStream/Writer out, boolean autoFlush) 自动刷新
PrintStream(OutputStream/Writer out, boolean autoFlush, String encoding)
常用方法
void print1n(Xxx xx) 常规方法:规则跟之前一样,将指定的字节写出
void print1n(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
void print(Xxx xx) 特有方法:打印任意数据,不换行
void printf(String format, object... args)特有方法: 带有占位符的打印语句,不换行
*/
public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
PrintStream printStream = new PrintStream(new FileOutputStream("MyFristJava\\src\\IOFlow\\outnputPrint"), false, "UTF-8");
printStream.print(123);
printStream.printf("%s%d%c","李俊是",2,'B');
printStream.close();
/*
打印流应用场景
public final static InputStream in = null;
public final static PrintStream out = null;
以上是Stystem类的in与out变量调用的时候会默认创建一个指向控制台的流
*/
//获取打印流对象,此打印流在虚拟机启动时,由虚拟机创建,默认指向控制台
//特殊的打印流,系统的默认输出流,不能关闭
PrintStream is = System.out;
//调用打印流的方法
is.println("标准输出流");
/*System中的其他方法
System.out就是标准流(打印流)
System.exit(0/其他数字) 0虚假正常关闭,其他数据虚拟机异常关闭
System.arraycopy(T,int,T,int,int) 数组扩容
System.setOut(File/Sting) 改变标准流输出方向
*/
}
}
二、File类
上代码
import java.io.File;
import java.io.IOException;
//File类与流不同File 类不能访问文件内容本身只能对目录进项操作,如果需要访问文件内容本身,则需要使用输入/输出流。
public class FilePractice {
/*
File 类提供了如下三种形式构造方法。
File(String path):如果 path 是实际存在的路径,则该 File 对象表示的是目录;如果 path 是文件名,
则该 File 对象表示的是文件。
File(String path, String name):path 是路径名,name 是文件名。
File(File dir, String name):dir 是路径对象,name 是文件名。
*/
public static void main(String[] args){
/*
File类的常用方法
boolean exists() 测试当前 File 是否存在
boolean isDirectory() 测试当前 File 对象表示的文件是否为一个路径
boolean isFile() 测试当前 File 对象表示的文件是否为一个“普通”文件
String getAbsolutePath() 返回由该对象表示的文件的绝对路径名
String getName() 返回表示当前对象的文件名或路径名(如果是路径,则返回最后一级子路径名)
String getParent() 返回当前 File 对象所对应目录(最后一级子目录)的父目录名
long lastModified() 返回当前 File 对象表示的文件最后修改的时间 (获取的是1970年到现在的总毫秒数)
long length() 返回当前 File 对象表示的文件长度
boolean mkdir() 创建一个目录,它的路径名由当前 File 对象指定 (父目录必须存在)
boolean mkdirs() 创建一个目录,它的路径名由当前 File 对象指定 (创建多重目录)
boolean createNewFile() 创建文件
注意:
只能创建文件
文件路径必须存在
文件存在则创建失败
boolean delete() 删除当前对象指定的文件
注意:
只能删除空文件夹或者删除文件
不走回收站
File[] listFiles() 返回当前目录下所有的子文件(返回的是File文件是对象)
注意:
只能文件夹使用,文件使用会得到一个null,空文件夹则会返回一个空数组(空数组是长度为0不是不存在)
有权限的文件夹不能用,如果使用返回为null
只能获取指定文件夹下的一级文件
String[] list() 返回当前 File 对象指定路径下文件列表(返回的是文件名)
*/
File file1 = new File("D:\\文件\\期末课程设计大作业");
File file2 = new File("D:\\文件\\期末课程设计大作业\\文件java");//用来创建目录的
File file3 = new File("D:\\文件\\期末课程设计大作业\\文件java.txt");
if (file2.exists()){//判断该文件/目录是否存在
file2.delete();//存在的话删除
}else {
file2.mkdir();//创建目录
}
if (file3.exists()) {
try {
file3.createNewFile();//创建文件
} catch (IOException e) {
throw new RuntimeException(e);
}
}
System.out.println(file1.getName());//返回文件名
System.out.println(file1.getAbsolutePath());//返回文件路径
if (file3.isFile()){
System.out.println("file3是一个文件长度为:"+file3.length());
}
File[] files = file1.listFiles();//获取目录下的所有子文件
for (File file:files){
System.out.println(file.getAbsolutePath());
}
}
}
三、序列化
通过对象流java.ObjectOutputStream和java.ObjectInputStream实现
package IOFlow;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
//序列化话和反序列化
public class ObjiectFlow {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Text text1 = new Text(23,"李俊");
//ObjectOutputStream(OutputStream in)对象流,用于序列化
ObjectOutputStream objectOutputStream1 = new ObjectOutputStream(new FileOutputStream("MyFristJava\\src\\IOFlow\\student1"));
ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(new FileOutputStream("MyFristJava\\src\\IOFlow\\student2"));
//writeObject(Object o) 序列化对象
objectOutputStream1.writeObject(text1);
//同一个文件中只能序列化一个对象,序列化多个对象需要借住集合
List list = new ArrayList();
list.add(new Text(23,"李俊"));
list.add(new Text(22,"唐小军"));
objectOutputStream2.writeObject(list);
//ObjectInputStream(InputStream in)对象流,用于反序列化
ObjectInputStream objectInputStream1 = new ObjectInputStream(new FileInputStream("MyFristJava\\src\\IOFlow\\student1"));
ObjectInputStream objectInputStream2 = new ObjectInputStream(new FileInputStream("MyFristJava\\src\\IOFlow\\student2"));
//readObjiect() 反序列化对象
System.out.println(objectInputStream1.readObject());
System.out.println(objectInputStream2.readObject());
objectOutputStream1.flush();
objectOutputStream2.flush();
objectOutputStream2.close();
objectInputStream1.close();
objectOutputStream1.close();
objectInputStream2.close();
}
}
//用于序列化/反序列化的对象必须实现其Serializable标志接口 该接口中无内容
class Text implements Serializable{
public Text(){}
public Text(int age,String name){
this.age = age;
this.name = name;
}
private int age;
//transient 关键字修饰的成员变量不参与序列化
private transient String name;
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
@Override
public String toString() {
return "Text{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
- 序列化版本号—序列化需要实现Serializable接口的原因就是给jvm标识该类生成序列化版本号
- 为什么要序列化
- 一些场景下需要把对象转变成字节序列
- 保存到存储介质上(磁盘等)
- 用于网络传输
- 一个很常见的应用是dubbo的RPC调用,如果参数是一个bean,那么远程调用的时候必然需要传递参数对象,这时候就必须将转变为字节序列(序列化)然后通过网络传输
- 一些场景下需要把对象转变成字节序列
- 序列化版本号的用处
- 在序列化存储/反序列化读取 或者是 序列化传输/反序列化接收 时,JVM 会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
- 在对实体类进行不影响业务流程的升级时,比如只追加了一个附加信息字段,可以不改变序列化版本号,来实现新旧实体类的兼容性(接收方的类里没有的字段被舍弃;多出来的字段赋初始值)。
- 比如上述例子,如果在Text类进行序列化后,删除类中地age属性添加一个String address属性未手动自定序列化版本号进行反序列化会报错,手动自定后的返回值为 name = null address=null
- 如何设定版本号
- 手动控制版本号
- private staticfinallongserialVersionUID =1L;
- 实体类升级以后,可以手动控制版本号升级与否。
- private staticfinallongserialVersionUID =1L;
- 让IDE根据类名、接口名、成员方法及属性等来自动生成一个64位的哈希字段
- private staticfinallongserialVersionUID =XXXL;
- 大部分IDE都提供了自动生成这个哈希数的功能(对于实现了Serializable接口的类都有提示)。
- 不显示定义 serialVersionUID
- 在这种情况下 jvm 会根据类的成员变量, 成员方法,构造等, 自动生成 serialVersionUID,如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID值会发生变化。而且类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,也有可能会导致不同的serialVersionUID。
- 手动控制版本号
四、Commons-util和IO流异常机制的新特性
1. Commons-util
2. IO流异常机制的新特性
IO流都实现了AutoCloseable接口都有close方法,try小括号中的语句作用域为try-catch外最近的{}内
五、特殊文件和日志
1. .properties文件
- 设计理念
- 经常改变的数据可以单独写到一个文件中,使用文件动态读取,将来只需要改动文件不需要动代码,不需要重新编译,也不需要重启服务器
- 一个key可以对应多个value
- Hashtable的子类Properties集合主要用来配合IO流一起完成对配置文件Properties的交换交互
- 经常改变的数据可以单独写到一个文件中,使用文件动态读取,将来只需要改动文件不需要动代码,不需要重新编译,也不需要重启服务器
集合方法
package IOFlow;
//IO与Properties集合的连用---将文件中的数据转到集合中 注意:数据必须是key=value格式
//properties是专一针对字符串键值对处理的集合,所以properties集合目前的唯一用途就是读写配置文件。
/*
Properties集合继承了Hashtable集合所以有map集合的相关方法put,get,keySet,erntrySet等,Properties在继承
Hashtable的时候指定了集合的泛型为Object,所以对应的方法参数类型均为Object。(这些功能不足与让properties活着)
Properties最重要的是,Properties集合提供了load(Reader/InputStream in)方法将文件中的键值对转换为Properties集合,
并且提供了setProperty(String,String)以及getPropert(String)来存入和取出String类型的数据,来简化对配置文件的操作(否则
都是Object类型,转String比较麻烦)
*/
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class IOproperties {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("MyFristJava\\src\\IOFlow\\IOProperties");
Properties properties = new Properties();
//void load(Reader/InputStream in)方法 将流转为Properties集合
/*
注意:
构造方法传入字节流和字符流区别:
(1)Properties内置有编码IS0-8859-1,如果你传入的字节流,他内部会将这个字节流和内置的编码组装成字符流
(2)忠告:以后尽量不要properties中写中文
(3)字符流则直接根据字符读取
*/
properties.load(fileReader);
//该方法会自动跳过‘#’的注释内容
fileReader.close();
Set<Map.Entry<Object, Object>> set = properties.entrySet();
for (Map.Entry<Object,Object> i :set) {
System.out.println(i);
}
//store(流,String 注释信息) 将集合内容写到流中 了解即可
//setProperty(String key,String vlue)
}
}
2. .XML文件
Proterties述配置文件只能以键值对的形式存在,无法写入多组数据,XML配置文件则可以解决这个问题
<?xml version="1.0" encoding="UTF-8" ?> <!-- 文档声明标必须在xml的中第一行顶头 -->
<!-- XML为可扩展标记语言,标签名想定义什么就定义什么 用于程序解析-->
<!-- ctrl + shift + / 注释-->
<!-- xml有且只能一个跟标签 -->
<users>
<!--
标签写法:
双标签(围堵标签): 开始标签和结束标签共同构成
单标签(自闭和标签): 只有开始标签, 开始标签也是结束标签
标签名:
符合合法的标识符
特殊字符的支持:
>:>
<:<
CDATA区域:可以在区域中直接写特殊字符
<![CDATA[
]]>
-->
<user>
<shuangbiaoqian>双标签</shuangbiaoqian>
<dangbiaoqian 属性1 = "属性值1" 属性2="属性值2" /> <!-- 一般属性会
写到user上,没必要为了属性在单端创建一个标签-->
<biaoqian>
<![CDATA[
age <<<<< 10;
]]>
</biaoqian>
</user>
</users>
XML文件解析步骤(了解即可,用的时候百度)
通过导入dom4j依赖包来完成
package day11;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.List;
public class Test1 {
public static void main(String[] args) throws DocumentException {
//1.需要将硬盘上的xml文件得到内存中, 以Document对象存储起来
//创建ASXReader对象,通过对象的reader方法获取xml文件对象Document
SAXReader reader = new SAXReader();
Document document = reader.read(new File("H:\\Java\\JAVASE\\HeiMaJavaSE\\JavaSEPro\\src\\day11\\b.XML"));
//2.通过document对象获取xml的跟标签
Element rootElement = document.getRootElement();
//3.通过跟标签获取一级子标签
List<Element> userList = rootElement.elements("user");
//4.遍历一级子元素
for (Element userElement : userList) {
String username = userElement.elementText("username");
String password = userElement.attributeValue("value");
System.out.println(username);
System.out.println(password);
}
/* for (Element userElement : userList) {
Element usernameElement = userElement.element("username");
//获取标签的标签体内容
String username = usernameElement.getText();
System.out.println(username);
//获取标签的属性
String password = userElement.attribute("value").getValue();
System.out.println(password);
}*/
}
}
XML文件内容
解析结果
3. .class文件的动态获取
- 编译器, 会将编译的后生成的.class文件放到对应的out文件下,out文件的结构与java模块结构类似,编译器会将模块中src下的.java的文件全部翻译成.class并放入字节码路径, 非.java会原封不动拷贝到字节码路径下对应的目录
- 所以配置文件要求必须在src下,以免造out文件夹下配置文件存在异常的问题
- 一般来说拿到的.class文件都是一个个完整的模块,在class文件中相对路径的跟路径为模块,相当于源文件的src
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Properties;
/*
如何读取字节码路径下的配置文件才是流的重-点, 因为将来以后交付给客户的是字节码路径的东西
动态获取字节码路径的方式
类加载器:0
1.启动类加载器:Bootstrap classloader
加载大部分的jdk原生类, 比如int.class, String.class, Object.class,
因为这个加载器是c写的, 所以直接获取得到是null
2.平台类加载器(扩展类加载器): Platform classloader
加载jdk中, ext包下扩展类
3.系统类加载器: System(App) classloader
加载我们自己写的类
加载器的的双亲委派模型(其实就是一种询问机制)保证类只会加载一次
比如,需要加载student类的时候去依次找其父类,判断父类有没有加载,如果加载过就不在加载
获取类加载器
ClassLoader c = ClassLoader.getSystemClassLoader();
类加载的作用
用于加载类的字节码, 当运行程序的时候, 其实运行的.class的字节码文件, 通过字节码文件的包路径找到
项目的字节码跟路径(只要通过类加载器获取东西, 默认就是从字节码的根路径开始找)
提供哪些方法来获取资源
URL url= getResource(String 相对路径);此相对路径为.class文件的相对路径
URL: 统一资源定位符, 精准定位, 里面含有一个绝对路径
String path = url.getPath();
URL路径的特点: 不能有中文或者特殊字符, 否则会经过URL编码解码
URLEncoder 编码
URLEncoder.encode(String path, 原来的字符集);
URLDecoder 解码
URLDecoder.decode(String path, 需要组装字符集);
*/
public class Test {
public static void main(String[] args) throws IOException {
/*方式一
1.通过类加载器的静态方法getSystemClassLoader()获取类加载器对象
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
2.通过类加载器的实例方法getResource(String 相对路径)获取URL 对象
注意:相对路径class文件的相对路径等同于java模块源文件下以src为跟路径的相对路径(即模块为根路径,但是out文件夹中少了src这一级)
URL resource = classLoader.getResource("a.properties");
3.通过URL的实例方法getPath()方法将URL转换为string字符串
String path = resource.getPath();
System.out.println(path);
4.如果路径有中文需要通过URLDecoder的静态方法decode(String 路径, String 编码)将其解码
String decode = URLDecoder.decode(path, "utf-8");
System.out.println(decode);
5.通过路径创建文件并读到Properties集合中
Properties properties = new Properties();
properties.load(new FileInputStream(decode));
System.out.println(properties);
*/
//方式二
//直接通过类加载器的静态方法getSystemResourceAsStream获取流对象
InputStream systemResourceAsStream = ClassLoader.getSystemResourceAsStream("a.properties");
/* 推荐这种
通过类加载器的实例方法getResourceAsStream获取流对象
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("a.properties");
*/
Properties properties = new Properties();
properties.load(systemResourceAsStream);
System.out.println(properties);
}
}
4. 日志
日志使用步骤
1.导入相关依赖
2.创建logback.xml配置文件
3.创建日志对象(org.slf4j包下的)
4.调用方法
示例
public class Test {
public static void main(String[] args) throws FileNotFoundException {
Logger logger = LoggerFactory.getLogger("作者");
logger.info("调试信息");
logger.error("错误信息");
logger.warn("警告信息");
logger.debug("测试信息");
int a = 1;
int b = 2;
//日志提供了一种特殊的打印方式如下:
logger.debug("a={}b={}",a,b); //{}代表参数位置,后边的参数以逗号分开,并按顺序插入‘{}’位置
}
}
logback.xml
<?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>H:/log/itheima-data.log</file>
<!--指定日志文件拆分和压缩规则-->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>D:/log/itheima-data-%i-%d{yyyy-MM-dd}-.log.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
1、控制日志的输出情况:如,开启日志,取消日志 off>error>warn>inof>debug>all
-->
<root level="debug"> <!-- 这里指定打印级别 定义后可以打印出 -->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>