最近的一個(gè)項(xiàng)目涉及到本地文件拷貝疤苹,本地文件網(wǎng)絡(luò)動(dòng)態(tài)更新方面的東西习勤。對(duì)于這兩方面踪栋,如何加快其處理速度??jī)?yōu)先想到的就是使用多線程并發(fā)技術(shù)图毕。同時(shí)在文件拷貝過程中夷都,為了加快拷貝速度,使用了java的零拷貝技術(shù)予颤,使用的是FileChannel囤官。
1冬阳,多線程+0拷貝實(shí)現(xiàn)文件高效拷貝
對(duì)于零拷貝,相比較于常規(guī)拷貝党饮,免去了cpu的兩次拷貝肝陪,同樣的也就免去了cpu拷貝過程中系統(tǒng)緩存區(qū)到用戶緩存區(qū),以及寫回文件時(shí)用戶緩存區(qū)到系統(tǒng)緩存區(qū)的線程上下文切換的時(shí)間花銷劫谅,也就是這些工作都不必要再做了见坑,cpu可以做其他更多有價(jià)值的事情,這就是零拷貝的一大優(yōu)點(diǎn)捏检。
零拷貝對(duì)于文件的高速復(fù)制的代碼如下(關(guān)鍵方法就是transferTo就是這么easy):
private fun copyFile(.....){
....
count.getAndIncrement()
executor.execute {
var fis: FileInputStream? = null
var inputChannel: FileChannel? = null
var outputChannel: FileChannel? = null
val fileName = path.substring(path.lastIndexOf("/") + 1)
val filePath = path.substring(0, path.lastIndexOf("/"))
try {
val afd = am.openFd(path)
fis = FileInputStream(afd.fileDescriptor)
inputChannel = fis.channel
val outDirFile = File(getBasePath(context) + "/" + filePath)
if (!outDirFile.exists()) outDirFile.mkdirs()
outputChannel = FileOutputStream(File(outDirFile, fileName)).channel
inputChannel.transferTo(afd.startOffset, afd.declaredLength, outputChannel)
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
fis?.close()
inputChannel?.close()
outputChannel?.close()
} catch (e: Exception) {
e.printStackTrace()
}
if (count.decrementAndGet() == 0) {
......
}
}
}
}
對(duì)于這些文件的拷貝荞驴,只存在于指定路徑不存在時(shí)才會(huì)拷貝,在所有文件拷貝結(jié)束之后贯城,或都無需拷貝時(shí)熊楼,我會(huì)從服務(wù)器讀取最新文件的映射json,如果某些文件不再使用或要更新時(shí)能犯,我就會(huì)動(dòng)態(tài)更新下來最新的指定文件鲫骗,同時(shí)刪除不要的老文件。
這時(shí)問題來了踩晶,因?yàn)樗械奈募截惗际窃诰€程池中處理的执泰,并且待拷貝的文件數(shù)量是未知的,如何得知所有文件都已拷貝結(jié)束渡蜻?
2术吝,Atomic無鎖技術(shù)實(shí)現(xiàn)拷貝進(jìn)度高效監(jiān)聽
我的做法是通過AtomicInteger原子計(jì)數(shù)方式,線程池添加某個(gè)runnable之前+1茸苇,某個(gè)runnable執(zhí)行完畢-1排苍,其一大好處就是不必像傳統(tǒng)的synchronized那樣需要給對(duì)象加鎖。對(duì)象加鎖就會(huì)造成線程阻塞学密,同時(shí)加鎖以及釋放鎖都涉及到cpu頻繁調(diào)度和線程上下文切換淘衙,從而為了簡(jiǎn)單的計(jì)數(shù)便造成了很多不必要的系統(tǒng)開銷。
可能有些人很好奇腻暮,難道不會(huì)中途會(huì)存在count.get()=0的情況嗎彤守?實(shí)際上這種情況是不存在的,因?yàn)閏opyFile是在當(dāng)前線程遞歸調(diào)用方法里哭靖,遞歸執(zhí)行速度必然比copyFile里異步線程調(diào)度執(zhí)行快具垫,就好比帶有漏水的瓶子,如果加的水比漏的快款青,那么只要在加水,必然不可能會(huì)存在水先漏完的情況霍狰。
3抡草,0拷貝技術(shù)實(shí)現(xiàn)文件高效下載
對(duì)于傳統(tǒng)的文件下載饰及,一般使用的都是while循環(huán)inputStream.read(...)或者bufferWrite.read(...),然后還要flush到本地康震,但是這也面臨1中讀取+寫入一共兩次不必要的拷貝以及內(nèi)核到用戶燎含,用戶到內(nèi)核的調(diào)度問題,所以為何不同樣使用文件管道方式腿短,直接對(duì)目標(biāo)文件的寫操作呢屏箍?
fun downloadFile(....){
InputStream is = null;
ReadableByteChannel readableByteChannel = null;
FileOutputStream fos = null;
FileChannel out = null;
ByteBuffer buffer = ByteBuffer.allocate(2048);
......
try {
is = response.body().byteStream();
readableByteChannel = Channels.newChannel(is);
fos = new FileOutputStream(file);
out = fos.getChannel();
// 使用文件管道高效緩存文件
while (readableByteChannel.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
out.write(buffer);
}
buffer.clear();
}
//下載完成
listener.onDownloadSuccess(file);
} catch (Exception e) {
e.printStackTrace();
listener.onDownloadFailed(e);
} finally {
try {
if (is != null) {
is.close();
}
if (readableByteChannel != null)
readableByteChannel.close();
if (fos != null) {
fos.close();
}
if (out != null)
out.close();
} catch (IOException e) {
}
}
}
更多java NIO技術(shù)請(qǐng)移步:https://blog.csdn.net/gwt0425/article/details/77980223