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.pswp.cn/news/388819.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/388819.shtml
英文地址,請注明出處:http://en.pswp.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; -->重要 首先確定設計稿的尺寸…