JAVA基础-IO
1,IO 概念
IO 通常表示计算机与外界数据的输入与输出。I/O 流:IO流又叫输入输出流 ,输入和输出均是以内存作为参照物
分类:
1)按照流的方向
以内存为参考物,往内存中去,叫做输入,从内存中出来,叫做输出。
2)按照读取数据方式
字节流,一次读取一个字节 byte
字符流,一次读取一个字符,具体几个字节取决于编码,只能处理文本文件。
3)按功能
节点流:可以从一个特定数据源读写数据,如 FileInputStream, FileReader
处理流(包装流):链接已存在的流之上,为程序停工更为强大的读写功能。处理流只需要关闭自己,内部的流不用关。
2,BIO
1,四大家族:
- java.io.InputStream 字节输入流
- java.io.OutputStream 字节输出流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
2,IO 流特征
1)所有的流都实现了 java.io.Colseable接口,都是可关闭的,都有close()方法. 流毕竟是一个管道,这个是内存和硬盘之间的通道,养成一个好习惯,用完流将其关闭
2)所有的输出流都实现了 java.io.Flushable接口,都是可刷新的,都有flush()方法,这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。如果没有flush()可能会导致数据丢失.
3)在java中只要"类名"以Stream结尾的都是字节流。以"Reader/Writer"结尾的都是字符流
3,16个流需要掌握
1)文件专属
java.io.FileinputStream (用得最多)
/** * FileInputStream * 文件字节输入流,万能的,任何类型的文件都可以采用这个流来读 * 字节的方式,完成输入的操作,完成读的操作(硬盘 —> 内存) * * 方法: * read() 读取一个字节,返回的是读取到的内容的 int 表示,返回 -1 表示没有读到。 * read(byte[] b) 读取一个指定数组大小的内容,将读取内容放到 byte 数组中。返回 -1 说明没有读取到任何内容。 * read(byte[] b, int off, int len) 读取 len 长度的字节,从数组的 off 位置开始填充数组 * */ @Test public void FileInputStream() throws IOException { FileInputStream fis = new FileInputStream("D:\\gaox\\files\\test\\io_demo\\fileInputStream.txt"); byte[] by = new byte[4]; int length; while((length = fis.read(by)) != -1){ System.out.println(new String(by, 0, length)); } fis.close(); }
java.io.FileOutputStream (用得最多)
/** * FileOutputStream * 文件字节输出流,负责写,从内存到硬盘 * 构造方法: * FileOutputStream(String name, boolean append) 第二个参数表示追加。如果根据文件路径找不到文件,就会自己创建一个文件 * * 方法: * write(int) 写入一个数字 * write(byte[] c) 写入一个字节数组 * <!---->write(byte[] c, int off, int len) 从数组 c 的 off 位置开始写入 len 个字节 * flush() 刷新 * */ @Test public void FileOutputStream() throws IOException { FileOutputStream fos = new FileOutputStream("D:\\gaox\\files\\test\\io_demo\\fileOutputStream.txt"); fos.write("yanqishiwode".getBytes(), 1, 10); fos.flush(); fos.close(); }
java.io.FileReader
/** * FileReader * 文件字符输入流,以字符 char 为单位,只能读文本 * * 方法: * getEncoding():获取编码 * read():读取一个字符,返回 int 类型,可以强转成 char, -1 表示没读到 * read(char[] c):读取字符数组长度的字符,然后填充到数组 c 中, -1 表示没读到,返回值为实际读的数据 * read(char[] c, int off, int len) 读取 len 长度的字符,放到从 off 开始的数组 c 中,-1表示没读到返回值为实际读到的数据 * */ @Test public void FileReader() throws IOException { FileReader fr = new FileReader("D:\\gaox\\files\\test\\io_demo\\fileReader.txt"); System.out.println((char)fr.read()); System.out.println(fr.read()); char[] chars = new char[10]; fr.read(chars); System.out.println(chars); //读取部分,chars 数组其他位置仍然是上面读取的内容 fr.read(chars, 4, 3); System.out.println(chars); int len; while((len = fr.read(chars)) != -1){ System.out.println(len); } fr.close(); }
java.io.FileWriter
/** * FileWriter * 文件字符输出流,以字符 char 为单位 * * 方法: * write():输出一个字符 * write(char[] c):输出整个字符数组 * write(char[] c, int off, int len):输入数组 c 中,从 off 开始共计 len 长度的字符 * write(String s):输出字符串 * write(String s, int off, int len):输出字符串的一部分 * flush() 刷新 * */ @Test public void fileWriter() throws IOException { FileWriter fw = new FileWriter("D:\\gaox\\files\\test\\io_demo\\fileWriter.txt", true); fw.write(49); char[] chr = new char[]{'张','非','又','要','大','人','了'}; fw.write(chr); fw.write(chr, 1, 3); fw.write("张三"); fw.write("woaiyanqi", 3, 5); fw.flush(); fw.close(); }
2)转换流 (将字节流转换成字符流)
InputStreamReader
/** * InputStreamReader * 输入转换流:将字节流转换为字符流 * * 构造方法 * InputStreamReader(InputStream in); 入参一个字节流,返回一个 inputStreamReader 转换流,该流是字符流 * * read 三个 read 方法跟 reader 保持一致 * * * */ @Test public void InputStreamReader() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\gaox\\files\\test\\io_demo\\fileInputReader.txt")); System.out.println((char)isr.read()); char[] chr = new char[10]; System.out.println(isr.read(chr)); System.out.println(chr); chr = new char[10]; System.out.println(isr.read(chr, 3,7)); System.out.println(chr); isr.close(); }
outputStreamWriter
/** * outputStreamWriter * 输出转换流 * * write 五个方法与 fileWriter 五个 write 方法一致 * */ @Test public void outputStreamWriter() throws IOException { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\gaox\\files\\test\\io_demo\\outputStreamWriter.txt", true)); //osw.write osw.write(49); osw.write(new char[]{'y','a','n','q','i'}); osw.write("不服来战"); osw.write(new char[]{'y','a','n','q','i'}, 1, 3); osw.write("hello world", 1, 3); osw.flush(); osw.close(); }
3)缓冲流专属: 缓冲流相比普通的流,多出一个缓冲区,可以读取比规定的多的数据,减少 io 次数,这类流构造方法都有个参数规定缓冲区大小
BufferedReader
/** * BufferedReader * 缓冲输入字符流 * * 构造方法: * BufferedReader(Reader r, int size) size 缓冲区大小 * * 方法: * read 常规三个方法 * readLine() 读一行 * */ @Test public void bufferedReader() throws IOException { BufferedReader br = new BufferedReader(new FileReader("D:\\gaox\\files\\test\\io_demo\\bufferedReader.txt"),1); System.out.println( (char)br.read()); char[] chr = new char[10]; br.read(chr); System.out.println(chr); chr = new char[10]; br.read(chr, 1, 5); System.out.println(chr); String s = br.readLine(); System.out.println(s); br.close(); }
bufferedWriter
/** * bufferedWriter * 缓冲字符输出流 * * 构造方法: * BufferedWriter(Writer out) * * 方法: * write 五个常规方法 * newLine() 创建新的一行 * * */ @Test public void bufferedWriter() throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\gaox\\files\\test\\io_demo\\bufferedWriter.txt")); bw.write(49); bw.write(new char[]{'1','2'}); bw.newLine(); bw.write("woaiyanqi"); bw.flush(); bw.close(); }
BufferedInputStream
/** * BufferedInputStream * 字节缓冲输入流 * * 构造方法: * BufferedInputStream(InputStream i); * * 方法: * read() 的三个方法 * */ @Test public void bufferedInputStream() throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\gaox\\files\\test\\io_demo\\bufferInputStream.txt")); System.out.println( bis.read() ); byte[] b = new byte[10]; bis.read(b); System.out.println(b); bis.read(b, 1, 9); System.out.println(b); bis.close(); }
BufferedOutputStream
/** * BufferedOutputStream * * 构造方法 * BufferedOutputStream(OutputStream os) * * 方法 * write() 三个方法 * */ @Test public void bufferedOutputStream() throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\gaox\\files\\test\\io_demo\\bufferedOutputStream.txt")); bos.write(49); String str = "wohao稀饭ya#@ &*qia"; bos.write(str.getBytes()); bos.write(str.getBytes(), 1, 10); bos.flush(); bos.close(); }
4)数据流
DataOutputStream
/** * DataOutputStream * 数据输出流 * * 构造方法 * DataOutputStream(OutputStream os) * * 方法: * write() 三件套 * writeInt() 等八大类型 * */ @Test public void dataOutputStream() throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\gaox\\files\\test\\io_demo\\dataInputStream.txt")); dos.writeChar('燕'); dos.writeInt(112); dos.writeDouble('燕'); dos.flush(); dos.close(); }
DataInputStream
/** * DataInputStream * 数据输入流:只能读 DataOutputStream() 写入的数据,并且需要知道顺序 * * 构造方法: * DataInputStream(InputStream is) * * 方法: * read() 三件套 * readInt() 等八大类型 * */ @Test public void dataInputStream() throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("D:\\gaox\\files\\test\\io_demo\\dataInputStream.txt")); System.out.println( dis.readChar() ); System.out.println(dis.readInt()); System.out.println(dis.readDouble()); dis.close(); }
5)标准输出流
PrintStream
/** * printStream * 标准输出流 * * System.out、System.err 等返回的就是 printStream * */ @Test public void printStream() throws FileNotFoundException { //默认输出到控制台 PrintStream out = System.out; out.println("yanqi"); out.println("nidei"); out.println("shenmedeganh"); //输出到日志去 PrintStream ps = new PrintStream(new FileOutputStream("D:\\gaox\\files\\test\\io_demo\\log.txt")); System.setOut(ps); ps.println("张三"); System.out.println("你跑哪去了"); }
PrintWriter 不知道咋用
6)对象专属流
ObjectOutputStream
/** * ObjectOutputStream * 对象输出流,配合序列化 * */ @Test public void objectOutputStream() throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\gaox\\files\\test\\io_demo\\objectOutputStream.txt")); oos.writeObject(new Cat("yanqi", 25, "white")); oos.writeObject(new Cat("yanqi", 26, "white")); oos.writeObject(new Cat("yanqi", 27, "white")); oos.writeObject(new Cat("yanqi", 28, "white")); oos.writeObject("张三"); oos.writeObject(new Cat("yanqi", 29, "white")); oos.flush(); oos.close(); }
ObjectInputStream
/** * ObjectInputStream * 对象输入流,配合序列化 * */ @Test public void ObjectInputStream() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\gaox\\files\\test\\io_demo\\objectOutputStream.txt")); System.out.println(ois.readObject()); System.out.println(ois.readObject()); System.out.println(ois.readObject()); System.out.println(ois.readObject()); System.out.println(ois.readObject()); System.out.println(ois.readObject()); ois.close(); }
4,文件操作 demo
字节流复制文件
/** * 字节流复制文件 * */ @Test public void fileCopy() { try( //卸载这里,会自动关闭流 FileInputStream fis = new FileInputStream("D:\\gaox\\files\\test\\io_demo\\bizhi.rar"); FileOutputStream fos = new FileOutputStream("D:\\gaox\\files\\test\\copy_io_demo\\bizhi.rar") ){ byte[] byt = new byte[1024]; int len; while( (len = fis.read(byt)) != -1 ){ //这里因为最后可能数组都不满,所以每次只需要将有数据的写入文件就好了 fos.write(byt, 0, len); } fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
字符流复制文件:只能复制文本
/** * 字符流复制文件:只能复制文本 * */ @Test public void copyFileByChar(){ try( FileReader fr = new FileReader("D:\\gaox\\files\\test\\io_demo\\catalina.out"); FileWriter fw = new FileWriter("D:\\gaox\\files\\test\\copy_io_demo\\catalina.txt") ){ char[] chr = new char[10]; int len; while( (len = fr.read(chr)) != -1 ){ fw.write(chr, 0, len); } fw.flush(); }catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
5,压缩流
ZipInputStream,ZipOutputStream,ZipFile,ZipEntry 等。
压缩流 demo
/** * ZipOutputStream * 压缩流:单个文件压缩 * */ @Test public void zipFile() throws IOException { //要压缩的文件 File file = new File("D:\\gaox\\files\\test\\io_demo\\catalina.out"); //压缩后的压缩包 File zipFile = new File("D:\\gaox\\files\\test\\io_demo\\catalina.zip"); FileInputStream fs = new FileInputStream(file); ZipInputStream zis = new ZipInputStream(fs); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)); zos.putNextEntry(new ZipEntry(file.getName())); byte[] byt = new byte[1024]; int len; while( (len = fs.read(byt)) != -1 ){ zos.write(byt, 0, len); } zos.close(); zis.close(); } /** * ZipOutputStream * 压缩流:文件夹压缩 * */ @Test public void zipFiles() throws IOException { //要压缩的文件夹 File file = new File("D:\\gaox\\files\\test\\io_demo"); //压缩后的压缩包 File zipFile = new File("D:\\gaox\\files\\test\\copy_io_demo\\io_demo.zip"); //定义文件输入流 InputStream is; //定义输出压缩流 ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)); zos.setComment("yanqi"); recursionZip(zos, file, ""); zos.close(); } //压缩方法抽出 private void recursionZip(ZipOutputStream zos, File file, String baseDir) throws IOException { byte[] byt = new byte[1024]; int len; if(file.isDirectory()){ //如果是文件夹,获取文件夹下所有文件 File[] files = file.listFiles(); for(File item : files){ recursionZip(zos, item, baseDir + File.separator + file.getName()); } }else{ //对文件做压缩处理 FileInputStream is = new FileInputStream(file); zos.putNextEntry(new ZipEntry(baseDir + File.separator + file.getName())); while(( len = is.read(byt)) != -1){ zos.write(byt, 0, len); } is.close(); } } /** * 解压缩 * ZipFile * */ @Test public void unzip() throws IOException { //压缩包 File inFile = new File("D:\\gaox\\files\\test\\copy_io_demo\\io_demo.zip"); ZipFile zipFile = new ZipFile(inFile); //解压后的文件路径 String path = inFile.getParent(); //压缩输入流 ZipInputStream zis = new ZipInputStream(new FileInputStream(inFile)); ZipEntry entity; while( (entity = zis.getNextEntry()) != null ){ System.out.println("解压缩:" + entity.getName()); File outFile = new File(path + File.separator + entity.getName()); if( !outFile.getParentFile().exists() ){ outFile.getParentFile().mkdir(); } outFile.createNewFile(); InputStream is = zipFile.getInputStream(entity); FileOutputStream fos = new FileOutputStream(outFile); byte[] byt = new byte[1024]; int len; while( (len = is.read(byt)) != -1 ){ fos.write(byt, 0, len); } is.close(); fos.close(); } }
3,NIO
1,NIO 简介
nio:new io,也称为非阻塞 io。与原来的 io 有同样的作用和目的,但是使用方式完全不同。
NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式
2,NIO 特点
1)NIO相关类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写。
2)NIO有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
3)Java NlO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
4)通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有1000个请求过来,根据实际情况,可以分配20或者80个线程来处理。不像之前的阻塞IO那样,非得分配1000个。
3,nio 和 io 比较
1)BIO以流的方式处理数据,而NIO以块的方式处理数据,块I/O的效率比流IO高很多
2)BIO是阻塞的,NIO则是非阻塞的(Java NIO 的非阻塞,是相对于连接的非阻塞,而不是指方法调用时的非阻塞)
3)BlO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道
4,NIO 三要素
1),Buffer 缓冲区
java.nio.Buffer 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。相比较直接对数组的操作,Buffer APl更加容易操作和管理。
nio 面向块,这个块就是 Buffer
2). Channel 通道
通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互
3),Selector 选择器
选择器(Selector)是SelectableChannle对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心
5,NIO demo
public class DemoMain { /** * nio 写本地文件 * */ @Test public void write() throws IOException { FileOutputStream fos = new FileOutputStream("D:\\gaox\\files\\日志\\copy.txt"); FileChannel channel = fos.getChannel(); ByteBuffer buf = ByteBuffer.allocate(1024); buf.put("yanqinidenalipaowl来两个中文ogaodingnil".getBytes()); buf.flip(); channel.write(buf); buf.clear(); channel.close(); } /** * nio 读本第文件 * */ @Test public void read() throws IOException { FileInputStream fis = new FileInputStream("D:\\gaox\\files\\日志\\test.log"); FileChannel chl = fis.getChannel(); int bufLength = 1024; ByteBuffer buf = ByteBuffer.allocate(bufLength); byte[] bytes = new byte[bufLength]; int read; while( (read = chl.read(buf)) != -1){ buf.flip(); buf.get(bytes, 0 , read); System.out.println(new String(bytes)); buf.clear(); } chl.close(); } /** * 使用 buffer复制 * */ @Test public void bufferCopy() throws IOException { FileChannel fic = new FileInputStream("D:\\gaox\\files\\日志\\catalina1231231241231312311312414.out").getChannel(); FileChannel foc = new FileOutputStream("D:\\gaox\\files\\日志\\bufferCopy.out").getChannel(); ByteBuffer buf = ByteBuffer.allocate(1024); while(fic.read(buf) != -1){ buf.flip(); foc.write(buf); buf.clear(); } fic.close(); foc.close(); } /** * transferFrom 复制 * */ @Test public void transferFrom() throws IOException { FileChannel fic = new FileInputStream("D:\\gaox\\files\\日志\\catalina1231231241231312311312414.out").getChannel(); FileChannel foc = new FileOutputStream("D:\\gaox\\files\\日志\\bufferCopy1.out").getChannel(); foc.transferFrom(fic, fic.position(), fic.size()); fic.close(); foc.close(); } /** * transferTo 复制 * */ @Test public void transferTo() throws IOException { FileChannel fic = new FileInputStream("D:\\mySoftWare\\jdk-17_windows-x64_bin.zip").getChannel(); FileChannel foc = new FileOutputStream("D:\\mySoftWare\\jdk-17_windows-x64_bi1n1.zip").getChannel(); long l = fic.transferTo(fic.position(), fic.size(), foc); System.out.println("传输完成!"); fic.close(); foc.close(); } /** * 案例6-分散 (Scatter) 和聚集 (Gather) * */ @Test public void test() throws IOException{ RandomAccessFile raf1 = new RandomAccessFile("D:\\gaox\\files\\日志\\catalina1231231241231312311312414.out", "rw"); //1. 获取通道 FileChannel channel1 = raf1.getChannel(); //2. 分配指定大小的缓冲区 ByteBuffer buf1 = ByteBuffer.allocate(100); ByteBuffer buf2 = ByteBuffer.allocate(1024); //3. 分散读取 ByteBuffer[] bufs = {buf1, buf2}; channel1.read(bufs); for (ByteBuffer byteBuffer : bufs) { byteBuffer.flip(); } System.out.println(new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("-----------------"); System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); //4. 聚集写入 RandomAccessFile raf2 = new RandomAccessFile("D:\\gaox\\files\\日志\\bufferCopy3.out", "rw"); FileChannel channel2 = raf2.getChannel(); channel2.write(bufs); } }
6,I/O 多路复用技术
在传统的IO编程中,每个socket连接都需要一个线程或进程来处理,这样就会导致系统资源的浪费和性能问题。而IO多路复用则可以通过一个线程或进程来同时管理多个socket连接,并且可以同时处理多个连接上的IO事件。
常用的IO多路复用技术有select、poll、epoll等。
以select为例,当一个进程调用select函数时,它会将多个文件描述符注册到一个监控集合中,并设置监听事件类型(如可读、可写、异常等)。然后,select函数会阻塞进程,直到监控集合中的任意一个文件描述符发生了监听事件(读、写)。此时,select函数就会返回,并告诉进程哪些文件描述符有事件发生,进程可以根据返回值来处理相应的IO事件。
7,Reactor 模式
Reactor 设计模式就是基于建立连接与具体服务之间线程分离的模式。在 Reactor 模式中,会有一个线程负责与所有客户端建立连接,这个线程通常称之为 Reactor。然后在建立连接之后,Reactor 线程 会使用其它线程(可以有多个)来处理与每一个客户端之间的数据传输,这个(些)线程通常称之为 Handler
4,AIO
1,简介
AIO,异步非阻塞 IO,又称 NIO2.0,最大的一个特性就是异步能力,这种能力对socket与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。
2,AIO demo
/** * * AsynchronousSocketChannel * AsynchronousServerSocketChannel * AsynchronousFileChannel * AsynchronousDatagramChannel * */ public class AioDemo { @Test public void test() throws IOException, ExecutionException, InterruptedException { AsynchronousFileChannel channel = open(Paths.get("D:\\gaox\\files\\模型\\科艺楼.安装模型 - 副本.zip"), StandardOpenOption.READ); ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024 * 1024); Future<Integer> read = channel.read(byteBuffer, 0); System.out.println("读了,但是不知道有没有读出来!"); read.get(); byteBuffer.flip(); System.out.println("读出来了:" + byteBuffer.get()); } }
5,参考文献
本文作者:primaryC
本文链接:https://www.cnblogs.com/cnff/p/17528938.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY