javaIO和netty的基本原理

This commit is contained in:
法然
2022-11-27 14:45:19 +08:00
parent f90e4ed166
commit 19d3b3e3e2
169 changed files with 5444 additions and 3691 deletions

BIN
.DS_Store vendored

Binary file not shown.

View 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 概览
![](image/2022-07-12-11-35-43.png)
本篇文章的范围应该是涵盖java.io包中的所有类。
### IO定义
> 该理解方式非常有价值。为什么IO的同步和异步如此重要因为IO操作是整个调用链路上的性能瓶颈。普通的A函数调用B函数为什么不采用异步操作呢因为函数都是计算任务都在内存完成。所以所有的操作都可以分为两种计算操作非IO操作内存中即可完成和IO操作从其他设备中读取、写入数据。计算操作是使用CPU的IO操作过程中CPU线程是挂起的等待中。函数、调用可以分为两种正常调用和IO调用。
缓冲区以及如何处理缓冲区是所有I / O的基础。 术语“输入/输出”仅意味着将数据移入和移出缓冲区。 只要时刻牢记这一点即可。 通常,进程通过请求操作系统从缓冲区中清空数据( write operation )或向缓冲区中填充数据( read operation 来执行I / O。 以上是I / O概念的全部摘要。
![](image/2022-11-26-17-31-29.png)
上图显示了块数据如何从外部源例如硬盘移动到正在运行的进程例如RAM内部的存储区的简化“逻辑”图。
1. 首先该进程通过进行read()系统调用来请求填充其缓冲区。
2. 此调用导致内核向磁盘控制器硬件发出命令以从磁盘获取数据。 磁盘控制器通过DMA将数据直接写入内核内存缓冲区而无需主CPU的进一步协助。
3. 磁盘控制器完成缓冲区填充后当它请求read()操作时。内核将数据从内核空间中的临时缓冲区复制到进程指定的缓冲区中;
4. 需要注意的一件事是内核尝试缓存和/或预取数据,因此进程请求的数据可能已经在内核空间中可用。 如果是这样,则将过程所请求的数据复制出来。 如果数据不可用,则该过程将在内核将数据带入内存时挂起。
### 虚拟内存
虚拟内存具有两个重要优点:
1多个虚拟地址可以引用相同的物理内存位置。
2虚拟内存空间可以大于可用的实际硬件内存。
对于第一个特性通过地址映射mmap将内核空间逻辑地址与用户空间中虚拟地址映射到相同物理空间。DMA硬件只能访问物理内存地址可以填充一个缓冲区该缓冲区同时对内核和用户空间进程可见。消除了内核空间和用户空间之间的副本
![](image/2022-11-26-18-03-55.png)
对于第二个特性,进行虚拟内存分页(通常称为交换)。将虚拟内存空间的页面持久保存到外部磁盘存储中,从而在物理内存中为其他虚拟页面腾出空间。物理内存充当页面调度区域的缓存,当虚拟内存的不在物理内存中时,由物理内存从磁盘空间交换。
### IO的分类
Java 的 I/O可以根据如下方式进行分类。
![](image/2022-11-26-19-31-24.png)
![](image/2022-11-26-19-20-23.png)
![](image/2022-11-27-00-08-19.png)
根据内容的不同可以分为:
- 字节操作InputStream 和 OutputStream
- 字符操作Reader 和 Writer
- 输入流 InputStream、Reader
- 输出流 OutputStream、Writer
- 字节转字符InputStreamReader/OutputStreamWriter
根据对象的不同可以分为:
- 文件操作File
- 管道操作Piped
- 数组操作ByteArray&CharArray
- 对象操作Object
- 过滤操作Filter添加额外特性
- Bufferd
- Data
- PushBack
- LineNumber
- 网络操作Socket
根据IO模型的不同可以分为
- 同步阻塞IOBIO
- 异步IONIO
- 异步IOAIO
根据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 字节操作
![](image/2022-11-26-19-40-09.png)
### 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()ByteCharDoubleFloatIntLong
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 字符操作
![](image/2022-11-26-20-24-47.png)
### 编码与解码
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
- UTF-16 be编码中中文字符和英文字符都占 2 个字节。
UTF-16be 中的 be 指的是 Big Endian也就是大端。相应地也有 UTF-16lele 指的是 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)

View 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/outputOIO)。被称为普通 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中线程可以请求将某些数据写入通道但不等待将其完全写入。 然后线程可以继续运行,同时执行其他操作。 单个线程现在可以管理输入和输出的多个通道。
![](image/2022-11-27-10-06-38.png)
## 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 变量不会改变,下面的讨论会忽略它。
![](image/2022-11-27-00-33-15.png)
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5limit 保持不变。
![](image/2022-11-27-00-33-28.png)
③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position并将 position 设置为 0。
![](image/2022-11-27-00-33-35.png)
④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
![](image/2022-11-27-00-33-45.png)
⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
![](image/2022-11-27-00-33-59.png)
### 创建缓冲区
缓冲区类都不能直接实例化。 它们都是抽象类,但是每个都包含静态工厂方法来创建相应类的新实例。
```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方法将缓冲区从可以附加数据元素的填充状态翻转到耗尽状态以准备读取元素 。
![](image/2022-11-27-00-48-14.png)
```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();
}
}
```

View 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)
- [BIOcreateNewFile](#biocreatenewfile)
- [BIOFileOutputStream](#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
### BIOcreateNewFile
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();
```
### BIOFileOutputStream
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()时才去加载静态内部类LazyHolderLazyHolder在加载过程中会实例化一个静态的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);
}
}
```

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Some files were not shown because too many files have changed in this diff Show More