JAVA-IO
来源:互联网 发布:ipv4无网络访问权限 编辑:程序博客网 时间:2024/06/16 18:54
JAVA-IO
尽可多创造快乐去填满时间,哪可活活缚着时间来陪着快乐。——闻一多
字节流与字符流综述
字符流和字节流:字节流两个基类:InputStream OutputStream字符流两个基类:Reader Writer先学习一下字符流的特点。既然IO流是用于操作数据的,那么数据的最常见体现形式是:文件。那么先以操作文件为主来演示。需求:在硬盘上,创建一个文件并写入一些文字数据。找到一个专门用于操作文件的Writer子类对象。FileWriter。 后缀名是父类名。 前缀名是该流对象的功能。
字符流
一个小的用于在硬盘上存储字符的Demo
import java.io.*;class FileWriterDemo{ public static void main(String[] args) throws IOException { //创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。 //而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。 //其实该步就是在明确数据要存放的目的地。 FileWriter fw = new FileWriter("demo.txt"); //调用write方法,将字符串写入到流中。 fw.write("abcde"); //刷新流对象中的缓冲中的数据。 //将数据刷到目的地中。 //fw.flush(); //关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。 //将数据刷到目的地中。 //和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。 fw.close(); }}
IO的异常处理方式
有时我们会遇见目的文件不存在、流没有正常的关闭等异常
import java.io.*;class FileWriterDemo2{ public static void main(String[] args) { FileWriter fw = null; try { fw = new FileWriter("demo.txt"); fw.write("abcdefg"); } catch (IOException e) { System.out.println("catch:"+e.toString()); } finally { try { if(fw!=null) fw.close(); } catch (IOException e) { System.out.println(e.toString()); } } }}
不覆盖续写文件
import java.io.*;class FileWriterDemo3{ public static void main(String[] args) throws IOException { //传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。 FileWriter fw = new FileWriter("demo.txt",true); fw.write("nihao\r\nxiexie"); fw.close(); }}
注意,在这里/r/n是windows下的换行符号,如果需要打印“/r/n”,就请再加/
进行转义。
利用FileReader进行读取
import java.io.*;class FileReaderDemo{ public static void main(String[] args) throws IOException { //创建一个文件读取流对象,和指定名称的文件相关联。 //要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException FileReader fr = new FileReader("demo.txt"); //调用读取流对象的read方法。 //read():一次读一个字符。而且会自动往下读。 int ch = 0; while((ch=fr.read())!=-1) { System.out.println( } /* while(true) { int ch = fr.read(); if(ch==-1) break; System.out.println("ch="+(char)ch); } */ fr.close(); }}
利用FileReader进行读取的第二种方式
import java.io.*;class FileReaderDemo2 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("demo.txt"); //定义一个字符数组。用于存储读到字符。 //该read(char[])返回的是读到字符个数。 char[] buf = new char[1024]; int num = 0; while((num=fr.read(buf))!=-1) { System.out.println(new String(buf,0,num)); } fr.close(); }}
读取一个.java文件,并打印在控制台上
import java.io.*;class FileReaderTest { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("DateDemo.java"); char[] buf = new char[1024]; int num = 0; while((num=fr.read(buf))!=-1) { System.out.print(new String(buf,0,num)); } fr.close(); }}
将C盘的一个文本文件复制到D盘当中
复制的原理:其实就是将C盘下的文件数据存储到D盘的一个文件中。步骤:1,在D盘创建一个文件。用于存储C盘文件中的数据。2,定义读取流和C盘文件关联。3,通过不断的读写完成数据存储。4,关闭资源。
import java.io.*;class CopyText { public static void main(String[] args) throws IOException { copy_2(); } public static void copy_2() { FileWriter fw = null; FileReader fr = null; try { fw = new FileWriter("SystemDemo_copy.txt"); fr = new FileReader("SystemDemo.java"); char[] buf = new char[1024]; int len = 0; while((len=fr.read(buf))!=-1) { fw.write(buf,0,len); } } catch (IOException e) { throw new RuntimeException("读写失败"); } finally { if(fr!=null) try { fr.close(); } catch (IOException e) { } if(fw!=null) try { fw.close(); } catch (IOException e) { } } } //从C盘读一个字符,就往D盘写一个字符。 public static void copy_1()throws IOException { //创建目的地。 FileWriter fw = new FileWriter("RuntimeDemo_copy.txt"); //与已有文件关联。 FileReader fr = new FileReader("RuntimeDemo.java"); int ch = 0; while((ch=fr.read())!=-1) { fw.write(ch); } fw.close(); fr.close(); }}
带有缓冲区的Writer
缓冲区的出现是为了提高流的操作效率而出现的。所以在创建缓冲区之前,必须要先有流对象。该缓冲区中提供了一个跨平台的换行符。newLine();
import java.io.*;class BufferedWriterDemo{ public static void main(String[] args) throws IOException { //创建一个字符写入流对象。 FileWriter fw = new FileWriter("buf.txt"); //为了提高字符写入流效率。加入了缓冲技术。 //只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。 BufferedWriter bufw = new BufferedWriter(fw); for(int x=1; x<5; x++) { bufw.write("abcd"+x); bufw.newLine(); bufw.flush(); } //记住,只要用到缓冲区,就要记得刷新。 //bufw.flush(); //其实关闭缓冲区,就是在关闭缓冲区中的流对象。 bufw.close(); }}
带有缓冲区的Reader
字符读取流缓冲区:该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取。当返回null时,表示读到文件末尾。readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。
import java.io.*;class BufferedReaderDemo{ public static void main(String[] args) throws IOException { //创建一个读取流对象和文件相关联。 FileReader fr = new FileReader("buf.txt"); //为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。 BufferedReader bufr = new BufferedReader(fr); String line = null; while((line=bufr.readLine())!=null) { System.out.print(line); } bufr.close(); }}
通过缓冲区来复制文件
import java.io.*;class CopyTextByBuf{ public static void main(String[] args) { BufferedReader bufr = null; BufferedWriter bufw = null; try { bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java")); bufw = new BufferedWriter(new FileWriter("bufWriter_Copy.txt")); String line = null; while((line=bufr.readLine())!=null) { bufw.write(line); bufw.newLine(); bufw.flush(); } } catch (IOException e) { throw new RuntimeException("读写失败"); } finally { try { if(bufr!=null) bufr.close(); } catch (IOException e) { throw new RuntimeException("读取关闭失败"); } try { if(bufw!=null) bufw.close(); } catch (IOException e) { throw new RuntimeException("写入关闭失败"); } } }}
模拟BufferedReader
明白了BufferedReader类中特有方法readLine的原理后,可以自定义一个类中包含一个功能和readLine一致的方法。来模拟一下BufferedReader
import java.io.*;class MyBufferedReader extends Reader{ private Reader r; MyBufferedReader(Reader r) { this.r = r; } //可以一次读一行数据的方法。 public String myReadLine()throws IOException { //定义一个临时容器。原BufferReader封装的是字符数组。 //为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch=r.read())!=-1) { if(ch=='\r') continue; if(ch=='\n') return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0) return sb.toString(); return null; } /* 覆盖Reader类中的抽象方法。 */ public int read(char[] cbuf, int off, int len) throws IOException { return r.read(cbuf,off,len) ; } public void close()throws IOException { r.close(); } public void myClose()throws IOException { r.close(); }}class MyBufferedReaderDemo{ public static void main(String[] args) throws IOException { FileReader fr = new FileReader("buf.txt"); MyBufferedReader myBuf = new MyBufferedReader(fr); String line = null; while((line=myBuf.myReadLine())!=null) { System.out.println(line); } myBuf.myClose(); }}
字节流
字符流:FileReaderFileWriter。BufferedReaderBufferedWriter字节流:InputStream OutputStream需求,想要操作图片数据。这时就要用到字节流。复制一个图片.
import java.io.*;class FileStream{ public static void main(String[] args) throws IOException { readFile_3(); } public static void readFile_3()throws IOException { FileInputStream fis = new FileInputStream("fos.txt");// int num = fis.available(); byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区。不用在循环了。 fis.read(buf); System.out.println(new String(buf)); fis.close(); } public static void readFile_2()throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); byte[] buf = new byte[1024]; int len = 0; while((len=fis.read(buf))!=-1) { System.out.println(new String(buf,0,len)); } fis.close(); } public static void readFile_1()throws IOException { FileInputStream fis = new FileInputStream("fos.txt"); int ch = 0; while((ch=fis.read())!=-1) { System.out.println((char)ch); } fis.close(); } public static void writeFile()throws IOException { FileOutputStream fos = new FileOutputStream("fos.txt"); fos.write("abcde".getBytes()); fos.close(); }}
一个复制图片的demo
复制一个图片思路:1,用字节读取流对象和图片关联。2,用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。3,通过循环读写,完成数据的存储。4,关闭资源。
import java.io.*;class CopyPic{ public static void main(String[] args) { FileOutputStream fos = null; FileInputStream fis = null; try { fos = new FileOutputStream("c:\\2.bmp"); fis = new FileInputStream("c:\\1.bmp"); byte[] buf = new byte[1024]; int len = 0; while((len=fis.read(buf))!=-1) { fos.write(buf,0,len); } } catch (IOException e) { throw new RuntimeException("复制文件失败"); } finally { try { if(fis!=null) fis.close(); } catch (IOException e) { throw new RuntimeException("读取关闭失败"); } try { if(fos!=null) fos.close(); } catch (IOException e) { throw new RuntimeException("写入关闭失败"); } } }}
一个复制mp3的demo
/*演示mp3的复制。通过缓冲区。BufferedOutputStreamBufferedInputStream*/import java.io.*;class CopyMp3{ public static void main(String[] args) throws IOException { long start = System.currentTimeMillis(); copy_2(); long end = System.currentTimeMillis(); System.out.println((end-start)+"毫秒"); } public static void copy_2()throws IOException { MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\9.mp3")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\3.mp3")); int by = 0; //System.out.println("第一个字节:"+bufis.myRead()); while((by=bufis.myRead())!=-1) { bufos.write(by); } bufos.close(); bufis.myClose(); } //通过字节流的缓冲区完成复制。 public static void copy_1()throws IOException { BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3")); int by = 0; while((by=bufis.read())!=-1) { bufos.write(by); } bufos.close(); bufis.close(); }}
读取键盘的录入
读取键盘录入。System.out:对应的是标准输出设备,控制台。System.in:对应的标准输入设备:键盘。需求:通过键盘录入数据。当录入一行数据后,就将该行数据进行打印。如果录入的数据是over,那么停止录入。
import java.io.*;class ReadIn{ public static void main(String[] args) throws IOException { InputStream in = System.in; StringBuilder sb = new StringBuilder(); while(true) { int ch = in.read(); if(ch=='\r') continue; if(ch=='\n') { String s = sb.toString(); if("over".equals(s)) break; System.out.println(s.toUpperCase()); sb.delete(0,sb.length()); } else sb.append((char)ch); } }}
字符转换流
字符流:FileReaderFileWriter。BufferedReaderBufferedWriter字节流:FileInputStreamFileOutputStreamBufferedInputStreamBufferedOutputStream通过刚才的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理。也就是readLine方法。能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?readLine方法是字符流BufferedReader类中的方法。而键盘录入的read方法是字节流InputStream的方法。那么能不能将字节流转成字符流在使用字符流缓冲去的readLine方法呢?
import java.io.*;class TransStreamDemo{ public static void main(String[] args) throws IOException { //获取键盘录入对象。 //InputStream in = System.in; //将字节流对象转成字符流对象,使用转换流。InputStreamReader //InputStreamReader isr = new InputStreamReader(in); //为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader //BufferedReader bufr = new BufferedReader(isr); //键盘的最常见写法。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));// OutputStream out = System.out;// OutputStreamWriter osw = new OutputStreamWriter(out);// BufferedWriter bufw = new BufferedWriter(osw); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); } bufr.close(); }}
转换流思维模型
1,源:键盘录入。目的:控制台。2,需求:想把键盘录入的数据存储到一个文件中。源:键盘。目的:文件。3,需求:想要将一个文件的数据打印在控制台上。源:文件。目的:控制台。流操作的基本规律:最痛苦的就是流对象有很多,不知道该用哪一个。通过三个明确来完成。1,明确源和目的。 源:输入流。InputStream Reader 目的:输出流。OutputStream Writer。2,操作的数据是否是纯文本。 是:字符流。 不是:字节流。3,当体系明确后,在明确要使用哪个具体的对象。 通过设备来进行区分: 源设备:内存,硬盘。键盘 目的设备:内存,硬盘,控制台。1,将一个文本文件中数据存储到另一个文件中。复制文件。 源:因为是源,所以使用读取流。InputStream Reader 是不是操作文本文件。 是!这时就可以选择Reader 这样体系就明确了。 接下来明确要使用该体系中的哪个对象。 明确设备:硬盘。上一个文件。 Reader体系中可以操作文件的对象是 FileReader 是否需要提高效率:是!。加入Reader体系中缓冲区 BufferedReader. FileReader fr = new FileReader("a.txt"); BufferedReader bufr = new BufferedReader(fr); 目的:OutputStream Writer 是否是纯文本。 是!Writer。 设备:硬盘,一个文件。 Writer体系中可以操作文件的对象FileWriter。 是否需要提高效率:是!。加入Writer体系中缓冲区 BufferedWriter FileWriter fw = new FileWriter("b.txt"); BufferedWriter bufw = new BufferedWriter(fw);练习:将一个图片文件中数据存储到另一个文件中。复制文件。要按照以上格式自己完成三个明确。---------------------------------------2,需求:将键盘录入的数据保存到一个文件中。 这个需求中有源和目的都存在。 那么分别分析 源:InputStream Reader 是不是纯文本?是!Reader 设备:键盘。对应的对象是System.in. 不是选择Reader吗?System.in对应的不是字节流吗? 为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。 所以既然明确了Reader,那么就将System.in转换成Reader。 用了Reader体系中转换流,InputStreamReader InputStreamReader isr = new InputStreamReader(System.in); 需要提高效率吗?需要!BufferedReader BufferedReader bufr = new BufferedReader(isr); 目的:OutputStream Writer 是否是存文本?是!Writer。 设备:硬盘。一个文件。使用 FileWriter。 FileWriter fw = new FileWriter("c.txt"); 需要提高效率吗?需要。 BufferedWriter bufw = new BufferedWriter(fw); ************** 扩展一下,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。 目的:OutputStream Writer 是否是存文本?是!Writer。 设备:硬盘。一个文件。使用 FileWriter。 但是FileWriter是使用的默认编码表。GBK. 但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。 所以要使用的对象是OutputStreamWriter。 而该转换流对象要接收一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8"); 需要高效吗?需要。 BufferedWriter bufw = new BufferedWriter(osw); 所以,记住。转换流什么使用。字符和字节之间的桥梁,通常,涉及到字符编码转换时, 需要用到转换流。练习:将一个文本数据打印在控制台上。要按照以上格式自己完成三个明确。
class TransStreamDemo2{ public static void main(String[] args) throws IOException { System.setIn(new FileInputStream("PersonDemo.java")); System.setOut(new PrintStream("zzz.txt")); //键盘的最常见写法。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); } bufr.close(); }}
打印流
打印流:该流提供了打印方法,可以将各种数据类型的数据都原样打印。字节打印流:PrintStream构造函数可以接收的参数类型:1,file对象。File2,字符串路径。String3,字节输出流。OutputStream字符打印流:PrintWriter构造函数可以接收的参数类型:1,file对象。File2,字符串路径。String3,字节输出流。OutputStream4,字符输出流,Writer。
import java.io.*;class PrintStreamDemo{ public static void main(String[] args) throws IOException { BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true); String line = null; while((line=bufr.readLine())!=null) { if("over".equals(line)) break; out.println(line.toUpperCase()); //out.flush(); } out.close(); bufr.close(); } }
PipedStream(管道流对象)
import java.io.*;class Read implements Runnable{ private PipedInputStream in; Read(PipedInputStream in) { this.in = in; } public void run() { try { byte[] buf = new byte[1024]; System.out.println("读取前。。没有数据阻塞"); int len = in.read(buf); System.out.println("读到数据。。阻塞结束"); String s= new String(buf,0,len); System.out.println(s); in.close(); } catch (IOException e) { throw new RuntimeException("管道读取流失败"); } }}class Write implements Runnable{ private PipedOutputStream out; Write(PipedOutputStream out) { this.out = out; } public void run() { try { System.out.println("开始写入数据,等待6秒后。"); Thread.sleep(6000); out.write("piped lai la".getBytes()); out.close(); } catch (Exception e) { throw new RuntimeException("管道输出流失败"); } }}class PipedStreamDemo{ public static void main(String[] args) throws IOException { PipedInputStream in = new PipedInputStream(); PipedOutputStream out = new PipedOutputStream(); in.connect(out); Read r = new Read(in); Write w = new Write(out); new Thread(r).start(); new Thread(w).start(); }}
RandomAccessFile
class RandomAccessFileDemo { public static void main(String[] args) throws IOException { //writeFile_2(); //readFile(); //System.out.println(Integer.toBinaryString(258)); } public static void readFile()throws IOException { RandomAccessFile raf = new RandomAccessFile("ran.txt","r"); //调整对象中指针。 //raf.seek(8*1); //跳过指定的字节数 raf.skipBytes(8); byte[] buf = new byte[4]; raf.read(buf); String name = new String(buf); int age = raf.readInt(); System.out.println("name="+name); System.out.println("age="+age); raf.close(); } public static void writeFile_2()throws IOException { RandomAccessFile raf = new RandomAccessFile("ran.txt","rw"); raf.seek(8*0); raf.write("周期".getBytes()); raf.writeInt(103); raf.close(); } public static void writeFile()throws IOException { RandomAccessFile raf = new RandomAccessFile("ran.txt","rw"); raf.write("李四".getBytes()); raf.writeInt(97); raf.write("王五".getBytes()); raf.writeInt(99); raf.close(); }}
操作基本数据类型流DataInputStream与DataOutputStream
import java.io.*;class DataStreamDemo { public static void main(String[] args) throws IOException { //writeData(); //readData(); //writeUTFDemo();// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");//// osw.write("你好");// osw.close();// readUTFDemo(); } public static void readUTFDemo()throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt")); String s = dis.readUTF(); System.out.println(s); dis.close(); } public static void writeUTFDemo()throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdate.txt")); dos.writeUTF("你好"); dos.close(); } public static void readData()throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("data.txt")); int num = dis.readInt(); boolean b = dis.readBoolean(); double d = dis.readDouble(); System.out.println("num="+num); System.out.println("b="+b); System.out.println("d="+d); dis.close(); } public static void writeData()throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt")); dos.writeInt(234); dos.writeBoolean(true); dos.writeDouble(9887.543); dos.close(); ObjectOutputStream oos = null; oos.writeObject(new O()); }}
/*DataInputStream与DataOutputStream可以用于操作基本数据类型的数据的流对象。*/import java.io.*;class DataStreamDemo { public static void main(String[] args) throws IOException { //writeData(); //readData(); //writeUTFDemo();// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");//// osw.write("你好");// osw.close();// readUTFDemo(); } public static void readUTFDemo()throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt")); String s = dis.readUTF(); System.out.println(s); dis.close(); } public static void writeUTFDemo()throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdate.txt")); dos.writeUTF("你好"); dos.close(); } public static void readData()throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("data.txt")); int num = dis.readInt(); boolean b = dis.readBoolean(); double d = dis.readDouble(); System.out.println("num="+num); System.out.println("b="+b); System.out.println("d="+d); dis.close(); } public static void writeData()throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt")); dos.writeInt(234); dos.writeBoolean(true); dos.writeDouble(9887.543); dos.close(); ObjectOutputStream oos = null; oos.writeObject(new O()); }}
ByteArrayStream
import java.io.*;class ByteArrayStream { public static void main(String[] args) { //数据源。 ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFD".getBytes()); //数据目的 ByteArrayOutputStream bos = new ByteArrayOutputStream(); int by = 0; while((by=bis.read())!=-1) { bos.write(by); } System.out.println(bos.size()); System.out.println(bos.toString()); // bos.writeTo(new FileOutputStream("a.txt")); }}
0 0
- 【Java-IO】IO入门
- Java IO:IO框架
- 【IO】java IO 总结
- java IO
- IO.java
- JAVA IO
- JAVA IO
- java IO
- java io
- JAVA IO
- java IO
- java IO
- java io
- java io
- java io
- java IO
- java IO
- java io
- php7的十件小事
- 程序员准备跳槽了,如何选择下一家IT公司?
- 浅析Java中的反射机制原理
- Android5.0--ToolBar学习
- OC中在.h和.m中声明的属性和成员变量
- JAVA-IO
- Angularjs学习笔记(三)表达式、指令、模型
- Medium 17题 Letter Combinations of a Phone Number
- Hibernate框架基础——Hibernate入门
- 无线局域网安全(三)————CCMP加密
- shiro配合html页面完成细粒化权限控制
- WordPress 开发调试
- 关于cisco vpn
- Python——正则RE之flags