javaIO和netty的基本原理
407
Java基础教程/JavaIO与网络编程/01 Java 标准IO.md
Normal file
@@ -0,0 +1,407 @@
|
||||
# Java 标准IO
|
||||
<!-- GFM-TOC -->
|
||||
- [Java 标准IO](#java-标准io)
|
||||
- [1 概览](#1-概览)
|
||||
- [IO定义](#io定义)
|
||||
- [虚拟内存](#虚拟内存)
|
||||
- [IO的分类](#io的分类)
|
||||
- [装饰者模式](#装饰者模式)
|
||||
- [2 字节操作](#2-字节操作)
|
||||
- [InputStream](#inputstream)
|
||||
- [OutputStream](#outputstream)
|
||||
- [3 字符操作](#3-字符操作)
|
||||
- [编码与解码](#编码与解码)
|
||||
- [String 的编码方式](#string-的编码方式)
|
||||
- [字节字符流转换Reader 与 Writer](#字节字符流转换reader-与-writer)
|
||||
- [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容)
|
||||
- [Reader](#reader)
|
||||
- [Writer](#writer)
|
||||
- [参考文献](#参考文献)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
## 1 概览
|
||||

|
||||
|
||||
本篇文章的范围应该是涵盖java.io包中的所有类。
|
||||
|
||||
### IO定义
|
||||
> 该理解方式非常有价值。为什么IO的同步和异步如此重要?因为IO操作是整个调用链路上的性能瓶颈。普通的A函数调用B函数,为什么不采用异步操作呢,因为函数都是计算任务,都在内存完成。所以所有的操作都可以分为两种:计算操作(非IO操作,内存中即可完成)和IO操作(从其他设备中读取、写入数据)。计算操作是使用CPU的,IO操作过程中CPU线程是挂起的,等待中。函数、调用可以分为两种,正常调用和IO调用。
|
||||
|
||||
缓冲区以及如何处理缓冲区是所有I / O的基础。 术语“输入/输出”仅意味着将数据移入和移出缓冲区。 只要时刻牢记这一点即可。 通常,进程通过请求操作系统从缓冲区中清空数据( write operation )或向缓冲区中填充数据( read operation )来执行I / O。 以上是I / O概念的全部摘要。
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
上图显示了块数据如何从外部源(例如硬盘)移动到正在运行的进程(例如RAM)内部的存储区的简化“逻辑”图。
|
||||
1. 首先,该进程通过进行read()系统调用来请求填充其缓冲区。
|
||||
2. 此调用导致内核向磁盘控制器硬件发出命令以从磁盘获取数据。 磁盘控制器通过DMA将数据直接写入内核内存缓冲区,而无需主CPU的进一步协助。
|
||||
3. 磁盘控制器完成缓冲区填充后,当它请求read()操作时。内核将数据从内核空间中的临时缓冲区复制到进程指定的缓冲区中;
|
||||
4. 需要注意的一件事是内核尝试缓存和/或预取数据,因此进程请求的数据可能已经在内核空间中可用。 如果是这样,则将过程所请求的数据复制出来。 如果数据不可用,则该过程将在内核将数据带入内存时挂起。
|
||||
|
||||
### 虚拟内存
|
||||
|
||||
虚拟内存具有两个重要优点:
|
||||
|
||||
1)多个虚拟地址可以引用相同的物理内存位置。
|
||||
2)虚拟内存空间可以大于可用的实际硬件内存。
|
||||
|
||||
对于第一个特性,通过地址映射mmap,将内核空间逻辑地址与用户空间中虚拟地址映射到相同物理空间。DMA硬件(只能访问物理内存地址)可以填充一个缓冲区,该缓冲区同时对内核和用户空间进程可见。消除了内核空间和用户空间之间的副本,
|
||||
|
||||

|
||||
|
||||
对于第二个特性,进行虚拟内存分页(通常称为交换)。将虚拟内存空间的页面持久保存到外部磁盘存储中,从而在物理内存中为其他虚拟页面腾出空间。物理内存充当页面调度区域的缓存,当虚拟内存的不在物理内存中时,由物理内存从磁盘空间交换。
|
||||
|
||||
|
||||
|
||||
### IO的分类
|
||||
Java 的 I/O可以根据如下方式进行分类。
|
||||

|
||||

|
||||

|
||||
|
||||
根据内容的不同可以分为:
|
||||
|
||||
- 字节操作:InputStream 和 OutputStream
|
||||
- 字符操作:Reader 和 Writer
|
||||
- 输入流 InputStream、Reader
|
||||
- 输出流 OutputStream、Writer
|
||||
- 字节转字符:InputStreamReader/OutputStreamWriter
|
||||
|
||||
|
||||
根据对象的不同可以分为:
|
||||
|
||||
- 文件操作:File
|
||||
- 管道操作:Piped
|
||||
- 数组操作:ByteArray&CharArray
|
||||
- 对象操作:Object
|
||||
- 过滤操作:Filter添加额外特性
|
||||
- Bufferd
|
||||
- Data
|
||||
- PushBack
|
||||
- LineNumber
|
||||
- 网络操作:Socket
|
||||
|
||||
|
||||
根据IO模型的不同可以分为:
|
||||
- 同步阻塞IO:BIO
|
||||
- 异步IO:NIO
|
||||
- 异步IO:AIO
|
||||
|
||||
根据IO的原理可以分为:
|
||||
* Block IO 块IO
|
||||
* Stream IO 流IO
|
||||
|
||||
|
||||
|
||||
|
||||
### 装饰者模式
|
||||
|
||||
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
|
||||
|
||||
- InputStream 是抽象组件;
|
||||
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
|
||||
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9709694b-db05-4cce-8d2f-1c8b09f4d921.png" width="650px"> </div><br>
|
||||
|
||||
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
|
||||
|
||||
```java
|
||||
FileInputStream fileInputStream = new FileInputStream(filePath);
|
||||
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
|
||||
```
|
||||
|
||||
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
|
||||
|
||||
|
||||
## 2 字节操作
|
||||

|
||||
|
||||
|
||||
### InputStream
|
||||
|
||||
* 基本的InputStream
|
||||
```
|
||||
int read()
|
||||
int read(byte[] b)
|
||||
int read(byte[] b, int off, int len)
|
||||
void close()
|
||||
long skip(long n)
|
||||
```
|
||||
* FileInputStream
|
||||
|
||||
```java
|
||||
FileInputStream(File file)
|
||||
FileInputStream(String name)
|
||||
```
|
||||
|
||||
* PipedInputStream
|
||||
|
||||
```java
|
||||
PipedInputStream(int pipeSize)
|
||||
PipedInputStream(PipedOutputStream src)
|
||||
PipedInputStream(PipedOutputStream src, int pipeSize)
|
||||
|
||||
connect(PipedOutputStream src)
|
||||
receive(int b)
|
||||
```
|
||||
|
||||
* ByteArrayInputStream
|
||||
|
||||
```java
|
||||
ByteArrayInputStream(byte[] buf)
|
||||
ByteArrayInputStream(byte[] buf, int offset, int length)
|
||||
```
|
||||
|
||||
* ObjectInputStream 持久化对象。用ObjectOutputStream写出,就用这个读入
|
||||
|
||||
```java
|
||||
ObjectInputStream(InputStream in)
|
||||
|
||||
readBoolean()、Byte、Char、Double、Float、Int、Long
|
||||
readFully()
|
||||
readObject()
|
||||
```
|
||||
* FilterInputStream->BufferedInputStream
|
||||
|
||||
```java
|
||||
|
||||
```
|
||||
|
||||
### OutputStream
|
||||
|
||||
* 基本的OutputStream
|
||||
|
||||
```
|
||||
close()
|
||||
flush()
|
||||
write(byte[] b)
|
||||
write(byte[] b, int off, int len)
|
||||
write(int b)
|
||||
```
|
||||
|
||||
* FileOutputStream
|
||||
|
||||
```java
|
||||
FileOutputStream(File file)
|
||||
FileOutputStream(File file, boolean append)
|
||||
FileOutputStream(String name)
|
||||
FileOutputStream(String name, boolean append)
|
||||
```
|
||||
* PipedOutputStream
|
||||
|
||||
```java
|
||||
PipedOutputStream(PipedInputStream snk)
|
||||
connect(PipedInputStream snk)
|
||||
```
|
||||
|
||||
* ByteArrayOutputStream,自带一个字节缓冲区
|
||||
|
||||
```java
|
||||
ByteArrayOutputStream(int size)
|
||||
size()
|
||||
```
|
||||
|
||||
* ObjectOutputStream
|
||||
|
||||
```java
|
||||
ObjectOutputStream(OutputStream out)
|
||||
|
||||
|
||||
writeBoolean(boolean val)
|
||||
writeByte(int val)
|
||||
writeBytes(String str)
|
||||
writeChar(int val)
|
||||
writeChars(String str)
|
||||
writeInt(int val)
|
||||
writeLong(long val)
|
||||
writeObject(Object obj)
|
||||
```
|
||||
|
||||
* FilterOutputStream->BufferedOutputStream
|
||||
|
||||
* PrintStream一个特殊的包装器类
|
||||
```java
|
||||
print(基础类型和对象)
|
||||
printf(String format, Object... args)
|
||||
println(基础类型和对象)
|
||||
```
|
||||
## 3 字符操作
|
||||

|
||||
|
||||
|
||||
### 编码与解码
|
||||
|
||||
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
|
||||
|
||||
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
|
||||
|
||||
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
|
||||
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
|
||||
- UTF-16 be编码中,中文字符和英文字符都占 2 个字节。
|
||||
|
||||
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
|
||||
|
||||
Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
|
||||
|
||||
### String 的编码方式
|
||||
|
||||
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
|
||||
|
||||
```java
|
||||
String str1 = "中文";
|
||||
byte[] bytes = str1.getBytes("UTF-8");
|
||||
String str2 = new String(bytes, "UTF-8");
|
||||
System.out.println(str2);
|
||||
```
|
||||
|
||||
在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。
|
||||
|
||||
```java
|
||||
byte[] bytes = str1.getBytes();
|
||||
```
|
||||
|
||||
### 字节字符流转换Reader 与 Writer
|
||||
|
||||
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
|
||||
|
||||
- InputStreamReader 实现从字节流解码成字符流;
|
||||
|
||||
```java
|
||||
InputStreamReader(InputStream in)
|
||||
InputStreamReader(InputStream in, Charset cs)
|
||||
InputStreamReader(InputStream in, CharsetDecoder dec)
|
||||
InputStreamReader(InputStream in, String charsetName)
|
||||
getEncoding()
|
||||
```
|
||||
- OutputStreamWriter 实现字符流编码成为字节流。
|
||||
|
||||
```java
|
||||
OutputStreamWriter(OutputStream out)
|
||||
OutputStreamWriter(OutputStream out, Charset cs)
|
||||
OutputStreamWriter(OutputStream out, CharsetEncoder enc)
|
||||
OutputStreamWriter(OutputStream out, String charsetName)
|
||||
|
||||
getEncoding()
|
||||
```
|
||||
|
||||
### 实现逐行输出文本文件的内容
|
||||
|
||||
```java
|
||||
public static void readFileContent(String filePath) throws IOException {
|
||||
|
||||
FileReader fileReader = new FileReader(filePath);
|
||||
BufferedReader bufferedReader = new BufferedReader(fileReader);
|
||||
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
System.out.println(line);
|
||||
}
|
||||
|
||||
// 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
|
||||
// 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法
|
||||
// 因此只要一个 close() 调用即可
|
||||
bufferedReader.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Reader
|
||||
|
||||
* 基本的Reader
|
||||
|
||||
```java
|
||||
close()
|
||||
read()
|
||||
read(char[] cbuf)
|
||||
read(char[] cbuf, int off, int len)
|
||||
read(CharBuffer target)
|
||||
ready()
|
||||
skip(long n)
|
||||
```
|
||||
* FileReader
|
||||
```java
|
||||
FileReader(File file)
|
||||
FileReader(String fileName)
|
||||
```
|
||||
|
||||
* PipedReader
|
||||
```java
|
||||
PipedReader(int pipeSize)
|
||||
PipedReader(PipedWriter src)
|
||||
PipedReader(PipedWriter src, int pipeSize)
|
||||
|
||||
connect(PipedWriter src)
|
||||
```
|
||||
* CharArrayReader
|
||||
```
|
||||
CharArrayReader(char[] buf)
|
||||
CharArrayReader(char[] buf, int offset, int length)
|
||||
```
|
||||
* BufferedReader
|
||||
```
|
||||
String readLine()
|
||||
Stream<String> lines()
|
||||
```
|
||||
|
||||
### Writer
|
||||
|
||||
* 基本的Writer
|
||||
```java
|
||||
append(char c)
|
||||
Writer append(CharSequence csq)
|
||||
Writer append(CharSequence csq, int start, int end)
|
||||
|
||||
close()
|
||||
flush()
|
||||
write(char[] cbuf)
|
||||
write(char[] cbuf, int off, int len)
|
||||
write(int c)
|
||||
write(String str)
|
||||
write(String str, int off, int len)
|
||||
```
|
||||
* FileWriter
|
||||
|
||||
```java
|
||||
OutputStreamWriter(OutputStream out)
|
||||
OutputStreamWriter(OutputStream out, Charset cs)
|
||||
OutputStreamWriter(OutputStream out, CharsetEncoder enc)
|
||||
OutputStreamWriter(OutputStream out, String charsetName)
|
||||
getEncoding()
|
||||
```
|
||||
* PipedWriter
|
||||
```java
|
||||
PipedWriter(PipedReader snk)
|
||||
connect(PipedReader snk)
|
||||
```
|
||||
* CharArrayWriter
|
||||
```java
|
||||
size()
|
||||
char[] toCharArray()
|
||||
String toString()
|
||||
```
|
||||
* BufferedWriter
|
||||
```
|
||||
BufferedWriter(Writer out)
|
||||
BufferedWriter(Writer out, int sz)
|
||||
newLine()
|
||||
```
|
||||
|
||||
* PrintWriter一个特殊的装饰器类,在write的基础上添加了许多print函数
|
||||
```java
|
||||
print(基础类型和对象)
|
||||
printf(String format, Object... args)
|
||||
println(基础类型和对象)
|
||||
```
|
||||
## 参考文献
|
||||
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
|
||||
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
|
||||
- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
|
||||
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
|
||||
- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html)
|
||||
- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html)
|
||||
- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html)
|
||||
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
|
||||
- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document)
|
||||
- [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html)
|
||||
713
Java基础教程/JavaIO与网络编程/02 Java NIO.md
Normal file
@@ -0,0 +1,713 @@
|
||||
|
||||
|
||||
# NIO
|
||||
|
||||
## 1 NIO概述
|
||||
|
||||
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
|
||||
|
||||
IO Stream 和 NIO Block。NIO将最耗时的IO活动(即填充和清空缓冲区)移回操作系统,从而极大地提高了速度。
|
||||
|
||||
阻塞 I/O(blocking I/O)是旧的输入/输出(old input/output,OIO)。被称为普通 I/O(plain I/O)
|
||||
|
||||
|
||||
### 标准IO与NIO的区别一:流与块
|
||||
|
||||
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
|
||||
|
||||
|
||||
* 面向流的I / O系统一次处理一个或多个字节的数据。 输入流产生一个字节的数据,而输出流消耗一个字节的数据。为流数据创建过滤器非常容易。 将几个过滤器链接在一起也是相对简单的,这样每个过滤器都能发挥自己的作用,相当于一个单一的复杂处理机制。 重要的是字节不会在任何地方缓存。 此外,您不能在流中的数据中来回移动。 如果需要来回移动从流中读取的数据,则必须先将其缓存在缓冲区中。
|
||||
|
||||
|
||||
* 面向块的I / O系统按块处理数据。 每个操作一步就产生或消耗一个数据块。 通过块可以处理数据,比处理(流式传输)字节快得多。 您可以根据需要在缓冲区中来回移动。 这使您在处理过程中更具灵活性。 但是,您还需要检查缓冲区是否包含您需要的所有数据,以便对其进行完全处理。 并且,您需要确保在将更多数据读入缓冲区时,不要覆盖尚未处理的缓冲区中的数据。 但是面向块的I / O缺少面向流的I / O的一些优雅和简单性。
|
||||
|
||||
|
||||
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
|
||||
|
||||
|
||||
### 标准IO与NIO区别二:同步异步
|
||||
|
||||
标准IO和NIO第二个主要的区别是,同步和异步。同步程序通常不得不诉诸于轮询或创建许多线程来处理大量连接。 使用异步I / O,您可以在任意数量的通道上侦听I / O事件,而无需轮询且无需额外的线程。异步I / O中的中心对象称为选择器。
|
||||
* Java IO当线程调用read()或write()时,该线程将被阻塞,直到有一些数据要读取或数据被完全写入为止。因此引入多线程增加并发性。
|
||||
* 异步IO中,线程可以请求将某些数据写入通道,但不等待将其完全写入。 然后线程可以继续运行,同时执行其他操作。 单个线程现在可以管理输入和输出的多个通道。
|
||||
|
||||
|
||||

|
||||
|
||||
## 2 Path
|
||||
|
||||
Path是NIO的入口点。
|
||||
|
||||
### 绝对路径
|
||||
绝对路径始终包含根元素和查找文件所需的完整目录列表。 不再需要更多信息来访问文件或路径。
|
||||
|
||||
```java
|
||||
//Starts with file store root or drive
|
||||
Path absolutePath1 = Paths.get("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
|
||||
Path absolutePath2 = Paths.get("C:/Lokesh/Setup/workspace", "NIOExamples/src", "sample.txt");
|
||||
Path absolutePath3 = Paths.get("C:/Lokesh", "Setup/workspace", "NIOExamples/src", "sample.txt");
|
||||
```
|
||||
|
||||
### 相对路径
|
||||
|
||||
```java
|
||||
Path relativePath1 = Paths.get("src", "sample.txt");
|
||||
```
|
||||
|
||||
### 通过Uri
|
||||
将格式为“ file:///src/someFile.txt”的文件路径转换为NIO路径。 让我们来看看如何做。
|
||||
|
||||
|
||||
```java
|
||||
//URI uri = URI.create("file:///c:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt"); //OR
|
||||
URI uri = URI.create("file:///Lokesh/Setup/workspace/NIOExamples/src/sample.txt");
|
||||
|
||||
String scheme = uri.getScheme();
|
||||
if (scheme == null)
|
||||
throw new IllegalArgumentException("Missing scheme");
|
||||
|
||||
//Check for default provider to avoid loading of installed providers
|
||||
if (scheme.equalsIgnoreCase("file"))
|
||||
{
|
||||
System.out.println(FileSystems.getDefault().provider().getPath(uri).toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
//If you do not know scheme then use this code. This code check file scheme as well if available.
|
||||
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
|
||||
if (provider.getScheme().equalsIgnoreCase(scheme)) {
|
||||
System.out.println(provider.getPath(uri).toAbsolutePath().toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
## 3 Buffer缓冲区
|
||||
|
||||
### 缓冲区
|
||||
Buffer对象可以称为固定数量数据的容器。 它充当存储箱或临时暂存区,可以在其中存储数据并在以后检索。
|
||||
|
||||
通道是进行I / O传输的实际门户。 缓冲区是这些数据传输的源或目标。
|
||||
|
||||
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
|
||||
|
||||
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
|
||||
|
||||
缓冲区包括以下类型,能够包含所有的类型。
|
||||
|
||||
- ByteBuffer
|
||||
- CharBuffer
|
||||
- ShortBuffer
|
||||
- IntBuffer
|
||||
- LongBuffer
|
||||
- FloatBuffer
|
||||
- DoubleBuffer
|
||||
|
||||
|
||||
|
||||
### 缓冲区状态变量
|
||||
|
||||
所有缓冲区拥有的四个属性可提供有关所包含数据元素的信息。 这些是:
|
||||
|
||||
* Capacity :缓冲区可以容纳的最大数据元素数。 容量是在创建缓冲区时设置的,无法更改。
|
||||
* Limit :不应读取或写入的缓冲区的第一个元素。 换句话说,缓冲区中活动元素的数量。
|
||||
* Position :下一个要读取或写入的元素的索引。 该位置由相对的get()和put()方法自动更新。
|
||||
* Mark :记忆中的位置。 调用mark()设置mark =位置。 调用reset()设置position =标记。 该标记在设置之前是不确定的。
|
||||
|
||||
> 0<= mark <= position <= limit <= capacity
|
||||
|
||||
状态变量的改变过程举例:
|
||||
|
||||
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
|
||||
|
||||

|
||||
|
||||
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。
|
||||
|
||||

|
||||
|
||||
③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
|
||||
|
||||

|
||||
|
||||
④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
|
||||
|
||||

|
||||
|
||||
⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
|
||||
|
||||

|
||||
|
||||
|
||||
### 创建缓冲区
|
||||
|
||||
缓冲区类都不能直接实例化。 它们都是抽象类,但是每个都包含静态工厂方法来创建相应类的新实例。
|
||||
|
||||
```java
|
||||
CharBuffer charBuffer = CharBuffer.allocate (100);
|
||||
|
||||
//调用put()对缓冲区所做的更改将反映在数组中,而直接对数组所做的任何更改将对缓冲区对象可见。
|
||||
char [] myArray = new char [100];
|
||||
CharBuffer charbuffer = CharBuffer.wrap (myArray);
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 反转缓冲区:从写状态转换为读状态
|
||||
|
||||
```java
|
||||
public abstract class ByteBuffer extends Buffer implements Comparable
|
||||
{
|
||||
// This is a partial API listing
|
||||
// get到当前元素,postion到下一个元素
|
||||
public abstract byte get();
|
||||
// get指定元素
|
||||
public abstract byte get (int index);
|
||||
public abstract ByteBuffer put (byte b);
|
||||
public abstract ByteBuffer put (int index, byte b);
|
||||
}
|
||||
```
|
||||
|
||||
* flip()方法将缓冲区从可以附加数据元素的填充状态翻转到耗尽状态,以准备读取元素 。
|
||||
|
||||

|
||||
|
||||
```java
|
||||
buffer.flip();
|
||||
buffer.limit( buffer.position() ).position(0);
|
||||
```
|
||||
* clear()方法将缓冲区重置为空状态。 它不会更改缓冲区的任何数据元素,而只是将限制设置为容量并将位置设置回0。这使缓冲区可以再次填充。
|
||||
|
||||
```java
|
||||
import java.nio.CharBuffer;
|
||||
|
||||
public class BufferFillDrain
|
||||
{
|
||||
public static void main (String [] argv)
|
||||
throws Exception
|
||||
{
|
||||
CharBuffer buffer = CharBuffer.allocate (100);
|
||||
|
||||
while (fillBuffer (buffer)) {
|
||||
buffer.flip( );
|
||||
drainBuffer (buffer);
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void drainBuffer (CharBuffer buffer)
|
||||
{
|
||||
while (buffer.hasRemaining()) {
|
||||
System.out.print (buffer.get());
|
||||
}
|
||||
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
private static boolean fillBuffer (CharBuffer buffer)
|
||||
{
|
||||
if (index >= strings.length) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
String string = strings [index++];
|
||||
|
||||
for (int i = 0; i > string.length( ); i++) {
|
||||
buffer.put (string.charAt (i));
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
private static int index = 0;
|
||||
|
||||
private static String [] strings = {
|
||||
"Some random string content 1",
|
||||
"Some random string content 2",
|
||||
"Some random string content 3",
|
||||
"Some random string content 4",
|
||||
"Some random string content 5",
|
||||
"Some random string content 6",
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 4 Channel通道
|
||||
|
||||
### Channel概念
|
||||
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
|
||||
|
||||
通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
|
||||
|
||||
通道包括以下类型:
|
||||
|
||||
- FileChannel:从文件中读写数据;
|
||||
- DatagramChannel:通过 UDP 读写网络中数据;
|
||||
- SocketChannel:通过 TCP 读写网络中数据;
|
||||
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
|
||||
|
||||
|
||||
|
||||
### 创建channel
|
||||
|
||||
* FileChannel:只能通过在打开的RandomAccessFile , FileInputStream或FileOutputStream对象上调用getChannel()方法来获取FileChannel对象。 您不能直接创建FileChannel对象。
|
||||
|
||||
```java
|
||||
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
|
||||
FileChannel fc = raf.getChannel();
|
||||
```
|
||||
|
||||
* SocketChannel:套接字通道具有工厂方法来直接创建新的套接字通道。
|
||||
|
||||
```java
|
||||
//How to open SocketChannel
|
||||
SocketChannel sc = SocketChannel.open();
|
||||
sc.connect(new InetSocketAddress("somehost", someport));
|
||||
|
||||
//How to open ServerSocketChannel
|
||||
ServerSocketChannel ssc = ServerSocketChannel.open();
|
||||
ssc.socket().bind (new InetSocketAddress (somelocalport));
|
||||
|
||||
//How to open DatagramChannel
|
||||
DatagramChannel dc = DatagramChannel.open();
|
||||
```
|
||||
|
||||
|
||||
### 使用Channel
|
||||
> 可以发现,通过Channel转换buffer上的数据,而不需要直接操作buffer。
|
||||
|
||||
|
||||
* 它通过实现不同的接口,表示其是双向或者单向的。 连接到只读文件的Channel实例无法写入。
|
||||
|
||||
* 快速复制文件
|
||||
|
||||
```java
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
public class ChannelCopyExample
|
||||
{
|
||||
public static void main(String args[]) throws IOException
|
||||
{
|
||||
FileInputStream input = new FileInputStream ("testIn.txt");
|
||||
ReadableByteChannel source = input.getChannel();
|
||||
|
||||
FileOutputStream output = new FileOutputStream ("testOut.txt");
|
||||
WritableByteChannel dest = output.getChannel();
|
||||
|
||||
copyData(source, dest);
|
||||
|
||||
source.close();
|
||||
dest.close();
|
||||
}
|
||||
|
||||
private static void copyData(ReadableByteChannel src, WritableByteChannel dest) throws IOException
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
|
||||
|
||||
while (src.read(buffer) != -1)
|
||||
{
|
||||
// Prepare the buffer to be drained
|
||||
buffer.flip();
|
||||
|
||||
// Make sure that the buffer was fully drained
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
dest.write(buffer);
|
||||
}
|
||||
|
||||
// Make the buffer empty, ready for filling
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Vectored IO
|
||||
|
||||
从通道读取的分散数据是将数据读取到多个缓冲区中的读取操作。 因此,通道将数据从通道“ scatters ”到多个缓冲区中。 收集到通道的写操作是一种写操作,它将来自多个缓冲区的数据写到单个通道中。 因此,通道gathers来自多个缓冲区的数据“ gathers ”到一个通道中。 在需要分别处理传输数据的各个部分的情况下,散布/收集可能非常有用。
|
||||
|
||||
在此示例中,我创建了两个缓冲区。 一个缓冲区将存储一个随机数,另一个缓冲区将存储一个随机字符串。 我将使用GatheringByteChannel读取写入文件通道中两个缓冲区中存储的数据。 然后,我将使用ScatteringByteChannel将文件中的数据读回到两个单独的缓冲区中,并在控制台中打印内容以验证存储和检索的数据是否匹配。
|
||||
|
||||
```java
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
|
||||
public class ScatteringAndGatheringIOExample
|
||||
{
|
||||
public static void main(String params[])
|
||||
{
|
||||
String data = "Scattering and Gathering example shown in howtodoinjava.com";
|
||||
|
||||
gatherBytes(data);
|
||||
scatterBytes();
|
||||
}
|
||||
|
||||
/*
|
||||
* gatherBytes() reads bytes from different buffers and writes to file
|
||||
* channel. Note that it uses a single write for both the buffers.
|
||||
*/
|
||||
public static void gatherBytes(String data)
|
||||
{
|
||||
//First Buffer holds a random number
|
||||
ByteBuffer bufferOne = ByteBuffer.allocate(4);
|
||||
|
||||
//Second Buffer holds data we want to write
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(200);
|
||||
|
||||
//Writing Data sets to Buffer
|
||||
bufferOne.asIntBuffer().put(13);
|
||||
buffer2.asCharBuffer().put(data);
|
||||
|
||||
//Calls FileOutputStream(file).getChannel()
|
||||
GatheringByteChannel gatherer = createChannelInstance("test.txt", true);
|
||||
|
||||
//Write data to file
|
||||
try
|
||||
{
|
||||
gatherer.write(new ByteBuffer[] { bufferOne, buffer2 });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* scatterBytes() read bytes from a file channel into a set of buffers. Note that
|
||||
* it uses a single read for both the buffers.
|
||||
*/
|
||||
public static void scatterBytes()
|
||||
{
|
||||
//First Buffer holds a random number
|
||||
ByteBuffer bufferOne = ByteBuffer.allocate(4);
|
||||
|
||||
//Second Buffer holds data we want to write
|
||||
ByteBuffer bufferTwo = ByteBuffer.allocate(200);
|
||||
|
||||
//Calls FileInputStream(file).getChannel()
|
||||
ScatteringByteChannel scatterer = createChannelInstance("test.txt", false);
|
||||
|
||||
try
|
||||
{
|
||||
//Reading from the channel
|
||||
scatterer.read(new ByteBuffer[] { bufferOne, bufferTwo });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
//Read the buffers seperately
|
||||
bufferOne.rewind();
|
||||
bufferTwo.rewind();
|
||||
|
||||
int bufferOneContent = bufferOne.asIntBuffer().get();
|
||||
String bufferTwoContent = bufferTwo.asCharBuffer().toString();
|
||||
|
||||
//Verify the content
|
||||
System.out.println(bufferOneContent);
|
||||
System.out.println(bufferTwoContent);
|
||||
}
|
||||
|
||||
|
||||
public static FileChannel createChannelInstance(String file, boolean isOutput)
|
||||
{
|
||||
FileChannel fc = null;
|
||||
try
|
||||
{
|
||||
if (isOutput) {
|
||||
fc = new FileOutputStream(file).getChannel();
|
||||
} else {
|
||||
fc = new FileInputStream(file).getChannel();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fc;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 内存映射文件
|
||||
|
||||
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
|
||||
|
||||
向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。
|
||||
|
||||
下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。
|
||||
|
||||
```java
|
||||
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
|
||||
```
|
||||
|
||||
|
||||
## 5 选择器
|
||||
|
||||
NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。
|
||||
|
||||
NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。
|
||||
|
||||
通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。
|
||||
|
||||
因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。
|
||||
|
||||
应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/093f9e57-429c-413a-83ee-c689ba596cef.png" width="350px"> </div><br>
|
||||
|
||||
### 1. 创建选择器
|
||||
|
||||
```java
|
||||
Selector selector = Selector.open();
|
||||
```
|
||||
|
||||
### 2. 将通道注册到选择器上
|
||||
|
||||
```java
|
||||
ServerSocketChannel ssChannel = ServerSocketChannel.open();
|
||||
ssChannel.configureBlocking(false);
|
||||
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
```
|
||||
|
||||
通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。
|
||||
|
||||
在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:
|
||||
|
||||
- SelectionKey.OP_CONNECT
|
||||
- SelectionKey.OP_ACCEPT
|
||||
- SelectionKey.OP_READ
|
||||
- SelectionKey.OP_WRITE
|
||||
|
||||
它们在 SelectionKey 的定义如下:
|
||||
|
||||
```java
|
||||
public static final int OP_READ = 1 << 0;
|
||||
public static final int OP_WRITE = 1 << 2;
|
||||
public static final int OP_CONNECT = 1 << 3;
|
||||
public static final int OP_ACCEPT = 1 << 4;
|
||||
```
|
||||
|
||||
可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如:
|
||||
|
||||
```java
|
||||
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
|
||||
```
|
||||
|
||||
### 3. 监听事件
|
||||
|
||||
```java
|
||||
int num = selector.select();
|
||||
```
|
||||
|
||||
使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。
|
||||
|
||||
### 4. 获取到达的事件
|
||||
|
||||
```java
|
||||
Set<SelectionKey> keys = selector.selectedKeys();
|
||||
Iterator<SelectionKey> keyIterator = keys.iterator();
|
||||
while (keyIterator.hasNext()) {
|
||||
SelectionKey key = keyIterator.next();
|
||||
if (key.isAcceptable()) {
|
||||
// ...
|
||||
} else if (key.isReadable()) {
|
||||
// ...
|
||||
}
|
||||
keyIterator.remove();
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 事件循环
|
||||
|
||||
因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。
|
||||
|
||||
```java
|
||||
while (true) {
|
||||
int num = selector.select();
|
||||
Set<SelectionKey> keys = selector.selectedKeys();
|
||||
Iterator<SelectionKey> keyIterator = keys.iterator();
|
||||
while (keyIterator.hasNext()) {
|
||||
SelectionKey key = keyIterator.next();
|
||||
if (key.isAcceptable()) {
|
||||
// ...
|
||||
} else if (key.isReadable()) {
|
||||
// ...
|
||||
}
|
||||
keyIterator.remove();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6 Socket套接字 NIO 实例
|
||||
|
||||
```java
|
||||
public class NIOServer {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
Selector selector = Selector.open();
|
||||
|
||||
ServerSocketChannel ssChannel = ServerSocketChannel.open();
|
||||
ssChannel.configureBlocking(false);
|
||||
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
|
||||
ServerSocket serverSocket = ssChannel.socket();
|
||||
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
|
||||
serverSocket.bind(address);
|
||||
|
||||
while (true) {
|
||||
|
||||
selector.select();
|
||||
Set<SelectionKey> keys = selector.selectedKeys();
|
||||
Iterator<SelectionKey> keyIterator = keys.iterator();
|
||||
|
||||
while (keyIterator.hasNext()) {
|
||||
|
||||
SelectionKey key = keyIterator.next();
|
||||
|
||||
if (key.isAcceptable()) {
|
||||
|
||||
ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
|
||||
|
||||
// 服务器会为每个新连接创建一个 SocketChannel
|
||||
SocketChannel sChannel = ssChannel1.accept();
|
||||
sChannel.configureBlocking(false);
|
||||
|
||||
// 这个新连接主要用于从客户端读取数据
|
||||
sChannel.register(selector, SelectionKey.OP_READ);
|
||||
|
||||
} else if (key.isReadable()) {
|
||||
|
||||
SocketChannel sChannel = (SocketChannel) key.channel();
|
||||
System.out.println(readDataFromSocketChannel(sChannel));
|
||||
sChannel.close();
|
||||
}
|
||||
|
||||
keyIterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
StringBuilder data = new StringBuilder();
|
||||
|
||||
while (true) {
|
||||
|
||||
buffer.clear();
|
||||
int n = sChannel.read(buffer);
|
||||
if (n == -1) {
|
||||
break;
|
||||
}
|
||||
buffer.flip();
|
||||
int limit = buffer.limit();
|
||||
char[] dst = new char[limit];
|
||||
for (int i = 0; i < limit; i++) {
|
||||
dst[i] = (char) buffer.get(i);
|
||||
}
|
||||
data.append(dst);
|
||||
buffer.clear();
|
||||
}
|
||||
return data.toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class NIOClient {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
Socket socket = new Socket("127.0.0.1", 8888);
|
||||
OutputStream out = socket.getOutputStream();
|
||||
String s = "hello world";
|
||||
out.write(s.getBytes());
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 7 文件IO
|
||||
|
||||
### 使用标准IO的示例代码
|
||||
|
||||
```java
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WithoutNIOExample
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
BufferedReader br = null;
|
||||
String sCurrentLine = null;
|
||||
try
|
||||
{
|
||||
br = new BufferedReader(
|
||||
new FileReader("test.txt"));
|
||||
while ((sCurrentLine = br.readLine()) != null)
|
||||
{
|
||||
System.out.println(sCurrentLine);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (br != null)
|
||||
br.close();
|
||||
} catch (IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NIO 读取文件
|
||||
|
||||
```java
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
public class ReadFileWithFixedSizeBuffer
|
||||
{
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
RandomAccessFile aFile = new RandomAccessFile
|
||||
("test.txt", "r");
|
||||
FileChannel inChannel = aFile.getChannel();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
while(inChannel.read(buffer) > 0)
|
||||
{
|
||||
buffer.flip();
|
||||
for (int i = 0; i < buffer.limit(); i++)
|
||||
{
|
||||
System.out.print((char) buffer.get());
|
||||
}
|
||||
buffer.clear(); // do something with the data and clear/compact it.
|
||||
}
|
||||
inChannel.close();
|
||||
aFile.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
973
Java基础教程/JavaIO与网络编程/03 Java IO文件.md
Normal file
@@ -0,0 +1,973 @@
|
||||
<!-- GFM-TOC -->
|
||||
- [File IO](#file-io)
|
||||
- [0 File对象](#0-file对象)
|
||||
- [File](#file)
|
||||
- [RandomAccessFile](#randomaccessfile)
|
||||
- [NIO:Files](#niofiles)
|
||||
- [1 列出:list](#1-列出list)
|
||||
- [BIO](#bio)
|
||||
- [2 复制:copy](#2-复制copy)
|
||||
- [BIO](#bio-1)
|
||||
- [NIO](#nio)
|
||||
- [common-util](#common-util)
|
||||
- [3 删除:delete](#3-删除delete)
|
||||
- [NIO](#nio-1)
|
||||
- [apache commons-io](#apache-commons-io)
|
||||
- [4 创建create](#4-创建create)
|
||||
- [BIO:createNewFile](#biocreatenewfile)
|
||||
- [BIO:FileOutputStream](#biofileoutputstream)
|
||||
- [NIO](#nio-2)
|
||||
- [5 写入:wrirte\&append](#5-写入wrirteappend)
|
||||
- [BIO:BufferedWriter](#biobufferedwriter)
|
||||
- [BIO:PrintWriter](#bioprintwriter)
|
||||
- [BIO:FileOutputStream](#biofileoutputstream-1)
|
||||
- [BIO:DataOutputStream](#biodataoutputstream)
|
||||
- [NIO:FileChannel](#niofilechannel)
|
||||
- [NIO:Files静态方法](#niofiles静态方法)
|
||||
- [6 读取:read](#6-读取read)
|
||||
- [BIO:BufferedReader按行读](#biobufferedreader按行读)
|
||||
- [BIO:FileInputStream 读取字节](#biofileinputstream-读取字节)
|
||||
- [NIO:Files按行读](#niofiles按行读)
|
||||
- [NIO:读取所有字节](#nio读取所有字节)
|
||||
- [commons-io](#commons-io)
|
||||
- [7 Properties](#7-properties)
|
||||
- [补充:静态内部类与懒汉式单例模式](#补充静态内部类与懒汉式单例模式)
|
||||
- [8 Resource File](#8-resource-file)
|
||||
- [在spring中可以这样](#在spring中可以这样)
|
||||
- [10 读写utf8数据](#10-读写utf8数据)
|
||||
- [11 从控制台读取输入](#11-从控制台读取输入)
|
||||
- [console对象](#console对象)
|
||||
- [System.in封装](#systemin封装)
|
||||
- [更加复杂的Scanner](#更加复杂的scanner)
|
||||
- [12 将String转换成输入流](#12-将string转换成输入流)
|
||||
- [ByteArrayInputStream](#bytearrayinputstream)
|
||||
- [apach.IOUtils](#apachioutils)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
|
||||
# File IO
|
||||
|
||||
|
||||
|
||||
## 0 File对象
|
||||
|
||||
### File
|
||||
java.io.File
|
||||
|
||||
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。有大量相关的方法
|
||||
|
||||
```java
|
||||
File(String pathname)
|
||||
File(URI uri)
|
||||
|
||||
createNewFile()
|
||||
createTempFile(String prefix, String suffix)
|
||||
delete()
|
||||
deleteOnExit()
|
||||
exists()
|
||||
getAbsoluteFile()
|
||||
getAbsolutePath()
|
||||
getName()
|
||||
getPath()
|
||||
isFile()
|
||||
isDirectory()
|
||||
list()
|
||||
listFiles()
|
||||
mkdir()
|
||||
mkdirs()
|
||||
setReadOnly()
|
||||
setExecutable(boolean executable)
|
||||
setWritable(boolean writable)
|
||||
toPath()
|
||||
toURI()
|
||||
|
||||
```
|
||||
|
||||
### RandomAccessFile
|
||||
java.io.RandomAccessFile
|
||||
|
||||
RandomAccessFile支持"随机访问"的方式,程序可以直接跳转到文件的任意地方来读写数据。
|
||||
|
||||
* RandomAccessFile可以自由访问文件的任意位置。
|
||||
* RandomAccessFile允许自由定位文件记录指针。
|
||||
* RandomAccessFile只能读写文件而不是流。
|
||||
|
||||
```java
|
||||
RandomAccessFile(String name, String mode):
|
||||
RandomAccessFile(File file, String mode)
|
||||
|
||||
read*()
|
||||
write*()
|
||||
long getFilePointer():返回文件记录指针的当前位置。(native方法)
|
||||
void seek(long pos):将文件记录指针定位到pos位置。(调用本地方法seek0)
|
||||
```
|
||||
|
||||
* 使用randomaccessfile插入内容。RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件中原有的内容。如果需要向指定位置插入内容,程序需要先把插入点后面的内容读入缓冲区,等把需要插入的数据写入文件后,再将缓冲区的内容追加到文件后面。
|
||||
|
||||
```java
|
||||
/**
|
||||
* 向指定文件的指定位置插入指定的内容
|
||||
*
|
||||
* @param fileName 指定文件名
|
||||
* @param pos 指定文件的指定位置
|
||||
* @param insertContent 指定文件的指定位置要插入的指定内容
|
||||
*/
|
||||
public static void insert(String fileName, long pos,
|
||||
String insertContent) throws IOException {
|
||||
RandomAccessFile raf = null;
|
||||
//创建一个临时文件来保存插入点后的数据
|
||||
File tmp = File.createTempFile("tmp", null);
|
||||
FileOutputStream tmpOut = null;
|
||||
FileInputStream tmpIn = null;
|
||||
tmp.deleteOnExit();
|
||||
try {
|
||||
raf = new RandomAccessFile(fileName, "rw");
|
||||
tmpOut = new FileOutputStream(tmp);
|
||||
tmpIn = new FileInputStream(tmp);
|
||||
raf.seek(pos);
|
||||
//--------下面代码将插入点后的内容读入临时文件中保存---------
|
||||
byte[] bbuf = new byte[64];
|
||||
//用于保存实际读取的字节数
|
||||
int hasRead = 0;
|
||||
//使用循环方式读取插入点后的数据
|
||||
while ((hasRead = raf.read(bbuf)) > 0) {
|
||||
//将读取的数据写入临时文件
|
||||
tmpOut.write(bbuf, 0, hasRead);
|
||||
}
|
||||
//----------下面代码插入内容----------
|
||||
//把文件记录指针重新定位到pos位置
|
||||
raf.seek(pos);
|
||||
//追加需要插入的内容
|
||||
raf.write(insertContent.getBytes());
|
||||
//追加临时文件中的内容
|
||||
while ((hasRead = tmpIn.read(bbuf)) > 0) {
|
||||
raf.write(bbuf, 0, hasRead);
|
||||
}
|
||||
} finally {
|
||||
if (raf != null) {
|
||||
raf.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NIO:Files
|
||||
|
||||
```java
|
||||
Files.exists(path); //true
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 1 列出:list
|
||||
|
||||
### BIO
|
||||
递归地列出一个目录下所有文件:
|
||||
|
||||
```java
|
||||
public static void listAllFiles(File dir) {
|
||||
if (dir == null || !dir.exists()) {
|
||||
return;
|
||||
}
|
||||
if (dir.isFile()) {
|
||||
System.out.println(dir.getName());
|
||||
return;
|
||||
}
|
||||
for (File file : dir.listFiles()) {
|
||||
listAllFiles(file);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
从 Java7 开始,可以使用 Paths 和 Files 代替 File。
|
||||
|
||||
|
||||
## 2 复制:copy
|
||||
### BIO
|
||||
|
||||
```java
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 利用文件输入流与输出流实现文件的复制操作
|
||||
*/
|
||||
public class CopyDemo {
|
||||
public static void main(String[] args) throws IOException {
|
||||
//用文件输入流读取待复制的文件
|
||||
FileInputStream fis = new FileInputStream("01.rmvb");
|
||||
//用文件输出流向复制文件中写入复制的数据
|
||||
FileOutputStream fos = new FileOutputStream("01_cp.rmvb");
|
||||
int d;//先定义一个变量,用于记录每次读取到的数据
|
||||
long start = System.currentTimeMillis();//获取当前系统时间
|
||||
while ((d = fis.read()) != -1) {
|
||||
fos.write(d);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("复制完毕!耗时:" + (end - start) + "ms");
|
||||
fis.close();
|
||||
fos.close();
|
||||
}
|
||||
}
|
||||
//带缓冲区的
|
||||
```java
|
||||
public static void copyFile(String src, String dist) throws IOException {
|
||||
FileInputStream in = new FileInputStream(src);
|
||||
FileOutputStream out = new FileOutputStream(dist);
|
||||
|
||||
byte[] buffer = new byte[20 * 1024];
|
||||
int cnt;
|
||||
|
||||
// read() 最多读取 buffer.length 个字节
|
||||
// 返回的是实际读取的个数
|
||||
// 返回 -1 的时候表示读到 eof,即文件尾
|
||||
while ((cnt = in.read(buffer, 0, buffer.length)) != -1) {
|
||||
out.write(buffer, 0, cnt);
|
||||
}
|
||||
|
||||
in.close();
|
||||
out.close();
|
||||
}
|
||||
```
|
||||
### NIO
|
||||
```java
|
||||
package com.howtodoinjava.examples.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class DirectoryCopyExample
|
||||
{
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
//Source directory which you want to copy to new location
|
||||
File sourceFolder = new File("c:\\temp");
|
||||
|
||||
//Target directory where files should be copied
|
||||
File destinationFolder = new File("c:\\tempNew");
|
||||
|
||||
//Call Copy function
|
||||
copyFolder(sourceFolder, destinationFolder);
|
||||
}
|
||||
/**
|
||||
* This function recursively copy all the sub folder and files from sourceFolder to destinationFolder
|
||||
* */
|
||||
private static void copyFolder(File sourceFolder, File destinationFolder) throws IOException
|
||||
{
|
||||
//Check if sourceFolder is a directory or file
|
||||
//If sourceFolder is file; then copy the file directly to new location
|
||||
if (sourceFolder.isDirectory())
|
||||
{
|
||||
//Verify if destinationFolder is already present; If not then create it
|
||||
if (!destinationFolder.exists())
|
||||
{
|
||||
destinationFolder.mkdir();
|
||||
System.out.println("Directory created :: " + destinationFolder);
|
||||
}
|
||||
|
||||
//Get all files from source directory
|
||||
String files[] = sourceFolder.list();
|
||||
|
||||
//Iterate over all files and copy them to destinationFolder one by one
|
||||
for (String file : files)
|
||||
{
|
||||
File srcFile = new File(sourceFolder, file);
|
||||
File destFile = new File(destinationFolder, file);
|
||||
|
||||
//Recursive function call
|
||||
copyFolder(srcFile, destFile);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Copy the file content from one place to another
|
||||
Files.copy(sourceFolder.toPath(), destinationFolder.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
System.out.println("File copied :: " + destinationFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
Directory created :: c:\tempNew
|
||||
File copied :: c:\tempNew\testcopied.txt
|
||||
File copied :: c:\tempNew\testoriginal.txt
|
||||
File copied :: c:\tempNew\testOut.txt
|
||||
```
|
||||
|
||||
### common-util
|
||||
|
||||
```java
|
||||
private static void fileCopyUsingApacheCommons() throws IOException
|
||||
{
|
||||
File fileToCopy = new File("c:/temp/testoriginal.txt");
|
||||
File newFile = new File("c:/temp/testcopied.txt");
|
||||
|
||||
FileUtils.copyFile(fileToCopy, newFile);
|
||||
|
||||
// OR
|
||||
|
||||
IOUtils.copy(new FileInputStream(fileToCopy), new FileOutputStream(newFile));
|
||||
}
|
||||
```
|
||||
|
||||
## 3 删除:delete
|
||||
|
||||
### NIO
|
||||
|
||||
```java
|
||||
public class DeleteDirectoryNIOWithStream
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
Path dir = Paths.get("c:/temp/innerDir");
|
||||
|
||||
Files.walk(dir)
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### apache commons-io
|
||||
|
||||
```
|
||||
public class DeleteDirectoryNIOWithStream
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
Path dir = Paths.get("c:/temp/innerDir");
|
||||
|
||||
Files.walk(dir)
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4 创建create
|
||||
|
||||
### BIO:createNewFile
|
||||
File.createNewFile()方法创建新文件。 此方法返回布尔值–
|
||||
* 如果文件创建成功,则返回true 。
|
||||
* 如果文件已经存在或操作由于某种原因失败,则返回false 。
|
||||
* 此方法不会像文件中写任何数据
|
||||
|
||||
```java
|
||||
File file = new File("c://temp//testFile1.txt");
|
||||
|
||||
//Create the file
|
||||
if (file.createNewFile())
|
||||
{
|
||||
System.out.println("File is created!");
|
||||
} else {
|
||||
System.out.println("File already exists.");
|
||||
}
|
||||
|
||||
//Write Content
|
||||
FileWriter writer = new FileWriter(file);
|
||||
writer.write("Test data");
|
||||
writer.close();
|
||||
```
|
||||
### BIO:FileOutputStream
|
||||
|
||||
FileOutputStream.write()方法自动创建一个新文件并向其中写入内容 。
|
||||
```java
|
||||
String data = "Test data";
|
||||
|
||||
FileOutputStream out = new FileOutputStream("c://temp//testFile2.txt");
|
||||
|
||||
out.write(data.getBytes());
|
||||
out.close();
|
||||
```
|
||||
|
||||
### NIO
|
||||
|
||||
Files.write()是创建文件的最佳方法,如果您尚未使用它,则应该是将来的首选方法。
|
||||
|
||||
此方法将文本行写入文件 。 每行都是一个char序列,并按顺序写入文件,每行由平台的line separator终止。
|
||||
|
||||
```java
|
||||
String data = "Test data";
|
||||
Files.write(Paths.get("c://temp//testFile3.txt"), data.getBytes());
|
||||
|
||||
//or
|
||||
|
||||
List<String> lines = Arrays.asList("1st line", "2nd line");
|
||||
|
||||
Files.write(Paths.get("file6.txt"),
|
||||
lines,
|
||||
StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.APPEND);
|
||||
```
|
||||
## 5 写入:wrirte&append
|
||||
* append模式,使用BufferedWritter , PrintWriter , FileOutputStream和Files类将内容追加到java中的 Files 。 在所有示例中,在打开要写入的文件时,您都传递了第二个参数true ,表示该文件以append mode打开
|
||||
|
||||
### BIO:BufferedWriter
|
||||
|
||||
通过BufferedWriter,进行更少的IO操作,提高了性能。
|
||||
|
||||
```java
|
||||
public static void usingBufferedWritter() throws IOException
|
||||
{
|
||||
String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";
|
||||
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter("c:/temp/samplefile1.txt"));
|
||||
writer.write(fileContent);
|
||||
writer.close();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### BIO:PrintWriter
|
||||
使用PrintWriter将格式化的文本写入文件。
|
||||
```java
|
||||
public static void usingPrintWriter() throws IOException
|
||||
{
|
||||
String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";
|
||||
|
||||
FileWriter fileWriter = new FileWriter("c:/temp/samplefile3.txt");
|
||||
PrintWriter printWriter = new PrintWriter(fileWriter);
|
||||
printWriter.print(fileContent);
|
||||
printWriter.printf("Blog name is %s", "howtodoinjava.com");
|
||||
printWriter.close();
|
||||
}
|
||||
```
|
||||
|
||||
### BIO:FileOutputStream
|
||||
|
||||
使用FileOutputStream 将二进制数据写入文件 。 FileOutputStream用于写入原始字节流,例如图像数据。 要编写字符流,请考虑使用FileWriter 。
|
||||
```java
|
||||
public static void usingFileOutputStream() throws IOException
|
||||
{
|
||||
String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream("c:/temp/samplefile4.txt");
|
||||
byte[] strToBytes = fileContent.getBytes();
|
||||
outputStream.write(strToBytes);
|
||||
|
||||
outputStream.close();
|
||||
}
|
||||
```
|
||||
### BIO:DataOutputStream
|
||||
|
||||
DataOutputStream允许应用程序以可移植的方式将原始Java数据类型写入输出流。 然后,应用程序可以使用数据输入流来读回数据。
|
||||
|
||||
```java
|
||||
public static void usingDataOutputStream() throws IOException
|
||||
{
|
||||
String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream("c:/temp/samplefile5.txt");
|
||||
DataOutputStream dataOutStream = new DataOutputStream(new BufferedOutputStream(outputStream));
|
||||
dataOutStream.writeUTF(fileContent);
|
||||
|
||||
dataOutStream.close();
|
||||
}
|
||||
```
|
||||
|
||||
### NIO:FileChannel
|
||||
|
||||
FileChannel可用于读取,写入,映射和操作文件。 如果要处理大文件,则FileChannel可能比标准IO快。
|
||||
|
||||
文件通道可以安全地供多个并发线程使用。
|
||||
|
||||
```java
|
||||
public static void usingFileChannel() throws IOException
|
||||
{
|
||||
String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";
|
||||
|
||||
RandomAccessFile stream = new RandomAccessFile("c:/temp/samplefile6.txt", "rw");
|
||||
FileChannel channel = stream.getChannel();
|
||||
byte[] strBytes = fileContent.getBytes();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(strBytes.length);
|
||||
buffer.put(strBytes);
|
||||
buffer.flip();
|
||||
channel.write(buffer);
|
||||
stream.close();
|
||||
channel.close();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### NIO:Files静态方法
|
||||
|
||||
```java
|
||||
public static void usingPath() throws IOException
|
||||
{
|
||||
String fileContent = "Hello Learner !! Welcome to howtodoinjava.com.";
|
||||
|
||||
Path path = Paths.get("c:/temp/samplefile7.txt");
|
||||
|
||||
Files.write(path, fileContent.getBytes());
|
||||
}
|
||||
|
||||
public static void usingPath() throws IOException
|
||||
{
|
||||
String textToAppend = "\r\n Happy Learning !!"; //new line in content
|
||||
|
||||
Path path = Paths.get("c:/temp/samplefile.txt");
|
||||
|
||||
Files.write(path, textToAppend.getBytes(), StandardOpenOption.APPEND); //Append mode
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 6 读取:read
|
||||
### BIO:BufferedReader按行读
|
||||
|
||||
```java
|
||||
//Using BufferedReader and FileReader - Below Java 7
|
||||
|
||||
private static String usingBufferedReader(String filePath)
|
||||
{
|
||||
StringBuilder contentBuilder = new StringBuilder();
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(filePath)))
|
||||
{
|
||||
|
||||
String sCurrentLine;
|
||||
while ((sCurrentLine = br.readLine()) != null)
|
||||
{
|
||||
contentBuilder.append(sCurrentLine).append("\n");
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return contentBuilder.toString();
|
||||
```
|
||||
|
||||
### BIO:FileInputStream 读取字节
|
||||
|
||||
```java
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
public class ContentToByteArrayExample
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
|
||||
File file = new File("C:/temp/test.txt");
|
||||
|
||||
readContentIntoByteArray(file);
|
||||
}
|
||||
|
||||
private static byte[] readContentIntoByteArray(File file)
|
||||
{
|
||||
FileInputStream fileInputStream = null;
|
||||
byte[] bFile = new byte[(int) file.length()];
|
||||
try
|
||||
{
|
||||
//convert file into array of bytes
|
||||
fileInputStream = new FileInputStream(file);
|
||||
fileInputStream.read(bFile);
|
||||
fileInputStream.close();
|
||||
for (int i = 0; i < bFile.length; i++)
|
||||
{
|
||||
System.out.print((char) bFile[i]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return bFile;
|
||||
}
|
||||
}
|
||||
```
|
||||
### NIO:Files按行读
|
||||
|
||||
lines()方法从文件中读取所有行以进行流传输,并在stream被消耗时延迟填充。 使用指定的字符集将文件中的字节解码为字符。
|
||||
readAllBytes()方法reads all the bytes from a file 。 该方法可确保在读取所有字节或引发I / O错误或其他运行时异常时关闭文件。
|
||||
```java
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ReadFileToString
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String filePath = "c:/temp/data.txt";
|
||||
|
||||
System.out.println( readLineByLineJava8( filePath ) );
|
||||
}
|
||||
|
||||
|
||||
//Read file content into string with - Files.lines(Path path, Charset cs)
|
||||
|
||||
private static String readLineByLineJava8(String filePath)
|
||||
{
|
||||
StringBuilder contentBuilder = new StringBuilder();
|
||||
|
||||
try (Stream<String> stream = Files.lines( Paths.get(filePath), StandardCharsets.UTF_8))
|
||||
{
|
||||
stream.forEach(s -> contentBuilder.append(s).append("\n"));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return contentBuilder.toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NIO:读取所有字节
|
||||
|
||||
|
||||
读取所有字节后,我们将这些字节传递给String类构造函数以创建一个字符串
|
||||
```java
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class ReadFileToString
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String filePath = "c:/temp/data.txt";
|
||||
|
||||
System.out.println( readAllBytesJava7( filePath ) );
|
||||
}
|
||||
|
||||
//Read file content into string with - Files.readAllBytes(Path path)
|
||||
|
||||
private static String readAllBytesJava7(String filePath)
|
||||
{
|
||||
String content = "";
|
||||
|
||||
try
|
||||
{
|
||||
content = new String ( Files.readAllBytes( Paths.get(filePath) ) );
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### commons-io
|
||||
|
||||
```java
|
||||
//Using FileUtils.readFileToByteArray()
|
||||
byte[] org.apache.commons.io.FileUtils.readFileToByteArray(File file)
|
||||
|
||||
//Using IOUtils.toByteArray
|
||||
byte[] org.apache.commons.io.IOUtils.toByteArray(InputStream input)
|
||||
```
|
||||
|
||||
## 7 Properties
|
||||
|
||||
任何复杂的应用程序都需要某种配置。 有时我们需要将此配置为只读(通常在应用程序启动时读取),有时(或很少)我们需要写回或更新这些属性配置文件上的内容。
|
||||
|
||||
在这个简单易用的教程中,学习使用Properties.load()方法读取Java中的Properties.load() 文件 。 然后,我们将使用Properties.setProperty()方法将新属性写入file 。
|
||||
|
||||
* 创建单实例的属性文件
|
||||
|
||||
|
||||
|
||||
```java
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
public class PropertiesCache
|
||||
{
|
||||
private final Properties configProp = new Properties();
|
||||
|
||||
private PropertiesCache()
|
||||
{
|
||||
//Private constructor to restrict new instances
|
||||
InputStream in = this.getClass().getClassLoader().getResourceAsStream("app.properties");
|
||||
System.out.println("Read all properties from file");
|
||||
try {
|
||||
configProp.load(in);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
//Bill Pugh Solution for singleton pattern
|
||||
private static class LazyHolder
|
||||
{
|
||||
private static final PropertiesCache INSTANCE = new PropertiesCache();
|
||||
}
|
||||
|
||||
public static PropertiesCache getInstance()
|
||||
{
|
||||
return LazyHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public String getProperty(String key){
|
||||
return configProp.getProperty(key);
|
||||
}
|
||||
|
||||
public Set<String> getAllPropertyNames(){
|
||||
return configProp.stringPropertyNames();
|
||||
}
|
||||
|
||||
public boolean containsKey(String key){
|
||||
return configProp.containsKey(key);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 补充:静态内部类与懒汉式单例模式
|
||||
|
||||
这种方式是当被调用getInstance()时才去加载静态内部类LazyHolder,LazyHolder在加载过程中会实例化一个静态的Singleton,因为利用了classloader的机制来保证初始化instance时只有一个线程,所以Singleton肯定只有一个,是线程安全的,这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
|
||||
|
||||
```java
|
||||
public class Singleton {
|
||||
private static class LazyHolder {
|
||||
private static final Singleton INSTANCE = new Singleton();
|
||||
}
|
||||
private Singleton (){}
|
||||
public static final Singleton getInstance() {
|
||||
return LazyHolder.INSTANCE;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8 Resource File
|
||||
|
||||
ClassLoader引用从应用程序的资源包中读取文件。加载上下文环境中的文件。
|
||||
|
||||
```java
|
||||
package com.howtodoinjava.demo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public class ReadResourceFileDemo
|
||||
{
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
String fileName = "config/sample.txt";
|
||||
ClassLoader classLoader = new ReadResourceFileDemo().getClass().getClassLoader();
|
||||
|
||||
File file = new File(classLoader.getResource(fileName).getFile());
|
||||
|
||||
//File is found
|
||||
System.out.println("File Found : " + file.exists());
|
||||
|
||||
//Read File Content
|
||||
String content = new String(Files.readAllBytes(file.toPath()));
|
||||
System.out.println(content);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在spring中可以这样
|
||||
|
||||
```java
|
||||
File file = ResourceUtils.getFile("classpath:config/sample.txt")
|
||||
|
||||
//File is found
|
||||
System.out.println("File Found : " + file.exists());
|
||||
|
||||
//Read File Content
|
||||
String content = new String(Files.readAllBytes(file.toPath()));
|
||||
System.out.println(content);
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 10 读写utf8数据
|
||||
|
||||
```java
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
|
||||
public class WriteUTF8Data
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
File fileDir = new File("c:\\temp\\test.txt");
|
||||
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileDir), "UTF8"));
|
||||
out.append("Howtodoinjava.com").append("\r\n");
|
||||
out.append("UTF-8 Demo").append("\r\n");
|
||||
out.append("क्षेत्रफल = लंबाई * चौड़ाई").append("\r\n");
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (UnsupportedEncodingException e)
|
||||
{
|
||||
System.out.println(e.getMessage());
|
||||
} catch (IOException e)
|
||||
{
|
||||
System.out.println(e.getMessage());
|
||||
} catch (Exception e)
|
||||
{
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class ReadUTF8Data
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
try {
|
||||
File fileDir = new File("c:\\temp\\test.txt");
|
||||
|
||||
BufferedReader in = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream(fileDir), "UTF8"));
|
||||
|
||||
String str;
|
||||
|
||||
while ((str = in.readLine()) != null) {
|
||||
System.out.println(str);
|
||||
}
|
||||
|
||||
in.close();
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 11 从控制台读取输入
|
||||
|
||||
### console对象
|
||||
```java
|
||||
private static void usingConsoleReader()
|
||||
{
|
||||
Console console = null;
|
||||
String inputString = null;
|
||||
try
|
||||
{
|
||||
// creates a console object
|
||||
console = System.console();
|
||||
// if console is not null
|
||||
if (console != null)
|
||||
{
|
||||
// read line from the user input
|
||||
inputString = console.readLine("Name: ");
|
||||
// prints
|
||||
System.out.println("Name entered : " + inputString);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### System.in封装
|
||||
|
||||
```java
|
||||
private static void usingBufferedReader()
|
||||
{
|
||||
System.out.println("Name: ");
|
||||
try{
|
||||
BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
|
||||
String inputString = bufferRead.readLine();
|
||||
|
||||
System.out.println("Name entered : " + inputString);
|
||||
}
|
||||
catch(IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 更加复杂的Scanner
|
||||
```java
|
||||
private static void usingScanner()
|
||||
{
|
||||
System.out.println("Name: ");
|
||||
|
||||
Scanner scanIn = new Scanner(System.in);
|
||||
String inputString = scanIn.nextLine();
|
||||
|
||||
scanIn.close();
|
||||
System.out.println("Name entered : " + inputString);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 12 将String转换成输入流
|
||||
|
||||
### ByteArrayInputStream
|
||||
```java
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ConvertStringToInputStreamExample
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String sampleString = "howtodoinjava.com";
|
||||
|
||||
//Here converting string to input stream
|
||||
InputStream stream = new ByteArrayInputStream(sampleString.getBytes());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### apach.IOUtils
|
||||
|
||||
```java
|
||||
import java.io.InputStream;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
public class ConvertStringToInputStreamExample
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String sampleString = "howtodoinjava.com";
|
||||
|
||||
//Here converting string to input stream
|
||||
InputStream stream = IOUtils.toInputStream(sampleString);
|
||||
}
|
||||
}
|
||||
```
|
||||
59
Java基础教程/JavaIO与网络编程/04 Java IO对象.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 对象操作
|
||||
|
||||
## 基本操作
|
||||
### 序列化
|
||||
|
||||
序列化就是将一个对象转换成字节序列,方便存储和传输。
|
||||
|
||||
- 序列化:ObjectOutputStream.writeObject()
|
||||
- 反序列化:ObjectInputStream.readObject()
|
||||
|
||||
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
|
||||
|
||||
### Serializable
|
||||
|
||||
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
|
||||
|
||||
```java
|
||||
public static void main(String[] args) throws IOException, ClassNotFoundException {
|
||||
|
||||
A a1 = new A(123, "abc");
|
||||
String objectFile = "file/a1";
|
||||
|
||||
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
|
||||
objectOutputStream.writeObject(a1);
|
||||
objectOutputStream.close();
|
||||
|
||||
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile));
|
||||
A a2 = (A) objectInputStream.readObject();
|
||||
objectInputStream.close();
|
||||
System.out.println(a2);
|
||||
}
|
||||
|
||||
private static class A implements Serializable {
|
||||
|
||||
private int x;
|
||||
private String y;
|
||||
|
||||
A(int x, String y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "x = " + x + " " + "y = " + y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### transient
|
||||
|
||||
transient 关键字可以使一些属性不会被序列化。
|
||||
|
||||
ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
|
||||
|
||||
```java
|
||||
private transient Object[] elementData;
|
||||
```
|
||||
191
Java基础教程/JavaIO与网络编程/05 Java IO网络.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# 网络操作
|
||||
|
||||
|
||||
## 1 网络编程基础
|
||||
Java 中的网络支持:
|
||||
|
||||
- InetAddress:用于表示网络上的硬件资源,即 IP 地址;
|
||||
- URL:统一资源定位符;
|
||||
- Sockets:使用 TCP 协议实现网络通信;
|
||||
- Datagram:使用 UDP 协议实现网络通信。
|
||||
|
||||
### InetAddress
|
||||
|
||||
没有公有的构造函数,只能通过静态方法来创建实例。
|
||||
|
||||
```java
|
||||
InetAddress.getByName(String host);
|
||||
InetAddress.getByAddress(byte[] address);
|
||||
```
|
||||
|
||||
### URL
|
||||
|
||||
可以直接从 URL 中读取字节流数据。
|
||||
|
||||
```java
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
URL url = new URL("http://www.baidu.com");
|
||||
|
||||
/* 字节流 */
|
||||
InputStream is = url.openStream();
|
||||
|
||||
/* 字符流 */
|
||||
InputStreamReader isr = new InputStreamReader(is, "utf-8");
|
||||
|
||||
/* 提供缓存功能 */
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
System.out.println(line);
|
||||
}
|
||||
|
||||
br.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Sockets
|
||||
|
||||
- ServerSocket:服务器端类
|
||||
- Socket:客户端类
|
||||
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1e6affc4-18e5-4596-96ef-fb84c63bf88a.png" width="550px"> </div><br>
|
||||
|
||||
### Datagram
|
||||
|
||||
- DatagramSocket:通信类
|
||||
- DatagramPacket:数据包类
|
||||
|
||||
|
||||
## 2 BIO Socket编程
|
||||
|
||||
```java
|
||||
public class PlainOioServer {
|
||||
|
||||
public void serve(int port) throws IOException {
|
||||
final ServerSocket socket = new ServerSocket(port); //1
|
||||
try {
|
||||
for (;;) {
|
||||
final Socket clientSocket = socket.accept(); //2
|
||||
System.out.println("Accepted connection from " + clientSocket);
|
||||
|
||||
new Thread(new Runnable() { //3
|
||||
@Override
|
||||
public void run() {
|
||||
OutputStream out;
|
||||
try {
|
||||
out = clientSocket.getOutputStream();
|
||||
out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); //4
|
||||
out.flush();
|
||||
clientSocket.close(); //5
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
clientSocket.close();
|
||||
} catch (IOException ex) {
|
||||
// ignore on close
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start(); //6
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 3 NIO Socket编程
|
||||
|
||||
|
||||
```java
|
||||
public class NIOServer {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
Selector selector = Selector.open();
|
||||
|
||||
ServerSocketChannel ssChannel = ServerSocketChannel.open();
|
||||
ssChannel.configureBlocking(false);
|
||||
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
|
||||
ServerSocket serverSocket = ssChannel.socket();
|
||||
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
|
||||
serverSocket.bind(address);
|
||||
|
||||
while (true) {
|
||||
|
||||
selector.select();
|
||||
Set<SelectionKey> keys = selector.selectedKeys();
|
||||
Iterator<SelectionKey> keyIterator = keys.iterator();
|
||||
|
||||
while (keyIterator.hasNext()) {
|
||||
|
||||
SelectionKey key = keyIterator.next();
|
||||
|
||||
if (key.isAcceptable()) {
|
||||
|
||||
ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
|
||||
|
||||
// 服务器会为每个新连接创建一个 SocketChannel
|
||||
SocketChannel sChannel = ssChannel1.accept();
|
||||
sChannel.configureBlocking(false);
|
||||
|
||||
// 这个新连接主要用于从客户端读取数据
|
||||
sChannel.register(selector, SelectionKey.OP_READ);
|
||||
|
||||
} else if (key.isReadable()) {
|
||||
|
||||
SocketChannel sChannel = (SocketChannel) key.channel();
|
||||
System.out.println(readDataFromSocketChannel(sChannel));
|
||||
sChannel.close();
|
||||
}
|
||||
|
||||
keyIterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
StringBuilder data = new StringBuilder();
|
||||
|
||||
while (true) {
|
||||
|
||||
buffer.clear();
|
||||
int n = sChannel.read(buffer);
|
||||
if (n == -1) {
|
||||
break;
|
||||
}
|
||||
buffer.flip();
|
||||
int limit = buffer.limit();
|
||||
char[] dst = new char[limit];
|
||||
for (int i = 0; i < limit; i++) {
|
||||
dst[i] = (char) buffer.get(i);
|
||||
}
|
||||
data.append(dst);
|
||||
buffer.clear();
|
||||
}
|
||||
return data.toString();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class NIOClient {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
Socket socket = new Socket("127.0.0.1", 8888);
|
||||
OutputStream out = socket.getOutputStream();
|
||||
String s = "hello world";
|
||||
out.write(s.getBytes());
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-08-10-51-54.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-08-10-54-19.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-26-17-31-29.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-26-18-03-55.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-26-19-20-23.png
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-26-19-31-24.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-09.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-26-19-40-59.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-26-20-24-47.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-00-08-19.png
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-15.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-28.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-35.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-45.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-00-33-59.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-00-48-14.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Java基础教程/JavaIO与网络编程/image/2022-11-27-10-06-38.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 263 B |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |