第六週總結
TCP多人聊天室實現
分析
- 客戶端
功能:
1. 數據發送
2. 數據接收
技術:
1. socket
2. 輸入流和輸出流
3. 多線程,客戶端功能模塊有兩個線程
聊天:
1. 羣聊
2. 私聊
私聊前綴 @服務器用戶ID號:msg - 服務器
功能:
1. 數據轉發
2. 用戶註冊
技術:
1. ServerSocket
2. 每一個用戶對應的Sokcet對象
3. 多線程同時在線
4. HashMap<Integer, 用戶>
數據轉發:
私聊前綴判斷
羣聊所有人發送
客戶端實現
數據發送:
使用輸出流發送數據給服務器
遵從Runnable接口
數據接收:
使用輸入流從服務器端接收數據
遵從Runnable接口
客戶端主方法:
用戶名提交
數據發送
數據接收
多線程啓動
資源關閉問題
-代碼中操作了大量的輸入流和輸出流,這裏都需要進行關閉操作。
DataInputStream, DataOutputStream, BufferedReader, Socket
以上這些資源都是Closeable接口的實現類,都有對應的Close方法
封裝一個工具類:
提供一個closeAll方法,參數爲符合Closeable接口的實現類對象。
這裏可以考慮可變長參數
Closeable… closeable
可變長參數在方法中使用的過程裏面是對應一個數組,這裏完成可以使用增強for來使用
工具類名:
CloseUtil
public static void closeAll(Closeable… closeable)
功能拓展
-
用戶退出
用戶輸入指定字段之後可以退出
客戶端Socket服務
服務端Socket服務
涉及資源關閉,線程關閉 -
用戶異常退出
在運行過程中發現問題,需要及時處理,關閉對應的資源,終止對應的線程 -
服務器保存所有的聊天記錄
JSON
JSON格式概述
- JSON
JavaScript
JavaScript Object Notation
(JavaScript Object Notation,JavaScript對象表示法,讀作/ˈdʒeɪsən/)是一種由道格拉斯·克羅克福特構想和設計、輕量級的數據交換語言,該語言以易於讓人閱讀的文字爲基礎,用來傳輸由屬性值或者序列性的值組成的數據對象。儘管JSON是JavaScript的一個子集,但JSON是獨立於語言的文本格式,並且採用了類似於C語言家族的一些習慣
數據格式
JSON對象
{
“ID”:001,
“name”:“騷磊”,
“age”:16
}
特徵:
1. 數據形式鍵值對形式
“鍵”:值
2. 數據支持 字符串,數字,true false
3. {} 大括號以內的數據
-
JSON對象數組
- [
{
“ID”:1,
“name”:“騷磊”,
“age”:16
},
{
“ID”:2,
“name”:“騷傑”,
“age”:66
},
{
“ID”:3,
“name”:“康康”,
“age”:15
}
]
- [
特徵:
1. 數據使用[]包含
2. 在[]都是JSON格式對象
3. 每一個對象之間使用逗號隔開,同時最後一個元素不需要逗號
-
JSON數據驗證
- JSON格式驗證
解析JSON格式工具
-
常用的工具:
Gson,fastjson, Jackson
以上都是第三方工具,需要導入對應的jar包按使用XML導包 -
FastJson內容
- JSON核心類
JSON核心類提供解析和轉化方法,用於解析JSON數據格式,同時用於轉換類對象到JSON格式,該類對象需要符合JavaBean規範
–| JSONArray
存在按照鍵值對方式解析獲取數據,同時存在一定的List方法
–| JSONObject
獲取對應的類對象,指定鍵值對對應數據的方法
- JSON核心類
-
解析演示
註解
註解概述
-
註解解釋
- 註釋:
解釋代碼,給程序員看
- 註釋:
註解:
Java語言中的類、方法、變量、參數和包等都可以被標註。和Javadoc不同,Java標註可以通過反射獲取標註內容。在編譯器生成類文件時,標註可以被嵌入到字節碼中。Java虛擬機可以保留標註內容,在運行時可以獲取到標註內容。 當然它也支持自定義Java標註
JDK1.5之後的特徵
用於說明程序
一般在框架中使用
格式:
@AnnotationName
文檔註釋:
@param @return @Exeception 從根本上是一個註釋,不存在代碼編譯,不會生成對應的.class字節碼問題,只是提供給JavaDoc API文件生成工具。作爲標記生成對應的文檔。
註解是有一部分參與編譯
@Override並不是沒編譯就有效果了,是因爲不管是Eclipse還是IDEA都可以預編譯Java代碼生成對應的.class文件的
-
註解作用
- 生成文檔:
代碼中生成對應的JavaDoc API文檔
@param @return
【IDEA JavaDoc工具使用參數】
Other Command Line Arguments : -encoding utf-8 -charset utf-8
解決中文亂碼,因爲IDEA默認編碼集爲UTF-8 Windows GKB - 生成文檔:
代碼檢查:
繼承重寫,或者說接口遵從之後的實現中,存在@Override
代碼數據獲取: [小框架]
通過反射獲取指定註解中的一些內容,例如 配置,數據,操作,驗證。。。
-
Java中預定義的一些註解
- @Override:
重寫/實現方法的情況下,檢查方法聲明是否和父類或者接口中的方法聲明一致。強制格式檢查。
- @Override:
@Deprecated
標註當前方法已過時,例如 Data日期類內的一些方法
@SuppressWarnings(“all”)
壓制警告,可以用於一些代碼中存在明確無異常的情況下,壓制一些警告
Java中自定義註解
-
Java中自定義註解的方式
- 格式:
public @interface AnnotationName {
屬性列表;
}
- 格式:
Annotation註解是可以編譯得到對應的.class字節碼文件,驗證了註解是可以參與編譯過程的
通過反編譯工具可以得到一下內容
【Annotation本質】
public interface MyAnnotation1 extends java.lang.annotation.Annotation {
}
MyAnnotation1
本質是一個interface,同時java.lang.annotation.Annotation 子接口
-
Annotation註解屬性【難點】
- 屬性:
開發書寫代碼使用註解的方式中,數據使用方式更加偏向於屬性概念。
使用
1. 在書寫代碼中使用
@MyAnnotation(id=1, name=“騷磊”, age=16)
2. 利用反射時,會涉及到getXXX方法
通過屬性名獲取對應值的概念來完成的
【但是實際上是利用abstract方法來完成屬性概念的】
- 屬性:
屬性使用的格式[實際按照方法格式操作]
1. 屬性的值數據類型和對應具體數據 ==> 返回值類型和返回的數據
屬性類型支持:
a. 基本數據類型
b. String類型
c. 其他的註解類型
d. 枚舉類型
枚舉就是一個帶有名字的常量,爲了更好的域閱讀性和操作
e. 以上類型對相應的數組
屬性值要求
a. 定義屬性時可以使用default關鍵字,加上默認值,該屬性在使用的過程中是
沒有強制要求屬性值,如果沒有賦予屬性值,採用對應的默認值操作,如果賦
值,使用對應值
b. 如果註解中有且只有一個value屬性,或者說註解中除value屬性之外,都有
默認值,不管是類,方法,成員變量,包使用當前註解是可以直接在括號內加入
對應數據類型數值、
c. 如果屬性是數組類型, {}大括號保存,並且不同的內容,使用,隔開
2. 屬性的鍵名字 ==> 方法的名字
-
元註解
- 給予註解的解釋,用於約束註解的一些操作問題
@Retention -
標識這個註解怎麼保存,是隻在代碼中,還是編入class文件中,或者是在運行時可以通過反射訪問。
RetentionPolicy.RUNTIME:當前註解會編譯生成對應的.class字節碼文件,並且可以加
載到JVM中,參與代碼執行
RetentionPolicy.CLASS:
- 給予註解的解釋,用於約束註解的一些操作問題
別糾結,記下就好:
RetentionPolicy.SOURCE:註解將被編譯器丟棄(該類型的註解信息只會保留在源碼裏,源碼經過編譯後,註解信息會被丟棄,不會保留在編譯好的class文件裏)
@Override
對應屬性RetentionPolicy.SOURCE
在代碼編譯過程中,檢查方法格式是否正確,不參與代碼運行和解析。
@Documented
標記這些註解是否包含在用戶文檔中。
是否可以通過JavaDoc工具,生成對應的API文檔
@Target
標記這個註解應該是哪種 Java 成員。
屬性:
ElementType
TYPE: 當前註解可以用於類聲明
METHOD: 當前註解可以用於方法聲明位置
FIELD:當前註解可以用於成員變量聲明位置
@Inherited
標記這個註解是繼承於哪個註解類(默認 註解並沒有繼承於任何子類)
【重點】
@Target目標
可以作用範圍 類,方法,成員變量…
@Retention
RetentionPolicy.RUNTIME 常用
- 使用反射獲取註解中的內容【用途】
- 使用註解測試代碼運行【用途】
註解使用總結
-
- 註解以後大多數情況下,都是使用過程,而不是自定義,會使用到框架中預處理好的註解。
- 註解是給誰用的?
a. 編譯器
b. 解析代碼使用
c. JVM運行代碼使用 - 註解是一個標籤,有時候是做標記的,有時候標籤是有屬性的。
函數式接口
函數式接口
-
概述
- 如果說一個接口內有且只有一個方法,而且該方法是一個缺省屬性爲public abstract方法,該接口可以稱之爲是一個函數式接口。
自定義函數式接口,還有系統中提供的函數式接口
Comparator Runnable
可以直接理解JDK1.8的新特徵,Lambda表達式來使用。
Lambda表達式對比匿名內部類使用
1. 簡化了代碼結構
2. 節約了內存資源
3. 讓程序員更加關注,我要做什麼,而不是爲了做什麼需要完成什麼 - 如果說一個接口內有且只有一個方法,而且該方法是一個缺省屬性爲public abstract方法,該接口可以稱之爲是一個函數式接口。
-
@FunctionalInterface 使用
- /**
- 使用@FunctionalInterface檢查函數式接口格式問題
- 要求當前接口中有且只有一個缺省屬性爲public abstract的方法
- @author Anonymous 2020/3/11 9:55
*/
@FunctionalInterface
public interface FunctionalType {
void test();
}
- 使用自定義的函數式接口作爲方法的參數使用
函數式編程思想
-
Lambda延遲執行
-
日誌記錄
- 日誌是否保存會存在等級限制
演示一個根據不同的等級來記錄log日誌
要求:
等級 == 1 記錄log日誌,其他情況不記錄
- 日誌是否保存會存在等級限制
-
使用函數式接口提供日誌信息功能
-
-
Lambda作爲方法參數和返回值
Java中提供的常用函數式接口
-
JDK常用函數式接口概述
- java.util.function包名 。提供了很多函數式接口
規範了一些操作,提升了開發效率,更加專注於目的性!!!
Supplier 生產者, 返回一個指定類型的數據
Consumer 消費者, 消耗一個指定類型的數據
Predicate 判斷調節,過濾使用
Function<T,R> 類型轉換,根據你指定的類型T, 轉換成對應類型R - java.util.function包名 。提供了很多函數式接口
-
Supplier 生產者,返回一個指定的數據類型
- java.util.function.Supplier
有且只有一個方法
T get();
不需要參數,返回指定T類型數據
什麼都不吃,擠的都是輸出。。。 - 找出數組中最大值所在下標位置
- 引出滿足更多普適性代碼的函數式接口使用方式
- java.util.function.Supplier
-
Consumer消費者,處理數據
- Consumer
操作使用的方式是
void accept(T t);
根據接口指定的數據類型接收對應數據,進行處理和消費,對外沒有任何的返回
至於處理的過程,展示,處理,計算。。。 - andThen
- Consumer
-
Predicate 判斷數據是否合適,返回true/false
- Predicate一般用於條件判斷,過濾數據的方法
函數式接口中指定的方法
boolean test(T t);
處理T類型數據,返回boolean true / false - and 與
- or 或
- negate 非
- ArrayList中使用Predicate刪除指定數據
- Predicate一般用於條件判斷,過濾數據的方法
-
Function<T,R> 類型轉換
- 使用R apply(T t)
轉換指定類型T到R - andThen
- 使用R apply(T t)
Stream流
Stream流引入
- Stream流完全不是I/O流,按照流水線處理方式來考慮代碼中的思想。
JDK1.8 之後,我們擁有了Lambda表達式,讓代碼的中心偏向解決實際問題,直到重點,可以提高效率。
Stream流中使用了大量Lambda表達式,利用Lambda操作方式,提供開發效率
傳統遍歷方式和Stream類處理方式對比
Stream流對應的思想
- Stream流有一些特徵:
- 帶有很多Stream流操作的方法, filter,limit,map,sorted,skip…這些方法大多是都會使用到函數式接口,那就意味着有lambda表達式
- 整個Stream流模型操作過程中,只有執行到count,foreach這些方法,操作真正的執行中的模型,如果不存在結果導向,中間的所有操作是無效的,這裏得益於Lambda表達式的延後性
- Stream流是存在一定的管道性 Pipelining 流水線
獲取Stream流
- java.util.stream.Stream JDK1.8的新特徵
- 所有的Collection集合都有對應的Stream();
- 可以通過Stream類中的static Stream of()獲取
static Stream of(T… t);
static Stream of(T t);
Stream常用方法
-
延遲方法:
返回值類型依然是Stream接口本身,並沒有影響我們操作真正的資源
允許鏈式操作,
例如
filter(XXX).limit(XXX).sorted(XXX).
終結方法:
返回值類型不是Stream接口本身,要麼處理數據,要麼返回其他類型數據,並且不再支持Stream流對象鏈式操作,count,foreach-
foreach方法【終結方法】
- void foreach(Consumer<? super T> action);
/*
終結方法:
需要一個Consumer接口進行操作處理,消耗一個數據
Consumer接口是一個【函數式接口】那就可以使用Lambda表達式
Consumer接口中方法是
void accept(T t);
*/
- void foreach(Consumer<? super T> action);
-
filter方法
-
Stream filter(Predicate<? super T> condition);
/*
filter是過濾方式,需要的參數是Predicate接口,Predicate是一個函數式接口,可以直接使用Lambda表達運行。
這裏返回值類型是Stream類對象,是經過過濾之後的Stream類型,可以進行鏈式操作
Predicate接口中需要實現的方法
boolean test(T t);
*/- stream has already been operated upon or closed
-
-
爲何會出現這個錯誤?
因爲調用終結方法後,Stream流已經被銷燬,所以不能再對Stream流進行操作。
- map方法
- <R> Stream<R> map(Function<? super T, ? super R> fun);
/*
類型轉換操作,得到的一個轉換之後數據類型的Stream流對象
這裏需要的參數是Function函數式接口,
R apply(T t);
T類型的數據轉換成R類型數據
*/
- count方法【終結方法】
- long count();
/*
返回當前Stream流對象中有多少個元素
類似有Collection接口下的size(). String的length();
【終結方法】
一旦執行Stream流對象被關閉
*/
- limit方法
- Stream<T> limit(long maxSize);
/*
對於當前Stream流對象操作的數據進行限制操作,限制個數到maxSize
例如:
Stream流中保存的有10個元素,limit 5 ==> 前五個元素
*/
- skip方法
- Stream<T> skip(long n);
/*
返回值依然是一個Stream流對象,這裏跳過當前Stream流對象前n個元素
*/
- concat方法
- static Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
/*
拼接兩個Stream流對象,是一個靜態方法,得到新的Stream流對象
*/
- 原始操作方式和Stream流方式對比
- 1. 一個String類型的字符串集合,"1,騷磊,16"
- 過濾沒有5的數據
- 跳過前三個數據
- 限制得到前5個數據
- 兩個String類型集合字符串合併
- 轉換成Person類型
- 展示數據
方法引用
Lambda冗餘問題以及方法引用初識
方法引用小要求
- testPrint(“鄭州加油!!!”, str -> System.out.println(str));
testPrint(“鄭州加油!!!”, System.out::println);
- 明確對象
對象 ==> 調用者
類對象,類名,super,this,構造方法,數組構造方法 - 明確的執行方法
該方法只有名字不需要顯式出現參數 - 需要處理的數據
【聯想,推導,省略】 - :: 方法引用格式
通過類對象來執行方法引用
-
- 明確對象
類對象
- 明確對象
- 明確執行的方法
自定義 - 處理的數據
簡單要求爲String類型
通過類名來執行方法引用
通過super關鍵字執行方法引用
通過this關鍵字執行方法引用
類構造方法引用
數組創建方式引用
單例模式
要求
- 當前類有且只有一個對象,一旦當前類存在一個對象之後,無法在重新創建當前類的對象。就算是你要創建,代碼返回的對象依然是上一次創建的對象。
懶漢模式,餓漢模式
單例模式推導【懶漢】
另一種單例模式【餓漢】
NIO
BIO概述
-
BIO
BIO ==> Basic IO (基本IO), Block IO(阻塞IO)
Scanner操作,文件讀寫操作,Socket數據傳輸操作… 都是BIO比如TCP羣聊,私聊聊天室
Socket涉及到的IO,也是BIO
資源浪費:
1. 多線程,每一個Socket會對應一個線程,如果用戶量巨大,會導致線程過
多,資源處理過多
2. 採用阻塞狀態,一旦進入阻塞,代碼無法執行其他操作。
3. 承載量一般,吞吐量比較小,同時可靠性不佳
NIO概述
- NIO
NIO ==> New IO(新IO), Non-Block IO(非阻塞IO)
NIO非阻塞IO,允許當前程序在處理IO事務時,不會影響其他程序的運行,可以在不使用多線程的情況下,滿足IO操作要求。
三大核心部分:
通道
Channel
文件操作,網絡數據傳遞操作使用的通道
緩衝
Buffer
緩衝使用可以提高操作效率,減少不必要的讀寫次數
選擇器
Selector
真·核心 老大 boss
Buffer Channel完成文件操作
-
常用API
- java.nio.Buffer
Buffer緩衝區
ByteBuffer 字節緩衝 常用
ShortBuffer
IntBuffer
LongBuffer
CharBuffer 字符緩衝 常用
FloatBuffer
DoubleBuffer
- java.nio.Buffer
常用方法:
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
- 操作文件數據
網絡編程使用NIO【重點】
-
Selector選擇器老大
- Selector
選擇器,網絡編程使用NIO的大哥!!!
服務器可以執行一個線程,運行Selector程序,進行監聽操作。
新連接, 已經連接, 讀取數據,寫入數據
- Selector
Selector常用方法:
public static Selector Open();
得到一個選擇器對象
public int select(long timeout);
監聽所有註冊通道,存在IO流操作是,會將對應的信息SelectionKey存入到內部的集
閤中,參數是一個超時時間
public Set selectionKeys();
返回當前Selector內部集合中保存的所有SelectionKey
-
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();
是否可以寫
- SelectionKey
-
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);
[重點方法]
註冊當前選擇器,並且選擇監聽什麼事件
- ServerSocketChannel
-
SocketChannel
- SocketChannel
客戶端Socket對應的Channel對象
- SocketChannel
常用方法:
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
XMind: ZEN - Trial Version