Java --> IO流进阶(缓冲流、转换流、对象序列化、Properties、commons-io框架)
- 缓冲流
1 import java.io.*;
2
3 //使用字节缓冲流完成数据的读写操作
4 public class ByteBufferDemo {
5 public static void main(String[] args) throws Exception{
6 try(
7 //这里边放置资源对象,用完后会自动关闭(即使出现异常)
8 //以防出现在代码块上边出现异常导致两个管道都为null ,如:此处出现 System.out.prinln(10 / 0);
9 InputStream inputStream = new FileInputStream("D:\\Intellij_IDEA_install\\resourse_imgs\\beach.jpeg");
10 //1.add : 把原始的字节输入流包装成高级的缓冲字节输入流
11 InputStream bufferedInputStream = new BufferedInputStream(inputStream);
12
13
14 //创建字节输出流管道与目标文件接通
15 OutputStream outputStream = new FileOutputStream("D:\\Intellij_IDEA_install\\resourse_imgs\\beach2.jpeg");
16 //2.add : 把原始的字节输出流管道包装成高级的缓冲字节输出流管道
17 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
18
19 //int age = 9; //这样的代码防止在这里是不被允许的,只能放置资源,而所谓的资源,就是实现了AutoCloseable接口
20 //创建字节输入流管道与原视频接通
21 ) {
22 //定义一个字节数组转移数据
23 byte[] bytes = new byte[1024];
24 int len = 0; //len 用于记录每次读取到的数据个数(读多少倒多少,防止最后的一组数据装不满)
25 //使用缓冲流进行读取
26 while ((len = bufferedInputStream.read(bytes)) != -1 ){
27 bufferedOutputStream.write(bytes,0,len);
28 }
29 System.out.println("复制完成");
30 } catch (Exception e) {
31 e.printStackTrace();
32 }
33 }
34 }
- 字节缓冲流的性能分析
- 需求 : 分别使用低级字节流和高级字节缓冲流拷贝视频,记录耗时
- 分析:
- 使用低级的字节流按照一个一个字节的形式复制文件;
- 使用低级的字节流按照一个一个字节数组的形式复制文件;
- 使用高级的缓冲流按照一个一个字节的形式复制文件;
- 使用高级的缓冲流按照一个一个字节数组的形式复制文件
1 import java.io.*;
2
3 public class ByteBufferTimeDemo {
4 public static final String SRC_FILE = "D:\\tencent\\QQ\\MobileFile\\src_video.mp4";
5 public static final String DEST_FILE = "D:\\tencent\\QQ\\MobileFile\\"; //灵活(拷贝4次)
6
7 public static void main(String[] args) {
8 // 使用低级的字节流以一个一个字节的形式复制文件
9 //byteCopyOneByOne(); //太慢了啊~~~
10 // 使用低级的字节流以一个一个字节数组的形式复制文件
11 byteArrayCopy();
12 // 使用高级的缓冲流一个一个字节的形式复制文件
13 superBufferStream();
14 // 使用高级的缓冲流以一个一个数组的形式复制文件
15 superBufferStreamArrayCopy();
16 }
17
18 private static void superBufferStreamArrayCopy() {
19 //定义开始时间
20 long startTime = System.currentTimeMillis();
21 try (
22 //创建字节输入流与源文件接通
23 InputStream inputStream = new FileInputStream(SRC_FILE);
24 //使用高级的缓冲流包装低级流
25 InputStream bufferedInputStream = new BufferedInputStream(inputStream);
26 //创建字节输出流与目标文件接通
27 OutputStream outputStream = new FileOutputStream(DEST_FILE + "video4.mp4");
28 //使用高级的缓冲流包装低级流
29 OutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)
30 ){
31 //创建数组定义每次可以复制的字节数
32 byte[] bytes = new byte[1024];
33 //循环读取,len表示每次可以读取到的字节数
34 int len = 0;
35 while (( len = bufferedInputStream.read(bytes)) != -1){
36 bufferedOutputStream.write(bytes,0,len);
37 }
38 }catch (Exception e){
39 e.printStackTrace();
40 }
41 //定义结束时间
42 long endTime = System.currentTimeMillis();
43 System.out.println("使用高级的缓冲流以一个一个数组的形式复制文件耗时:" + (endTime - startTime)/1000.0 + "s" );
44 }
45
46 private static void superBufferStream() {
47 //定义开始时间
48 long startTime= System.currentTimeMillis();
49 try(
50 //创建字节输入流管道与源文件接通
51 InputStream inputStream = new FileInputStream(SRC_FILE);
52 //把低级的流包装成字节缓冲流
53 InputStream bufferedInputStream = new BufferedInputStream(inputStream);
54 //创建字节输出流管道与目的文件接通
55 OutputStream outputStream = new FileOutputStream(DEST_FILE + "video3.mp4");
56 //低级的字节输出流管道包装成缓冲字节输出流
57 OutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)
58 ){
59 //循环读取,定义len记录每次读取到的字节数
60 int len = 0;
61 while((len = bufferedInputStream.read()) != -1){
62 bufferedOutputStream.write(len);
63 }
64 }catch (Exception e){
65 e.printStackTrace();
66 }
67 //定义结束时间
68 long endTime = System.currentTimeMillis();
69 System.out.println("使用高级的缓冲字节流以一个一个字节复制的形式耗时:" + (endTime - startTime)/1000.0 + "s");
70 }
71
72 private static void byteArrayCopy() {
73 //记录开始时间
74 long startTime = System.currentTimeMillis();
75 try(
76 //创建字节输入流管道与源文件接通
77 InputStream inputStream = new FileInputStream(SRC_FILE);
78 //创建字节输出流管道与目的文件接通
79 OutputStream outputStream = new FileOutputStream(DEST_FILE + "video2.mp4")
80 ){
81 //创建字节数组定义每次可以复制的数据
82 byte[] bytes = new byte[1024]; //1k
83 //定义变量len用于记录每次读取到的字节个数(读多少倒多少)
84 int len = 0;
85 while((len = inputStream.read(bytes)) != -1){
86 outputStream.write(bytes,0,len);
87 }
88 }catch (Exception e){
89 e.printStackTrace();
90 }
91 //记录结束时间
92 long endTime = System.currentTimeMillis();
93 System.out.println("使用低级的字节流以一个一个字节数组的形式复制文件耗时:" + (endTime - startTime)/1000.0 + "s");
94 }
95
96 private static void byteCopyOneByOne() {
97 //开始时间毫秒值
98 long startTime = System.currentTimeMillis();
99 try(
100 //1、创建字节输入流管道与目标文件接通
101 InputStream inputStream = new FileInputStream(SRC_FILE);
102 //2、创建字节输出流管道与目的文件接通
103 OutputStream outputStream = new FileOutputStream(DEST_FILE + "video1.mp4");
104 ){
105 //定义变量len记录每次读取到的字节数
106 int len = 0;
107 while ((len = inputStream.read()) != -1){
108 outputStream.write(len);
109 }
110
111 }catch (Exception e){
112 e.printStackTrace();
113 }
114 //结束时间毫秒值
115 long endTime = System.currentTimeMillis();
116 System.out.println("使用低级的字节流以一个一个字节的形式复制文件耗时:" + (endTime - startTime)/1000.0 + "s");
117 }
118 }
示例程序运行结果(注:由于CPU不同时间的运算效率都不相同,所每次运行的结果都或多或少会有些许差异):
由于改视频文件有一亿八千多万字节(意味着要循环一亿八千多玩次),所以一个一个字节复制实在是太慢了,所以在此处就把改方法屏蔽掉了:
相对来说,所定义的字节数组越大,文件的复制速度就会越快。但是也并非绝对,如果把定义的数组大小比作一个桶,数据比作池子,相对来说是桶越大,装水的速度是越快,但是需要明白的一点是:桶大了之后那么取水和装水的速度会相应地增加,所以说在一定地情况下,尽可能地把桶定义地大一点有利于文件地复制速度。【一般字节是1kb,即1024个byte】。
总结:目前来看,如果要读写文件数据,使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,性能是最好的。
- 缓冲字符输入流
1 import java.io.BufferedReader;
2 import java.io.FileReader;
3 import java.io.Reader;
4
5 //使用缓冲字符输入流提高输入流的性能,新增了按照行读取的方法
6 public class BufferedReaderDemo1 {
7 public static void main(String[] args) {
8
9 try(
10 //创建文件字符输入流管道与源文件接通
11 Reader reader = new FileReader("file-io-app\\src\\data06.txt");
12 //将低级的字符输入流管道包装成高级的缓冲字符输入流
13 //Reader bufferedReader = new BufferedReader(reader);
14
15 //readLine:使用子类独有的功能时不能用多态的写法
16 BufferedReader bufferedReader = new BufferedReader(reader)
17 ) {
18 // //创建字节数组定义每次可以读取到的字节个数
19 // char[] chars = new char[1024];
20 // //定义变量用于记录每次读取到的字符个数
21 // int len = 0;
22 // while ((len = bufferedReader.read(chars)) != -1){
23 // String str = new String(chars,0,len);
24 // System.out.print(str);
25
26 //按照行进行读取(JDK8开始)
27 String line = null;
28 while ((line = bufferedReader.readLine()) != null) {
29 System.out.println(line); //因为是按行读取的(一行行读),所以此处需要换行
30 }
31 }catch(Exception e){
32 e.printStackTrace();
33 }
34 }
35 }
- 缓冲字符输出流
1 import java.io.BufferedWriter;
2 import java.io.FileWriter;
3 import java.io.Writer;
4
5 //缓冲字符输出流的使用
6 public class BufferedWriterDemo2 {
7 public static void main(String[] args) {
8 try(
9 //创建字符输出流管道与目标文件接通 【注:如果是追加管道在原始流的构造器里边添加true】
10 //Writer writer = new FileWriter("io-app2\\src\\out1.txt",true);
11 Writer writer = new FileWriter("io-app2\\src\\out1.txt");
12 //把低级的字符输出流管道包装成高级的缓冲字符输出流管道
13 BufferedWriter bufferedWriter = new BufferedWriter(writer)
14 ){
15 bufferedWriter.write("各种各样的数据:单个字符、字符数组、字符串、基本数据类型的数据");
16 bufferedWriter.newLine(); //换行新方法
17 bufferedWriter.write("第二行");
18 }catch (Exception e){
19 e.printStackTrace();
20 }
21 }
22 }
由于写法和之前的字符输出流类似,功能也只是套了层壳而已,所以就不再一 一演示各种类型的数据了。
- 案例:拷贝出师表到另一个文件,回复顺序
- 需求:把《出师表》的文章顺序进行回复到一个新的文件中去。
分析:
- 定义一个缓冲字符输入流管道与源文件接通;
- 定义一个List集合存储读取到的每行数据;
- 定义一个循环按照行读取数据、存入到List集合中去;
- 对List集合中的每行数据按照首字符编号进行排序;
- 定义一个缓冲字符输出流管道与目标文件接通;
- 定义List集合中的每个元素,用缓冲输出流管道写出并换行。
1 import java.io.*;
2 import java.util.ArrayList;
3 import java.util.Collections;
4 import java.util.List;
5
6 public class BufferedCharTest3 {
7 public static void main(String[] args) {
8 try (
9 //定义一个字符输出流管道与源文件接通
10 Reader reader = new FileReader("io-app2\\src\\csb.txt") {
11 };
12 //把低级的流包装成高级的字符缓冲输出流
13 BufferedReader bufferedReader = new BufferedReader(reader)
14 ) {
15 //定义List集合存储读取到的数据
16 List<String> datas = new ArrayList<>();
17 String str;
18 while ((str = bufferedReader.readLine()) != null){
19 datas.add(str);
20 }
21 System.out.println(datas);
22 Collections.sort(datas);
23 datas.forEach(s -> System.out.println(s));
24 //System.out.println(datas);
25 } catch (Exception e) {
26 e.printStackTrace();
27 }
28 }
29 }
csb.txt文件内容:
程序运行结果:
- 在上述示例中,没行数据的标号用的都是阿拉伯数字1~9,如果是汉字 一 ~ 九会怎么样呢?
首先:对csb.txt中的内容做少许的改动:
1 import java.io.*;
2 import java.util.*;
3
4 public class BufferedCharTest3 {
5 public static void main(String[] args) {
6 try (
7 //定义一个字符输入流管道与源文件接通
8 Reader reader = new FileReader("io-app2\\src\\csb.txt");
9 //把低级的流包装成高级的字符缓冲输出流
10 BufferedReader bufferedReader = new BufferedReader(reader);
11
12 //定义缓冲字符输出流管道与目标文件接通
13 Writer writer = new FileWriter("io-app2\\src\\new_csb.txt");
14 //用高级的缓冲字符输出流管道包装低级的字符
15 BufferedWriter bufferedWriter = new BufferedWriter(writer)
16 ) {
17 //定义List集合存储读取到的数据
18 List<String> datas = new ArrayList<>();
19 String str;
20 while ((str = bufferedReader.readLine()) != null){
21 datas.add(str);
22 }
23 System.out.println(datas);
24 //自定义排序规则
25 List<String> numbers = new ArrayList<>();
26 Collections.addAll(numbers,"一","二","三","四","五","六","七","八","九","十","十一","十二");
27 Collections.sort(datas, new Comparator<String>() {
28 @Override
29 public int compare(String o1, String o2) {
30 return numbers.indexOf(o1.substring(0,o1.indexOf("."))) -
31 numbers.indexOf(o2.substring(0,o2.indexOf(".")));
32 }
33 });
34 datas.forEach(s -> System.out.println(s));
35
36 for (String data : datas) {
37 bufferedWriter.write(data);
38 bufferedWriter.newLine();
39 }
40 } catch (Exception e) {
41 e.printStackTrace();
42 }
43 }
44 }
new_csb.txt文件中的内容:
至此,便实现了自定义排序规则。
- 代码与文件编码不一致导致读取乱码的问题、转换流来解决
- 步骤一:使用相同编码读取不同编码的文件内容
- 需求:分别以如下两种方式读取文件内容
- 代码编码是UTF-8,文件编码也是UTF-8,使用字符流读取观察输出的中文结果。
- 代码编码是UTF-8,文件编码是GBK,使用字符流读取观察输出的中文字符结果。
文件编码格式更改:【ANSI : 使用两个字节表示中文字符,一个字节表示英文字符】
1 import java.io.BufferedReader;
2 import java.io.FileReader;
3 import java.io.Reader;
4
5 public class CharSetTest1 {
6 public static void main(String[] args) {
7 try(
8 //代码:UTF-8,文件:UTF-8不会出现乱码
9 // //创建字符输入流管道与源文件接通
10 // Reader reader = new FileReader("io-app2\\src\\api_reader.txt");
11
12 //代码:UTF-8(三个字节 --> 一个字符),文件:ANSI,出现乱码(两个字节 --> 一个字符)
13 //创建字符输入流管道与源文件接通
14 Reader reader = new FileReader("D:\\Intellij_IDEA_install\\resourse_imgs\\love.txt");
15 //把低级的字符输入流包装成高级的缓冲字符输入流
16 BufferedReader bufferedReader = new BufferedReader(reader);
17 ){
18 String str = null;
19 while ((str = bufferedReader.readLine()) != null){
20 System.out.println(str);
21 }
22 }catch(Exception e){
23 e.printStackTrace();
24 }
25 }
26 }
示例代码运行结果(由于代码编码和文件编码不一致造成的乱码问题)
1 import java.io.*;
2
3 public class InputStreamReaderDemo1 {
4 public static void main(String[] args) {
5 //代码:UTF-8,文件:ANSI
6 try(
7 //提取ANSI文件的原始字节流
8 InputStream inputStream = new FileInputStream("D:\\Intellij_IDEA_install\\resourse_imgs\\love.txt");
9 //缓冲流
10 //默认还是以UTF-8的形式读取,还是会乱码的
11 //Reader reader = new InputStreamReader(inputStream) ;
12 Reader reader = new InputStreamReader(inputStream,"GBK");
13 //包装成缓冲字符输入流管道
14 BufferedReader bufferedReader = new BufferedReader(reader)
15 ){
16 String str_line = null;
17 while ((str_line = bufferedReader.readLine()) != null){
18 System.out.println(str_line);
19 }
20 }catch (Exception e){
21 e.printStackTrace();
22 }
23 }
24 }
总结一句话就是:要想不乱码,就要使得文件编码、解码一致
如何想要控制写出去的字符使用的编码?【两种方法】
- 可以把字符以指定编码获取字节后在使用字节输入流写出去:"abc一二三".getBytes(编码)
- 使用字符输出流实现
1 import java.io.*;
2
3 public class InputStreamWriterDemo1 {
4 public static void main(String[] args) {
5 try(
6 //创建字节输出流管道与目的文件接通
7 OutputStream outputStream = new FileOutputStream("io-app2\\src\\out2.txt");
8
9 //把原始的字节流包装成字符转换流
10 //以默认字符(UTF-8)写入,与直接new FileWriter的性质是一样的
11 //Writer writer = new OutputStreamWriter(outputStream);
12 Writer writer = new OutputStreamWriter(outputStream,"GBK"); //【低级流,并没有缓冲池】
13 //把低级的流包装成高级的缓冲字符输出流
14 BufferedWriter bw = new BufferedWriter(writer)
15 ){
16 bw.write("小");
17 bw.write("熊");
18 bw.write("饼");
19 bw.write("干");
20 System.out.println("写入完成");
21 }catch (Exception e){
22 e.printStackTrace();
23 }
24 }
25 }
out2.txt文档内容:【编码不一致问题导致乱码】
- 对象序列化【把对象数据存入到磁盘文件中去】
1 import java.io.FileOutputStream;
2 import java.io.ObjectOutputStream;
3
4 //对象序列化:使用ObjectOutputStream把内存中的对象存储到磁盘文件中
5 public class ObjectOutputStreamDemo1 {
6 public static void main(String[] args) {
7 Student student = new Student("admin","123456");
8 try(
9 //1、对象序列化:使用对象字节输出流包装字节输出流管道
10 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io-app2/src/obj.txt"));
11 ){
12 //直接往文件中写入数据
13 oos.writeObject(student);
14 System.out.println("序列化完毕");
15 }catch(Exception e){
16 e.printStackTrace();
17 }
18 }
19 }
- 对象反序列化【把存储到磁盘文件中的数据对象恢复成内存中的对象】
1 import java.io.FileInputStream;
2 import java.io.ObjectInputStream;
3
4 //使用对象反序列化把磁盘文件中的对象数据恢复成内存中的Java对象数据
5 public class ObjectInputStreamDemo2 {
6 public static void main(String[] args) {
7 try(
8 //1、创建字节输入流管道包含低级流管道与目标文件接通
9 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io-app2/src/obj.txt"));
10 ){
11 Student student = (Student) ois.readObject(); //Object --> 强转 : Student
12 System.out.println(student);
13 }catch (Exception e){
14 e.printStackTrace();
15 }
16 }
17 }
示例程序运行结果:
条件序列化:
在此对obj.txt进行序列化、反序列化后:
序列化版本号:
- 打印流【用着超爽】
1 import java.io.FileOutputStream;
2 import java.io.PrintStream;
3
4 //使用打印流写数据
5 public class PrintDemo1 {
6 public static void main(String[] args) {
7 try(
8 //1、打印里对象包装低级的文件字节输出流管道
9 // PrintStream ps = new PrintStream(new FileOutputStream("io-app2/src/ps.txt"));
10 PrintStream ps = new PrintStream("io-app2/src/ps.txt")
11 ){
12 ps.println(97);
13 ps.println('A');
14 ps.println("一串打印流数据");
15 }catch (Exception e){
16 e.printStackTrace();
17 }
18 }
19 }
ps.txt文档内容:
- 输出重定向(改变输出位置定位到文件)【了解】
让我们回到最初的起点,发现out原来是一个静态的(PrintStream)流对象,因为被static修饰了,说明它隶属于System类,在此调用时直接 类名.变量名 ,之后改对象会在静态代码块中被初始化,又因为改对象是个打印流,把它的输出位置定位到了控制台(其实本质上来说,控制台就是一个可以实时打印数据的文件)。当然了,以上这些只是很简陋的分析,真正的过程还是比较复杂的,可参考这篇博客:
https://www.cnblogs.com/zhujiqian/p/13832506.html
- Properties【属性集对象】
1 import java.io.FileWriter;
2 import java.util.Map;
3 import java.util.Properties;
4
5 //使用Properties把键值对信息存储到属性文件中去
6 public class PropertiesDemo1 {
7 public static void main(String[] args) throws Exception{
8 //Map map = new Properties();
9 Properties properties = new Properties();
10 // properties.put("admin","123456");
11 // properties.put("Jack","234567");
12 // properties.put("Tom","345678");
13 properties.setProperty("admin","123456"); //setProperty内部仍然是put方法
14 properties.setProperty("Jack","234567");
15 properties.setProperty("Tom","345678");
16
17 properties.store(new FileWriter("io-app2/src/users.properties"),"改参数用于properties文件的注释");
18 }
19 }
users.properties文件中的内容:
回复users.properties文件中的数据到Java中去:
1 import java.io.FileReader;
2 import java.util.Properties;
3
4 //加载Properties文件中的数据到Java开发环境中去
5 public class PropertiesLoadDemo {
6 public static void main(String[] args) throws Exception{
7 Properties properties = new Properties();
8 System.out.println(properties);
9
10 //加载指定的properties文件
11 properties.load(new FileReader("io-app2/src/users.properties"));
12 System.out.println(properties);
13
14 //通过键取值
15 String pwd = properties.getProperty("J1ack"); //getProperty方法里边包的还是get【get需要强转】方法
16 System.out.println(pwd);
17 }
18 }
commons-io框架:
- 导入commons-io jar包做开发
- 需求:使用commons-io简化io流读写
分析:
- 在项目中创建一个文件夹: lib;
- 将commons - io - 2.11.0.jar文件复制到lib文件夹;
- 在jar文件夹上点右键,选择Add as Library - > 点击OK
- 在类中导包使用
1 import org.apache.commons.io.FileUtils;
2 import org.apache.commons.io.IOUtils;
3
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileOutputStream;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
9
10 //使用commons-io框架复制文件
11 public class DommonsIODemo {
12 public static void main(String[] args) throws Exception {
13 //1、复制文件
14 // IOUtils.copy(new FileInputStream("D:\\Intellij_IDEA_install\\resourse_imgs\\natural_view.jpeg"),
15 // new FileOutputStream("D:\\Intellij_IDEA_install\\resourse_imgs\\natural_view2.jpeg"));
16
17 //2、拷贝文件到文件夹
18 // FileUtils.copyFileToDirectory(new File("D:\\Intellij_IDEA_install\\resourse_imgs\\doraemon.jpeg"),
19 // new File("D:\\Intellij_IDEA_install\\resourse_imgs\\emptyFile"));
20
21 //3、文件夹复制到文件夹
22 //FileUtils.copyDirectoryToDirectory(new File("D:\\Intellij_IDEA_install\\resourse_imgs\\emptyFile"),
23 // new File("D:\\Intellij_IDEA_install\\resourse_imgs\\newEmptyFile"));
24
25 //4、删除文件夹【不经过回收站】
26 //FileUtils.deleteDirectory(new File("D:\\Intellij_IDEA_install\\resourse_imgs\\newEmptyFile"));
27
28 //... ...
29
30 //5、JDK1.7自己也做了一行代码可以复制文件的操作 NIO
31 //【复制文件夹之后为空文件夹,复制文件没问题】
32 // Files.copy(Path.of("D:\\Intellij_IDEA_install\\resourse_imgs\\emptyFile"),
33 // Path.of("D:\\Intellij_IDEA_install\\resourse_imgs\\emptyFileAAA"));
34
35 // Files.copy(Path.of("D:\\Intellij_IDEA_install\\resourse_imgs\\natural_view.jpeg"),
36 // Path.of("D:\\Intellij_IDEA_install\\resourse_imgs\\natural_viewAAA.jpeg"));
37
38 //【DirectoryNotEmptyException】只能删除空文件夹
39 // Files.deleteIfExists(Path.of("D:\\Intellij_IDEA_install\\resourse_imgs\\emptyFileAAA"));
40 }
41 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理