最近的一个项目涉及到本地文件拷贝,本地文件网络动态更新方面的东西。对于这两方面,如何加快其处理速度?优先想到的就是使用多线程并发技术。同时在文件拷贝过程中,为了加快拷贝速度,使用了java的零拷贝技术,使用的是FileChannel。
1,多线程+0拷贝实现文件高效拷贝
对于零拷贝,相比较于常规拷贝,免去了cpu的两次拷贝,同样的也就免去了cpu拷贝过程中系统缓存区到用户缓存区,以及写回文件时用户缓存区到系统缓存区的线程上下文切换的时间花销,也就是这些工作都不必要再做了,cpu可以做其他更多有价值的事情,这就是零拷贝的一大优点。
零拷贝对于文件的高速复制的代码如下(关键方法就是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) {
......
}
}
}
}
对于这些文件的拷贝,只存在于指定路径不存在时才会拷贝,在所有文件拷贝结束之后,或都无需拷贝时,我会从服务器读取最新文件的映射json,如果某些文件不再使用或要更新时,我就会动态更新下来最新的指定文件,同时删除不要的老文件。
这时问题来了,因为所有的文件拷贝都是在线程池中处理的,并且待拷贝的文件数量是未知的,如何得知所有文件都已拷贝结束?
2,Atomic无锁技术实现拷贝进度高效监听
我的做法是通过AtomicInteger原子计数方式,线程池添加某个runnable之前+1,某个runnable执行完毕-1,其一大好处就是不必像传统的synchronized那样需要给对象加锁。对象加锁就会造成线程阻塞,同时加锁以及释放锁都涉及到cpu频繁调度和线程上下文切换,从而为了简单的计数便造成了很多不必要的系统开销。
可能有些人很好奇,难道不会中途会存在count.get()=0的情况吗?实际上这种情况是不存在的,因为copyFile是在当前线程递归调用方法里,递归执行速度必然比copyFile里异步线程调度执行快,就好比带有漏水的瓶子,如果加的水比漏的快,那么只要在加水,必然不可能会存在水先漏完的情况。
3,0拷贝技术实现文件高效下载
对于传统的文件下载,一般使用的都是while循环inputStream.read(...)或者bufferWrite.read(...),然后还要flush到本地,但是这也面临1中读取+写入一共两次不必要的拷贝以及内核到用户,用户到内核的调度问题,所以为何不同样使用文件管道方式,直接对目标文件的写操作呢?
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技术请移步:https://blog.csdn.net/gwt0425/article/details/77980223