IO流总结(基础知识)
IO流总结
IO这章的知识在上面一篇博客也说过一点,主要要体会一下装饰者设计模式和适配器设计模式,这样更利于我们理解复杂的IO体系结构。今天就让我们看一看。不过在讲IO
之前,我们先把文件(File)的知识简单过一下。
一、文件File
文件大家都不陌生,查看JDK帮助文档,我们知道File的定义——文件和目录(文件夹)路径名的抽象表示形式。
1、构造方法
File(String pathname):根据一个路径得到File对象
File(String parent, String child):根据一个目录和一个子文件/目录得到File对象
File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对象
2、创建功能
public boolean createNewFile():创建文件 如果存在这样的文件,就不创建了
public boolean mkdir():创建文件夹 如果存在这样的文件夹,就不创建了
public boolean mkdirs():创建文件夹,如果父文件夹不存在,会帮你创建出来
举例:
//需求1:我要在e盘目录下创建一个文件demo
File file = new File("e:\\a.txt");
大家可能会认为这里用了关键字new,那么文件File就会出来了,其实不是,因为File的定义是:文件和目录(文件夹)路径名的抽象表示形式。它仅仅是文件的表示
形式,不是真实的文件。 所以需要调用这样一句代码file.createNewFile()
//需求2:我要在e盘目录test下创建一个文件b.txt
File file = new File("e:\\test\\b.txt");
System.out.println("createNewFile:" + file3.createNewFile());
运行之后可以看到控制台打印出:
Exception in thread "main" java.io.IOException: 系统找不到指定的路径。
所以注意:要想在某个目录下创建内容,该目录首先必须存在。我们可以这么做:File file1=new File("e:\\test);file1.mkdir();//先把文件夹(目录)创建出来然后在:
File file2=new File(e:\\test\\b.txt);file2.createNewFile();//在创建文件
//看下面这行代码
File file = new File("e:\\liuyi\\a.txt");
System.out.println("mkdirs:" + file8.mkdirs());
你会看到a.txt是个目录,不要认为指定了.txt后缀就是文件了,要看它调用的是creatNewFile()还是mkdir();
3、删除功能
public boolean delete()
这个方法比较简单,注意:
A:如果你创建文件或者文件夹忘了写盘符路径,那么,默认在项目路径下。
B:Java中的删除不走回收站。
C:要删除一个文件夹,请注意该文件夹内不能包含文件或者文件夹,所以delete一次只能删除一个文件,如果想删除一个文件夹,那必须要循环遍历了。
4、重命名功能
public boolean renameTo(File dest)
如果路径名相同,就是改名。
如果路径名不同,就是改名并剪切。
需求:将G盘所有视频文件该名成“0?-网络编程(概述).avi”,原来视频文件名称为:黑马程序员_毕向东_Java基础视频教程第23天-0?-网络编程(概述).avi
<span style="font-size:18px;">public class FileDemo {
private static File dest;
public static void main(String[] args) {
//封装目录
File file=new File("G:\\黑马入学\\网络编程1");
File[] sorcFolder = file.listFiles();
for (File f : sorcFolder) {
String name = f.getName();
//黑马程序员_毕向东_Java基础视频教程第23天-01-网络编程(概述).avi
//System.out.println(name);
int index = name.indexOf('-');
String string = name.substring(index+1);
//再次封装新的文件目录
dest = new File(file,string);
f.renameTo(dest);
}
}
}</span>
执行结果:
一看全部改名成功,哈哈。。。
5、判断功能
public boolean isDirectory():判断是否是目录
public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
6、简单获取功能
public String getAbsolutePath():获取绝对路径
public String getPath():获取相对路径
public String getName():获取名称
public long length():获取长度。字节数
public long lastModified():获取最后一次的修改时间,毫秒值
示例代码:
<span style="font-size:18px;"> public class FileDemo {
public static void main(String[] args) {
// 创建文件对象
File file = new File("demo\\test.txt");
System.out.println("getAbsolutePath:" + file.getAbsolutePath());
System.out.println("getPath:" + file.getPath());
System.out.println("getName:" + file.getName());
System.out.println("length:" + file.length());
System.out.println("lastModified:" + file.lastModified());
// 1432518063391
Date d = new Date(1432518063391L);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);
}
}</span>
执行结果是:
7、高级获取功能
public String[] list():获取指定目录下的所有文件或者文件夹的名称数组
public File[] listFiles():获取指定目录下的所有文件或者文件夹的File数组
后者功能更为强大一点,因为返回的是File对象,所以可以得到File的各种属性。
8.文件名称过滤器——FilenameFilter
需求:判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出此文件名称
public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)
示例代码:
<span style="font-size:18px;">public class FileDemo {
public static void main(String[] args) {
//封装e盘
File file=new File("e:\\");
// 获取该目录下所有文件或者文件夹的String数组
String[] list = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//如果是文件并且是以.jpg结尾的,返回true,一旦返回true,name就会被放进String数组中,
return new File(dir, name).isFile() && name.endsWith(".jpg");
}
});
for (String string : list) {
System.out.println(string);
}
}
}</span>
虽然普通的方式也能实现,这样看起来,代码更加优雅,现在我们来看看list(FilenameFilter filter)的源码
<span style="font-size:18px;">public String[] list(FilenameFilter filter) {
String names[] = list();
if ((names == null) || (filter == null)) {
return names;
}
List<String> v = new ArrayList<>();
for (int i = 0 ; i < names.length ; i++) {
if (filter.accept(this, names[i])) {
v.add(names[i]);
}
}
return v.toArray(new String[v.size()]);
}</span>
二、IO相关操作
IO因为体系过于庞大,这里并不面面俱到,捡其重点来说。上面简单说了一下File的使用,File的读取,写入,复制。相信都是我们经常使用的功能。谈起IO流,首先我们要对它有个大致的认识,也就是IO的分类,请看下面:
A:按流向分
输入流 读取数据
输出流 写出数据
B:按数据类型
字节流
字节输入流
字节输出流
字符流
字符输入流
字符输出流
如果你不知道什么是字节流,什么是字符流,我告诉你,如果电脑记事本可以打开的并且可以看得懂的,那就是可以用字符流操作的文件,当然也可以用字节流来操作,如果电脑记事本能打开,但是我们看不懂的,那就是用字节流来操作的文件,比如MP3文件、JPG文件等,现在我们来看看IO是怎么来操作File的。
1、字节流操作文件
1.1、FileOutputStream写出数据
A:操作步骤
a:创建字节输出流对象
b:调用write()方法
c:释放资源
B:示例代码:
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("hello".getBytes());
fos.close();
1.2、FileInputStream读取数据
FileInputStream读取数据
A:操作步骤
a:创建字节输入流对象
b:调用read()方法
c:释放资源
B:代码体现:
FileInputStream fis = new FileInputStream("fos.txt");
//方式1,我们一次读取一个字节
int by = 0;
while((by=fis.read())!=-1) {
System.out.print((char)by);
}
//方式2 我们一次读取一个字节数组
byte[] bys = new byte[1024];
int len = 0;
while((len=fis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
fis.close();
1.3、四种方式复制文件效率比较
FileInputStream、FileOutputStream这两个类我们称之为基本字节流,相对于基本字节流,IO中还有高效字节流BufferedInputStream、BufferedOutputStream
看完了这个,那么问题来,哪种方式好呢?
下面我们来看一个例子:
需求:把G:\IO流(IO流小结图解).avi复制到当前项目目录下的copy.avi中
字节流四种方式复制文件:
* 基本字节流一次读写一个字节
* 基本字节流一次读写一个字节数组
* 高效字节流一次读写一个字节
* 高效字节流一次读写一个字节数组
G:\IO流(IO流小结图解).av的信息如下:
示例代码:
<span style="font-size:18px;">public class CopyAVI {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
method1("G:\\IO流(IO流小结图解).avi", "copy1.avi");
//method2("G:\\IO流(IO流小结图解).avi", "copy2.avi");
//method3("G:\\IO流(IO流小结图解).avi", "copy3.avi");
//method4("G:\\IO流(IO流小结图解).avi", "copy4.avi");
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - start) + "毫秒");
}
// 高效字节流一次读写一个字节数组:
public static void method4(String srcString, String destString)throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
// 高效字节流一次读写一个字节:
public static void method3(String srcString, String destString)throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));
int by = 0;
while ((by = bis.read()) != -1) {
bos.write(by);
}
bos.close();
bis.close();
}
// 基本字节流一次读写一个字节数组
public static void method2(String srcString, String destString)throws IOException {
FileInputStream fis = new FileInputStream(srcString);
FileOutputStream fos = new FileOutputStream(destString);
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
// 基本字节流一次读写一个字节
public static void method1(String srcString, String destString)throws IOException {
FileInputStream fis = new FileInputStream(srcString);
FileOutputStream fos = new FileOutputStream(destString);
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
fos.close();
fis.close();
}
}</span>
执行结果:
方法一执行:
方法二执行:
方法三执行:
方法四执行:
四种方式的性能差异,通过这个执行结果,想必大家也知道了 ,所以在实际开发中选择哪一个来操作文件,相信大家已经有了选择了,反正我是喜欢用方法四!!
2、字符流操作文件
大家可能会问,操作文件已经有了字节流,为什么还要说字符流呢,哎,谁叫咱们是中国人呢,汉字比较复杂,一个汉字站了两个字节,所以直接用字符流操作不够方便所以就有了字符流。我们来看看字符流的体系结构。
2.1字符流体系结构
|--字符流
|--字符输入流
Reader
int read():一次读取一个字符
int read(char[] chs):一次读取一个字符数组
|--InputStreamReader
|--FileReader
|--BufferedReader
String readLine():一次读取一个字符串
|--字符输出流
Writer
void write(int ch):一次写一个字符
void write(char[] chs,int index,int len):一次写一个字符数组的一部分
|--OutputStreamWriter
|--FileWriter
|--BufferedWriter
void newLine():写一个换行符
void write(String line):一次写一个字符串
字符流的读写是由基类Reader和Writer来完成的,它们各有两个子类来帮助我们来操作文件。Reader类的子类BufferedReader与Writer的子类BufferedWriter,我们一定可以猜到这是高效字符流操作文件使用的!那么,Reader类的子类InputStreamReader与Writer的子类OutputStreamWriter是干什么的呢?从类的名称来来看,前面是字节的,后面是字符的,我告诉你,这两个类就是转换流,把字节流转换成字符流,那么这有什么用呢?-----键盘录入!!!一会你们就知道了。。。。。注意OutputStreamWriter还有子类FileWriter,InputStreamReader还有子类FileReader。
2.1、字符流操作文件
我们先看一个字符流操作文件的例子。
需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
数据源:
a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader -- BufferedReader
目的地:
b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
示例代码:
<span style="font-size:18px;">public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 两种方式其中的一种一次读写一个字符数组,当然此处你也可以一次读取一个字节的方法
// char[] chs = new char[1024];
// int len = 0;
// while ((len = br.read(chs)) != -1) {
// bw.write(chs, 0, len);
// bw.flush();
// }
// 还有一种方式读写数据,这就是BufferedWriter、BufferedReader的强大之处。。。。
String line = null;
//下面三句话,我习惯连着写,写入、换行、刷新,动作相当连贯有木有啊!!!!!!
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
// 释放资源
bw.close();
br.close();
}
}</span>
代码解读: bw.newLine();这句话的意思是换行,调用的效果和我们写“\r\n”是一样的,但是newLine可以夸平台的,这就是区别!
readLine()这个方法有点厉害,因为它一次读一行!!!
博客写到这里,我们发现,复制文件文件起码有5种方式,前面所讲的1.基本字节流一次读写一个字节、2.基本字节流一次读写一个字节数组、3.高效字节流一次读写一个字节、4.高效字节流一次读写一个字节数组共4种,再加上上面的一种,共5种,而且第五种是最好的,个人感觉。。。。
3、IO编程实战
3.1、复制单级文件夹
/*
* 需求:复制指定目录下的指定文件,并修改后缀名。
* 指定的文件是:.java文件。
* 指定的后缀名是:.txt
* 指定的目录是:txt
* 数据源:G:\designModel
* 目的地:G:\txt
*
* 分析:
* A:封装目录
* B:获取该目录下的java文件的File数组
* C:遍历该File数组,得到每一个File对象
* D:把该File进行复制
* E:在目的地目录下改名
*/
* 数据源:G:\designModel如下图所示:
示例代码:
<span style="font-size:18px;">public class CopyFolder {
public static void main(String[] args) throws IOException {
//A:封装目录
File srcFolder=new File("G:\\designModel");
//封装目的地
File destFolder=new File("G:\\myNote");
//如果目的地文件不存在,就创建一个
if(!destFolder.exists()){
destFolder.mkdir();
}
//B:获取该目录下的java文件的File数组
File[] srcFiles = srcFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// 文件过滤器,得到.java文件,去除其他类型的文件
return new File(dir,name).isFile()&&name.endsWith(".java");
}
});
// C:遍历该File数组,得到每一个File对象
for (File file : srcFiles) {
String name = file.getName();
File newFile=new File(destFolder,name);
copyFile(file,newFile);
}
//E:在目的地目录下改名
for (File file :destFolder.listFiles()) {
String name = file.getName();
String newName = name.replace(".java", ".txt");
File newFile=new File(destFolder,newName);
file.renameTo(newFile);
}
}
// D:把该File进行复制
private static void copyFile(File file, File newFile) throws IOException {
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(newFile));
byte [] bys=new byte[1024];
int len=0;
while((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
//关闭资源
bis.close();
bos.close();
}
}</span>
执行结果:
3.2、复制多级文件夹
思路分析
数据源:G:\\
目的地:G:\\
A:封装数据源File
B:封装目的地File
C:判断该File是文件夹还是文件
a:是文件夹
就在目的地目录下创建该文件夹
获取该File对象下的所有文件或者文件夹File对象
遍历得到每一个File对象
回到C
b:是文件
复制(字节流)
示例代码:
<span style="font-size:18px;">public class CopyFolders{
public static void main(String[] args) throws IOException {
// 封装数据源File
File srcFile = new File("G:\\");
// 封装目的地File
File destFile = new File("E:\\");
// 复制文件夹的功能
copyFolder(srcFile, destFile);
}
private static void copyFolder(File srcFile, File destFile)throws IOException {
// 判断该File是文件夹还是文件
if (srcFile.isDirectory()) {
// 文件夹
File newFolder = new File(destFile, srcFile.getName());
newFolder.mkdir();
// 获取该File对象下的所有文件或者文件夹File对象
File[] fileArray = srcFile.listFiles();
for (File file : fileArray) {
copyFolder(file, newFolder);
}
} else {
// 文件
File newFile = new File(destFile, srcFile.getName());
copyFile(srcFile, newFile);
}
}
//复制文件
private static void copyFile(File srcFile, File newFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}</span>
3.3 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低存入文本文件
思路分析:
A:创建学生类
B:创建集合对象
因为要排序,所以用TreeSet<Student>
C:键盘录入学生信息存储到集合
D:遍历集合,把数据写到文本文件
<span style="font-size:18px;">public class Student {
// 姓名
private String name;
// 语文成绩
private int chinese;
// 数学成绩
private int math;
// 英语成绩
private int english;
//构造方法和setter/getter方法省去了。。。。。。。。
}</span>
<span style="font-size:18px;">public class StudentIO {
public static void main(String[] args) throws IOException {
// 创建集合对象(采用匿名内部类的方式,创建带有比较器的集合对象)
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s2.getSum() - s1.getSum();
int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;
int num4 = num3 == 0 ? s1.getName().compareTo(s2.getName()): num3;
return num4;
}
});
// 键盘录入学生信息存储到集合
for (int x = 1; x <= 5; x++) {
Scanner sc = new Scanner(System.in);
System.out.println("请录入第" + x + "个的学习信息");
System.out.println("姓名:");
String name = sc.nextLine();
System.out.println("语文成绩:");
int chinese = sc.nextInt();
System.out.println("数学成绩:");
int math = sc.nextInt();
System.out.println("英语成绩:");
int english = sc.nextInt();
// 创建学生对象
Student s = new Student();
s.setName(name);
s.setChinese(chinese);
s.setMath(math);
s.setEnglish(english);
// 把学生信息添加到集合
ts.add(s);
}
// 遍历集合,把数据写到文本文件
BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt"));
//以下三行代码连写
bw.write("学生信息如下:");
bw.newLine();
bw.flush();
bw.write("姓名\t语文成绩\t数学成绩\t英语成绩");
bw.newLine();
bw.flush();
for (Student s : ts) {
StringBuilder sb = new StringBuilder();
sb.append(s.getName()).append("\t").append(s.getChinese())
.append("\t").append(s.getMath()).append("\t")
.append(s.getEnglish());
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
// 释放资源
bw.close();
System.out.println("学习信息存储完毕");
}
}</span>
输入:
执行结果:
好啦,如果上面的例子可以弄懂,IO就基本没有什么问题啦,哈哈。。。
IO流总结(基础知识)相关推荐
- WinCE流驱动基础知识小结
WinCE流驱动基础知识小结 1.基础知识: 1)系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件细节,在应用程序看来硬件只 ...
- java程序运行必须得三个io类_Java基础知识(三)
一.hashCode 与 equals (重要) 面试官可能会问你:"你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?" 1 ...
- java io 对象_JAVA基础知识之IO——对象序列化
对象序列化 Java对象序列化(Serialize)是指将Java对象写入IO流,反序列化(Deserilize)则是从IO流中恢复该Java对象. 对象序列化将程序运行时内存中的对象以字节码的方式保 ...
- java中IO流的相关知识
目录 一.背景 二.IO流的分类 三.字符流操作相关代码示例 1.FileReader读取文件内容 2.FileWriter向文件写数据 3.实现文件内容复制 4.注意事项 5.利用try-catch ...
- java byte char io流_Java基础进阶 IO流之字节流
1.IO流 1.1.概述 之前学习的File类它只能操作文件或文件夹,并不能去操作文件中的数据.真正保存数据的是文件,数据是在文件中.而File类它只是去对文件本身做操作,不能对文件中的数据进行操作. ...
- java boolean io流_java基础入门-day22-IO流
(1)I/O:Input/Output(了解) java中对数据的输入输出问题是通过io技术解决的. java提供的对象都在java.io包下. (2)IO的分类(掌握) A:按照数据的流向 输入流 ...
- feed流和瀑布流_基础知识讲解:什么是feed流?(图文)
feed是将用户主动订阅的若干消息源组合在一起形成内容聚合器,帮助用户持续地获取最新的订阅源内容.feed流即持续更新并呈现给用户内容的信息流. 最近常听朋友说中了抖音的毒,一有时间就刷抖音,根本停不 ...
- feed流和瀑布流_基础知识讲解:什么是feed流?
feed是将用户主动订阅的若干消息源组合在一起形成内容聚合器,帮助用户持续地获取最新的订阅源内容.feed流即持续更新并呈现给用户内容的信息流. 最近常听朋友说中了抖音的毒,一有时间就刷抖音,根本停不 ...
- java.io.file()_JAVA基础知识之IO-File类
File类介绍 File是java.io包下面的一个类,代表与平台无关的文件或者目录.JAVA中,无论文件还是目录,都可以看作File类的一个对象.File类能对文件或目录新建,删除,获取属性等操作, ...
- linux命令基础知识 管道流,linux基础知识-I/O重定向,管道(示例代码)
系统设定 默认输出设备:标准输出,STDOUT, 1 默认输入设备:标准输入, STDIN, 0 标准错误输出:STDERR, 2 标准输入:键盘 标准输出和错误输出:显示器 I/O重定向: Linu ...
最新文章
- mysql 存储过程插入慢_mysql存储过程太慢怎么办
- 结合PE格式对linker分析1
- 后序遍历的非递归算法python_Python非递归实现二叉树的后续遍历
- 再测Golang JSON类库
- 闪修侠、极客修、千机网,手机维修选哪家?
- Golang (Go语言) Mac OS X下环境搭建 环境变量配置 开发工具配置 Sublime Text 2 【转】...
- iOS 使用ZipArchive压缩文件
- 鸿蒙系统是华容网格吗,鸿蒙上手机还在迟疑,国内对手却已悄然来到
- 读书感受 之 《反脆弱 · 做一个内心强大的人》
- 查看Win10是否永久激活
- dsp和通用计算机的区别,DSP处理器与通用处理器的比较
- 网易云音乐虚化图片html,CSS之高斯模糊—(网易云歌单)
- java 银联支付_java服务器端移动银联支付的流程
- matlab小数分数互换
- 友盟社会化分享+第三方登录 2022.03.28 最新版
- 了解寄存器: EBP寄存器
- 对接钉钉审批_钉钉审批对接是什么-和钉钉审批对接相关的问题-阿里云开发者社区...
- ubuntu20.04显卡1080ti安装cuda和cudnn
- ASP社群直播/聊天室/分销/系统源码
- excel字符串拆分为行
热门文章
- java 直角坐标系_直角坐标系(Java)
- mac 安装qemu的方法
- 在creo中,如何在轴上画一个键槽?
- taobao.itemprops.get( 获取标准商品类目属性 )
- 项目管理高手常用的10种图表!
- 纪念谢尔盖·科尔塔科夫
- 锐安信sslTrus 5年期证书订阅套餐 简化SSL证书管理
- macbook python文件路径_mac下python目录
- 盘点“PayPal黑帮”成员的传奇创业故事
- Android Studio Arctic Fox | 2020.3.1、Gradle 7.0升级记录