io是什么
io是輸入輸出流,他的作用就是對(duì)外部進(jìn)行數(shù)據(jù)交互使用的,內(nèi)部和外部分別表示的是內(nèi)存以及內(nèi)存以外的,外部包括手機(jī)文件,電腦文件和網(wǎng)絡(luò), 服務(wù)器等都稱為外部 ,外部統(tǒng)稱為文件和網(wǎng)絡(luò)
什么叫輸入輸出
輸入就是從文件或者網(wǎng)絡(luò)上的數(shù)據(jù)讀取到內(nèi)存里面,輸出就是把內(nèi)存的數(shù)據(jù)寫入到文件或者發(fā)送到網(wǎng)絡(luò)上。
java io的api作用就是為了和外部進(jìn)行讀寫操作
先上一個(gè)簡(jiǎn)單例子
public class Main {
public static void main(String[] args[]){
try {
OutputStream outputStream = new FileOutputStream("./app/text.txt");
outputStream.write('a');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件輸出流的方式在文件中寫入a,我們運(yùn)行這段代碼,就能夠在app文件目錄下找到text文件,打開文件能夠看到文件內(nèi)容 "a"

new FileOutputStream 通過文件存放的路徑,就能夠在文件路徑下新建txt文件,通過outputStream.write "a"這個(gè)字符就寫到文件中
再上一個(gè)輸入流的例子
private static void io2(){
try {
InputStream inputStream = new FileInputStream("./app/text.txt");
System.out.println((char) inputStream.read());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
這樣一個(gè)簡(jiǎn)單的輸入流就算寫好了,簡(jiǎn)單講解下,剛才我們定義了輸出流也就是新建一個(gè)text的文件并在文件上寫入一個(gè)a字符,通過輸入流把文件存放到內(nèi)存中,這里我們通過打印的方式把文件的內(nèi)容打印到控制臺(tái) 截圖如下:

這樣一個(gè)簡(jiǎn)單的文件輸入輸出流就算完成了
但我們?cè)趯?shí)際的開發(fā)中面對(duì)的問題要比這個(gè)復(fù)雜的多,并且在實(shí)際開發(fā)中輸入輸出流讀寫完數(shù)據(jù)后需要關(guān)閉掉輸入輸出流。
java7有一個(gè)新特性可以不用關(guān)閉流,也能實(shí)現(xiàn)關(guān)閉流的效果也就是在try塊中寫相關(guān)代碼,代碼如下:
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private static void io1(){
try (OutputStream outputStream = new FileOutputStream("./app/text.txt")){
outputStream.write('a');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
字符 Reader 和Writer
這兩者可以配合輸入輸出流一起使用,Reader和Writer又是什么呢,他們可以自動(dòng)的把InputStream和OutputStream中的字節(jié)數(shù)據(jù)轉(zhuǎn)換成字符數(shù)據(jù)
一般情況下InputStream和Reader,OutputStream和Writer結(jié)合使用
我們還可以在上面的基礎(chǔ)上再套一層BufferReader或者BufferWriter,這又是什么呢?這個(gè)就是相當(dāng)于給你的字符加一個(gè)buffer(緩沖),這樣能夠讓我們讀取或者寫入的內(nèi)容更加的穩(wěn)定高效,主要的功能就是提高讀寫性能。
try(InputStream inputStream = new FileInputStream("./app/text.txt");
Reader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader)){
System.out.println(bufferedReader.readLine());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
這里我在text文件中外加了一些字符組合成子長(zhǎng)串的字符再次運(yùn)行,這樣就可以不用使用單個(gè)的字符讀取就能很方便的讀取數(shù)據(jù)了

outputStream.flush()
這又是什么呢,flush使用的主要場(chǎng)景就是在輸出流中用的比較常見,作用就是把沒有到文件管道的數(shù)據(jù)flush到文件中,也就不會(huì)因?yàn)楣艿乐幸恢贝嬖谝粋€(gè)比較難處理的文件數(shù)據(jù)而一直運(yùn)行,這樣就避免程序出問題的情況,從而提高性能。
文件復(fù)制原理
文件復(fù)制就集合了輸入輸出流相關(guān)的知識(shí),就是通過輸入流的數(shù)據(jù)通過輸出流把數(shù)據(jù)copy到另外一個(gè)文件的過程,這里要注意的就是這里是使用的字節(jié)的方式輸入輸出的,如果文件比較大的話可以給他外加一個(gè)buffer,還有一點(diǎn)就是判斷readCount不為-1,這是什么呢,我這里是使用了一個(gè)循環(huán),其實(shí)任何的讀取或者寫入文件都可以使用這種方式來操作,這也是比較通用的。如果不等于-1也就是管道的數(shù)據(jù)還有那么就繼續(xù)讀寫數(shù)據(jù)直到為-1管道數(shù)據(jù)沒有,也就是讀寫操作完成。
private static void io4(){
try(InputStream inputStream = new FileInputStream("./app/text.txt");
OutputStream outputStream = new FileOutputStream("./app/text_copy.txt")){
byte[] bytes = new byte[1024];
int readCount;
while ((readCount = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,readCount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
執(zhí)行結(jié)果:

nio的簡(jiǎn)單使用
nio使用的是RandomAccessFile來創(chuàng)建文件對(duì)象,這里需要注意的是他有兩個(gè)關(guān)鍵參數(shù),這個(gè)也比io流要簡(jiǎn)潔一些。根據(jù)上面的講解,我們知道io流有輸入和輸出流。而nio的輸入輸出流通過參數(shù)就可以配置。第一個(gè)參數(shù)是文件路徑的配置,第二個(gè)參數(shù)就是配置你的這個(gè)文件是輸入的還是輸出的"r"讀,"w"寫這個(gè)其實(shí)也很好理解。
ByteBuffer的原理
ByteBuffer的讀寫操作,中學(xué)我們了解了游標(biāo)卡尺,ByteBuffer的原理就有點(diǎn)類似于游標(biāo)卡尺,
position和capacitylimit,他們分別表示的是讀或?qū)懙奈恢靡约拔覀冏约涸O(shè)置的容器大小(這個(gè)參數(shù)是在new 構(gòu)造器的一個(gè)參數(shù)),通過游標(biāo)position讀或?qū)懸粋€(gè)字符,游標(biāo)position向右移動(dòng)一個(gè)字符長(zhǎng)度的位置,讀或?qū)懙乃悸啡缦拢?/p>

注意:為了更好的理解NIO中的Bytebuffer,可以把ByteBuffer看著是內(nèi)存,如果相對(duì)于ByteBuffer自身而言是讀的操作,反之是寫的操作。
NIO在網(wǎng)絡(luò)中實(shí)現(xiàn)通信
private static void nio7(){
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (socketChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
注意:大部分的網(wǎng)絡(luò)io操作都是阻塞的方式進(jìn)行網(wǎng)絡(luò)通信的,阻塞處serverSocketChannel.accept()
什么意思,也就是如果沒有網(wǎng)絡(luò)通信這段代碼是阻塞的,當(dāng)進(jìn)行網(wǎng)絡(luò)通信這段代碼就開始正常工作,實(shí)現(xiàn)正常網(wǎng)絡(luò)通信操作。
有些讀者會(huì)問了,為什么要用阻塞的不用阻塞可以嗎?NIO中也有這樣的api,可以通過socketChannel.configureBlocking(false);
但是我不建議大家使用這個(gè)api,為什么。如果沒有阻塞,那么在沒有通信的情況下也能正常執(zhí)行代碼,就會(huì)出現(xiàn)我們拿不到socketChannel,導(dǎo)致阻塞后面的代碼因?yàn)閟ocketChannel為null而使我們的程序crash ,異常也就是空指針異常,大家可以自行測(cè)試下
Okio
由于okio配置不是androidstudio自帶的所以我們這里需要添加依賴
okio依賴鏈接
okio也是通過輸入輸出流的方式對(duì)文件進(jìn)行io操作,只不過okio的輸入輸出名稱不一樣,我們可以對(duì)比理解下,通過上面的講解,io的輸入使用的InputStream 可以對(duì)比okio的source ,io的輸出使用的是OutputStream可以對(duì)比與okio的sink,這樣我們就可以更快速的了解到okio了,okio也是支持buffer功能的。
還是上面的例子,在控制臺(tái)通過okio讀取text的內(nèi)容
一種方式是通過實(shí)例 buffer也就是通過new Buffer() ,然后通過source.read(buffer) 這樣我們就可以把數(shù)據(jù)緩沖到buffer中了
當(dāng)然okio提供了帶有buffer的BufferedSource,完成對(duì)文件的讀操作
private static void okio9(){
try(BufferedSource source = Okio.buffer(Okio.source(new File("./app/text.txt")))){
System.out.println(source.readUtf8());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
針對(duì)buffer,值得注意的是在buffer中寫入數(shù)據(jù)使用的是輸出而不是使用的輸入buffer.outputStream()
傳統(tǒng)io和okio進(jìn)行交互
private static void okio10(){
Buffer buffer = new Buffer();
try(BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(buffer.outputStream()));) {
bufferedWriter.write("hahahhaha");
bufferedWriter.flush();
System.out.println("read:"+buffer.readUtf8());
} catch (IOException e) {
e.printStackTrace();
}
}
通過一個(gè)簡(jiǎn)單的例子來了解下io和okio的交互,如上,在我們實(shí)際的開發(fā)中也會(huì)遇到這樣的情況,就是兩者結(jié)合起來用有可能會(huì)更好些
上面我們也講到從buffer緩沖中寫入數(shù)據(jù)使用的是outputstream輸出的方式,從這個(gè)例子也能夠感受到 然后通過buffer.readUtf8()把數(shù)據(jù)讀取出來,這里也可以把buffer理解為外部對(duì)象
這里我把結(jié)果打印出來,滿足下我小小小的虛榮心,這也能讓我們感受到對(duì)原理的理解通過實(shí)踐來驗(yàn)證這些理論知識(shí)其實(shí)也能讓我們更好的理解這些理論知識(shí)。

講到這里我們就把io和okio相關(guān)的知識(shí)點(diǎn)講完了,如果有疑問的地方或者比我理解的更深入或好的可以在評(píng)論區(qū)留言。如有寫錯(cuò)誤地地方歡迎指正。
代碼傳送門