Android Netroid解析之——断点续传下载及问题修正
提到Netroid也许非常多人不知道这个框架,但我假设说Volley想必没有人不知道吧。
Netroid是一个基于Volley实现的Android
Http库。提供运行网络请求、缓存返回结果、批量图片载入、大文件断点下载的常见Http交互功能,关于网络请求,图片载入没什么好说的,Volley已经有非常多人解析过了,这里来说一下大文件断点下载。
关于大文件断点下载,网上也有非常多实现的demo,为什么要单单说Netroid呢?由于Netroid断点续传不依赖数据库,我在网上看到过非常多的断点续传的样例,无一例外都是依赖于数据库。包含DownloadManager,大名鼎鼎的xutils,可是这两个都有一定的问题。
1.DownloadManager在三星手机上必须打开下载管理才干应用,而打开这个管理必须须要手动打开,普通情况下无伤大雅。视情况而定
2.xutils这个框架别的不知道。文件下载这块慎用
好了。进入正题,Netroid的地址:https://github.com/vince-styling/,以下简单的说一下这个框架文件下载的实现和原理,
// 1
RequestQueue queue = Netroid.newRequestQueue(getApplicationContext(), null);
// 2
mDownloder = new FileDownloader(queue, 1) {
@Override
public FileDownloadRequest buildRequest(String storeFilePath, String url) {
return new FileDownloadRequest(storeFilePath, url) {
@Override
public void prepare() {
addHeader("Accept-Encoding", "identity");
super.prepare();
}
};
}
};
// 3
task.controller = mDownloder.add(mSaveDirPath + task.storeFileName, task.url, new Listener<Void>() {
@Override
public void onPreExecute() {
task.invalidate();
} @Override
public void onSuccess(Void response) {
showToast(task.storeFileName + " Success!");
} @Override
public void onError(NetroidError error) {
NetroidLog.e(error.getMessage());
} @Override
public void onFinish() {
NetroidLog.e("onFinish size : " + Formatter.formatFileSize(
FileDownloadActivity.this, new File(mSaveDirPath + task.storeFileName).length()));
task.invalidate();
} @Override
public void onProgressChange(long fileSize, long downloadedSize) {
task.onProgressChange(fileSize, downloadedSize);
// NetroidLog.e("---- fileSize : " + fileSize + " downloadedSize : " + downloadedSize);
}
});
实现的话非常easy,主要分为三步就能够了
1.创建一个请求队列
2.构建一个文件下载管理器
3.将下载任务加入到队列
如今依据上面的三步来看一下它的实现原理:
第一步:创建一个请求队列:RequestQueue queue = Netroid.newRequestQueue(getApplicationContext(), null);
/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
* @param context A {@link Context} to use for creating the cache dir.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context, DiskCache cache) {
int poolSize = RequestQueue.DEFAULT_NETWORK_THREAD_POOL_SIZE; HttpStack stack;
String userAgent = "netroid/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
stack = new HurlStack(userAgent, null);
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(userAgent);
}
//实例化BasicNetwork,主要用于运行下载请求
Network network = new BasicNetwork(stack, HTTP.UTF_8);
//创建请求队列
RequestQueue queue = new RequestQueue(network, poolSize, cache);
//非常重要的一步
queue.start(); return queue;
}
com.duowan.mobile.netroid.RequestQueue.start():
/**
* Starts the dispatchers in this queue.
*/
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start(); // Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
//一个线程,从请求队列中获取任务并运行
NetworkDispatcher networkDispatcher =
new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
//Thread run()
networkDispatcher.start();
}
} /**
* Stops the cache and network dispatchers.
*/
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (NetworkDispatcher mDispatcher : mDispatchers) {
//Thread interrupt()线程中断
if (mDispatcher != null) mDispatcher.quit();
}
}
框架中对于文件是没有缓存机制的。所以mCacheDispatcher能够不用理它。看一下NetworkDispatcher这个线程做了什么:com.duowan.mobile.netroid.NetworkDispatcher
public class NetworkDispatcher extends Thread { @Override
public void run() {
//设置线程优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request request;
while (true) {
try {
// Take a request from the queue.假设队列为空,则堵塞
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.唯有线程中断的时候mQuit才为true,InterruptedException为中断异常
//mQueue.take()假设队列为null,仅仅会堵塞,不会跑出异常
if (mQuit) return;
continue;
} try {
request.addMarker("network-queue-take");
//准备运行
mDelivery.postPreExecute(request); // If the request was cancelled already,
// do not perform the network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
mDelivery.postCancel(request);
mDelivery.postFinish(request);
continue;
} // Perform the network request.最重要一步。Netroid实例化的BasicNetwork在这里运行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete"); // Parse the response here on the worker thread.重命名一下。没做什么
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete"); // Write to cache if applicable.
if (mCache != null && request.shouldCache() && response.cacheEntry != null) {
response.cacheEntry.expireTime = request.getCacheExpireTime();
mCache.putEntry(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
} // Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (NetroidError netroidError) {
mDelivery.postError(request, request.parseNetworkError(netroidError));
} catch (Exception e) {
NetroidLog.e(e, "Unhandled exception %s", e.toString());
mDelivery.postError(request, new NetroidError(e));
}
}
} }
这里最重要的一步就是NetworkResponse networkResponse = mNetwork.performRequest(request);运行网络请求,可是我们不要忘记我们的mQueue还是空的,mQueue.take()正在堵塞着呢,所以,如今还没有办法进行网络请求,因此我们须要在mQueue中填充任务,才干进行我们的网络请求。
不要忘记这里哦。由于我们还会回到这里!
第二步:创建一个文件下载管理器:new FileDownloader(queue, 1)
mDownloder = new FileDownloader(queue, 1) {
@Override
public FileDownloadRequest buildRequest(String storeFilePath, String url) {
return new FileDownloadRequest(storeFilePath, url) {
@Override
public void prepare() {
addHeader("Accept-Encoding", "identity");
super.prepare();
}
};
}
};
这里有没有看着非常吓人,我起初看的时候也吓了一跳,事实上就是实例化的时候,顺手override了一下
/** The parallel task count, recommend less than 3. */
private final int mParallelTaskCount; /** The linked Task Queue. */
private final LinkedList<DownloadController> mTaskQueue; /**
* Construct Downloader and init the Task Queue.
* @param queue The RequestQueue for dispatching Download task.
* @param parallelTaskCount
* Allows parallel task count,
* don't forget the value must less than ThreadPoolSize of the RequestQueue.
*/
public FileDownloader(RequestQueue queue, int parallelTaskCount) {
if (parallelTaskCount >= queue.getThreadPoolSize()) {
throw new IllegalArgumentException("parallelTaskCount[" + parallelTaskCount
+ "] must less than threadPoolSize[" + queue.getThreadPoolSize() + "] of the RequestQueue.");
} mTaskQueue = new LinkedList<DownloadController>();
mParallelTaskCount = parallelTaskCount;
mRequestQueue = queue;
}
这里是须要注意的一点,mParallelTaskCount并发的数量最好<3.
第三步:将下载任务加入到队列,task.controller = mDownloder.add(mSaveDirPath + task.storeFileName, task.url, new Listener<Void>():
/**
* Create a new download request, this request might not run immediately because the parallel task limitation,
* you can check the status by the {@link DownloadController} which you got after invoke this method.
*
* Note: don't perform this method twice or more with same parameters, because we didn't check for
* duplicate tasks, it rely on developer done.
*
* Note: this method should invoke in the main thread.
*
* @param storeFilePath Once download successed, we'll find it by the store file path.
* @param url The download url.
* @param listener The event callback by status;
* @return The task controller allows pause or resume or discard operation.
*/
public DownloadController add(String storeFilePath, String url, Listener<Void> listener) {
// only fulfill requests that were initiated from the main thread.(reason for the Delivery?)
//看名字就知道
throwIfNotOnMainThread();
//创建一个下载控制器
DownloadController controller = new DownloadController(storeFilePath, url, listener);
synchronized (mTaskQueue) {
//这可不是mQueue,这里仅仅是一个DownloadController的LinkedList集合
mTaskQueue.add(controller);
}
//重点来了
schedule();
return controller;
}
/**
* Traverse the Task Queue, count the running task then deploy more if it can be.
*/
private void schedule() {
// make sure only one thread can manipulate the Task Queue.
synchronized (mTaskQueue) {
// counting ran task.
int parallelTaskCount = 0;
for (DownloadController controller : mTaskQueue) {
//累计队列中正在下载的的任务数
if (controller.isDownloading()) parallelTaskCount++;
}
//当正在下载的个数大于并行任务数的时候,不在运行下载任务
/*
* 这里举个样例说明一下:我们默认mParallelTaskCount=1
* 当我们加入第一个任务的时候。这个的controller.isDownloading()肯定是false
* 所以parallelTaskCount >= mParallelTaskCount是不成立的,当我们再加入一个任务的时候,如今mTaskQueue.size是2了
* 且第一个isDownloading,为了保证并发数量为1,会return,说的有点乱。不知道说明确了没有
*/
if (parallelTaskCount >= mParallelTaskCount) return; // try to deploy all Task if they're await.
for (DownloadController controller : mTaskQueue) {
//deploy(),将任务加入到队列中
if (controller.deploy() && ++parallelTaskCount == mParallelTaskCount) return;
}
}
}
/**
* For the parallel reason, only the {@link FileDownloader#schedule()} can call this method.
* @return true if deploy is successed.
*/
private boolean deploy() {
if (mStatus != STATUS_WAITING) return false;
//第二步我说非常吓人那个地方
mRequest = buildRequest(mStoreFilePath, mUrl); // we create a Listener to wrapping that Listener which developer specified,
// for the onFinish(), onSuccess(), onError() won't call when request was cancel reason.
mRequest.setListener(new Listener<Void>() {
boolean isCanceled; @Override
public void onPreExecute() {
mListener.onPreExecute();
} @Override
public void onFinish() {
// we don't inform FINISH when it was cancel.
if (!isCanceled) {
mStatus = STATUS_PAUSE;
mListener.onFinish();
// when request was FINISH, remove the task and re-schedule Task Queue.
// remove(DownloadController.this);
}
} @Override
public void onSuccess(Void response) {
// we don't inform SUCCESS when it was cancel.
if (!isCanceled) {
mListener.onSuccess(response);
mStatus = STATUS_SUCCESS;
remove(DownloadController.this);
}
} @Override
public void onError(NetroidError error) {
// we don't inform ERROR when it was cancel.
if (!isCanceled) mListener.onError(error);
} @Override
public void onCancel() {
mListener.onCancel();
isCanceled = true;
} @Override
public void onProgressChange(long fileSize, long downloadedSize) {
mListener.onProgressChange(fileSize, downloadedSize);
}
}); mStatus = STATUS_DOWNLOADING;
//我擦,最终把任务加到队列中了
mRequestQueue.add(mRequest);
return true;
}
mRequestQueue.add(mRequest);任务加到队列中了,都到了这里了看一下怎么加的吧
public Request add(Request request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
} // Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue"); // If the request is uncacheable or forceUpdate, skip the cache queue and go straight to the network.
if (request.isForceUpdate() || !request.shouldCache()) {
mDelivery.postNetworking(request);
mNetworkQueue.add(request);
return request;
} }
request.shouldCache()有兴趣的能够自己去看一下。这里说明了文件下载没有缓存机制,这里就不多说了,由于假设你还没有忘记的话。mQueue.take()还在堵塞着呢。好了让我们回到第一步,运行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
@Override
public NetworkResponse performRequest(Request<?> request) throws NetroidError {
// Determine if request had non-http perform.
NetworkResponse networkResponse = request.perform();
if (networkResponse != null) return networkResponse; long requestStart = SystemClock.elapsedRealtime();
while (true) {
// If the request was cancelled already,
// do not perform the network request.
if (request.isCanceled()) {
request.finish("perform-discard-cancelled");
mDelivery.postCancel(request);
throw new NetworkError(networkResponse);
} HttpResponse httpResponse = null;
byte[] responseContents = null;
try {
// prepare to perform this request, normally is reset the request headers.
request.prepare(); httpResponse = mHttpStack.performRequest(request); StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseContents = request.handleResponse(httpResponse, mDelivery);
if (statusCode < 200 || statusCode > 299) throw new IOException(); // if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine); return new NetworkResponse(statusCode, responseContents, parseCharset(httpResponse));
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
if (httpResponse == null) throw new NoConnectionError(e); int statusCode = httpResponse.getStatusLine().getStatusCode();
NetroidLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents, parseCharset(httpResponse));
if (statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN) {
attemptRetryOnException("auth", request, new AuthFailureError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(networkResponse);
}
}
}
}
这里我给改了一下,详细的能够看一下作者的。他有一块dead code,网络请求这一块没什么好说的。可是这里有一句非常重要的代码
responseContents = request.handleResponse(httpResponse, mDelivery);。写文件,断点续传的原理
/**
* In this method, we got the Content-Length, with the TemporaryFile length,
* we can calculate the actually size of the whole file, if TemporaryFile not exists,
* we'll take the store file length then compare to actually size, and if equals,
* we consider this download was already done.
* We used {@link RandomAccessFile} to continue download, when download success,
* the TemporaryFile will be rename to StoreFile.
*/
@Override
public byte[] handleResponse(HttpResponse response, Delivery delivery) throws IOException, ServerError {
// Content-Length might be negative when use HttpURLConnection because it default header Accept-Encoding is gzip,
// we can force set the Accept-Encoding as identity in prepare() method to slove this problem but also disable gzip response.
HttpEntity entity = response.getEntity();
//获取文件的总大小
long fileSize = entity.getContentLength();
if (fileSize <= 0) {
NetroidLog.d("Response doesn't present Content-Length!");
} long downloadedSize = mTemporaryFile.length();
/*
* 是否支持断点续传
*
* client每次提交下载请求时。服务端都要加入这两个响应头,以保证client和服务端将此下载识别为能够断点续传的下载:
* Accept-Ranges:告知下载client这是一个能够恢复续传的下载,存放本次下载的開始字节位置、文件的字节大小;
* ETag:保存文件的唯一标识(我在用的文件名称+文件最后改动时间,以便续传请求时对文件进行验证)。
* Last-Modified:可选响应头,存放服务端文件的最后改动时间,用于验证
*/
boolean isSupportRange = HttpUtils.isSupportRange(response);
if (isSupportRange) {
fileSize += downloadedSize; // Verify the Content-Range Header, to ensure temporary file is part of the whole file.
// Sometime, temporary file length add response content-length might greater than actual file length,
// in this situation, we consider the temporary file is invalid, then throw an exception.
String realRangeValue = HttpUtils.getHeader(response, "Content-Range");
// response Content-Range may be null when "Range=bytes=0-"
if (!TextUtils.isEmpty(realRangeValue)) {
String assumeRangeValue = "bytes " + downloadedSize + "-" + (fileSize - 1);
if (TextUtils.indexOf(realRangeValue, assumeRangeValue) == -1) {
throw new IllegalStateException(
"The Content-Range Header is invalid Assume[" + assumeRangeValue + "] vs Real[" + realRangeValue + "], " +
"please remove the temporary file [" + mTemporaryFile + "].");
}
}
} // Compare the store file size(after download successes have) to server-side Content-Length.
// temporary file will rename to store file after download success, so we compare the
// Content-Length to ensure this request already download or not.
if (fileSize > 0 && mStoreFile.length() == fileSize) {
// Rename the store file to temporary file, mock the download success. ^_^
mStoreFile.renameTo(mTemporaryFile); // Deliver download progress.
delivery.postDownloadProgress(this, fileSize, fileSize); return null;
}
//之所以能够实现断点续传的原因所在
RandomAccessFile tmpFileRaf = new RandomAccessFile(mTemporaryFile, "rw"); // If server-side support range download, we seek to last point of the temporary file.
if (isSupportRange) {
//移动文件读写指针位置
tmpFileRaf.seek(downloadedSize);
} else {
// If not, truncate the temporary file then start download from beginning.
tmpFileRaf.setLength(0);
downloadedSize = 0;
} try {
InputStream in = entity.getContent();
// Determine the response gzip encoding, support for HttpClientStack download.
if (HttpUtils.isGzipContent(response) && !(in instanceof GZIPInputStream)) {
in = new GZIPInputStream(in);
}
byte[] buffer = new byte[6 * 1024]; // 6K buffer
int offset; while ((offset = in.read(buffer)) != -1) {
//写文件
tmpFileRaf.write(buffer, 0, offset); downloadedSize += offset;
long currTime = SystemClock.uptimeMillis();
//控制下载进度的速度
if (currTime - lastUpdateTime >= DEFAULT_TIME) {
lastUpdateTime = currTime;
delivery.postDownloadProgress(this, fileSize,
downloadedSize);
} if (isCanceled()) {
delivery.postCancel(this);
break;
}
}
} finally {
try {
// Close the InputStream and release the resources by "consuming the content".
if (entity != null) entity.consumeContent();
} catch (Exception e) {
// This can happen if there was an exception above that left the entity in
// an invalid state.
NetroidLog.v("Error occured when calling consumingContent");
}
tmpFileRaf.close();
} return null;
}
实现断点续传主要靠的RandomAccessFile,你假设对c语言不陌生的话tmpFileRaf.seek(downloadedSize)和int fseek(FILE *stream, long
offset, int fromwhere);是不是有点眼熟,仅仅与RandomAccessFile就不说了。
好了,Netroid的原理基本上就是这些了,讲一下我用的时候遇到的两个问题:
1.下载进度的速度太快。你假设用notifition来显示,会出现ANR,所以我们要控制一下它的速度,详细方法在上面
//控制下载进度的速度
if (currTime - lastUpdateTime >= DEFAULT_TIME) {
lastUpdateTime = currTime;
delivery.postDownloadProgress(this, fileSize,
downloadedSize);
}
2.第二个问题是当你下载的时候。假设把WiFi关掉。即使没下完。也会被标记为done,改动主要是在在FileDownloader.DownloadController的deploy()中
@Override
public void onFinish() {
// we don't inform FINISH when it was cancel.
if (!isCanceled) {
mStatus = STATUS_PAUSE;
mListener.onFinish();
// when request was FINISH, remove the task and re-schedule Task Queue.
// remove(DownloadController.this);
}
} @Override
public void onSuccess(Void response) {
// we don't inform SUCCESS when it was cancel.
if (!isCanceled) {
mListener.onSuccess(response);
mStatus = STATUS_SUCCESS;
remove(DownloadController.this);
}
}
把onFinish的status改成STATUS_PAUSE。并去掉remove(DownloadController.this);。在onSuccess中再将status改动为STATUS_SUCCESS,并remove,当然这个办法治标不治本。假设有谁知道请告之,谢谢。
Android Netroid解析之——断点续传下载及问题修正的更多相关文章
- Android实现网络多线程断点续传下载(转)
本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...
- Android实现网络多线程断点续传下载
本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...
- 网络编程之PC版与Android手机版带断点续传的多线程下载
一.多线程下载 多线程下载就是抢占服务器资源 原理:服务器CPU 分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服 ...
- 实现android支持多线程断点续传下载器功能
多线程断点下载流程图: 多线程断点续传下载原理介绍: 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度手机端下载数据时难免会出现无信号断线.电量不足等情况,所以需要断点续传功能根据下 ...
- android 多线程断点续传下载
今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...
- Android开发多线程断点续传下载器
使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点 ...
- Android之断点续传下载
今天学习了Android开发中比较难的一个环节,就是断点续传下载,很多人看到这个标题就感觉头大,的确,如果没有良好的逻辑思维,这块的确很难搞明白.下面我就将自己学到的知识和一些见解写下供那些在这个环节 ...
- [Android实例] Android之断点续传下载
在我们做开发的时候经常遇到的就是下载了,现在下载的方法有很多很多,那么怎么做到断点续传下载呢!很多人都头疼这个问题,如果我们没有很好的逻辑真不是很容易解决啊.我参考了一下前辈们的资料了整理了一个项目, ...
- Android网络多线程断点续传下载
本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...
随机推荐
- 面向对象(OOP)五大基本原则
书单 <Object-Oriented Analysis & Design with Application>:Grady Booch, 下载地址:object-oriented- ...
- 微信小程序领取卡券
微信小程序领取卡券 标签(空格分隔): php 开发前需要准备的工作 1 小程序和公众号要有绑定 2 小程序和该公众号要绑定到同一个开发平台下 [https://open.weixin.qq.com/ ...
- 3.Linux系统信息
arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI ...
- yarn平台的任务调度和执行过程
- PostgreSQL Replication之第四章 设置异步复制(5)
4.5 使流复制更健壮 当连接到master时,slave要做的第一件事情是赶上master.但是,这会一直工作吗?我们已经看到,我们可以使用由基于流和基于文件组成的混合设置.这给了我们一些额外的安全 ...
- python制造模块
制造模块: 方法一: 1.mkdir /xxcd /xx 2.文件包含: 模块名.py setup.py setup.py内容如下:#!/usr/bin/env pythonfrom distutil ...
- php动态导出数据成Excel表格
一.封装 Excel 导出类 include/components/ExecExcel.php <?php /*** * @Excel 导入导出类. */ class ExecExcel { / ...
- NodeJS学习笔记 (28)流操作-stream(ok)
模块概览 nodejs的核心模块,基本上都是stream的的实例,比如process.stdout.http.clientRequest. 对于大部分的nodejs开发者来说,平常并不会直接用到str ...
- js001 ---- async
Node.js异步流,详细见https://caolan.github.io/async/docs.html#parallel 1, async 用的比较多的是 waterfall, 瀑布流, 就是每 ...
- Win10+CUDA9.0+cuDNN7.2 下载 安装 配置
官方提示Note: CUDA must be installed only after that MSVS2015 had been installed.安装CUDA前须安装VS2015 cuDNN与 ...