1. File 类
文件分隔符
String pathSeparator = File.pathSeparator;//与系统有关的路径分隔符 windows分号,linux冒号
String separator = File.separator;// 文件名称分隔符,windows 反斜杠\ ,linux正斜杠/
构造文件对象 and 获取文件属性 ```java // 构造一:File(String pathName) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例 File f1 = new File(“D:\JavaLearing\code\testIO\cyt.txt”); System.out.println(f1.length()); //获取文件大小,若不存在,则大小为 0; System.out.println(f1.getPath()); //output:D:\JavaLearing\code\testIO\cyt.txt System.out.println(f1.getAbsolutePath()); //output:D:\JavaLearing\code\testIO\cyt.txt
File f2 = new File(“E:\JavaLearing\code\testIO”); System.out.println(f2.length()); //文件夹没有大小概念,返回为 0
File f3 = new File(“java.txt”);//文件会在当前路径下新建 java.txt System.out.println(f3.getPath()); //output: java.txt System.out.println(f3.getAbsolutePath()); //output: E:\JavaLearing\code\testIO\java.txt
// 构造二:File(String parent, String child) File f4 = new File(“e:\“,”cyt.txt”); System.out.println(f4.getName());//获取的是路径的结尾部分,即最后一个分隔符之后的部分
// 构造三:File(File parent, String child) File parent = new File(“c:\“); File f5 = new File(parent, “cyt.java”);
// 构造四:File(String url)
- 创建与删除文件夹文件
```java
// 创建一:createNewFile
File f1 = new File("E:\\JavaLearing\\code\\testIO\\1.txt");
try {
boolean b = f1.createNewFile();
System.out.println(b);
} catch (IOException e) {
e.printStackTrace();
}
// 创建二 :mkdir 创建单级文件夹
File f2 = new File("E:\\JavaLearing\\code\\testIO\\hhhhh");
System.out.println(f2.mkdir());
// 创建三:mkdirs 创建多级文件夹
File f3= new File("E:\\JavaLearing\\code\\testIO\\hh\\cuwegui");
System.out.println(f3.mkdirs());
// 删除文件夹
f2.delete();
- 遍历文件目录 ```java // File 类遍历目录,遍历的是构造方法中给出的目录,如果构造方法中的目录路径不存在或不是一个目录,会抛出空指针异常 File f = new File(“E:\JavaLearing\code\testIO”); // 方式一: String[] list() String[] arr = f.list();
// 方式二: File[] listFiles() File[] files = f.listFiles();
// list 输出的是该路径下子文件的名称, listFiles 输出的是子文件的全路径名称 // eg:testIO.iml , E:\JavaLearing\code\testIO\testIO.iml
- 递归实现文件搜索
- 实现一:FileFilter
```java
//1. 普通表达
class MyFileFilter implements FileFilter {
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return true;
}
return pathname.getName().toLowerCase().endsWith(".java");
}
}
// 依据过滤器返回的 boolean 值决定是否加入 files 数组,为 true 时才加入
File[] files = dir.listFiles(new MyFileFilter());
for (File f : files) {
if(f.isDirectory()){
testFileFilter(f);
}else{
System.out.println(f);
}
}
//2. Lambda 表达
File[] files = dir.listFiles(f->f.isDirectory() || f.getName().toLowerCase().endsWith(".java"));
- 实现二:FilenameFilter ```java //方式一:匿名内部内 File[] files = dir.listFiles(new FilenameFilter(){ public boolean accept(File dir, String name) { return new File(dir,name).isDirectory() || name.toLowerCase().endsWith(“java”); }});
//方式二:Lambda 表达式 File[] files = dir.listFiles((d, name)->new File(d,name).isDirectory() || name.toLowerCase().endsWith(“.java”) ); for (File f : files) { if(f.isDirectory()){ testFileFilter(f); }else{ System.out.println(f); } }
<a name="0is3b"></a>
## 2. 基本 IO 流
![](https://cdn.nlark.com/yuque/0/2019/png/611598/1576299703610-56446926-b0d2-4cdb-a6ca-01d0e171b0c2.png#align=left&display=inline&height=732&originHeight=732&originWidth=934&size=0&status=done&style=none&width=934)
- 注意
- 字节使用 byte 数组,字符使用 char 数组;
- 相比 InputStream,writer 是先把数据写入到内存缓冲区中,然后再从缓冲区刷新到内存或文件中 flush();
- 字符流是字节流的包装,字节流不支持直接写入或读取 Unicode 码元,字符流可直接接受字符串,其内部将字符串转成字节,再写入底层设备;
<a name="HMIcT"></a>
### 2.1 字节流
- 复制文件
```java
// 复制文件
FileInputStream in = new FileInputStream("C:\\Users\\yuting\\Desktop\\1.jpg");
FileOutputStream out = new FileOutputStream("C:\\Users\\yuting\\Desktop\\1.1.jpg");
byte[] bytes = new byte[1024];//缓存读取到的多个字节,数组大小为 1024 的整数倍
int len = 0;//记录已经存取的有效字节个数,结尾读取的为 -1;
while((len = in.read(bytes)) != -1) {
// out.write(bytes);
out.write(bytes, 0,len);//从 偏移量 offer 开始写len个字节
out.write("\r\n".getBytes());//换行
}
out.close();
in.close();
// 开启续写
// FileOutputStream out = new FileOutputStream("E:\\JavaLearing\\code\\testIO\\1.txt",true);
2.2 字符流
FileReader
public static void testFileReader() throws IOException { FileReader reader = new FileReader("E:\\JavaLearing\\code\\testIO\\1.txt"); int len = 0; char[] cs = new char[1024]; while ((len = reader.read(cs)) != -1){ System.out.print(new String(cs,0,len)); } reader.close(); }
FileWriter
//写数据的时候会把十进制的整数转换为二进制,任意的文本编辑器在打开文件的时候,都会查询编码表,把字节转换为字符表示; //写入字节数组,如果写的第一个字节是正数(0~127),则会查询ASCLL码,若为负数,则第一个和第二个字节组成中文显示,查询默认码表GBK; public static void testFileWriter() throws IOException { FileWriter writer = new FileWriter("E:\\JavaLearing\\code\\testIO\\1.txt",true); writer.write(97);//写入单个字符 writer.flush();//刷新缓冲区,流对象可以继续使用 writer.write(98); char[] cs = {'a','b','c','d','e'}; writer.write(cs);//写入字符数组 writer.write("天津\r\n");//换行 writer.write("just have a good day",1,2); writer.close();//close 先刷新缓冲区,然后通知系统释放资源,流对象不可再被使用 }
2.3 缓冲流
处理字节流
public static void testBuffer() throws IOException{ FileInputStream in = new FileInputStream("C:\\Users\\yuting\\Desktop\\1.jpg"); FileOutputStream out = new FileOutputStream("C:\\Users\\yuting\\Desktop\\1.1.jpg"); BufferedInputStream bis = new BufferedInputStream(in); BufferedOutputStream bos = new BufferedOutputStream(out); int len = 0; byte[] bytes = new byte[1024]; while((len = bis.read(bytes)) != -1) { bos.write(bytes,0,len); } bos.flush(); bos.close(); bis.close(); }
处理字符流
public static void testBuffer() throws IOException{ FileWriter out = new FileWriter("E:\\JavaLearing\\code\\testIO\\1.txt",true); BufferedWriter bw = new BufferedWriter(out); for (int i = 0; i < 5; i++){ // bw.write("cyt\r\n"); bw.write("hahahhh"); bw.newLine();//换行 } bw.flush(); bw.close(); FileReader in = new FileReader("E:\\JavaLearing\\code\\testIO\\1.txt"); BufferedReader br = new BufferedReader(in); String line = null; // 在进行文本复制时需注意,readLine() 读的内容不包括终止符号 while ((line = br.readLine()) != null){ System.out.println(line); } }
2.4 转换流
InputStreamWriter
public static void testInPutStreamWriter() throws IOException{ //文件的编码格式和读取的编码格式要一致,否则会乱码 InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\JavaLearing\\code\\testIO\\gbk.txt"),"gbk"); int len = 0; while ((len=isr.read()) != -1){ System.out.print((char) len); } isr.close(); }
OutputStreamWriter
public static void testOutPutStreamWriter() throws IOException { // OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\JavaLearing\\code\\testIO\\UTF_8.txt",true),"utf-8");//不指定默认utf-8编码 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\JavaLearing\\code\\testIO\\gbk.txt",true),"gbk"); //会有乱码 osw.write("陈玉婷"); osw.flush(); osw.close(); }
2.5 序列化与反序列化流
说明
- 继承 Serializable 的类(Person)中,static 和 transient 修饰的成员变量不能被序列化;
- 为了防止 InvalidClassException 异常,可以在 实现类中设定 版本号;
class Person implemnets Serialiable{ private static final long serialVersionUID = 1L; }
序列化
public static void testObjectInputStream() throws IOException, ClassNotFoundException { // 如果 对象类是匿名的,则会报错 java.io.InvalidClassException: ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\JavaLearing\\code\\testIO\\person.txt")); Object o = ois.readObject(); ois.close(); System.out.println(o); }
反序列化
public static void testObjectOutputStream() throws IOException { Person p = new Person("cyt",25); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\JavaLearing\\code\\testIO\\person.txt")); oos.writeObject(p);//person 类以二进制保存 oos.close(); }
2.6 打印流
public static void main(String[] args) throws FileNotFoundException { /** * PrintStream 特点: * 1)只负责数据的额输出,不负责数据的读取 * 2)与其他输出流不同,printStream 永远不会输出 IOException * 3)有特有的方法 print.println * 4)可以输出任意类型的值,注意 write()由码表翻译后输出,println直接输出并换行 * 5) 可以改变输出语句的目的地(即打印流的流向)**/ PrintStream ps = new PrintStream("E:\\JavaLearing\\code\\testIO\\print.txt"); ps.write(97); ps.println(97); ps.println('a'); ps.println("hello world"); ps.println(true); System.out.println("在控制台输出的语句"); System.setOut(ps); System.out.println("在打印流的目的地输出"); ps.close(); /** person.txt 中的结果 * a97 * a * hello world * true * 在打印流的目的地输出 */ }
3. 异常处理
public static void testTryCatch(){ FileReader reader = null; try{ reader = new FileReader("E:\\JavaLearing\\code\\testIO\\1.txt"); int len = 0; char[] cs = new char[1024]; while ((len = reader.read(cs)) != -1){ System.out.print(new String(cs,0,len)); } }catch (IOException e){ e.printStackTrace(); }finally { if (reader != null){//不做此判断的话,若不能进行正常的 try 操作,则 reader=null,会报空指针异常 try { reader.close(); } catch (IOException e){ e.printStackTrace(); } } } // jdk 1.7 后可以不用写 finally 来关闭流,但是需要在 try 后加括号 () /** * jdk 1.7 支持的异常处理格式: * try(定义流对象1,定义流对象2,...){ * * }catch(){ * * } */ /** * jdk 1.9 支持的异常处理格式: * A a = new A(); * B b = new B(); * try(a,b){ * * }catch(){ * * } */ }
4. IO 模型
4.1. 同步阻塞
4.2. 同步非阻塞(论询)
4.3. 信号驱动式
4.4. I/O 多路复用【事件驱动】
背景:多线程
- 使用一个线程来监控多个文件描述符(Socket)的就绪状态
少量线程数,减少内存开销和上下文切换的CPU开销;
![](https://cdn.nlark.com/yuque/0/2019/jpeg/611598/1576146869799-594ccca1-e9ee-44eb-9b09-3dd41907ff58.jpeg#align=left&display=inline&height=229&originHeight=272&originWidth=500&size=0&status=done&style=none&width=421)
select、poll 和 epoll 的区别
- select
- 缺点:
- 每次调用 select ,都需要把fd(文件描述符)集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
- 每次调用 select 都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- select 支持的文件描述符数量太小,默认是1024;
- poll
- 与 select 非常相似,只是描述 fd 集合的方式不同,poll 使用 pollfd 结构而不是 select 的 fd_set 结构;
- epoll
- 三个函数:
- epoll_create:创建一个 epoll 句柄;
- epoll_ctl:注册监听的事件类型;
- epoll_wait:等待事件的产生;
- 三点改进:
- 1)每次注册新的事件到 epoll 句柄中时(在 epoll_ctl 中指定 EPOLL_CTL_ADD ),会把所有的 fd 拷贝进内核,而不是在 epoll_wait 的时候重复拷贝,即 epoll 保证了每个 fd 在整个过程中只会拷贝一次;
- 2)epoll_ctl 只把 current 挂一遍(这一遍必不可少)并为每个 fd 指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表,epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd;
- 所支持的 fd 无限制,为最大可以打开文件的数目;
- 三个函数:
- 相同点:select,poll,epoll本质上都是同步阻塞 I/O,应用程序需在读写事件就绪后自己负责进行读写;
- 不同点:
- 1)select,poll 实现需要自己不断轮询所有 fd 集合,直到设备就绪,期间可能要睡眠和唤醒多次交替,而 epoll 其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪 fd 放入就绪链表中,并唤醒在 epoll_wait 中进入睡眠的进程,虽然都要睡眠和交替,但是 select 和 poll 在“醒着”的时候要遍历整个 fd 集合,而 epoll 在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的 CPU 时间;
- 2)select,poll 每次调用都要把 fd 集合从用户态往内核态拷贝一次,并且要把 current 往设备等待队列中挂一次,而 epoll 只要一次拷贝,而且把 current 往等待队列上挂也只挂一次(在 epoll_wait 的开始,注意这里的等待队列并不是设备等待队列,只是一个 epoll 内部定义的等待队列);
4.5. 异步非阻塞
- 异步 IO :无需应用程序自己读写,回调后数据会直接从内核拷贝到用户空间;
4.6. 异步 IO + 协程
4.7 小结
5. IO 与多线程
5.1 管道流
- 管道流仅用于多个线程之间传递信息,若用在同一个线程中可能造成死锁;
- 管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或 connect 函数进行连接;
- 一对管道流包含一个缓冲区,其默认值为 1024 个字节,若要改变缓冲区大小,可使用带有参数的构造函数;
- 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞,当缓冲区满时,写操作阻塞;
- 管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
管道流的读取方法与普通流不同,只有输出流正确 close 时,输出流才能读到 -1 值; ```java public static void testPipedStream() throws IOException {
//创建一个线程池 ExecutorService executorService = Executors.newCachedThreadPool(); try { //创建输入和输出管道流 PipedOutputStream pos = new PipedOutputStream(); PipedInputStream pis = new PipedInputStream(pos);//必须传入此参数,否则报错: Pipe not connected //创建发送线程和接收线程 Sender sender = new Sender(pos); Receiver receiver = new Receiver(pis); //提交给线程池运行线程 executorService.execute(sender); executorService.execute(receiver); } catch (IOException e) { e.printStackTrace(); } //通知线程池,不再接受新的任务,并执行完成当前正在运行的线程后关闭线程池 executorService.shutdown(); try{ //shutdown 后可能正在运行的线程很长时间都运行不完成,设置超时处理 executorService.awaitTermination(1, TimeUnit.HOURS); } catch (InterruptedException e) { e.printStackTrace(); } class Sender extends Thread { private PipedOutputStream pos; public Sender(PipedOutputStream pos){ this.pos = pos; } public void run(){ String s = "have a good day"; System.out.println("Sender : " + s); byte[] bytes = s.getBytes(); try { pos.write(bytes,0,bytes.length); pos.close(); TimeUnit.SECONDS.sleep(5); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } } class Receiver extends Thread{ private PipedInputStream pis; public Receiver(PipedInputStream pis){ this.pis = pis; } public void run(){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = 0; byte[] buf = new byte[1024]; try { while (((len = pis.read(buf)) != -1)) { baos.write(buf,0, len); } byte[] results = baos.toByteArray(); String s = new String(results,0, results.length); System.out.println("Reciever : " + s); } catch (IOException e) { e.printStackTrace(); } } }
} 输出:
Sender : have a good day Reciever : have a good day
<a name="6Fmcw"></a>
### 5.2 Properties 集合
- Java.util.Properties 集合 extends HashMap implements Map;
- Properties 类表示一个持久的属性集,Properties 可保存在流中或从流中加载;
- Properties 集合是一个唯一和 IO 流相结合的集合
- 可以使用 Properties 集合中的方法 store 把集合中的临时数据持久化写到硬盘中存储;
- 可以使用 Properties 集合中的方法 load 把硬盘中保存的文件(键值对)读取到集合中使用;
- Properties 是双列集合,key 和 value 默认都是字符串;
```java
public static void testProperties() throws IOException{
Properties p = new Properties();
p.setProperty("a","123");
p.setProperty("b","456");
p.setProperty("c","789");
// 保存
FileWriter fw = new FileWriter("E:\\JavaLearing\\code\\testIO\\prop.properties");
p.store(fw, "save data");//若是字节流,会乱码,而且 comment 是中文的话也会乱码
fw.close();
// 加载
p.load(new FileReader("E:\\JavaLearing\\code\\testIO\\prop.properties"));
Set<String> set = p.stringPropertyNames();
for (String key: set) {
String value = p.getProperty(key);
System.out.println(value);
}
}