当前位置:网站首页>十六、IO流(二)
十六、IO流(二)
2022-06-13 06:18:00 【初犊】
一、字符流
1.1 引入
public class Demo1 {
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream("a.txt");
int res = -1;
while ((res = fi.read()) != -1) {
System.out.print((char)res); // ä½ å¥½world 出现乱码
// 中文UTF-8编码占用3个字节,read()每次读1个字节,所以有乱码
}
fi.close();
}
}
字符流的介绍
由于字节流操作中文不是特别的方便,所以
Java
就提供字符流字符流 = 字节流 + 编码表
中文的字节存储方式
- 用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
- 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
1.2 编码表
什么是字符集
- 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
- 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有
ASCII
字符集、GBXXX
字符集、Unicode
字符集等
常见的字符集
ASCII
字符集:ASCII
:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
基本的
ASCII
字符集,使用7位表示一个字符,共128字符。ASCII
的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等GBXXX
字符集GBK
:最常用的中文码表。是在GB2312
标准基础上的扩展规范,使用了双字节编码方案,共收录了21003
个汉字,完全兼容GB2312
标准,同时支持繁体汉字以及日韩汉字等
Unicode
Unicode
,是计算机科学领域里的一项业界标准。为每种语言中的每个字符设定了统一并且唯一的整数值,把这个数值称为码点(Code Point)
。但是它并不规定计算机如何存储和传输这个数值(以多少个字节存储,是定长还是变长,
Unicode
没有字节长度的概念)。Unicode
定义了一个码点空间包含1,114,112
个码点,范围从0
到0x10FFFF
,也就是说,它仅仅是一个字符映射集。其中0x0000 ~ 0xFFFF
的字符表示常用字符集,称BMP
字符,0x10000 ~ 0x10FFFF
字符叫增补字符。Unicode
目前规划的总空间是17个平面(平面0至16),0x0000 至 0x10FFFF
,每个平面有 65536 个码点。
例如,"AB中国"这个字符串,对应的Unicode
编码为:A -> \u0041 B -> \u0042 中 -> \u0049 国 -> \u56FD
UTF32
编码- 4个字节表示一个字符,总共
2^32=4294967296
- 其高位均为0,空间浪费比较严重,因此应用很少
- 4个字节表示一个字符,总共
常用的
UTF16
一般使用两个字节表示常用字符,对于不能表示的或不常用的字符才使用32位编码,这是Windows
程序默认的Unicode
编码方式- 使用 1 ~ 2 个
16bit
变长编码表示1,112,064
个Unicode
码点 - 它扩展于固定
16bit
长度的UCS-2
- 使用 1 ~ 2 个
UTF8
编码按照不同的国家文字的多少分别使用1个字节、2个字节、3个字节和4个字节表示,常用于网络传速。- 使用 1 ~ 4 个字节变长编码表示
1,112,064
个Unicode
码点 - 兼容
ASCII
- 码点数值越小,使用的字节数越少,出现的频率越高
- 使用 1 ~ 4 个字节变长编码表示
1.3 字符串中的编码解码问题
相关方法
方法名 说明 byte[] getBytes()
使用平台的默认字符集将该 String
编码为一系列字节byte[] getBytes(String charsetName)
使用指定的字符集将该 String
编码为一系列字节String(byte[] bytes)
使用平台的默认字符集解码指定的字节数组来创建字符串 String(byte[] bytes, String charsetName)
通过指定的字符集解码指定的字节数组来创建字符串 代码演示
public class Demo2 { public static void main(String[] args) throws UnsupportedEncodingException { // byte[] getBytes() 使用平台的默认字符集将该String编码为一系列字节 // byte[] getBytes(String charsetName) 使用指定的字符集将该String编码为一系列字节 String str = "字符流编码"; byte[] bytes1 = str.getBytes(); System.out.println(Arrays.toString(bytes1)); byte[] gbks = str.getBytes("GBK"); System.out.println(Arrays.toString(gbks)); // String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串 // String(byte[] bytes, String charsetName)通过指定的字符集解码指定的字节数组来创建字符串 String s1 = new String(bytes1); String s2 = new String(gbks,"gbk"); System.out.println(s1); System.out.println(s2); } }
1.4 字符流写数据
介绍
Writer
:用于写入字符流的抽象父类FileWriter
:用于写入字符流的常用子类
字符流的底层依旧调用了字节流,其实质:字节流+编码表
构造方法
方法名 说明 FileWriter(File file)
根据给定的 File
对象构造一个FileWriter
对象FileWriter(File file, boolean append)
根据给定的 File
对象构造一个FileWriter
对象FileWriter(String fileName)
根据给定的文件名构造一个 FileWriter
对象FileWriter(String fileName, boolean append)
根据给定的文件名以及指示是否附加写入数据的 boolean
值来构造FileWriter
对象注:
- 若文件不存在会默认创建,但要先确保其父级路径存在
- 若文件存在,则默认会清空文件内容!!,除非设定追加
成员方法
方法名 说明 void write(int c)
写一个字符 void write(char[] cbuf)
写入一个字符数组 void write(char[] cbuf, int off, int len)
写入字符数组的一部分 void write(String str)
写一个字符串 void write(String str, int off, int len)
写一个字符串的一部分 - 注:写入的默认都是数字在编码表对应的字符,若要写入数字,可以使用字符串的方式写入
刷新和关闭的方法
方法名 说明 flush()
刷新流,之后还可以继续写数据 close()
关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 - 注:刷新流会立即将缓冲区的内容刷新到磁盘上
代码演示
public class Demo3 { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter("a.txt"); //void write(int c):写一个字符 fw.write(97); //void writ(char[] cbuf):写入一个字符数组 char[] chars = { 97, 98, 99, 100}; fw.write(chars); //void write(char[] cbuf, int off, int len):写入字符数组的一部分 fw.write(chars, 0, 3); //void write(String str):写一个字符串 String str1 = "字符流写入字符串"; fw.write(str1); //void write(String str, int off, int len):写一个字符串的一部分 fw.write(str1, 1, 2); // a.txt内容:aabcdabc字符流写入字符串符流 //释放资源 fw.close(); } }
1.5 字符流读数据
- 介绍
Reader
:用于读取字符流的抽象父类FileReader
:用于读取字符流的常用子类
构造方法
方法名 说明 FileReader(File file)
在给定从中读取数据的 File
的情况下创建一个新FileReader
FileReader(String fileName)
在给定从中读取数据的文件名的情况下创建一个新 FileReader
成员方法
方法名 说明 int read()
一次读一个字符数据 int read(char[] cbuf)
一次读一个字符数组数据 代码演示
public class Demo4 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("a.txt"); System.out.println(fr.read()); // 一次读一个字符数据 // 一次读取一个字符数组 char[] chars = new char[1024]; int len; while ((len = fr.read(chars)) != -1) { System.out.println(new String(chars, 0, len)); } fr.close(); } }
1.6 字符流用户注册案例
案例需求
- 将键盘录入的用户名和密码保存到本地实现永久化存储
实现步骤
- 获取用户输入的用户名和密码
- 将用户输入的用户名和密码写入到本地文件中
- 关流,释放资源
代码实现
public class Demo5 { public static void main(String[] args) throws IOException { // 实现键盘录入,把用户名和密码录入进来 Scanner sc = new Scanner(System.in); System.out.println("请录入用户名"); String username = sc.next(); System.out.println("请录入密码"); String password = sc.next(); // 分别把用户名和密码写到本地文件。 FileWriter fw = new FileWriter("a.txt"); // 将用户名和密码写到文件中 fw.write(username); // 表示写出一个回车换行符 windows \r\n MacOS \r Linux \n fw.write("\r\n"); fw.write(password); // 关流,释放资源 fw.close(); } }
1.7 字符缓冲流
字符缓冲流介绍
BufferedWriter
:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大(8192),可用于大多数用途BufferedReader
:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大(8192),可用于大多数用途
构造方法
方法名 说明 BufferedWriter(Writer out)
创建字符缓冲输出流对象 BufferedReader(Reader in)
创建字符缓冲输入流对象 代码演示
public class Demo6 { public static void main(String[] args) throws IOException { // BufferedWriter(Writer out) BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); // 存在默认清空 bw.write("line1"); bw.write("\r\n"); bw.write("line2"); bw.close(); // BufferedReader(Reader in) BufferedReader br = new BufferedReader(new FileReader("a.txt")); char[] chars = new char[1024]; int len; while ((len = br.read(chars)) != -1) { System.out.println(new String(chars, 0, len)); } br.close(); } }
1.8 字符缓冲流特有功能
方法介绍
BufferedWriter
类中:
方法名 说明 void newLine()
写一行 行分隔符,行分隔符字符串由系统属性定义 BufferedReader
类中:
方法名 说明 String readLine()
读一行文字。结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为 null
代码演示
public class Demo7 { public static void main(String[] args) throws IOException { // BufferedWriter(Writer out) BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); // 存在默认清空 bw.write("line1"); bw.newLine(); bw.write("line2"); bw.close(); // BufferedReader(Reader in) BufferedReader br = new BufferedReader(new FileReader("a.txt")); System.out.println(br.readLine()); System.out.println(br.readLine()); br.close(); } }
1.9 字符缓冲流操作文件中数据排序案例
案例需求
使用字符缓冲流读取文件中的数据,排序后再次写到本地文件
实现步骤
- 将文件中的数据读取到程序中
- 对读取到的数据进行处理
- 将处理后的数据添加到集合中
- 对集合中的数据进行排序
- 将排序后的集合中的数据写入到文件中
代码实现
import java.io.*; import java.util.Arrays; public class Demo8 { public static void main(String[] args) throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); bw.write("1 3 4 6 2 7 2 5 3 9"); bw.close(); // 读取文件中乱序的字符串 BufferedReader br = new BufferedReader(new FileReader("a.txt")); String s = br.readLine(); System.out.println("原内容:" + s); String[] strings = s.split(" "); int[] array = new int[strings.length]; for (int i = 0; i < strings.length; i++) { array[i] = Integer.parseInt(strings[i]); } // 排序 Arrays.sort(array); System.out.println("排序后:" + Arrays.toString(array)); // 清空源文件写入排序后的数组 BufferedWriter bw2 = new BufferedWriter(new FileWriter("a.txt")); for (int i = 0; i < array.length; i++) { bw2.write(array[i] + " "); } bw2.close(); } }
1.10 IO流小结
- IO流小结
二、转换流
2.1 字符流中和编码解码问题相关的两个类
InputStreamReader
:是从字节流到字符流的桥梁,父类是Reader
- 它读取字节,并使用指定的编码将其解码为字符
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
OutputStreamWriter
:是从字符流到字节流的桥梁,父类是Writer
- 是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
JDK 11
以后可以直接使用FileReader(String fileName, Charset charset)
指定字符集读取文件
2.2 转换流读写数据
构造方法
方法名 说明 InputStreamReader(InputStream in)
使用默认字符编码创建 InputStreamReader
对象InputStreamReader(InputStream in,String chatset)
使用指定的字符编码创建 InputStreamReader
对象OutputStreamWriter(OutputStream out)
使用默认字符编码创建 OutputStreamWriter
对象OutputStreamWriter(OutputStream out,String charset)
使用指定的字符编码创建 OutputStreamWriter
对象代码演示
public class Demo9 { public static void main(String[] args) throws IOException { outputStreamWriter(); // 写一个GBK编码的文件 inputStreamReader(); // 以GBK解码读取 InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\16085\\Desktop\\a.txt")); char[] chars = new char[1024]; int len; while ((len = isr.read(chars)) != -1) { // 读取GBK编码的文件出现乱码 System.out.println(new String(chars, 0, len)); // GBK�����ַ� } isr.close(); // JDK11 以后可以直接指定以何种方式解码 FileReader fileReader = new FileReader("C:\\Users\\16085\\Desktop\\a.txt",Charset.forName("GBK")); BufferedReader br = new BufferedReader(fileReader); System.out.println(br.readLine()); // GBK编码字符 } private static void outputStreamWriter() throws IOException { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\16085\\Desktop\\a.txt"),"GBK"); osw.write("GBK编码字符"); osw.close(); } private static void inputStreamReader() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\16085\\Desktop\\a.txt"),"GBK"); char[] chars = new char[1024]; int len; while ((len = isr.read(chars)) != -1) { // 指定GBK解码以后正常打印 System.out.println(new String(chars, 0, len)); // GBK编码字符 } isr.close(); } }
三、对象操作流
3.1 对象序列化流
对象序列化介绍
- 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
- 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
- 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
对象序列化流:
ObjectOutputStream
- 将
Java
对象的原始数据类型和图形写入OutputStream
。 可以使用ObjectInputStream
读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
- 将
构造方法
方法名 说明 ObjectOutputStream(OutputStream out)
创建一个写入指定的 OutputStream
的ObjectOutputStream
序列化对象的方法
方法名 说明 void writeObject(Object obj)
将指定的对象写入 ObjectOutputStream
示例代码
- 学生类
public class Student implements Serializable { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
测试类
public class Demo10 { public static void main(String[] args) throws IOException { Student s = new Student("zhangsan", 23); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); oos.writeObject(s); // 将对象s保存到文件中 oos.close(); } }
注意事项
- 一个对象要想被序列化,该对象所属的类必须必须实现
Serializable
接口 Serializable
是一个标记接口,实现该接口,不需要重写任何方法
- 一个对象要想被序列化,该对象所属的类必须必须实现
3.2 对象反序列化流
对象反序列化流:
ObjectInputStream
ObjectInputStream
反序列化先前使用ObjectOutputStream
编写的原始数据和对象
构造方法
方法名 说明 ObjectInputStream(InputStream in)
创建从指定的 InputStream
读取的ObjectInputStream
反序列化对象的方法
方法名 方法名 Object readObject()
从 ObjectInputStream
读取一个对象几种读写结束符对比
方法名 | 说明 |
---|---|
read() | 读取到文件末尾返回值是 -1 |
readLine() | 读取到文件的末尾返回值 null |
readObject() | 读取到文件的末尾 直接抛出异常 |
如果要序列化的对象有多个,不建议直接将多个对象序列化到文件中,因为反序列化时容易出异常
- 建议:将要序列化的多个对象存储到集合中,然后将集合序列化到文件中
示例代码
public class Demo11 { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); //Object readObject():从ObjectInputStream读取一个对象 System.out.println(ois.readObject()); // Student{name='zhangsan', age=23} ois.close(); } }
3.3 serialVersionUID&transient
serialVersionUID
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
- 会出问题,会抛出
InvalidClassException
异常
- 会出问题,会抛出
- 如果出问题了,如何解决呢?
- 重新序列化
- 给对象所属的类加一个
serialVersionUID
private static final long serialVersionUID = 42L;
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
transient
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
- 给该成员变量加
transient
关键字修饰,该关键字标记的成员变量不参与序列化过程
- 给该成员变量加
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
示例代码
学生类
public class Student implements Serializable { private static final long serialVersionUID = 42L; private String name; // private int age; private transient int age; ...... }
测试类
public class Demo12 { public static void main(String[] args) throws IOException, ClassNotFoundException { write(); // age被transient修饰,存储int默认值0 read(); } //反序列化 private static void read() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); //Object readObject():从ObjectInputStream读取一个对象 System.out.println(ois.readObject()); // Student{name='zhangsan', age=0} ois.close(); } //序列化 private static void write() throws IOException { Student s = new Student("zhangsan", 23); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); oos.writeObject(s); // 将对象s保存到文件中 oos.close(); } }
3.4 对象操作流练习
案例需求
- 创建多个学生类对象写到文件中,再次读取到内存中
实现步骤
- 创建序列化流对象
- 创建多个学生对象
- 将学生对象添加到集合中
- 将集合对象序列化到文件中
- 创建反序列化流对象
- 将文件中的对象数据,读取到内存中
代码实现
学生类(同上)
测试类
public class Demo13 { public static void main(String[] args) throws Exception { // 序列化 // 创建序列化流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); ArrayList<Student> arrayList = new ArrayList<>(); // 创建多个学生对象 Student s1 = new Student("zhangsan", 23); Student s2 = new Student("lisi", 24); // 将学生对象添加到集合中 arrayList.add(s1); arrayList.add(s2); // 将集合对象序列化到文件中 oos.writeObject(arrayList); oos.close(); // 反序列化 // 创建反序列化流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); // 将文件中的对象数据,读取到内存中 Object obj = ois.readObject(); ArrayList<Student> arrayList2 = (ArrayList<Student>) obj; ois.close(); // 遍历 for (Student s : arrayList2) { System.out.println(s); } } }
四、Properties集合
4.1 Properties作为Map集合的使用
Properties
介绍- 是一个
Map
体系的集合类 Properties
可以保存到流中或从流中加载- 属性列表中的每个键及其对应的值都是一个字符串
- 是一个
Properties
基本使用public class Demo14 { public static void main(String[] args) { Properties properties = new Properties(); // 增加键值 properties.put("id1", "zhangsan"); properties.put("id2", "lisi"); properties.put("id3", "wangwu"); // 删除键值 properties.remove("id3"); // 修改键值 properties.put("id2", "zhaoliu"); // 查询键值 System.out.println(properties.get("id1")); // 遍历 // 先获取键,再获取值 Set<Object> keySet = properties.keySet(); for (Object key : keySet) { Object value = properties.get(key); System.out.println(key + "=" + value); } System.out.println("--------------"); // 先获取entry集合,再分别获取键值 Set<Map.Entry<Object, Object>> entries = properties.entrySet(); for (Map.Entry<Object, Object> entry : entries) { Object key = entry.getKey(); Object value = entry.getValue(); System.out.println(key + "=" + value); } } }
4.2 Properties作为Map集合的特有方法
特有方法
方法名 说明 Object setProperty(String key, String value)
设置集合的键和值,都是 String
类型,底层调用Hashtable
方法put
String getProperty(String key)
使用此属性列表中指定的键搜索属性 Set<String> stringPropertyNames()
从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 示例代码
public class Demo15 { public static void main(String[] args) { Properties prop = new Properties(); // Object setProperty(String key, String value):设置集合的键和值,都是String类型 prop.setProperty("id1", "zhangsan"); prop.setProperty("id2", "lisi"); prop.setProperty("id3", "wangwu"); // String getProperty(String key):使用此属性列表中指定的键搜索属性 System.out.println(prop.getProperty("id1")); // Set<String> stringPropertyNames():从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 Set<String> names = prop.stringPropertyNames(); for (String key : names) { String value = prop.getProperty(key); System.out.println(key + "=" + value); } } }
4.3 Properties和IO流相结合的方法
和
IO
流结合的方法方法名 说明 void load(Reader reader)
从输入字符流读取属性列表(键和元素对) void store(Writer writer, String comments)
将此属性列表(键和元素对)写入此 Properties
表中,以适合使用load(Reader)
方法的格式写入输出字符流示例代码
public class Demo16 {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
// void load(Reader reader):从输入字符流读取属性列表(键和元素对)
FileReader fr = new FileReader("prop.properties");
prop.load(fr);
fr.close();
System.out.println(prop);
// void store(Writer writer, String comments)
// 将此属性列表(键和元素对)写入此Properties表中,comments为开头注释
prop.setProperty("id3","wangwu");
FileWriter fw = new FileWriter("prop.properties");
prop.store(fw,"");
fw.close();
}
}
prop.properties
内容
边栏推荐
- Recent problems
- Basic knowledge of knowledge map
- Wechat applet (pull-down refresh data) novice to
- 1+1 > 2, share creators can help you achieve
- Dynamic link library nesting example
- ADB shell content command debug database
- RFID process management solution for electroplating fixture
- After clicking the uniapp e-commerce H5 embedded applet, the page prompts "the page iframe does not support referencing non business domain names"
- Kotlin collaboration - start and cancel, scope
- Glide usage notes
猜你喜欢
Binary search
【虚拟机】 VMware虚拟机占用空间过大解决
Wechat applet: basic review
MFS详解(七)——MFS客户端与web监控安装配置
Echart histogram: stacked histogram displays value
JS convert text to language for playback
SSM integration
Notes on wechat applet development
Learning records countless questions (JS)
‘ipconfig‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。
随机推荐
App performance test: (II) CPU
Glide usage notes
Custom view subtotal
【sketchup 2021】草图大师的图像输出与渲染之样式说明【边线设置、平面设置、背景设置、水印设置、建模设置、天空背景创建天空、利用水印背景创建天空(重要)】
[JS] array de duplication
Hidden and wx:if
vue3路由缓存组件状态以及设置转场动画
Recyclerview has data flicker problem using databinding
Wechat applet custom tabbar (session customer service) vant
MFS explanation (VI) -- MFS chunk server installation and configuration
Use of kotlin basic common sets list, set and map
The processing and application of C language to documents
The boys x pubgmobile linkage is coming! Check out the latest game posters
SSM integration
杨辉三角形详解
[FAQs for novices on the road] understand program design step by step
【新手上路常见问答】一步一步理解程序设计
ADB shell content command debug database
Kotlin basic definition class, initialization and inheritance
The jadx decompiler can decompile jars and apks