概述
流是一組有順序的制圈,有起點(diǎn)和終點(diǎn)的字節(jié)集合,是對數(shù)據(jù)傳輸?shù)目偡Q或抽象。即數(shù)據(jù)在兩設(shè)備間的傳輸稱為流,流的本質(zhì)是數(shù)據(jù)傳輸仲翎,根據(jù)數(shù)據(jù)傳輸特性將流抽象為各種類浮入,方便更直觀的進(jìn)行數(shù)據(jù)操作牡拇。IO其實(shí)有兩類帚稠,一類是BIO(BlockingIO),一類是NIO(Non-BlockingIO)卿叽,不過我們通常說的是IO默認(rèn)指的是BIO;
正文
基礎(chǔ)知識
字符
字節(jié)是計(jì)算機(jī)中存儲數(shù)據(jù)的單元桥胞,一個(gè)8位的二進(jìn)制數(shù),是一個(gè)很具體的存儲空間考婴。
字符編碼
字符是指人們使用的記號埠戳,抽象意義上的一個(gè)符號,比如1蕉扮、2整胃、3、·#¥%喳钟。
字節(jié)
字符編碼(Character encoding)是一套法則屁使,使用該法則能夠?qū)ψ匀徽Z言的字符的一個(gè)集合(如字母表或音節(jié)表),與其他東西的一個(gè)集合進(jìn)行配對奔则。各個(gè)國家和地區(qū)所制定的不同 ANSI 編碼標(biāo)準(zhǔn)中蛮寂,都只規(guī)定了各自語言所需的字符。
常見的編碼方式
ASCII編碼:美國制定了一套字符編碼易茬,對英語字符與二進(jìn)制位之間的關(guān)系酬蹋,做了統(tǒng)一規(guī)定。這被稱為 ASCII 碼抽莱,一直沿用至今范抓。
非ASCII 編碼:英語用128個(gè)符號編碼就夠了,但是用來表示其他語言食铐,128個(gè)符號是不夠的匕垫,所以在別的國家編碼符號會比128要多,所以問題就出現(xiàn)了虐呻,不同的國家有不同的字母象泵,因此寞秃,哪怕它們都使用256個(gè)符號的編碼方式,代表的字母卻不一樣偶惠。
UTF-8編碼:UTF-8最大的一個(gè)特點(diǎn)春寿,就是它是一種變長的編碼方式。它可以使用1~4個(gè)字節(jié)表示一個(gè)符號忽孽,根據(jù)不同的符號而變化字節(jié)長度绑改。。UTF-8 就是在互聯(lián)網(wǎng)上使用最廣的一種 Unicode 的實(shí)現(xiàn)方式扒腕。其他實(shí)現(xiàn)方式還包括 UTF-16(字符用兩個(gè)字節(jié)或四個(gè)字節(jié)表示)和 UTF-32(字符用四個(gè)字節(jié)表示),不過在互聯(lián)網(wǎng)上基本不用萤悴。重復(fù)一遍瘾腰,這里的關(guān)系是,UTF-8 是 Unicode 的實(shí)現(xiàn)方式之一覆履。
聯(lián)系與區(qū)別
很多時(shí)候我們經(jīng)常提及到字符跟字節(jié)之間的關(guān)系蹋盆,這個(gè)問題的前提是基于某一種編程語言比如說Java或者C來說的,因?yàn)樽址止?jié)之間的關(guān)系跟字符編碼是有著緊密聯(lián)系的硝全,所以單獨(dú)討論字符跟字節(jié)之間的關(guān)系沒有意義栖雾,下面簡單來看一下他們在不同編碼上的的對應(yīng)關(guān)系:
語言 | 中文字符 | 英文字符 |
---|---|---|
GBK | 2個(gè)字節(jié) | 1個(gè)字節(jié) |
UTF-8 | 2個(gè)字節(jié) | 2個(gè)字節(jié) |
java語言默認(rèn)是采用Utf-8來進(jìn)行編碼的,下面用Java來測試一下:
測試GBK編碼
public static void main(String[] args) {
String str = "Hello_安卓";
int byte_len = 0;
try {
byte_len = str.getBytes("gbk").length;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("字節(jié)長度:" + byte_len);
}
字節(jié)長度:10
測試UTF-8編碼
public static void main(String[] args) {
String str = "Hello_安卓";
int byte_len =str.getBytes().length;
System.out.println("字節(jié)長度:" + byte_len);
}
字節(jié)長度:12
輸出的結(jié)果伟众,跟之前的規(guī)則是一致的析藕,到這里,字節(jié)凳厢,編碼方式账胧,字符,以及他們之間的聯(lián)系基本上介紹完了先紫,理解了他們之間的關(guān)系治泥,下面的File類跟IO之間的關(guān)系也就比較好理解了。
File類
File類翻譯過來是一個(gè)文件遮精,實(shí)際上它并不是一個(gè)文件居夹,定義為Path更為合適,這個(gè)Path可以是文件的路徑也可以是文件夾的路徑本冲,因?yàn)楫?dāng)我們new File的時(shí)候准脂,只是創(chuàng)建了一個(gè)路徑,這個(gè)路徑如果創(chuàng)建成功檬洞,沒有后綴名就是文件夾意狠,有后綴名則創(chuàng)建了一個(gè)空文件。下面看一下File類的繼承關(guān)系:
構(gòu)造函數(shù)
列舉幾個(gè)常見的構(gòu)造函數(shù)
File(String pathname)
File(String parent,String child)
File(File parent,String child)
因?yàn)镴ava命名比較規(guī)范疮胖,所以很好理解环戈,有一點(diǎn)需要注意的是闷板,這個(gè)方法不能保證文件一定會創(chuàng)建成功,但是即使失敗也不會報(bào)異常院塞,所以一般我們在文件創(chuàng)建之后需要判斷一下當(dāng)前文件是否創(chuàng)建成功遮晚,調(diào)用一下 exists()方法來判斷文件是否創(chuàng)建成功,不成功則調(diào)用createNewFile(),此方法失敗會拋異常拦止,歸納起來就是:
File file=new File("E:\\demo","a.txt");
if (file.exists()){
//繼續(xù)文件的操作
}else {
try {
boolean result = file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
路徑:
- 相對路徑:./表示當(dāng)前路徑../表示上一級路徑
- 絕對路徑:絕對路徑名是完整的路徑名县遣,不需要任何其他信息就可以定位自身表示的文件
路徑分隔符:
- windows: "/" "" 都可以
- linux/unix: "/"
注意:如果windows選擇用""做分割符的話,那么請記得替換成"\",因?yàn)镴ava中""代表轉(zhuǎn)義字符所以推薦都使用"/",也可以直接使用代碼File.separator汹族,表示跨平臺分隔符萧求。
創(chuàng)建與刪除
boolean createNewFile();//創(chuàng)建具體的文件
boolean mkdir();//創(chuàng)建單個(gè)目錄
boolean mkdirs();//創(chuàng)建多個(gè)目錄
boolean delete();//刪除File
判斷方法
boolean canRead();//判斷文件是否可讀
boolean canWrite();//判斷文件是否可寫
boolean exists();//判斷文件是否存在
boolean isDirectory();//判斷是否是目錄
boolean isFile();//判斷是否是文件
boolean isAbsolute();//判斷是否是絕對路徑
獲取方法
String getName();//返回文件或者是目錄的名稱
String getPath();//返回路徑
String getAbsolutePath();//返回絕對路徑
String getParent();//返回父目錄,如果沒有父目錄則返回null
long lastModified();//返回最后一次修改的時(shí)間
long length();//返回文件的長度
File[] listRoots();// 列出所有的根目錄(Window中就是所有系統(tǒng)的盤符)
String[] list() ;//返回一個(gè)字符串?dāng)?shù)組顶瞒,給定路徑下的文件或目錄名稱字符串
String[] list(FilenameFilter filter);//返回滿足過濾器要求的一個(gè)字符串?dāng)?shù)組
File[] listFiles();//返回一個(gè)文件對象數(shù)組夸政,給定路徑下文件或目錄
文件過濾
File[] listFiles(FilenameFilter filter);//返回滿足過濾器要求的一個(gè)文件對象數(shù)組
其中包含了一個(gè)重要的接口FileNameFilter,該接口是個(gè)文件過濾器榴徐,包含了一個(gè)accept(File dir,String name)方法守问,該方法依次對指定File的所有子目錄或者文件進(jìn)行迭代,按照指定條件坑资,進(jìn)行過濾耗帕,過濾出滿足條件的所有文件。
// 文件過濾
File[] files = file.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String filename) {
return filename.endsWith(".apk");
}
});
file目錄下的所有子文件如果滿足后綴是.apk的條件的文件都會被過濾出來袱贮。
分類
按數(shù)據(jù)類型分:
- 字節(jié)流:字節(jié)流主要是操作byte類型數(shù)據(jù)
- 字符流: 因?yàn)閿?shù)據(jù)編碼的不同仿便,而有了對字符進(jìn)行高效操作的流對象。本質(zhì)其實(shí)就是基于字節(jié)流讀取時(shí)攒巍,去查了指定的碼表探越。
區(qū)別:
- 讀寫單位不同:字節(jié)流以字節(jié)(8bit)為單位,字符流以字符為單位窑业,根據(jù)碼表映射字符钦幔,一次可能讀16位字節(jié)。
- 處理對象不同:字節(jié)流能處理所有類型的數(shù)據(jù)(如image常柄、avi等)鲤氢,而字符流只能處理字符類型的數(shù)據(jù)。
設(shè)備上的數(shù)據(jù)無論是圖片或者視頻西潘,文字卷玉,它們都以二進(jìn)制存儲的。二進(jìn)制的最終都是以一個(gè)8位為數(shù)據(jù)單元進(jìn)行體現(xiàn)喷市,所以計(jì)算機(jī)中的最小數(shù)據(jù)單元就是字節(jié)相种。意味著,字節(jié)流可以處理設(shè)備上的所有數(shù)據(jù)品姓,所以字節(jié)流一樣可以處理字符數(shù)據(jù)寝并。
結(jié)論:只要是處理純文本數(shù)據(jù)箫措,就優(yōu)先考慮使用字符流。換句話說衬潦,能使用字符流的一定也可以使用字節(jié)流斤蔓。
按照數(shù)據(jù)流向分:
- 輸入流:InputStream或者Reader:從文件中讀到程序中;
- 輸出流:OutputStream或者Writer:從程序中輸出到文件中镀岛;
這里的輸入和輸出都是以程序?yàn)閰⒄瘴?/p>
按照流的角色分
- 節(jié)點(diǎn)流:直接與數(shù)據(jù)源相連弦牡,讀入或讀出,可以從/向一個(gè)特定的IO設(shè)備(如磁盤漂羊、網(wǎng)絡(luò))讀/寫數(shù)據(jù)的流驾锰,稱為節(jié)點(diǎn)流,節(jié)點(diǎn)流也被成為低級流走越。
- 處理流:處理流是對一個(gè)已存在的流進(jìn)行連接或封裝椭豫,通過封裝后的流來實(shí)現(xiàn)數(shù)據(jù)讀/寫功能,處理流也被稱為高級流买喧。
當(dāng)使用處理流進(jìn)行輸入/輸出時(shí)捻悯,程序并不會直接連接到實(shí)際的數(shù)據(jù)源匆赃,沒有和實(shí)際的輸入/輸出節(jié)點(diǎn)連接淤毛。使用處理流的一個(gè)明顯好處是,只要使用相同的處理流算柳,程序就可以采用完全相同的輸入/輸出代碼來訪問不同的數(shù)據(jù)源低淡,隨著處理流所包裝節(jié)點(diǎn)流的變化,程序?qū)嶋H所訪問的數(shù)據(jù)源也相應(yīng)地發(fā)生變化瞬项。
常用的節(jié)點(diǎn)流
父類 :InputStream 蔗蹋、OutputStream、 Reader囱淋、 Writer
文件 :FileInputStream 猪杭、 FileOutputStrean 、FileReader 妥衣、FileWriter 文件進(jìn)行處理的節(jié)點(diǎn)流
數(shù)組:ByteArrayInputStream皂吮、 ByteArrayOutputStream、 CharArrayReader 税手、CharArrayWriter 對數(shù)組進(jìn)行處理的節(jié)點(diǎn)流(對應(yīng)的不再是文件蜂筹,而是內(nèi)存中的一個(gè)數(shù)組)
字符串 :StringReader、 StringWriter 對字符串進(jìn)行處理的節(jié)點(diǎn)流
管 道 :PipedInputStream 芦倒、PipedOutputStream 艺挪、PipedReader 、PipedWriter 對管道進(jìn)行處理的節(jié)點(diǎn)流ByteArrayOutputStream兵扬、FileOutputStream 是兩種基本的介質(zhì)流麻裳,它們分別向Byte 數(shù)組口蝠、和本地文件中寫入數(shù)據(jù)。
PipedOutputStream 是向與其它線程共用的管道中寫入數(shù)據(jù)掂器。
ObjectOutputStream和所有FilterOutputStream 的子類都是裝飾流亚皂。
常用的處理流
緩沖流:BufferedInputStrean 、BufferedOutputStream国瓮、 BufferedReader灭必、 BufferedWriter 增加緩沖功能,避免頻繁讀寫硬盤乃摹。
轉(zhuǎn)換流:InputStreamReader 禁漓、OutputStreamReader實(shí)現(xiàn)字節(jié)流和字符流之間的轉(zhuǎn)換。
數(shù)據(jù)流: DataInputStream 孵睬、DataOutputStream 等-提供將基礎(chǔ)數(shù)據(jù)類型寫入到文件中播歼,或者讀取出來。
轉(zhuǎn)換流:InputStreamReader 掰读、OutputStreamWriter 要InputStream或OutputStream作為參數(shù)秘狞,實(shí)現(xiàn)從字節(jié)流到字符流的轉(zhuǎn)換。
父類介紹
InputStream
InputStream 是所有的輸入字節(jié)流的父類蹈集,它是一個(gè)抽象類烁试,主要包含三個(gè)方法:
//讀取一個(gè)字節(jié)并以整數(shù)的形式返回(0~255),如果返回-1已到輸入流的末尾。
int read() 拢肆;
//讀取一系列字節(jié)并存儲到一個(gè)數(shù)組buffer减响,返回實(shí)際讀取的字節(jié)數(shù),如果讀取前已到輸入流的末尾返回-1郭怪。
int read(byte[] buffer) 支示;
//讀取length個(gè)字節(jié)并存儲到一個(gè)字節(jié)數(shù)組buffer,從off位置開始存,最多l(xiāng)en鄙才, 返回實(shí)際讀取的字節(jié)數(shù)颂鸿,如果讀取前以到輸入流的末尾返回-1。
int read(byte[] buffer, int off, int len) 攒庵;
ByteArrayInputStream嘴纺、StringBufferInputStream、FileInputStream 是三種基本的介質(zhì)流叙甸,它們分別從Byte 數(shù)組颖医、StringBuffer、和本地文件中讀取數(shù)據(jù)裆蒸。
PipedInputStream 是從與其它線程共用的管道中讀取數(shù)據(jù)熔萧,與Piped 相關(guān)的知識后續(xù)單獨(dú)介紹。
ObjectInputStream 和所有FilterInputStream 的子類都是裝飾流(裝飾器模式的主角)
Reader
Reader 是所有的輸入字符流的父類,它是一個(gè)抽象類佛致,主要包含三個(gè)方法:
//讀取一個(gè)字符并以整數(shù)的形式返回(0~255),如果返回-1已到輸入流的末尾贮缕。
int read() ;
//讀取一系列字符并存儲到一個(gè)數(shù)組buffer俺榆,返回實(shí)際讀取的字符數(shù)感昼,如果讀取前已到輸入流的末尾返回-1。
int read(char[] cbuf) 罐脊;
//讀取length個(gè)字符,并存儲到一個(gè)數(shù)組buffer定嗓,從off位置開始存,最多讀取len,返回實(shí)際讀取的字符數(shù)萍桌,如果讀取前以到輸入流的末尾返回-1宵溅。
int read(char[] cbuf, int off, int len)
對比InputStream和Reader所提供的方法,就不難發(fā)現(xiàn)兩個(gè)基類的功能基本一樣的上炎,只不過讀取的數(shù)據(jù)單元不同恃逻。
在執(zhí)行完流操作后,要調(diào)用close()方法來關(guān)系輸入流藕施,因?yàn)槌绦蚶锎蜷_的IO資源不屬于內(nèi)存資源寇损,垃圾回收機(jī)制無法回收該資源,所以應(yīng)該顯式關(guān)閉文件IO資源裳食。
除此之外矛市,InputStream和Reader還支持如下方法來移動流中的指針位置:
//在此輸入流中標(biāo)記當(dāng)前的位置
//readlimit - 在標(biāo)記位置失效前可以讀取字節(jié)的最大限制。
void mark(int readlimit)
// 測試此輸入流是否支持 mark 方法
boolean markSupported()
// 跳過和丟棄此輸入流中數(shù)據(jù)的 n 個(gè)字節(jié)/字符
long skip(long n)
//將此流重新定位到最后一次對此輸入流調(diào)用 mark 方法時(shí)的位置
void reset()
OutputStream
OutputStream 是所有的輸出字節(jié)流的父類胞谈,它是一個(gè)抽象類尘盼,主要包含如下四個(gè)方法:
//向輸出流中寫入一個(gè)字節(jié)數(shù)據(jù),該字節(jié)數(shù)據(jù)為參數(shù)b的低8位憨愉。
void write(int b) ;
//將一個(gè)字節(jié)類型的數(shù)組中的數(shù)據(jù)寫入輸出流烦绳。
void write(byte[] b);
//將一個(gè)字節(jié)類型的數(shù)組中的從指定位置(off)開始的,len個(gè)字節(jié)寫入到輸出流。
void write(byte[] b, int off, int len);
//將輸出流中緩沖的數(shù)據(jù)全部寫出到目的地配紫。
void flush();
Writer
Writer 是所有的輸出字符流的父類径密,它是一個(gè)抽象類,主要包含如下六個(gè)方法:
//向輸出流中寫入一個(gè)字符數(shù)據(jù),該字節(jié)數(shù)據(jù)為參數(shù)b的低16位。
void write(int c);
//將一個(gè)字符類型的數(shù)組中的數(shù)據(jù)寫入輸出流躺孝,
void write(char[] cbuf)
//將一個(gè)字符類型的數(shù)組中的從指定位置(offset)開始的,length個(gè)字符寫入到輸出流享扔。
void write(char[] cbuf, int offset, int length);
//將一個(gè)字符串中的字符寫入到輸出流。
void write(String string);
//將一個(gè)字符串從offset開始的length個(gè)字符寫入到輸出流植袍。
void write(String string, int offset, int length);
//將輸出流中緩沖的數(shù)據(jù)全部寫出到目的地惧眠。
void flush()
可以看出,Writer比OutputStream多出兩個(gè)方法于个,主要是支持寫入字符和字符串類型的數(shù)據(jù)氛魁。
使用Java的IO流執(zhí)行輸出時(shí),不要忘記關(guān)閉輸出流,關(guān)閉輸出流除了可以保證流的物理資源被回收之外秀存,還能將輸出流緩沖區(qū)的數(shù)據(jù)flush到物理節(jié)點(diǎn)里(因?yàn)樵趫?zhí)行close()方法之前捶码,自動執(zhí)行輸出流的flush()方法)
IO中的一股清流——RandomAccessFIle
我們發(fā)現(xiàn)RandomAccessFIle跟File類并沒有聯(lián)系,只是實(shí)現(xiàn)了DataOutput跟DataInput兩個(gè)接口或链,完全自己重新定義了一遍File的讀取操作
RandomAccessFile是Java中輸入惫恼,輸出流體系中功能最豐富的文件內(nèi)容訪問類,它提供很多方法來操作文件澳盐,包括讀寫支持祈纯,與普通的IO流相比,它最大的特別之處就是支持任意訪問的方式叼耙,程序可以直接跳到任意地方來讀寫數(shù)據(jù)盆繁。
如果我們只希望訪問文件的部分內(nèi)容,而不是把文件從頭讀到尾旬蟋,使用RandomAccessFile將會帶來更簡潔的代碼以及更好的性能油昂。
方法
方法名 | 作用 |
---|---|
getFilePointer() | 返回文件記錄指針的當(dāng)前位置 |
seek(long pos) | 將文件記錄指針定位到pos的位置 |
功能
- 1.讀取任意位置的數(shù)據(jù)
- 2.追加數(shù)據(jù)
- 3.任意位置插入數(shù)據(jù)
NIO
Java NIO是java 1.4之后新出的一套IO接口,這里的的新是相對于原有標(biāo)準(zhǔn)的Java IO和Java Networking接口倾贰。NIO提供了一種完全不同的操作方式冕碟。標(biāo)準(zhǔn)的IO編程接口是面向字節(jié)流和字符流的。而NIO是面向Channel(通道)和Buffer(緩沖區(qū))的匆浙,數(shù)據(jù)總是從Channel中讀到Buffer內(nèi)安寺,或者從Buffer寫入到Channel中,Channel是需要注冊到Selector(選擇器)中去首尼。我們知道不管是NIO還是BIO在讀寫數(shù)據(jù)的過程中都有兩個(gè)操作:等待就緒和操作挑庶。舉例來說,讀函數(shù)软能,分為等待系統(tǒng)可讀和真正的讀迎捺;同理,寫函數(shù)分為等待網(wǎng)卡可以寫和真正的寫查排。NIO跟BIO的操作都是一樣的凳枝,區(qū)別在于等待就緒的這一過程,看看下面這張圖:
NIO跟BIO的區(qū)別在于不管現(xiàn)在有沒有數(shù)據(jù)跋核,都會給調(diào)用者一個(gè)返回值岖瑰,在沒有準(zhǔn)備就緒之前,當(dāng)前線程可以進(jìn)行其他的操作砂代,而不會阻塞蹋订。
緩沖區(qū)
緩沖區(qū)實(shí)質(zhì)上就是一個(gè)數(shù)組,但它不僅僅是一個(gè)數(shù)組刻伊,緩沖區(qū)還提供了對數(shù)據(jù)的結(jié)構(gòu)化訪問露戒,而且還可以跟蹤系統(tǒng)的讀/寫進(jìn)程难礼。
通道
通道用于在緩沖區(qū)和位于通道另一側(cè)的實(shí)體(文件、套接字)之間有效的傳輸數(shù)據(jù)
選擇器
選擇器類Selector并沒有和通道有直接的關(guān)系玫锋,而是通過叫選擇鍵的對象SelectionKey來聯(lián)系的,而且Selector可以注冊過個(gè)key蛾茉,也就是說可以同時(shí)管理多個(gè)通道。選擇鍵代表了通道與選擇 器之間的一種注冊關(guān)系撩鹿,channel()和selector()方法分別返回注冊的通道與選擇器谦炬。
工作原理
NIO調(diào)用不會被阻塞,在IO開始的時(shí)候需要在分發(fā)器那里注冊感興趣的事件节沦,并提供相應(yīng)的處理者(event handler)键思,或者是回調(diào)函數(shù);事件分發(fā)器在適當(dāng)?shù)臅r(shí)候甫贯,會將請求的事件分發(fā)給這些handler或者回調(diào)函數(shù):如可讀數(shù)據(jù)到達(dá)吼鳞,新的套接字連接等等,在發(fā)生特定事件時(shí)叫搁,系統(tǒng)再通知我們赔桌。NIO中實(shí)現(xiàn)非阻塞I/O的核心對象就是Selector,Selector就是注冊各種I/O事件地 方渴逻,而且當(dāng)那些事件發(fā)生時(shí)疾党,就是這個(gè)對象告訴我們所發(fā)生的事件,如下圖所示:
從圖中可以看出惨奕,當(dāng)有讀或?qū)懙热魏巫缘氖录l(fā)生時(shí)雪位,可以從Selector中獲得相應(yīng)的SelectionKey,同時(shí)從 SelectionKey中可以找到發(fā)生的事件和該事件所發(fā)生的具體的SelectableChannel梨撞,以獲得客戶端發(fā)送過來的數(shù)據(jù)雹洗。
如何選擇
NIO的優(yōu)勢在于單線程管理多個(gè)連接,可以在線程數(shù)較少的情況下實(shí)現(xiàn)快速地讀取卧波,當(dāng)連接數(shù)<1000时肿,并發(fā)程度不高并沒有顯著的性能優(yōu)勢。NIO可讓您只使用一個(gè)(或幾個(gè))單線程管理多個(gè)通道(網(wǎng)絡(luò)連接或文件)幽勒,但付出的代價(jià)是解析數(shù)據(jù)可能會比從一個(gè)阻塞流中讀取數(shù)據(jù)更復(fù)雜嗜侮。如果需要管理同時(shí)打開的成千上萬個(gè)連接港令,這些連接每次只是發(fā)送少量的數(shù)據(jù)啥容,例如聊天服務(wù)器,實(shí)現(xiàn)NIO的服務(wù)器可能是一個(gè)優(yōu)勢顷霹。如果你有少量的連接使用非常高的帶寬咪惠,一次發(fā)送大量的數(shù)據(jù),也許典型的BIO服務(wù)器實(shí)現(xiàn)可能非常契合淋淀。
IO與裝飾者模式
其實(shí)裝飾者模式在IO中的運(yùn)用非常廣泛遥昧,先看一下什么是裝飾者。
裝飾者(Decorator)模式:動態(tài)將職責(zé)附加到對象上,若要擴(kuò)展功能炭臭,裝飾者提供了比繼承更具彈性的代替方案永脓。
設(shè)計(jì)原則:開閉原則(一個(gè)軟件實(shí)體如類、模塊和函數(shù)應(yīng)該對擴(kuò)展開放鞋仍,對修改關(guān)閉)常摧。
下面用圖簡單描述一下:
下面解釋一下這幾個(gè)變量:
- Component:抽象組件
- ConcreteComponent:抽象組件的具體實(shí)現(xiàn)類
- Decorator: 裝飾者的抽象類,繼承自Component
- DecoratorA/B:具體的裝飾實(shí)現(xiàn)類
通過裝飾者對已有的對象進(jìn)行包裝威创,可以擴(kuò)展已有類的方法跟屬性落午,下面通過代碼來說明:
Component
public abstract class Component {
protected String description;
protected abstract String getDescription();
public abstract int getAge();
}
ConcreteComponent
public class ConcreteComponent extends Component {
public ConcreteComponent() {
description = "ConcreteComponent";
}
@Override
protected String getDescription() {
return description;
}
@Override
public int getAge() {
return 10;
}
}
Decorator
public abstract class Decorator extends Component {
//空實(shí)現(xiàn),具體的實(shí)現(xiàn)放在子類
}
DecoratorA/B
public class DecoratorA extends Decorator {
private Component mComponent;
public DecoratorA(Component component) {
this.mComponent = component;
}
@Override
protected String getDescription() {
return mComponent.getDescription();
}
@Override
public int getAge() {
return mComponent.getAge() + 1;
}
public String getTime() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
測試代碼:
Component component = new ConcreteComponent();
System.out.println("裝飾前的參數(shù):" + "描述:" + component.getDescription() + " 年齡:" + component.getAge());
DecoratorA decoratorA = new DecoratorA(component);
System.out.println("裝飾后的參數(shù):" + "描述:" + decoratorA.getDescription() + " 年齡
使用裝飾者模式之后肚豺,不修改Description溃斋,將age+1,同時(shí)增加一個(gè)getTime方法來獲取當(dāng)前的時(shí)間
輸出結(jié)果
裝飾前的參數(shù):描述:ConcreteComponent 年齡:10
裝飾后的參數(shù):描述:ConcreteComponent 年齡:11 時(shí)間:2017-11-04 15:11:03
跟我們預(yù)期的一樣吸申,不再贅述梗劫。下面看看IO中的設(shè)計(jì)模式,以O(shè)utpuStream為例:
對照著上面的裝飾者模式圖應(yīng)該很容易看出來截碴,F(xiàn)ileterOutputStream就是我們裝飾者的抽象類在跳,看一下他的構(gòu)造方法,確實(shí)裝飾了OutputStream :
public FilterOutputStream(OutputStream out) {
this.out = out;
}
Android中的"path"
在開發(fā)Android的過程中,也會涉及到很多的IO操作隐岛,比如說網(wǎng)絡(luò)請求猫妙,下載圖片等,由于很多框架平時(shí)已經(jīng)幫我們封裝好了聚凹,所以平時(shí)容易忽略割坠,下面簡單分析一下Android下的存儲目錄:
內(nèi)部存儲
data文件夾就是我們常說的內(nèi)部存儲,對于沒有root的手機(jī)來說妒牙,我們是沒有權(quán)限打開這個(gè)文件夾的但是可以訪問到彼哼,
外部存儲
外部存儲才是我們平時(shí)操作最多的,外部存儲一般就是我們上面看到的storage文件夾湘今,當(dāng)然也有可能是mnt文件夾敢朱,這個(gè)名稱不影響我們操作數(shù)據(jù)。
路徑獲取
兩種存儲方式都是通過Context類來進(jìn)行獲取的
內(nèi)部存儲:
getFilesDir();//獲取內(nèi)部存儲的File路徑
getCacheDir();//獲取內(nèi)部存儲的Cache路徑
getDatabasePath("demo.db");//獲取database路徑
getSharedPreferences("demo",MODE_PRIVATE);//獲取SP
外部存儲:
getExternalCacheDir();//獲取外部存儲私有目錄
getExternalFilesDir(Environment.DIRECTORY_DCIM);//獲取外部存儲公有目錄
清除緩存/清除數(shù)據(jù)
清除緩存:緩存是程序運(yùn)行時(shí)的臨時(shí)存儲空間摩瞎,它可以存放從網(wǎng)絡(luò)下載的臨時(shí)圖片拴签,從用戶的角度出發(fā)清除緩存對用戶并沒有太大的影響,但是清除緩存后用戶再次使用該APP時(shí)旗们,由于本地緩存已經(jīng)被清理蚓哩,所有的數(shù)據(jù)需要重新從網(wǎng)絡(luò)上獲取,注意:為了在清除緩存的時(shí)候能夠正常清除與應(yīng)用相關(guān)的緩存上渴,請將緩存文件存放在getCacheDir()或者 getExternalCacheDir()路徑下岸梨。
清除數(shù)據(jù):清除用戶配置喜颁,比如SharedPreferences、數(shù)據(jù)庫等等曹阔,這些數(shù)據(jù)都是在程序運(yùn)行過程中保存的用戶配置信息半开,清除數(shù)據(jù)后,下次進(jìn)入程序就和第一次進(jìn)入程序時(shí)一樣
關(guān)于權(quán)限
Android6.0以后赃份,谷歌加強(qiáng)了對用戶權(quán)限的控制稿茉,但是這個(gè)權(quán)限只是針對于外部存儲的公有目錄,對于私有目錄的芥炭,仍然可以正常訪問漓库。所以當(dāng)遇到有些手機(jī)權(quán)限很難適配的時(shí)候可以把文件存儲在外部存儲的私有目錄。