繼承的缺點
如果要給一個類擴展功能應(yīng)該怎么做挨厚?
繼承與組合:
復(fù)用代碼是進行程序設(shè)計的一個重要原因,組合和繼承被委以重任,其中繼承更是面向?qū)ο蟮幕唬啾扔诮M合或颊,繼承其實有諸多缺點。組合只要持有另一個類的對象倍踪,就可以使用它暴露的所有功能梯捕,同時也隱藏了具體的實現(xiàn)(黑盒復(fù)用
);組合之間的關(guān)系是動態(tài)的钥勋,在運行才確定炬转;組合有助于保持每個類被封裝,并被集中在單個任務(wù)上(單一原則
)算灸。而然扼劈,類繼承允許我們根據(jù)自己的實現(xiàn)來覆蓋重寫父類的實現(xiàn)細節(jié),父類的實現(xiàn)對于子類是可見的(白盒復(fù)用
)菲驴;繼承是在編譯時刻靜態(tài)定義的荐吵,即是靜態(tài)復(fù)用,在編譯后子類已經(jīng)確定了赊瞬;繼承中父類定義了子類的部分實現(xiàn)先煎,而子類中又會重寫這些實現(xiàn),修改父類的實現(xiàn)巧涧,這是一種破壞了父類的封裝性的表現(xiàn)薯蝎。總之組合相比繼承更具靈活性。即便如此谤绳,我們有不得不使用繼承的理由:
向上轉(zhuǎn)型占锯,復(fù)用接口
如果用繼承來擴展功能會遇到上面所說的諸多問題袒哥,對父類的方法做了修改的話,則子類的方法必須做出相應(yīng)的修改消略。所以說子類與父類是一種高耦合堡称,并且因為子類是靜態(tài)的,當(dāng)擴展的功能是多種情況的組合的話艺演,你必須枚舉出所有的情況為它們定義子類却紧。比如咖啡店里有四種咖啡:
現(xiàn)在還可以給咖啡添加額外的四種調(diào)料
Milk,Chocolate胎撤,Icecream晓殊,Whip如果為每一種咖啡和調(diào)料的組合編寫子類將有64種情況,顯然這種類型體系臃腫是無法接受的哩照!
那么有什么方法可以即保留向上轉(zhuǎn)型的繼承結(jié)構(gòu)挺物,又避免繼承帶來的問題呢 ?
裝飾者優(yōu)化繼承結(jié)構(gòu)
ConcreteComponent
和Component
是原有的繼承結(jié)構(gòu),相比于直接在ConcreteComponent
上開刀來擴展功能飘弧,我們重新定義了一個Decorator
類识藤,Decorator
用組合的方式持有一個Component
對象,同時繼承Component
這樣就實現(xiàn)了保留向上轉(zhuǎn)型的繼承結(jié)構(gòu)的同時次伶,擁有組合的優(yōu)點:
- 通過動態(tài)的方式來擴展一個對象的功能
- 通過裝飾類的排列組合痴昧,可以創(chuàng)造恒多不同行為的組合
- 裝飾類
Decorator
和構(gòu)建類ConcreteComponent
可以獨立變化
OKio原理分析
好了,終于進入正題了冠王。和Java的io流相同赶撰,Okio的整體設(shè)計也是裝飾者模式,一層層的拼接流(Stream)正是在使用使用裝飾者在裝飾的過程柱彻。
- Okio封裝了java.io,java.nio的功能使用起來更方便
- Okio優(yōu)化了緩存豪娜,使io操作更高效
Source和Sink流程
Source
和Sink
類似于InputStream
和OutputStream
,是io操作的頂級接口類哟楷,Source
和Sink
中只定義了三個方法:
public interface Source extends Closeable {
/**
* 定義基礎(chǔ)的read操作,該方法將字節(jié)寫入Buffer
*/
long read(Buffer sink, long byteCount) throws IOException;
/** Returns the timeout for this source. */
Timeout timeout();
/**
* Closes this source and releases the resources held by this source. It is an
* error to read a closed source. It is safe to close a source more than once.
*/
@Override void close() throws IOException;
}
Sink
的結(jié)構(gòu)是相同的瘤载,就不廢話了。那么Source和Sink的具體實現(xiàn)在哪里呢卖擅?Okio
類提供了靜態(tài)的方法生產(chǎn)Sink和Source鸣奔,這個方法也比較簡單,將InputStream
中的數(shù)據(jù)寫入到Buffer
的Segment
中惩阶,Buffer
和Segment
是Okio對io流操作進行優(yōu)化的關(guān)鍵類挎狸,后面在詳細討論,先把讀寫操作的流程走完断楷。
private static Source source(final InputStream in, final Timeout timeout) {
//.....
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try {
timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
//寫入Segment
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) {
if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}
//.....
}
不同的讀取操作定義在BufferedSource中锨匆,它同樣也是個接口:
BufferedSource
的具體實現(xiàn)是RealBufferedSource,可以看到RealBufferedSource
其實是個裝飾類冬筒,內(nèi)部管理Source
對象來擴展Source
的功能恐锣,同時擁有Source
讀取數(shù)據(jù)時用到的Buffer
對象紊遵。
final class RealBufferedSource implements BufferedSource {
public final Buffer buffer = new Buffer();
public final Source source;
boolean closed;
//....
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (sink == null) throw new IllegalArgumentException("sink == null");
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
//先將數(shù)據(jù)讀到buffer中
if (buffer.size == 0) {
//source是被裝飾的對象
long read = source.read(buffer, Segment.SIZE);
if (read == -1) return -1;
}
long toRead = Math.min(byteCount, buffer.size);
return buffer.read(sink, toRead);
}
//...
}
小結(jié)一下:
Source
對象每次read,Sink
對象每次write都需要一個Buffer
對象侥蒙,Buffer
管理者循環(huán)雙向鏈表Segment,每次讀寫數(shù)據(jù)都先保存在segment
中進行緩沖匀奏,BufferedSource
和BufferedSink
進行讀寫操作時都是間接調(diào)用Buffer
對Segment
的操作來完成的鞭衩,整個過程層層嵌套還是有點繞的。
InputStream--Source--BufferedSource--Buffer--segment--Buffer--Sink--BufferedSink--OutputStream
為什么Okio更高效
在buffer
的注釋中說明了Okio的高效性:
- 采用了segment的機制進行內(nèi)存共享和復(fù)用娃善,避免了copy數(shù)組论衍;
- 根據(jù)需要動態(tài)分配內(nèi)存大小聚磺;
- 避免了數(shù)組創(chuàng)建時的zero-fill坯台,同時降低GC的頻率。
Segment和SegmentPool:
Segment是一個循環(huán)雙向列表瘫寝,內(nèi)部維護者固定長度的byte[]數(shù)組:
static final int SIZE = 8192;
/** Segments 用分享的方式避免復(fù)制數(shù)組 */
static final int SHARE_MINIMUM = 1024;
final byte[] data;
/** data[]中第一個可讀的位置*/
int pos;
/** data[]中第一個可寫的位置 */
int limit;
/**與其它Segment共享 */
boolean shared;
boolean owner;
Segment next;
Segment prev;
/**
* 將當(dāng)前segment從鏈表中移除
*/
public Segment pop() {
//....
}
/**
* 將一個segment插入到當(dāng)前segment后
*/
public Segment push(Segment segment) {
//....
}
SegmentPool是一個Segment池蜒蕾,由一個單向鏈表構(gòu)成。該池負(fù)責(zé)Segment的回收和閑置Segment的管理焕阿,也就是說Buffer使用的Segment是從Segment單向鏈表中取出的咪啡,這樣有效的避免了GC頻率。
/** 總?cè)萘?*/
static final long MAX_SIZE = 64 * 1024; // 64 KiB.
/**用Segment實現(xiàn)的單向鏈表,next是表頭*/
static Segment next;
/** Total bytes in this pool. */
static long byteCount;
//回收閑置的segment暮屡,插在鏈表頭部
static void recycle(Segment segment) {
if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
if (segment.shared) return; // This segment cannot be recycled.
synchronized (SegmentPool.class) {
if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
byteCount += Segment.SIZE;
segment.next = next;
segment.pos = segment.limit = 0;
next = segment;
}
}
//從鏈表頭部取出一個
static Segment take() {
synchronized (SegmentPool.class) {
if (next != null) {
Segment result = next;
next = result.next;
result.next = null;
byteCount -= Segment.SIZE;
return result;
}
}
return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
}
Segment中還有兩個特殊的方法split()
和compact()
撤摸,split()
根據(jù)當(dāng)前的Segment產(chǎn)生一個新的Segment,新的Segment與原來的Segment共用同一個data[]數(shù)組褒纲,但是改變了讀寫的標(biāo)記位pos
和limit
准夷,從原來的
[pos..limit]拆分為[pos..pos+byteCount]和[pos+byteCount..limit],從而避免了復(fù)制數(shù)組帶來的性能消耗莺掠。前一個和自身的數(shù)據(jù)量都不足一半時衫嵌,compact()
會對segement進行壓縮,把自身的數(shù)據(jù)寫入到前一Segment中汁蝶,然后將自身進行回收渐扮,使Segment的利用更高效!
public Segment split(int byteCount) {
if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
Segment prefix;
// We have two competing performance goals:
// - Avoid copying data. We accomplish this by sharing segments.
// - Avoid short shared segments. These are bad for performance because they are readonly and
// may lead to long chains of short segments.
// To balance these goals we only share segments when the copy will be large.
if (byteCount >= SHARE_MINIMUM) {
prefix = new Segment(this);
} else {
prefix = SegmentPool.take();
System.arraycopy(data, pos, prefix.data, 0, byteCount);
}
prefix.limit = prefix.pos + byteCount;
pos += byteCount;
prev.push(prefix);
return prefix;
}
Okio實戰(zhàn)
Okio封裝了io操作底層操縱字節(jié)的細節(jié)掖棉,使用起來更簡單了墓律。但一般來說高度的封裝意味著無法定制,比如說在網(wǎng)絡(luò)應(yīng)用中經(jīng)常要監(jiān)聽文件的上傳下載進度幔亥,顯然Okio默認(rèn)是沒有這個功能的耻讽,應(yīng)該怎么擴展呢?別忘了帕棉,裝飾者模式针肥。實際上Okio已經(jīng)提供了Decorator
類:ForwardingSink
饼记,ForwardingSource
,只要繼承這兩個類就可以自己定制功能了慰枕。
class CountingSink extends ForwardingSink{
private long bytesWritten = 0;
private Listen listen;
private File file;
private long totalLength;
public CountingSink(Sink delegate,File file,Listen listen) {
super(delegate);
this.listen = listen;
this.file = file;
totalLength = contentLength();
}
public long contentLength(){
if (file != null) {
long length = file.length();
Log.d("abc : length :", length + "");
return length;
}else {
return 0;
}
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
listen.onProgress(bytesWritten, totalLength);
}
interface Listen{
void onProgress(long bytesWritten, long contentLength);
}
}
我們復(fù)制一首歌做測試:
File fileSrc = new File(Environment.getExternalStorageDirectory() + "/000szh", "pain.mp3");
File fileCopy = new File(Environment.getExternalStorageDirectory() + "/000szh","pain3.mp3");
CountingSink.Listen listen = new CountingSink.Listen() {
@Override
public void onProgress(long bytesWritten, long contentLength) {
long total = contentLength;
float pos = bytesWritten *1.0f / total;
}
};
BufferedSink bufferedSink = null;
Source source = null;
try {
//包裝sink
Sink sink= Okio.sink(fileCopy);
CountingSink countingSink = new CountingSink(sink, fileSrc,listen);
bufferedSink = Okio.buffer(countingSink);
source = Okio.source(fileSrc);
bufferedSink.writeAll(source);
bufferedSink.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
closeAll(bufferedSink, source);
} catch (IOException e) {
e.printStackTrace();
}
}
}