Day 30
Author : ScorpioDong
1. 單例模式
1.1 要求
當前類只有一個對象,一旦當前類存在一個對象之後,無法在重新創建當前類的對象,就算是你要創建,代碼返回的對象依然是上一次創建的對象。
懶漢模式,餓漢模式
1.2 單例模式推導
package com.qfedu.a_single;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* SingleDog要求是一個單例類,整個代碼運行週期內有且只有一個類對象
*
* 不可取方式:
* @全體成員 SingleDog類使用一個單例類,我創建過對象了,你們不要搞事情
* 紅包
* 【期望】
* 從語法角度約束一些行爲。
* 目前創建對象過於簡單,new + 構成方法組合非常easy
* 【解決方案】
* private修飾構造方法
*
* 【問題】
* 構造方法私有化之後,類外沒有操作構造方法的權限,沒辦法創建對象
* 【解決方案】
* 1. 暴力反射
* 暴力反射,給予構造方法操作權限,這裏和沒有使用private有什麼區別
* 【不合適】
* 2. 期望
* 類外最起碼有一個對象。
* a. 類外沒有SingleDog類對象
* b. 期望獲取到一個SingleDog對象
*
* 需要方法來完成
* 1. 該方法要求靜態成員方法,沒有對象,需要通過類名調用
* 2. 類外要求使用public修飾
* 3. 該方法需要得到一個SingleDog對象,返回值類型是SingleDog
* 4. 方法參數爲無參數,構造方法也是無參
* 5. 方法名 getInstance
* public static SingleDog getInstance()
*
* 【問題】
* 依然是不同對象
*
* 【期望】
* 1. 調用該方法每一次調用都是new新的對象。
* 2. 這個new得有限制,如果之前創建過對象,你就不能再new了,沒有對象可以new
* 3. 存在一個變量可以保存之前創建對象的空間首地址,並且可以持久性保存。
* 分析:
* a. 需要保存SingleDog類對象空間首地址
* SingleDog類型
* b. 該數據不能類外輕鬆獲取
* private
* c.
* (1) 靜態方法可以使用
* (2) 有一定的持久性
* static修飾成員變量
* private static SingleDog sd = null;
* 【getInstance方法】
* 需要判斷
* 判斷SingleDog類型的靜態成員變量 sd是否保存有之前創建的空間首地址
*
* 【問題】
* 多線程情況下,存在線程搶佔問題,極有可能導致當前方法被兩個線程同時執行,同時
* 創建對象
* com.qfedu.a_single.SingleDog@22673956
* com.qfedu.a_single.SingleDog@6223c513
*
* 【期望】
* 在多線程情況下,也是安全的
* 【解決方案】
* 加鎖
* 【牆裂推薦】synchronized同步代碼塊
* 使用同步代碼塊,用什麼做鎖對象?
* 最合適的鎖對象依然是Single.class
*
* 【牆裂推薦】synchronized同步方法
* static修飾靜態方法的情況下,什麼是鎖對象?
* 當前SingleDog類.class字節碼文件
*
* 【不推薦】Lock鎖對象
* 和以上兩個方式對比?
* new ==> lock ==> 多出一個Lock鎖空間
* (1) 在getInstance方法new lock是不合適,多個線程情況下
* 可能會出現不同鎖情況
* (2) Lock做成靜態成員變量
* 浪費空間了!!!
*
*
* @author Anonymous 2020/3/13 10:53
*/
public class SingleDog {
private static SingleDog sd = null;
private SingleDog() {}
// 同步方法
public static synchronized SingleDog getInstance() {
if (null == sd) {
sd = new SingleDog();
}
return sd;
}
}
public class SingleDog1 {
private static SingleDog1 sd = null;
private SingleDog1() {}
// 同步代碼塊
public static SingleDog1 getInstance() {
synchronized (SingleDog1.class) {
if (null == sd) {
sd = new SingleDog1();
}
}
return sd;
}
}
package com.qfedu.a_single;
/**
* 另一個單例模式
*
* @author Anonymous 2020/3/13 11:44
*/
public class SingleCat {
/*
static修飾,在代碼的加載階段創建完成
並且使用final修飾,保存當前指向不可以改變
private修飾類外無法直接獲取,不能修改
*/
private static final SingleCat sc = new SingleCat();
private SingleCat() {}
/*
使用方法做的統一
*/
public static SingleCat getInstance() {
return sc;
}
}
2. NIO
2.1 BIO概述
BIO
BIO ==> Basic IO (基本IO), Block IO(阻塞IO)
Scanner操作,文件讀寫操作,Socket數據傳輸操作... 都是BIO
比如TPC羣聊,私聊聊天室
Socket涉及到的IO,也是BIO
資源浪費:
1. 多線程,每一個Socket會對應一個線程,如果用戶量巨大,會導致線程過
多,資源處理過多
2. 採用阻塞狀態,一旦進入阻塞,代碼無法執行其他操作。
3. 承載量一般,吞吐量比較小,同時可靠性不佳
2.2 NIO概述
NIO
NIO ==> New IO(新IO), Non-Block IO(非阻塞IO)
NIO非阻塞IO,運行當前程序在處理IO事務時,不會影響其他程序的運行,可以在不使用多線程的情況下,滿足IO操作要求。
三大核心部分:
通道
Channel
文件操作,網絡數據傳遞操作使用的通道
緩衝
Buffer
緩衝使用可以提供操作效率,減少不必要的讀寫次數
選擇器
Selector
真·核心 老大 boss
2.3 Buffer Channel完成文件操作
2.3.1 常用API
java.nio.Buffer
Buffer緩衝區
ByteBuffer 字節緩衝 常用
ShortBuffer
IntBuffer
LongBuffer
CharBuffer 字節緩衝 常用
FloatBuffer
DoubleBuffer
常用方法:
public static ByteBuffer allocate(int capacity);
按照指定的字節數分配對應的緩衝區空間,保存字節數據
public byte get();
從字節緩衝區對象中讀取一個byte類型數組
public final Buffer flip();
翻轉緩衝區,回到緩衝區的開始位置。
public static ByteBuffer wrap(byte[] arr);
存入一個byte類型數組到緩衝區,會得到一個新的ByteBuffer
public static ByteBuffer put(byte[] b);
將字節數組存入緩衝去
Channel接口,通道接口
FileChannel 文件操作通道
DatagramChannel UDP協議數據包操作的Channel
ServerSocketChannel TCP服務端ServerSocket對應Channel
SocketChannel TCP客戶端Socket對應Channel
首先操作文件,以FileChannel爲例
public long read(ByteBuffer buffer);
從通道中讀取數據到ByteBuffer中
public long write(ByteBuffer buffer);
從Buffer中寫數據到通道中
public long transferFrom(ReadableByteChannel src, long position, long count)
從指定srcChannel中,指定位置position開始,讀取count個元素,到當前通道中
文件複製操作。
public long transferTo(long position, long count, WritableByteChannel target)
將當前通道中的數據寫入到target中,從當前通道的position位置開始,計數count
2.3.2 操作文件數據
package com.qfedu.b_niofile;
import org.junit.Test;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author Anonymous 2020/3/13 15:17
*/
public class FileNioTest {
/*
通過NIO寫入數據到文件中的操作
*/
@Test
public void testNioFileWrite() throws IOException {
// 1. 文件操作字節輸出流
FileOutputStream fos = new FileOutputStream("D:/aaa/1.txt");
// 2. 利用文件操作輸出字節流對象獲取對應的Channel通道
FileChannel foc = fos.getChannel();
// 3. 準備一個緩衝區 4KB緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);
// 4. 準備數據,放入緩衝區
String str = "測試NIO";
buffer.put(str.getBytes());
// 5. 存在緩衝區數據放入之後,緩衝區指針發生改變,到達存入數據的末尾
// 如果此時調用寫入操作,會從存入緩衝區之後開始保存
// 讓緩衝區指針回到最初的起點,並且操作寫入程序,只會保存緩衝區內的有效數據
buffer.flip();
// 6. 緩衝區數據寫入到通道中
foc.write(buffer);
// 7. 關閉資源
fos.close();
}
@Test
public void testNioFileRead() throws IOException {
// 1. 文件字節操作輸入流
FileInputStream fis = new FileInputStream("D:/aaa/1.txt");
// 2. FileChannel
FileChannel fic = fis.getChannel();
// 3. 準備緩衝
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 4. 從Channel讀取數據保存到緩衝區中
int read = fic.read(buffer);
System.out.println(read);
// 5. 展示數據
// String(byte[] arr, int off, int count)
System.out.println(new String(buffer.array(), 0, read));
// 6. 關閉資源
fis.close();
}
// 130
@Test
public void testCopyFile() throws IOException {
long start = System.currentTimeMillis();
// 1. 安排輸出流和輸入流
FileInputStream fis = new FileInputStream("D:/aaa/1.mp4");
FileOutputStream fos = new FileOutputStream("D:/aaa/2.mp4");
// 2. 準備兩個Channel
FileChannel srcChannel = fis.getChannel();
FileChannel dstChannel = fos.getChannel();
// 3. 拷貝方法
srcChannel.transferTo(0, srcChannel.size(), dstChannel);
// dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
// 4. 關閉資源
fos.close();
fis.close();
long end = System.currentTimeMillis();
System.out.println("Time:" + (end - start));
}
// 300
@Test
public void testCopyUseBuffer() throws IOException {
long start = System.currentTimeMillis();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/aaa/1.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/aaa/3.mp4"));
int length = -1;
byte[] buf = new byte[4 * 1024];
while ((length = bis.read(buf)) != -1) {
bos.write(buf, 0, length);
}
bos.close();
bis.close();
long end = System.currentTimeMillis();
System.out.println("Time:" + (end - start));
}
}
2.4 網絡編程使用NIO【重點】
2.4.1 Selector選擇器老大
Selector
選擇器,網絡編程使用NIO的大哥!!!
服務器可以執行一個線程,運行Selector程序,進行監聽操作。
新連接, 已經連接, 讀取數據,寫入數據
Selector常用方法:
public static Selector Open();
得到一個選擇器對象
public int select(long timeout);
監聽所有註冊通道,存在IO流操作是,會將對應的信息SelectionKey存入到內部的集
閤中,參數是一個超時時間
public Set<SelectionKey> selectionKeys();
返回當前Selector內部集合中保存的所有SelectionKey
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sqtsK1TL-1584098459593)(day30.assets/Selector大哥.png)]
2.4.2 SelectionKey
SelectionKey
表示Selector和網絡通道直接的關係
int OP_ACCEPT; 16 需要連接
int OP_CONNECT; 8 已經連接
int OP_READ; 1 讀取操作
int OP_WRITE; 4 寫入操作
SelectionKey
public abstract Selector selector();
得到與之關聯的 Selector 對象
public abstract SelectableChannel channel();
得到與之關聯的通道
public final Object attachment();
得到與之關聯的共享數據
public abstract SelectionKey interestOps(int ops);
設置或改變監聽事件
public final boolean isAcceptable();
是否可以 accept
public final boolean isReadable();
是否可以讀
public final boolean isWritable();
是否可以寫
2.4.3 ServerSocketChannel
ServerSocketChannel
服務端Socket程序對應的Channel通道
常用方法:
public static ServerSocketChannel open();
開啓服務器ServerSocketChannel通道,等於開始服務器程序
public final ServerSocketChannel bind(SocketAddress local);
設置服務器端端口號
public final SelectableChannel configureBlocking(boolean block);
設置阻塞或非阻塞模式, 取值 false 表示採用非阻塞模式
public SocketChannel accept();
[非阻塞]
獲取一個客戶端連接,並且得到對應的操作通道
public final SelectionKey register(Selector sel, int ops);
[重點方法]
註冊當前選擇器,並且選擇監聽什麼事件
2.4.4 SocketChannel
SocketChannel
客戶端Socket對應的Channel對象
常用方法:
public static SocketChannel open();
打卡一個Socket客戶端Channel對象
public final SelectableChannel configureBlocking(boolean block)
這裏可以設置是阻塞狀態,還是非阻塞狀態
false,表示非阻塞
public boolean connect(SocketAddress remote);
連接服務器
public boolean finishConnect();
如果connect連接失敗,可以通過finishConnect繼續連接
public int write(ByteBuffer buf);
寫入數據到緩衝流中
public int read(ByteBuffer buf); 、
從緩衝流中讀取數據
public final SelectionKey register(Selector sel, int ops, Object attechment);
註冊當前SocketChannel,選擇對應的監聽操作,並且可以帶有Object attachment參數
public final void close();
關閉SocketChannel