Volley 源码解析之网络请求

Volley源码分析三部曲
Volley 源码解析之网络请求
Volley 源码解析之图片请求
Volley 源码解析之缓存机制

Volley 是 Google 推出的一款网络通信框架,非常适合数据量小、通信频繁的网络请求,支持并发、缓存和容易扩展、调试等;不过不太适合下载大文件、大量数据的网络请求,因为volley在解析期间将响应放到内存中,我们可以使用okhttp或者系统提供的DownloadManager来下载文件。

一、简单使用

首先在工程引入volley的library:

dependencies {implementation 'com.android.volley:volley:1.1.1'
}
复制代码

然后需要我们打开网络权限,我这里直接贴出官网简单请求的示例代码:

final TextView mTextView = (TextView) findViewById(R.id.text);
// ...// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,new Response.Listener<String>() {@Overridepublic void onResponse(String response) {// Display the first 500 characters of the response string.mTextView.setText("Response is: "+ response.substring(0,500));}
}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {mTextView.setText("That didn't work!");}
});// Add the request to the RequestQueue.
queue.add(stringRequest);
复制代码

使用相对简单,回调直接在主线程,我们取消某个请求直接这样操作:

  1. 定义一个标记添加到requests中

    public static final String TAG = "MyTag";
    StringRequest stringRequest; // Assume this exists.
    RequestQueue mRequestQueue;  // Assume this exists.// Set the tag on the request.
    stringRequest.setTag(TAG);// Add the request to the RequestQueue.
    mRequestQueue.add(stringRequest);
    复制代码
  2. 然后我们可以在 onStop() 中取消所有标记的请求

    @Override
    protected void onStop () {super.onStop();if (mRequestQueue != null) {mRequestQueue.cancelAll(TAG);}
    }
    复制代码

二、源码分析

我们先从Volley这个类入手:

public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {BasicNetwork network;if (stack == null) {if (Build.VERSION.SDK_INT >= 9) {network = new BasicNetwork(new HurlStack());} else {String userAgent = "volley/0";try {String packageName = context.getPackageName();PackageInfo info =context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);userAgent = packageName + "/" + info.versionCode;} catch (NameNotFoundException e) {}network =new BasicNetwork(new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));}} else {network = new BasicNetwork(stack);}return newRequestQueue(context, network);
}private static RequestQueue newRequestQueue(Context context, Network network) {File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);queue.start();return queue;
}public static RequestQueue newRequestQueue(Context context) {return newRequestQueue(context, (BaseHttpStack) null);
}
复制代码

当我们传递一个Context的时候,首先为BaseHttpStack为null,会执行到创建BaseHttpStackBaseHttpStack是一个网络具体的处理请求,Volley默认提供了基于HttpURLCollectionHurlStack和基于HttpClientHttpClientStack。Android6.0移除了HttpClient,Google官方推荐使用HttpURLCollection类作为替换。所以这里在API大于9的版本是用的是HurlStack,为什么这样选择,详情可见这篇博客Android访问网络,使用HttpURLConnection还是HttpClient?。我们使用的是默认的构造,BaseHttpStack传入为null,如果我们想使用自定义的okhttp替换底层,我们直接继承HttpStack重写即可,也可以自定义NetworkRequestQueue,Volley的高扩展性充分体现。接下来则创建一个Network对象,然后实例化RequestQueue,首先创建了一个用于缓存的文件夹,然后创建了一个磁盘缓存,将文件缓存到指定目录的硬盘上,默认大小是5M,但是大小可以配置。接下来调用RequestQueuestart()方法进行启动,我们进入这个方法查看一下:

public void start() {stop(); mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);mCacheDispatcher.start();for (int i = 0; i < mDispatchers.length; i++) {NetworkDispatcher networkDispatcher =new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);mDispatchers[i] = networkDispatcher;networkDispatcher.start();}
}
复制代码

开始启动的时候先停止所有的请求线程和网络缓存线程,然后实例化一个缓存线程并运行,然后一个循环开启DEFAULT_NETWORK_THREAD_POOL_SIZE(4)个网络请求线程并运行,一共就是5个线程在后台运行,不断的等待网络请求的到来。

构造了RequestQueue之后,我们调用add()方法将相应的Request传入就开始执行网络请求了,我们看看这个方法:

public <T> Request<T> add(Request<T> request) {//将请求队列和请求关联起来request.setRequestQueue(this);//添加到正在请求中但是还未完成的集合中synchronized (mCurrentRequests) {mCurrentRequests.add(request);}//设置请求的一个序列号,通过原子变量的incrementAndGet方法,//以原子方式给当前值加1并获取新值实现请求的优先级request.setSequence(getSequenceNumber());//添加一个调试信息request.addMarker("add-to-queue");//如果不需要缓存则直接加到网络的请求队列,默认每一个请求都是缓存的,//如果不需要缓存需要调用Request的setShouldCache方法来修改if (!request.shouldCache()) {mNetworkQueue.add(request);return request;}//加到缓存的请求队列mCacheQueue.add(request);return request;
}
复制代码

关键地方都写了注释,主要作用就是将请求加到请求队列,执行网络请求或者从缓存中获取结果。网络和缓存的请求都是一个优先级阻塞队列,按照优先级出队。上面几个关键步骤,添加到请求集合里面还有设置优先级以及添加到缓存和请求队列都是线程安全的,要么加锁,要么使用线程安全的队列或者原子操作。

接下来我们看看添加到CacheDispatcher缓存请求队列的run方法:

@Override
public void run() {if (DEBUG) VolleyLog.v("start new dispatcher");Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//初始化DiskBasedCache的缓存类mCache.initialize();while (true) {try {processRequest();} catch (InterruptedException e) {if (mQuit) {Thread.currentThread().interrupt();return;}VolleyLog.e("Ignoring spurious interrupt of CacheDispatcher thread; "+ "use quit() to terminate it");}}
}
复制代码

接下来的重点是看看processRequest()这个方法:

private void processRequest() throws InterruptedException {//从缓存队列取出请求final Request<?> request = mCacheQueue.take();processRequest(request);
}@VisibleForTesting
void processRequest(final Request<?> request) throws InterruptedException {request.addMarker("cache-queue-take");// 如果请求被取消,我们可以通过RequestQueue的回调接口来监听if (request.isCanceled()) {request.finish("cache-discard-canceled");return;}// 从缓存中获取Cache.EntryCache.Entry entry = mCache.get(request.getCacheKey());//没有取到缓存if (entry == null) {request.addMarker("cache-miss");// 缓存未命中,对于可缓存的请求先去检查是否有相同的请求是否已经在运行中,//如果有的话先加入请求等待队列,等待请求完成,返回true;如果返回false则表示第一次请求if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {//加入到网络请求的阻塞队列mNetworkQueue.put(request);}return;}// 如果缓存完全过期,处理过程跟上面类似if (entry.isExpired()) {request.addMarker("cache-hit-expired");//设置请求缓存的entry到这个request中request.setCacheEntry(entry);if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {mNetworkQueue.put(request);}return;}//缓存命中,将数据解析并返回到request的抽象方法中request.addMarker("cache-hit");Response<?> response =request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));request.addMarker("cache-hit-parsed");//判断请求结果是否需要刷新if (!entry.refreshNeeded()) {// 未过期的缓存命中,通过ExecutorDelivery回调给我们的request子类的接口中,// 我们在使用的时候就可以通过StringRequest、JsonRequest等拿到结果,// 切换到主线程也是在这个类里执行的mDelivery.postResponse(request, response);} else {request.addMarker("cache-hit-refresh-needed");request.setCacheEntry(entry);// 将这个响应标记为中间值,即这个响应是新鲜的,那么第二个响应正在请求随时到来response.intermediate = true;if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {//发起网络请求,这里为什么直接调用上面的mNetworkQueue.put(request);呢,//主要是为了添加一个已经分发的标记,在响应分发的时候不再回调给用户,//不然就回调了两次mDelivery.postResponse(request,response,new Runnable() {@Overridepublic void run() {try {mNetworkQueue.put(request);} catch (InterruptedException e) {// Restore the interrupted statusThread.currentThread().interrupt();}}});} else {//这里第三个参数传递null,不用再去分发,因为已经有相同的请求已经在执行,//直接添加到了等待请求的列表中,然后返回的时候从已经执行的请求收到响应mDelivery.postResponse(request, response);}}
}
复制代码

这部分主要是对请求的缓存判断,是否过期以及需要刷新缓存。我们调用取消所有请求或者取消某个请求实质上就是对mCanceled这个变量赋值,然后在缓存线程或者网络线程里面都回去判断这个值,就完成了取消。上面的isExpiredrefreshNeeded,两个区别就是,前者如果过期就直接请求最新的内容,后者就是还在新鲜的时间内,但是把内容返回给用户还是会发起请求,两者一个与ttl值相比,另一个与softTtl相比。

其中有一个WaitingRequestManager,如果有相同的请求那么就需要一个暂存的地方,这个类就是做的这个操作

private static class WaitingRequestManager implements Request.NetworkRequestCompleteListener {//所有等待请求的集合,键是缓存的keyprivate final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>();private final CacheDispatcher mCacheDispatcher;WaitingRequestManager(CacheDispatcher cacheDispatcher) {mCacheDispatcher = cacheDispatcher;}//请求接受到一个有效的响应,后面等待的相同请求就可以使用这个响应@Overridepublic void onResponseReceived(Request<?> request, Response<?> response) {//如果缓存为空或者已经过期,那么就释放等待的请求if (response.cacheEntry == null || response.cacheEntry.isExpired()) {onNoUsableResponseReceived(request);return;}String cacheKey = request.getCacheKey();//等待的请求的集合List<Request<?>> waitingRequests;synchronized (this) {//从map里面移除这个请求的集合waitingRequests = mWaitingRequests.remove(cacheKey);}if (waitingRequests != null) {if (VolleyLog.DEBUG) {VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",waitingRequests.size(), cacheKey);}// 里面所有的请求都分发到相应的回调执行,下面会讲解for (Request<?> waiting : waitingRequests) {mCacheDispatcher.mDelivery.postResponse(waiting, response);}}}//没有收到相应,则需要释放请求@Overridepublic synchronized void onNoUsableResponseReceived(Request<?> request) {String cacheKey = request.getCacheKey();List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);if (waitingRequests != null && !waitingRequests.isEmpty()) {if (VolleyLog.DEBUG) {VolleyLog.v("%d waiting requests for cacheKey=%s; resend to network",waitingRequests.size(), cacheKey);}//下面这个请求执会重新执行,将这个移除添加到Request<?> nextInLine = waitingRequests.remove(0);//将剩下的请求放到等待请求的map中mWaitingRequests.put(cacheKey, waitingRequests);//在request里面注册一个回调接口,因为重新开始请求,需要重新注册一个监听,//后面请求成功失败以及取消都可以收到回调nextInLine.setNetworkRequestCompleteListener(this);try {//从上面if判断方法可以得出:waitingRequests != null && !waitingRequests.isEmpty()//排除了第一次请求失败、取消的情况,后面的那个条件则表示这个等待请求队列必须要有一个请求,//同时满足才会执行这里面的代码,一般只要这里面的请求执行成功一次后续所有的请求都会被移除,//所以这里对多个请求的情况,失败一次,那么后续的请求会继续执行mCacheDispatcher.mNetworkQueue.put(nextInLine);} catch (InterruptedException iex) {VolleyLog.e("Couldn't add request to queue. %s", iex.toString());// Restore the interrupted status of the calling thread (i.e. NetworkDispatcher)Thread.currentThread().interrupt();// Quit the current CacheDispatcher thread.mCacheDispatcher.quit();}}}//对于可以缓存的请求,相同缓存的请求已经在运行中就添加到一个发送队列,//等待运行中的队列请求完成,返回true表示已经有请求在运行,false则是第一次执行private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {String cacheKey = request.getCacheKey();// 存在相同的请求则把请求加入到相同缓存键的集合中if (mWaitingRequests.containsKey(cacheKey)) {// There is already a request in flight. Queue up.List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);//如果包含相同的请求但是有可能是第二次请求,前面第一次请求插入null了if (stagedRequests == null) {stagedRequests = new ArrayList<>();}request.addMarker("waiting-for-response");stagedRequests.add(request);mWaitingRequests.put(cacheKey, stagedRequests);if (VolleyLog.DEBUG) {VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);}return true;} else {//第一次请求那么则插入一个null,表示当前有一个请求正在运行mWaitingRequests.put(cacheKey, null);//注册一个接口监听request.setNetworkRequestCompleteListener(this);if (VolleyLog.DEBUG) {VolleyLog.d("new request, sending to network %s", cacheKey);}return false;}}
}
复制代码

这个类主要是避免相同的请求多次请求,而且在NetworkDispatcher里面也会通过这个接口回调相应的值在这里执行,最终比如在网络请求返回304、请求取消或者异常那么都会在这里来处理,如果收到响应则会把值回调给用户,后面的请求也不会再去请求,如果无效的响应则会做一些释放等待的请求操作,请求完成也会将后面相同的请求回调给用户,三个方法都在不同的地方发挥作用。

我们接下来看看NetworkDispatcher网络请求队列的run方法中的processRequest方法:

@VisibleForTesting
void processRequest(Request<?> request) {long startTimeMs = SystemClock.elapsedRealtime();try {request.addMarker("network-queue-take");// 请求被取消了,就不执行网络请求,if (request.isCanceled()) {request.finish("network-discard-cancelled");request.notifyListenerResponseNotUsable();return;}addTrafficStatsTag(request);// 这里就是执行网络请求的地方NetworkResponse networkResponse = mNetwork.performRequest(request);request.addMarker("network-http-complete");// 如果服务器返回304响应,即没有修改过,//缓存依然是有效的并且是在需要刷新的有效期内,那么则不需要解析响应if (networkResponse.notModified && request.hasHadResponseDelivered()) {request.finish("not-modified");//没有收到来自网络的有效响应,释放请求request.notifyListenerResponseNotUsable();return;}// 在工作线程中解析这些响应Response<?> response = request.parseNetworkResponse(networkResponse);request.addMarker("network-parse-complete");// 将缓存写入到应用if (request.shouldCache() && response.cacheEntry != null) {mCache.put(request.getCacheKey(), response.cacheEntry);request.addMarker("network-cache-written");}// 标记此请求已将分发request.markDelivered();//将请求的响应回调给用户mDelivery.postResponse(request, response);//请求接受到了一个响应,其他相同的请求可以使用这个响应request.notifyListenerResponseReceived(response);} catch (VolleyError volleyError) {...}
}
复制代码

这里才是网络请求的真正执行以及解析分发的地方,重点看两个地方的代码,执行和解析,我们先看看执行网络请求这个代码,执行的地方是BasicNetwork.performRequest,下面看看这个方法:

@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {long requestStart = SystemClock.elapsedRealtime();while (true) {HttpResponse httpResponse = null;byte[] responseContents = null;List<Header> responseHeaders = Collections.emptyList();try {// 构造缓存的头部,添加If-None-Match和If-Modified-Since,都是http/1.1中控制协商缓存的两个字段,   			  // If-None-Match:客服端再次发起请求时,携带上次请求返回的唯一标识Etag值,//服务端用携带的值和最后修改的值作对比,最后修改时间大于携带的字段值则返回200,否则304;// If-Modified-Since:客服端再次发起请求时,携带上次请求返回的Last-Modified值,//服务端用携带的值和服务器的Etag值作对比,一致则返回304Map<String, String> additionalRequestHeaders =getCacheHeaders(request.getCacheEntry());//因为现在一般的sdk都是大于9的,那么这里执行的就是HurlStack的executeRequest方法,//执行网络请求,和我们平时使用HttpURLConnection请求网络大致相同httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);int statusCode = httpResponse.getStatusCode();responseHeaders = httpResponse.getHeaders();// 服务端返回304时,那么就表示资源无更新,可以继续使用缓存的值if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {Entry entry = request.getCacheEntry();if (entry == null) {return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED,/* data= */ null,/* notModified= */ true,SystemClock.elapsedRealtime() - requestStart,responseHeaders);}// 将缓存头和响应头组合在一起,一次响应就完成了List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED,entry.data,/* notModified= */ true,SystemClock.elapsedRealtime() - requestStart,combinedHeaders);}// 如果返回204,执行成功,没有数据,这里需要检查InputStream inputStream = httpResponse.getContent();if (inputStream != null) {responseContents =inputStreamToBytes(inputStream, httpResponse.getContentLength());} else {//返回204,就返回一个空的byte数组responseContents = new byte[0];}// if the request is slow, log it.long requestLifetime = SystemClock.elapsedRealtime() - requestStart;logSlowRequests(requestLifetime, request, responseContents, statusCode);if (statusCode < 200 || statusCode > 299) {throw new IOException();}return new NetworkResponse(statusCode,responseContents,/* notModified= */ false,SystemClock.elapsedRealtime() - requestStart,responseHeaders);} catch (SocketTimeoutException e) {//异常进行重新请求等...}}
}
复制代码

这里主要执行了添加缓存头并发起网络请求,然后将返回值组装成一个NetworkResponse值返回,接下来我们看看是如何解析这个值的,解析是由Request的子类去实现的,我们就看系统提供的StringRequest

@Override
@SuppressWarnings("DefaultCharset")
protected Response<String> parseNetworkResponse(NetworkResponse response) {String parsed;try {parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));} catch (UnsupportedEncodingException e) {// Since minSdkVersion = 8, we can't call// new String(response.data, Charset.defaultCharset())// So suppress the warning instead.parsed = new String(response.data);}return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
复制代码

我们可以看到将值组装成一个String,然后组装成一个Response返回,接下来看看这里如何将这个值回调给用户的这个方法mDelivery.postResponse(request, response),这里我们先重点看看这个类ExecutorDelivery:

public class ExecutorDelivery implements ResponseDelivery {//构造执行已提交的Runnable任务对象private final Executor mResponsePoster;//这里在RequestQueue构造参数中初始化,new ExecutorDelivery(new Handler(Looper.getMainLooper())),//那么这里runnable就通过绑定主线程的Looper的Handler对象投递到主线程中执行public ExecutorDelivery(final Handler handler) {// Make an Executor that just wraps the handler.mResponsePoster =new Executor() {@Overridepublic void execute(Runnable command) {handler.post(command);}};}public ExecutorDelivery(Executor executor) {mResponsePoster = executor;}//这个方法就是我们NetworkDispatcher里面调用的方法,调用下面这个三个参数的构造方法@Overridepublic void postResponse(Request<?> request, Response<?> response) {postResponse(request, response, null);}@Overridepublic void postResponse(Request<?> request, Response<?> response, Runnable runnable) {request.markDelivered();request.addMarker("post-response");//构造了一个ResponseDeliveryRunnable类,传入execute,现在这个runnable就是在主线程里执行mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));}@Overridepublic void postError(Request<?> request, VolleyError error) {request.addMarker("post-error");Response<?> response = Response.error(error);mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));}/** A Runnable used for delivering network responses to a listener on the main thread. */@SuppressWarnings("rawtypes")private static class ResponseDeliveryRunnable implements Runnable {private final Request mRequest;private final Response mResponse;private final Runnable mRunnable;public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {mRequest = request;mResponse = response;mRunnable = runnable;}@SuppressWarnings("unchecked")@Overridepublic void run() {//请求取消,那么就不分发给用户if (mRequest.isCanceled()) {mRequest.finish("canceled-at-delivery");return;}// 根据isSuccess这个值来提供相应的回调给用户,调用Response会通过error的值是否为null来确定这个值,//我们调用VolleyError这个构造函数的时候就为这个值就为falseif (mResponse.isSuccess()) {mRequest.deliverResponse(mResponse.result);} else {mRequest.deliverError(mResponse.error);}// 如果这是一个在新鲜的时间内的请求的响应,就添加一个标记,否则就结束if (mResponse.intermediate) {mRequest.addMarker("intermediate-response");} else {mRequest.finish("done");}// 在CacheDispatcher里面当请求第一次请求时直接调用三个参数的构造方法,通过这个runnable就执行run方法if (mRunnable != null) {mRunnable.run();}}}
}复制代码

上面方法主要是将值回调给用户,那么整个网络请求大致就完成了,其中还涉及很多细节的东西,但是大致流程是走通了,不得不说这个库有很多值得我们学习的地方。

三、总结

现在我们看官网的一张图,总结一下整个流程:

  • 蓝色是主线程
  • 绿色是缓存线程
  • 黄色是网络线程

我们可以看到首先是请求添加到RequestQueue里,首先是添加到缓存队列,然后查看是否已经缓存,如果有并且在有效期内的缓存直接回调给用户,如果没有查找到,那么则需要添加到网络请求队列重新请求并且解析响应、写入缓存在发送到主线程给用户回调。

参考以及相关链接

  • 【第1250期】彻底理解浏览器的缓存机制
  • Android Volley完全解析(四),带你从源码的角度理解Volley
  • Volley 源码解析
  • Volley 源码解析

转载于:https://juejin.im/post/5c1c58b35188251f1f320e70

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/388819.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么修改了ie级别里的activex控件为启用后,还是无法下载,显示还是ie级别设置太高?

如果下载插件时下载不了&#xff0c;这样设置&#xff0c;打开IE选工具/Internet 选项/安全/自定义级别/设置中的ActiveX控件自动提示“禁用”。 对标记为可安全执行脚本ActiveX控件执行脚本“启用” 对没有标记为安全的ActiveX初始化和脚本运行“启用”&#xff08;下载插件后…

mysql 迁移到tidb_通过从MySQL迁移到TiDB来水平扩展Hive Metastore数据库

mysql 迁移到tidbIndustry: Knowledge Sharing行业&#xff1a;知识共享 Author: Mengyu Hu (Platform Engineer at Zhihu)作者&#xff1a;胡梦瑜(Zhhu的平台工程师) Zhihu which means “Do you know?” in classical Chinese, is the Quora of China: a question-and-ans…

两个日期相差月份 java_Java获取两个指定日期之间的所有月份

String y1 "2016-02";//开始时间String y2 "2019-12";//结束时间try{Date startDate new SimpleDateFormat("yyyy-MM").parse(y1);Date endDate new SimpleDateFormat("yyyy-MM").parse(y2);Calendar calendarCalendar.getInstance(…

js前端日期格式化处理

js前端日期格式化处理 1.项目中时间返回值&#xff0c;很过时候为毫秒值&#xff0c;我们需要转换成 能够看懂的时间的格式&#xff1b; 例如&#xff1a; ​ yyyy-MM-dd HH:mm:ss 2.处理方法&#xff08;处理方法有多种&#xff0c;可以传值到前端处理&#xff0c;也可以后台可…

如何用sysbench做好IO性能测试

sysbench 是一个非常经典的综合性能测试工具&#xff0c;通常都用它来做数据库的性能压测&#xff0c;但也可以用来做CPU&#xff0c;IO的性能测试。而对于IO测试&#xff0c;不是很推荐sysbench&#xff0c;倒不是说它有错误&#xff0c;工具本身没有任何问题&#xff0c;它的…

XCode、Objective-C、Cocoa 说的是几样东西

大部分有一点其他平台开发基础的初学者看到XCode&#xff0c;第一感想是磨拳擦掌&#xff0c;看到 Interface Builder之后&#xff0c;第一感想是跃跃欲试&#xff0c;而看到Objective-C的语法&#xff0c;第一感想就变成就望而却步了。好吧&#xff0c;我是在说我自己。 如果…

java http2_探索HTTP/2: HTTP 2协议简述(原)

探索HTTP/2: HTTP/2协议简述HTTP/2的协议包含着两个RFC&#xff1a;Hypertext Transfer Protocol Version 2 (RFC7540)&#xff0c;即HTTP/2&#xff1b;HPACK: Header Compression for HTTP/2 (RFC7541)&#xff0c;即HPACK。RFC7540描述了HTTP/2的语义&#xff0c;RFC7541则描…

错误处理

错误处理&#xff1a; 许多系统调用和函数在失败后&#xff0c;会在失败时设置外部变量errno的值来指明失败原因。许多不同的函数库都把这个变量作为报告错误的标准方法。程序必须在函数报告出错后立刻检查errno变量&#xff0c;因为它可能被下一个函数调用所覆盖&#xff…

Android类库介绍

Android类库介绍 GPhone开发包Android SDK含了很多丰富的类库&#xff1a; android.util 涉及系统底层的辅助类库 android.os 提供了系统服务、消息传输、IPC管道 android.graphics GPhone图形库&#xff0c;包含了文本显示、输入输出、文字样式 android.database 包含底层的AP…

递归函数基例和链条_链条和叉子

递归函数基例和链条因果推论 (Causal Inference) This is the fifth post on the series we work our way through “Causal Inference In Statistics” a nice Primer co-authored by Judea Pearl himself.这是本系列的第五篇文章&#xff0c;我们通过“因果统计推断”一书进行…

前端技能拾遗

本文主要是对自己前端知识遗漏点的总结和归纳&#xff0c;希望对大家有用&#xff0c;会持续更新的~ 解释语言和编译型语言 解释型语言与编译型语言的区别翻译时间的不同。 编译型语言在程序执行之前&#xff0c;有一个单独的编译过程&#xff0c;将程序翻译成机器语言&#xf…

java lock 信号_java各种锁(ReentrantLock,Semaphore,CountDownLatch)的实现原理

先放结论&#xff1a;主要是实现AbstractQueuedSynchronizer中进入和退出函数&#xff0c;控制不同的进入和退出条件&#xff0c;实现适用于各种场景下的锁。JAVA中对于线程的同步提供了多种锁机制&#xff0c;比较著名的有可重入锁ReentrantLock&#xff0c;信号量机制Semapho…

Intent.ACTION_MAIN

1 Intent.ACTION_MAIN String: android.intent.action.MAIN 标识Activity为一个程序的开始。比较常用。 Input:nothing Output:nothing 例如&#xff1a; 1 <activity android:name".Main"android:label"string/app_name">2 <intent-filter…

足球预测_预测足球热

足球预测By Aditya Pethe通过阿蒂亚皮特(Aditya Pethe) From September to January every year, football takes over America. Games dominate TV Sunday and Monday nights, and my brother tears his hair out each week over his consistently underperforming fantasy te…

C#的特性Attribute

一、什么是特性 特性是用于在运行时传递程序中各种元素&#xff08;比如类、方法、结构、枚举、组件等&#xff09;的行为信息的声明性标签&#xff0c;这个标签可以有多个。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号&am…

java 技能鉴定_JAVA试题-技能鉴定

一、单选题1.以下创建了几个对象( B)String A,B,CA"a";B"b":AAB;StringBuffer Dnew StringBuffer("abc");DD.append("567");A.6B.4C.3D.52.关于以下程序段&#xff0c;正确的说法是( C )1&#xff0e;String s1“a”“b”;2&#xff0…

ADD_SHORTCUT_ACTION

String ADD_SHORTCUT_ACTION 动作&#xff1a;在系统中添加一个快捷方式。. “android.intent.action.ADD_SHORTCUT”   String ALL_APPS_ACTION 动作&#xff1a;列举所有可用的应用。   输入&#xff1a;无。 “android.intent.action.ALL_APPS”   String ALTERNATIVE…

python3中朴素贝叶斯_贝叶斯统计:Python中从零开始的都会都市

python3中朴素贝叶斯你在这里 (You are here) If you’re reading this, odds are: (1) you’re interested in bayesian statistics but (2) you have no idea how Markov Chain Monte Carlo (MCMC) sampling methods work, and (3) you realize that all but the simplest, t…

java映射的概念_Java 反射 概念理解

文章来源:http://hollischuang.gitee.io/tobetopjavaer/#/basics/java-basic/reflection反射反射机制指的是程序在运行时能够获取自身的信息。在java中&#xff0c;只要给定类的名字&#xff0c;那么就可以通过反射机制来获得类的所有属性和方法。反射有什么作用在运行时判断任…

【转载】移动端布局概念总结

布局准备工作及布局思想及概念: 一个显示器&#xff08;pc端显示器 及 手机屏显示器&#xff09;&#xff0c;既有物理像素&#xff0c;又有独立像素&#xff08;独立像素也叫作css像素&#xff0c;用于前端人员使用&#xff09;&#xff1b; -->重要 首先确定设计稿的尺寸…