javaNIO 學習筆記(二)
參考文檔:http://tutorials.jenkov.com/java-nio/channels.html
Java NIO Channel
和傳統的IO中的 InputStream
、OutputStream
相比,主要區別就是傳統IO中的stream是單向的。但是在NIO中的channel
是可讀可寫的。並且讀寫是可以異步進行的。另外channel
都是從buffer
中讀取或者寫入數據的
上一篇學習筆記中的主要實現的管道類型
-
FileChannel 從文件和文件讀取數據
-
DatagramChannel 可以通過UDP在網絡上讀寫數據。
-
SocketChannel 可以通過TCP在網絡上讀寫數據。
-
ServerSocketChannel 允許您偵聽傳入的TCP連接,就像web服務器一樣。爲每個傳入連接創建SocketChannel。
這4個類在idea中可以查看相應的UML圖
FileChannel
先看下面的程序
讀取文件數據:
public class FileChannelPrc {
public static void main(String[] args) throws IOException {
// 創建一個rw模式的隨機文件
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
// 獲取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// 設定緩衝區大小48
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = fileChannel.read(buf);
while(bytesRead !=-1){
System.out.println("\nRead " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = fileChannel.read(buf);
}
randomAccessFile.close();
}
}
// 運行結果:
Hi, I am jimmy.
上面程序要正確執行需要,現在本地對應文件夾中創建相應文件。我在文件中寫入的是Hi, I am jimmy.
這裏就可以看到讀取出來並且打印了。
然後我們將文件內容修改爲Hi, I am jimmy. I am learning Java NIO. Hope I can hold it.
(大於48字符)。在運行程序,此時返回值
Read 48
Hi, I am jimmy. I am learning Java NIO. Hope I
Read 13
can hold it.
通過上面的測試我們大概可以猜測。
// 通過fileChannel將數據讀取到緩衝區,返回值是緩衝區讀取的字節數
int bytesRead = fileChannel.read(buf);
// 判斷緩衝區是否還有數據
buf.hasRemaining()
// 獲取當前緩衝區的第一個元素
buf.get()
// 切換緩衝區讀寫模式
buf.flip();
// 清空緩存區
buf.clear();
我們現在先只來看channel
相關的猜測。改造程序(改造部分如下)
// 設定緩衝區大小
ByteBuffer buf = ByteBuffer.allocate(48);
ByteBuffer buf2 = ByteBuffer.allocate(48);
int bytesRead = fileChannel.read(buf);
int bytesRead2 = fileChannel.read(buf);
System.out.println("bytesRead" + bytesRead + " bytesRead2" + bytesRead2);
此時走debug或者查看打印語句就可以看出來,bytesRead = 16 bytesRead2 = -1
(文件信息Hi, I am jimmy.
)。那麼這樣就可以得出一個結論
fileChannel.read(buf);會將channel
的數據flush
到緩衝區,而不是複製。
下面是程序則是使用fileChannel寫信息到文件中
public class FileChannelPrc {
public static void main(String[] args) throws IOException {
...
// 寫文件
// ByteBuffer buf2 = ByteBuffer.allocate(48);
String newData = "do not worry, be happy 時間:" + System.currentTimeMillis();
buf.clear();
buf.put(newData.getBytes());
while(buf.hasRemaining()) {
buf.flip();
fileChannel.write(buf);
}
fileChannel.close();
randomAccessFile.close();
}
}
查看文件,文件文本顯示
Hi, I am jimmy. do not worry, be happy 時間:1591970658685
看這樣的結果可以看出來,fileChannel
寫數據的時候是在文件結尾處增加寫入數據,而非覆蓋。
那麼根據上面的例子我們先簡單的總結下fileChannel
簡單使用方法。
- 獲取fileChannel的實例(注意
public abstract class FileChannel
這是個抽象類),獲得方式
可以使用RandomAccessFile
的實例來獲取
- 讀取數據的方法使用
read
將內容讀取到ByteBuffer
,當然這裏也設計ByteBuffer
的使用,這個後面在學習總結 - 寫過程呢則是使用
write
方法向文件寫入ByteBuffer
的信息。 - 當然最後和傳統IO一樣,需要調用
close
來關閉fileChannel
接下來稍微看下fileChannel
的源碼(這裏也算是寫一個備忘錄,以後遇到相關問題的時候可以回來看下,或許會有幫助)
// ---- read方法 -------
public abstract int read(ByteBuffer dst) throws IOException;
// 從指定位置開始讀取數據
public abstract int read(ByteBuffer dst, long position) throws IOException;
public final long read(ByteBuffer[] dsts) throws IOException {
return read(dsts, 0, dsts.length);
}
public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
// ------ write方法 ------
public abstract int write(ByteBuffer src) throws IOException;
public abstract int write(ByteBuffer src, long position) throws IOException;
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}
public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
// ---- open方法 ------ @since 1.7 這個方法可以獲取fileChannel
public static FileChannel open(Path path, OpenOption... options) throws IOException
{
Set<OpenOption> set = new HashSet<OpenOption>(options.length);
Collections.addAll(set, options);
return open(path, set, NO_ATTRIBUTES);
}
public static FileChannel open(Path path, Set<? extends OpenOption> options,
FileAttribute<?>... attrs) throws IOException
{
FileSystemProvider provider = path.getFileSystem().provider();
return provider.newFileChannel(path, options, attrs);
}
// --- position --
// 獲取一個新的fileChannel
public abstract FileChannel position(long newPosition) throws IOException;
// 返回buffer讀取的位置
public abstract long position() throws IOException;
// --- force ----
// 強制將channel的數據寫入,根據metaData決定是否寫入本地磁盤存儲
public abstract void force(boolean metaData) throws IOException;
// Returns the current size of this channel's file. 返回當前通道中的文件大小
public abstract long size() throws IOException;
// 截取指定長度的數據,從開始進行截取長度爲 size的
public abstract FileChannel truncate(long size) throws IOException;
當然還有一些其他方法,這些等以後涉及使用的時候在補充吧。這裏附上測試的時候的代碼(非全部,有的API直接可使用idea的片段執行查看結果。)
public class FileChannelPrc {
public static void main(String[] args) throws IOException {
// 創建一個rw模式的隨機文件
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
// 獲取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// fileChannel.position();
FileChannel fileChannel2 = fileChannel.truncate(10);
// 設定緩衝區大小
ByteBuffer buf = ByteBuffer.allocate(48);
// int bytesRead = fileChannel.read(buf,10L);
int bytesRead = fileChannel2.read(buf);
// ByteBuffer buf2 = ByteBuffer.allocate(48);
// int bytesRead = fileChannel.read(buf);
/*int bytesRead2 = fileChannel.read(buf);
System.out.println("bytesRead" + bytesRead + " bytesRead2" + bytesRead2);*/
while(bytesRead !=-1){
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
// bytesRead = fileChannel.read(buf);
bytesRead = fileChannel2.read(buf);
}
// 寫文件
// ByteBuffer buf2 = ByteBuffer.allocate(48);
/* String newData = "do not worry, be happy 時間:" + System.currentTimeMillis();
buf.clear();
buf.put(newData.getBytes());
while(buf.hasRemaining()) {
buf.flip();
fileChannel.write(buf);
}*/
fileChannel.close();
randomAccessFile.close();
}
}
SocketChannel 和 ServerSocketChannel
socketChannel
是一個用來處理TCP網絡套接字的通道。有倆種方式來獲取SocketChannel
- 使用open()可以直接獲取一個實例
- 通過
ServerSocketChannel
來獲取
SocketChannel socketChannel = SocketChannel.open();
ServerSocketChannel
類似於標準Java網絡中的ServerSocket
一樣,是一個可以偵聽傳入TCP連接的通道.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
no talk,show code
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 15:03
* @Description:
*
* 理解爲server
*/
public class ServerSocketChannelPrc {
public static void main(String[] args) throws IOException {
// 獲取實例
ServerSocketChannel server = ServerSocketChannel.open();
// 綁定ip和端口
server.socket().bind(new InetSocketAddress("127.0.0.1",2999));
//設置爲非阻塞模式
server.configureBlocking(false);
// 獲取 SocketChannel
while(true){
SocketChannel serverChannel = server.accept();
if(serverChannel != null) {
ByteBuffer buf = ByteBuffer.allocate(512);
int byteRead = serverChannel.read(buf);
StringBuilder req = new StringBuilder();
buf.flip();
while(buf.hasRemaining()){
req.append((char) buf.get());
}
buf.clear();
System.out.println("客戶端發來消息" + req);
buf.put("hi,this is jimmy. be happy ".getBytes());
buf.flip();
serverChannel.write(buf);
// serverChannel.close();
}
}
}
}
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 12:04
* @Description:
*
* 可以理解爲client
*/
public class SocketChannelPrc {
/**
* 爲了方便,這裏直接將異常拋出,實際開發要進行異常捕捉
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// 獲取 SocketChannel 的實例
SocketChannel client = SocketChannel.open();
/**
* 連接服務端,這裏面傳入的參數對象爲 SocketAddress
* 查看源碼可以找到一個實現類 InetSocketAddress 構造方法 public InetSocketAddress(String hostname, int port)
* 看到這個構造就很熟悉了,
*
* 連接服務 127.0.0.1 和 2999 端口
*/
client.connect(new InetSocketAddress("127.0.0.1",2999));
// 有channel就需要有緩衝區buffer
ByteBuffer buf = ByteBuffer.allocate(512);
buf.put("hi, jimmy,this is client.".getBytes());
// 切換讀寫模式
buf.flip();
// 寫數據去服務器
client.write(buf);
// 清空buffer,此時相當於將buffer重置爲讀模式
buf.clear();
int byteRead = client.read(buf);
buf.flip();
StringBuilder stringBuffer=new StringBuilder();
while (buf.hasRemaining()){
stringBuffer.append((char) buf.get());
}
buf.clear();
System.out.println("從服務端接收到的數據:"+stringBuffer);
// 關閉 socketChannel
client.close();
}
}
先啓動服務端ServerSocketChannel
,然後U啓動SocketChannel
.返回結果如下:
客戶端發來消息hi, jimmy,this is client.
從服務端接收到的數據:hi,this is jimmy. be happy
整體看來和fileChannel
差不太多,基本都是要先有一個打開的channel
(有一個方法是isOpen()可以獲取是否打開)。然後使用相應的緩衝區來將數據從通道寫入緩衝區或者從緩衝區讀取數據。(關於buffer相關的方法後面在學習)。不過要注意一些特別的地方SocketChannel``ServerSocketChannel
的一個方法configureBlocking()
這個方法表示這個SocketChannel``ServerSocketChannel
是阻塞還是非阻塞的,具體使用方式等學習了selector
的時候在進行補充。當然上面程序是不完善的,接受放緩衝區不夠大的時候會報錯。具體可以改下代碼看看。
DatagramChannel
DatagramChannel
類似於java 網絡編程的DatagramSocket類;使用UDP進行網絡傳輸
同樣的可以使用open方法獲取一個實例
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 17:32
* @Description:
* server
*/
public class DatagramChannelPrcS {
public static void main(String[] args) throws IOException {
DatagramChannel s = DatagramChannel.open();
s.bind(new InetSocketAddress("127.0.0.1",3999));
ByteBuffer buf = ByteBuffer.allocate(48);
s.receive(buf);
buf.clear();
while(buf.hasRemaining()){
System.out.print((char)buf.get());
}
}
}
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 17:26
* @Description:
* client
*/
public class DatagramChannelPrc {
public static void main(String[] args) throws IOException {
DatagramChannel channel = DatagramChannel.open();
// 綁定端口
// channel.bind(new InetSocketAddress(3999));
ByteBuffer buf = ByteBuffer.allocate(48);
buf.put("datagrameChannel info".getBytes());
// datagrameChannel不需要使用這個方法
// channel.write(buf);
buf.flip();
int a = channel.send(buf,new InetSocketAddress("127.0.0.1",3999));
channel.close();
}
}
使用起來比較簡單,服務端可以直接使用bind方法綁定端口、ip。客戶端從緩衝區直接將數據發哦是那個至服務器。是否發送成功或者成功被接收到是沒有保證的;發送消息通過send方法發出,改方法返回一個int值,表示成功發送的字節數:
那麼這學習留下一個尾巴就是關於阻塞模式和非阻塞模式的區別以及應用。這個在學習完其他類之後在進行補充學習
buf = ByteBuffer.allocate(48);
buf.put(“datagrameChannel info”.getBytes());
// datagrameChannel不需要使用這個方法
// channel.write(buf);
buf.flip();
int a = channel.send(buf,new InetSocketAddress(“127.0.0.1”,3999));
channel.close();
}
}
使用起來比較簡單,服務端可以直接使用bind方法綁定端口、ip。客戶端從緩衝區直接將數據發哦是那個至服務器。是否發送成功或者成功被接收到是沒有保證的;發送消息通過send方法發出,改方法返回一個int值,表示成功發送的字節數:
那麼這學習留下一個尾巴就是關於阻塞模式和非阻塞模式的區別以及應用。這個在學習完其他類之後在進行補充學習