日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

IO 概括

發(fā)布時(shí)間:2023/12/10 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 IO 概括 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

# 一、概覽

Java 的 I/O 大概可以分成以下幾類:

- 磁盤操作:File
- 字節(jié)操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 對(duì)象操作:Serializable
- 網(wǎng)絡(luò)操作:Socket
- 新的輸入/輸出:NIO

# 二、磁盤操作

File 類可以用于表示文件和目錄的信息,但是它不表示文件的內(nèi)容。

遞歸地列出一個(gè)目錄下所有文件:

```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);
}
}
```

# 三、字節(jié)操作

## 實(shí)現(xiàn)文件復(fù)制

```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];

// read() 最多讀取 buffer.length 個(gè)字節(jié)
// 返回的是實(shí)際讀取的個(gè)數(shù)
// 返回 -1 的時(shí)候表示讀到 eof,即文件尾
while (in.read(buffer, 0, buffer.length) != -1) {
out.write(buffer);
}

in.close();
out.close();
}
```

## 裝飾者模式

Java I/O 使用了裝飾者模式來實(shí)現(xiàn)。以 InputStream 為例,

- InputStream 是抽象組件;
- FileInputStream 是 InputStream 的子類,屬于具體組件,提供了字節(jié)流的輸入操作;
- FilterInputStream 屬于抽象裝飾者,裝飾者用于裝飾組件,為組件提供額外的功能。例如 BufferedInputStream 為 FileInputStream 提供緩存的功能。

<div align="center"> <img src="../pics//DP-Decorator-java.io.png" width="500"/> </div><br>

實(shí)例化一個(gè)具有緩存功能的字節(jié)流對(duì)象時(shí),只需要在 FileInputStream 對(duì)象上再套一層 BufferedInputStream 對(duì)象即可。

```java
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
```

DataInputStream 裝飾者提供了對(duì)更多數(shù)據(jù)類型進(jìn)行輸入的操作,比如 int、double 等基本類型。

# 四、字符操作

## 編碼與解碼

編碼就是把字符轉(zhuǎn)換為字節(jié),而解碼是把字節(jié)重新組合成字符。

如果編碼和解碼過程使用不同的編碼方式那么就出現(xiàn)了亂碼。

- GBK 編碼中,中文字符占 2 個(gè)字節(jié),英文字符占 1 個(gè)字節(jié);
- UTF-8 編碼中,中文字符占 3 個(gè)字節(jié),英文字符占 1 個(gè)字節(jié);
- UTF-16be 編碼中,中文字符和英文字符都占 2 個(gè)字節(jié)。

UTF-16be 中的 be 指的是 Big Endian,也就是大端。相應(yīng)地也有 UTF-16le,le 指的是 Little Endian,也就是小端。

Java 使用雙字節(jié)編碼 UTF-16be,這不是指 Java 只支持這一種編碼方式,而是說 char 這種類型使用 UTF-16be 進(jìn)行編碼。char 類型占 16 位,也就是兩個(gè)字節(jié),Java 使用這種雙字節(jié)編碼是為了讓一個(gè)中文或者一個(gè)英文都能使用一個(gè) char 來存儲(chǔ)。

## String 的編碼方式

String 可以看成一個(gè)字符序列,可以指定一個(gè)編碼方式將它編碼為字節(jié)序列,也可以指定一個(gè)編碼方式將一個(gè)字節(jié)序列解碼為 String。

```java
String str1 = "中文";
byte[] bytes = str1.getBytes("UTF-8");
String str2 = new String(bytes, "UTF-8");
System.out.println(str2);
```

在調(diào)用無參數(shù) getBytes() 方法時(shí),默認(rèn)的編碼方式不是 UTF-16be。雙字節(jié)編碼的好處是可以使用一個(gè) char 存儲(chǔ)中文和英文,而將 String 轉(zhuǎn)為 bytes[] 字節(jié)數(shù)組就不再需要這個(gè)好處,因此也就不再需要雙字節(jié)編碼。getBytes() 的默認(rèn)編碼方式與平臺(tái)有關(guān),一般為 UTF-8。

```java
byte[] bytes = str1.getBytes();
```

## Reader 與 Writer

不管是磁盤還是網(wǎng)絡(luò)傳輸,最小的存儲(chǔ)單元都是字節(jié),而不是字符。但是在程序中操作的通常是字符形式的數(shù)據(jù),因此需要提供對(duì)字符進(jìn)行操作的方法。

- InputStreamReader 實(shí)現(xiàn)從字節(jié)流解碼成字符流;
- OutputStreamWriter 實(shí)現(xiàn)字符流編碼成為字節(jié)流。

## 實(shí)現(xiàn)逐行輸出文本文件的內(nèi)容

```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 組合了一個(gè) Reader 對(duì)象
// 在調(diào)用 BufferedReader 的 close() 方法時(shí)會(huì)去調(diào)用 Reader 的 close() 方法
// 因此只要一個(gè) close() 調(diào)用即可
bufferedReader.close();
}
```

# 五、對(duì)象操作

## 序列化

序列化就是將一個(gè)對(duì)象轉(zhuǎn)換成字節(jié)序列,方便存儲(chǔ)和傳輸。

- 序列化:ObjectOutputStream.writeObject()
- 反序列化:ObjectInputStream.readObject()

不會(huì)對(duì)靜態(tài)變量進(jìn)行序列化,因?yàn)樾蛄谢皇潜4鎸?duì)象的狀態(tài),靜態(tài)變量屬于類的狀態(tài)。

## Serializable

序列化的類需要實(shí)現(xiàn) Serializable 接口,它只是一個(gè)標(biāo)準(zhǔn),沒有任何方法需要實(shí)現(xiàn),但是如果不去實(shí)現(xiàn)它的話而進(jìn)行序列化,會(huì)拋出異常。

```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 關(guān)鍵字可以使一些屬性不會(huì)被序列化。

ArrayList 中存儲(chǔ)數(shù)據(jù)的數(shù)組 elementData 是用 transient 修飾的,因?yàn)檫@個(gè)數(shù)組是動(dòng)態(tài)擴(kuò)展的,并不是所有的空間都被使用,因此就不需要所有的內(nèi)容都被序列化。通過重寫序列化和反序列化方法,使得可以只序列化數(shù)組中有內(nèi)容的那部分?jǐn)?shù)據(jù)。

```java
private transient Object[] elementData;
```

?

# 七、NIO

- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
- [Java NIO 淺析](https://tech.meituan.com/nio.html)
- [IBM: NIO 入門](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)

新的輸入/輸出 (NIO) 庫是在 JDK 1.4 中引入的,彌補(bǔ)了原來的 I/O 的不足,提供了高速的、面向塊的 I/O。

## 流與塊

I/O 與 NIO 最重要的區(qū)別是數(shù)據(jù)打包和傳輸?shù)姆绞?#xff0c;I/O 以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù)。

面向流的 I/O 一次處理一個(gè)字節(jié)數(shù)據(jù):一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)數(shù)據(jù),一個(gè)輸出流消費(fèi)一個(gè)字節(jié)數(shù)據(jù)。為流式數(shù)據(jù)創(chuàng)建過濾器非常容易,鏈接幾個(gè)過濾器,以便每個(gè)過濾器只負(fù)責(zé)復(fù)雜處理機(jī)制的一部分。不利的一面是,面向流的 I/O 通常相當(dāng)慢。

面向塊的 I/O 一次處理一個(gè)數(shù)據(jù)塊,按塊處理數(shù)據(jù)比按流處理數(shù)據(jù)要快得多。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優(yōu)雅性和簡(jiǎn)單性。

I/O 包和 NIO 已經(jīng)很好地集成了,java.io.\* 已經(jīng)以 NIO 為基礎(chǔ)重新實(shí)現(xiàn)了,所以現(xiàn)在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些類包含以塊的形式讀寫數(shù)據(jù)的方法,這使得即使在面向流的系統(tǒng)中,處理速度也會(huì)更快。

## 通道與緩沖區(qū)

### 1. 通道

通道 Channel 是對(duì)原 I/O 包中的流的模擬,可以通過它讀取和寫入數(shù)據(jù)。

通道與流的不同之處在于,流只能在一個(gè)方向上移動(dòng)(一個(gè)流必須是 InputStream 或者 OutputStream 的子類),而通道是雙向的,可以用于讀、寫或者同時(shí)用于讀寫。

通道包括以下類型:

- FileChannel:從文件中讀寫數(shù)據(jù);
- DatagramChannel:通過 UDP 讀寫網(wǎng)絡(luò)中數(shù)據(jù);
- SocketChannel:通過 TCP 讀寫網(wǎng)絡(luò)中數(shù)據(jù);
- ServerSocketChannel:可以監(jiān)聽新進(jìn)來的 TCP 連接,對(duì)每一個(gè)新進(jìn)來的連接都會(huì)創(chuàng)建一個(gè) SocketChannel。

### 2. 緩沖區(qū)

發(fā)送給一個(gè)通道的所有數(shù)據(jù)都必須首先放到緩沖區(qū)中,同樣地,從通道中讀取的任何數(shù)據(jù)都要先讀到緩沖區(qū)中。也就是說,不會(huì)直接對(duì)通道進(jìn)行讀寫數(shù)據(jù),而是要先經(jīng)過緩沖區(qū)。

緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組,但它不僅僅是一個(gè)數(shù)組。緩沖區(qū)提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪問,而且還可以跟蹤系統(tǒng)的讀/寫進(jìn)程。

緩沖區(qū)包括以下類型:

- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer

## 緩沖區(qū)狀態(tài)變量

- capacity:最大容量;
- position:當(dāng)前已經(jīng)讀寫的字節(jié)數(shù);
- limit:還可以讀寫的字節(jié)數(shù)。

狀態(tài)變量的改變過程舉例:

① 新建一個(gè)大小為 8 個(gè)字節(jié)的緩沖區(qū),此時(shí) position 為 0,而 limit = capacity = 8。capacity 變量不會(huì)改變,下面的討論會(huì)忽略它。

<div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>

② 從輸入通道中讀取 5 個(gè)字節(jié)數(shù)據(jù)寫入緩沖區(qū)中,此時(shí) position 移動(dòng)設(shè)置為 5,limit 保持不變。

<div align="center"> <img src="../pics//80804f52-8815-4096-b506-48eef3eed5c6.png"/> </div><br>

③ 在將緩沖區(qū)的數(shù)據(jù)寫到輸出通道之前,需要先調(diào)用 flip() 方法,這個(gè)方法將 limit 設(shè)置為當(dāng)前 position,并將 position 設(shè)置為 0。

<div align="center"> <img src="../pics//952e06bd-5a65-4cab-82e4-dd1536462f38.png"/> </div><br>

④ 從緩沖區(qū)中取 4 個(gè)字節(jié)到輸出緩沖中,此時(shí) position 設(shè)為 4。

<div align="center"> <img src="../pics//b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png"/> </div><br>

⑤ 最后需要調(diào)用 clear() 方法來清空緩沖區(qū),此時(shí) position 和 limit 都被設(shè)置為最初位置。

<div align="center"> <img src="../pics//67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br>

## 文件 NIO 實(shí)例

以下展示了使用 NIO 快速復(fù)制文件的實(shí)例:

```java
public static void fastCopy(String src, String dist) throws IOException {

/* 獲得源文件的輸入字節(jié)流 */
FileInputStream fin = new FileInputStream(src);

/* 獲取輸入字節(jié)流的文件通道 */
FileChannel fcin = fin.getChannel();

/* 獲取目標(biāo)文件的輸出字節(jié)流 */
FileOutputStream fout = new FileOutputStream(dist);

/* 獲取輸出字節(jié)流的文件通道 */
FileChannel fcout = fout.getChannel();

/* 為緩沖區(qū)分配 1024 個(gè)字節(jié) */
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

while (true) {

/* 從輸入通道中讀取數(shù)據(jù)到緩沖區(qū)中 */
int r = fcin.read(buffer);

/* read() 返回 -1 表示 EOF */
if (r == -1) {
break;
}

/* 切換讀寫 */
buffer.flip();

/* 把緩沖區(qū)的內(nèi)容寫入輸出文件中 */
fcout.write(buffer);

/* 清空緩沖區(qū) */
buffer.clear();
}
}
```

## 選擇器

NIO 常常被叫做非阻塞 IO,主要是因?yàn)?NIO 在網(wǎng)絡(luò)通信中的非阻塞特性被廣泛使用。

NIO 實(shí)現(xiàn)了 IO 多路復(fù)用中的 Reactor 模型,一個(gè)線程 Thread 使用一個(gè)選擇器 Selector 通過輪詢的方式去監(jiān)聽多個(gè)通道 Channel 上的事件,從而讓一個(gè)線程就可以處理多個(gè)事件。

通過配置監(jiān)聽的通道 Channel 為非阻塞,那么當(dāng) Channel 上的 IO 事件還未到達(dá)時(shí),就不會(huì)進(jìn)入阻塞狀態(tài)一直等待,而是繼續(xù)輪詢其它 Channel,找到 IO 事件已經(jīng)到達(dá)的 Channel 執(zhí)行。

因?yàn)閯?chuàng)建和切換線程的開銷很大,因此使用一個(gè)線程來處理多個(gè)事件而不是一個(gè)線程處理一個(gè)事件,對(duì)于 IO 密集型的應(yīng)用具有很好地性能。

應(yīng)該注意的是,只有套接字 Channel 才能配置為非阻塞,而 FileChannel 不能,為 FileChannel 配置非阻塞也沒有意義。

<div align="center"> <img src="../pics//4d930e22-f493-49ae-8dff-ea21cd6895dc.png"/> </div><br>

### 1. 創(chuàng)建選擇器

```java
Selector selector = Selector.open();
```

### 2. 將通道注冊(cè)到選擇器上

```java
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
```

通道必須配置為非阻塞模式,否則使用選擇器就沒有任何意義了,因?yàn)槿绻ǖ涝谀硞€(gè)事件上被阻塞,那么服務(wù)器就不能響應(yīng)其它事件,必須等待這個(gè)事件處理完畢才能去處理其它事件,顯然這和選擇器的作用背道而馳。

在將通道注冊(cè)到選擇器上時(shí),還需要指定要注冊(cè)的具體事件,主要有以下幾類:

- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE

它們?cè)?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;
```

可以看出每個(gè)事件可以被當(dāng)成一個(gè)位域,從而組成事件集整數(shù)。例如:

```java
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
```

### 3. 監(jiān)聽事件

```java
int num = selector.select();
```

使用 select() 來監(jiān)聽到達(dá)的事件,它會(huì)一直阻塞直到有至少一個(gè)事件到達(dá)。

### 4. 獲取到達(dá)的事件

```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. 事件循環(huán)

因?yàn)橐淮?select() 調(diào)用不能處理完所有的事件,并且服務(wù)器端有可能需要一直監(jiān)聽事件,因此服務(wù)器端處理事件的代碼一般會(huì)放在一個(gè)死循環(huán)內(nèi)。

```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();
}
}
```

## 套接字 NIO 實(shí)例

```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();

// 服務(wù)器會(huì)為每個(gè)新連接創(chuàng)建一個(gè) SocketChannel
SocketChannel sChannel = ssChannel1.accept();
sChannel.configureBlocking(false);

// 這個(gè)新連接主要用于從客戶端讀取數(shù)據(jù)
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();
}
}
```

## 內(nèi)存映射文件

內(nèi)存映射文件 I/O 是一種讀和寫文件數(shù)據(jù)的方法,它可以比常規(guī)的基于流或者基于通道的 I/O 快得多。

向內(nèi)存映射文件寫入可能是危險(xiǎn)的,只是改變數(shù)組的單個(gè)元素這樣的簡(jiǎn)單操作,就可能會(huì)直接修改磁盤上的文件。修改數(shù)據(jù)與將數(shù)據(jù)保存到磁盤是沒有分開的。

下面代碼行將文件的前 1024 個(gè)字節(jié)映射到內(nèi)存中,map() 方法返回一個(gè) MappedByteBuffer,它是 ByteBuffer 的子類。因此,可以像使用其他任何 ByteBuffer 一樣使用新映射的緩沖區(qū),操作系統(tǒng)會(huì)在需要時(shí)負(fù)責(zé)執(zhí)行映射。

```java
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
```

## 對(duì)比

NIO 與普通 I/O 的區(qū)別主要有以下兩點(diǎn):

- NIO 是非阻塞的;
- NIO 面向塊,I/O 面向流。

轉(zhuǎn)載于:https://www.cnblogs.com/kakaisgood/p/9579734.html

總結(jié)

以上是生活随笔為你收集整理的IO 概括的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。