HttpClient、OKhttp、RestTemplate对比
一、三者的对比
- HttpClient:代码复杂,还得操心资源回收等。
代码很复杂,冗余代码多,不建议直接使用。
- RestTemplate: 是
Spring 提供的用于访问Rest服务的客户端
, RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。 - okhttp:OkHttp是一个高效的HTTP客户端,
允许所有同一个主机地址的请求共享同一个socket连接
;连接池减少请求延时;透明的GZIP压缩减少响应数据的大小;缓存响应内容,避免一些完全重复的请求
二、HttpClient的使用
HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包
,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。
- 使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。
- 创建HttpClient对象。
- 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
- 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
- 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
- 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
- 释放连接。无论执行方法是否成功,都必须释放连接
- pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency><!--json--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.0</version></dependency>
- httpClient–Get
public class HttpClientExample_get {public static void main(String[] args) throws IOException {HttpGet request = new HttpGet("https://httpbin.org/get");// add request headersrequest.addHeader("custom-key", "test");request.addHeader(HttpHeaders.USER_AGENT, "Googlebot");try (CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = httpClient.execute(request)) {// Get HttpResponse StatusSystem.out.println(response.getProtocolVersion()); // HTTP/1.1System.out.println(response.getStatusLine().getStatusCode()); // 200System.out.println(response.getStatusLine().getReasonPhrase()); // OKSystem.out.println(response.getStatusLine().toString()); // HTTP/1.1 200 OKHttpEntity entity = response.getEntity();if (entity != null) {// return it as a StringString result = EntityUtils.toString(entity);System.out.println(result);}}}
}
- httpClient–Post
public class HttpClientExample_post {public static void main(String[] args) {try {String result = sendPOST("https://httpbin.org/post");System.out.println(result);} catch (IOException e) {e.printStackTrace();}}private static String sendPOST(String url) throws IOException {String result = "";HttpPost post = new HttpPost(url);// add request parameters or form parametersList<NameValuePair> urlParameters = new ArrayList<>();urlParameters.add(new BasicNameValuePair("username", "abc"));urlParameters.add(new BasicNameValuePair("password", "123"));urlParameters.add(new BasicNameValuePair("custom", "secret"));post.setEntity(new UrlEncodedFormEntity(urlParameters));try (CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = httpClient.execute(post)){result = EntityUtils.toString(response.getEntity());}return result;}
}
- httpClient–PostWithJson
public class HttpClientExample_JsonPost {public static void main(String[] args) {try {String result = sendPOST("https://httpbin.org/post");System.out.println(result);} catch (IOException e) {e.printStackTrace();}}private static String sendPOST(String url) throws IOException {String result = "";HttpPost post = new HttpPost(url);String json = "{" +"\"name\":\"mkyong\"," +"\"notes\":\"hello\"" +"}";// send a JSON datapost.setEntity(new StringEntity(json));try (CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = httpClient.execute(post)) {result = EntityUtils.toString(response.getEntity());}return result;}
}
- HttpClient认证
public class HttpClientAuthentication {public static void main(String[] args) throws IOException {HttpGet request = new HttpGet("http://localhost:8080/books");CredentialsProvider provider = new BasicCredentialsProvider();provider.setCredentials(AuthScope.ANY,new UsernamePasswordCredentials("user", "password"));try (CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();CloseableHttpResponse response = httpClient.execute(request)) {// 401 if wrong user/passwordSystem.out.println(response.getStatusLine().getStatusCode());HttpEntity entity = response.getEntity();if (entity != null) {// return it as a StringString result = EntityUtils.toString(entity);System.out.println(result);}}}
}
参考文章
参考文章
三、OKHttp的使用
出现背景
网络访问的高效性要求,可以说是为高效而生
解决思路
- 提供了对 HTTP/2 和 SPDY 的支持,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接
- 如果 HTTP/2 和 SPDY 不可用,OkHttp会使用连接池来复用连接以提高效率
- 提供了对 GZIP 的默认支持来降低传输内容的大小
- 提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求
- 当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址
- OkHttp3设计思路
- Requests(请求): 每一个HTTP请求中都应该包含一个URL,一个GET或POST方法以及Header或其他参数,当然还可以含特定内容类型的数据流。
- Responses(响应): 响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。
- 代码
- pom.xml
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.1</version>
</dependency>
- 创建OkHttpClient实例
简单来说,通过OkHttpClient可以发送一个Http请求,并读取该Http请求的响应,它是一个生产Call的工厂
。此外,受益于一个共享的响应缓存/线程池/复用的连接等因素,绝大多数应用使用一个OkHttpClient实例,便可以满足整个应用的Http请求。
三种创建实例的方法:
- 创建一个默认配置OkHttpClient,可以使用默认的构造函数。
- 通过new OkHttpClient.Builder()方法来一步一步配置一个OkHttpClient实例。
- 如果要求使用现有的实例,可以通过newBuilder()方法来进行构造。
OkHttpClient client = new OkHttpClient();
OkHttpClient clientWith30sTimeout = client.Builder().readTimeout(30, TimeUnit.SECONDS).build();
OkHttpClient client = client.newBuilder().build();
看一下OkHttpClient的源码,会发现缓存/代理等等需求,一应俱全的按照类封装到了Builder中。
Dispatcher dispatcher; // 分发
Proxy proxy; // 代理
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>(); // 拦截器
final List<Interceptor> networkInterceptors = new ArrayList<>(); // 网络拦截器
ProxySelector proxySelector;
CookieJar cookieJar;
Cache cache; // 缓存
InternalCache internalCache;
SocketFactory socketFactory;
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator; // 代理证书
Authenticator authenticator; // 证书
ConnectionPool connectionPool;
Dns dns; // DNS
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
- okhttp–get
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {Request request = new Request.Builder().url(url).build();Response response = client.newCall(request).execute();return response.body().string();
}
- Request
简单看一下Request类,可以发现它代表一个Http请求,需要注意的是Request一旦build()之后,便不可修改。主要通过new Request.Builder()来一步一步构造的
。看一下Builder的代码。
public Builder() {this.method = "GET";this.headers = new Headers.Builder();
}
- 默认是Get方法,
此外还创建了头信息。Headers类中是通过List<String> namesAndValues = new ArrayList<>(20)
,来存放头信息的,一开始我也很纳闷,头信息都是一对一对的为什么要用List,看一下源码发现,在存取的时候都是将索引+2或者-2。并且头信息可以存在多个相同的Key信息。
- 发起请求
跟到newCall()方法中发现,又使用OkHttpClient实例和Request的实例,一起构造了一个RealCall的实例。RealCall类简单做了一个托管并通过Dispather类对请求进行分发和执行,实际开启线程发起请求的方法就在这个类中
。随后又调用execute()方法,拿到了一个响应。这个execute()方法,实际上执行的就是RealCall中的execute()方法,最后调用了Dispatcher的execute()方法。
- Response
Response代表一个Http的响应,这个类的实例不可修改。
一个简单的Get请求和说明就结束了
- okhttp–post
- POST提交字符串
public static final MediaType JSON= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {RequestBody body = RequestBody.create(JSON, json);Request request = new Request.Builder().url(url).post(body).build();Response response = client.newCall(request).execute();return response.body().string();
}
MediaType用于描述Http请求和响应体的内容类型
,也就是Content-Type。一次请求就是向目标服务器发送一串文本。什么样的文本?有下面结构的文本。
- 例子
POST /meme.php/home/user/login HTTP/1.1
Host: 114.215.86.90
Cache-Control: no-cache
Postman-Token: bd243d6b-da03-902f-0a2c-8e9377f6f6ed
Content-Type: application/x-www-form-urlencodedtel=13637829200&password=123456
例如,MediaType.parse(“application/json; charset=utf-8”);这个就带表请求体的类型为JSON格式的。定义好数据类型,还要将其变为请求体,最后通过post()方法,随请求一并发出。
- POST提交键值对
OkHttp也可以通过POST方式把键值对数据传送到服务器
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {RequestBody formBody = new FormBody.Builder().add("platform", "android").add("name", "bug").add("subject", "XXXXXXXXXXXXXXX").build();Request request = new Request.Builder().url(url).post(formBody).build();Response response = client.newCall(request).execute();if (response.isSuccessful()) {return response.body().string();} else {throw new IOException("Unexpected code " + response);}
}
- HTTP头部的设置和读取
HTTP 头的数据结构是 Map<String, List<String>>类型。
也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。至于name的取值说明,可以查看这个请求头大全。
OkHttp的处理方式是:
- 使用header(name,value)来设置HTTP头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
- 使用addHeader(name,value)来补充新值,如果请求头中已经存在name的name-value,那么还会继续添加,请求头中便会存在多个name相同而value不同的“键值对”。
- 使用header(name)读取唯一值或多个值的最后一个值
- 使用headers(name)获取所有值
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("https://github.com").header("User-Agent", "My super agent").addHeader("Accept", "text/html").build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {throw new IOException("服务器端错误: " + response);
}
System.out.println(response.header("Server"));
System.out.println(response.headers("Set-Cookie"));
参考文章
四、RestTemplate的使用
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。
RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。
- pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 初始化(3种)
RestTemplate restTemplate = new RestTemplate();
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
当然如果想使用OkHttp的话也得引入相应的jar包
- 默认配置
默认情况下RestTemplate自动帮我们注册了一组HttpMessageConverter用来处理一些不同的contentType的请求
。如果现有的转换器不能满足你的需求,你还可以实现org.springframework.http.converter.HttpMessageConverter接口自己写一个。详情参考官方api。其他相关的配置,也可以在官方文档中查看。当然,对于一个请求来说,超期时间,请求连接时间等都是必不可少的参数,为了更好的适应业务需求,所以可以自己修改restTemplate的配置
。
- RestTemplate使用总结
RestTemplate提供了六种常用的HTTP方法实现远程服务调用,
RestTemplate的方法名遵循一定的命名规范,第一部分表示用哪种HTTP方法调用(get,post),第二部分表示返回类型。
getForObject – 发送GET请求,
将HTTP response转换成一个指定的object对象
postForEntity –发送POST请求,
将给定的对象封装到HTTP请求体,返回类型是一个HttpEntity对象
(包含响应数据和响应头)每个HTTP方法对应的RestTemplate方法都有3种。
其中2种的url参数为字符串,URI参数变量分别是Object数组和Map,第3种使用URI类型作为参数
exchange 和execute方法比上面列出的其它方法(如getForObject、postForEntity等)使用范围更广,
允许调用者指定HTTP请求的方法(GET、POST、PUT等),并且可以支持像HTTP PATCH(部分更新)
。
- GET 请求
做好了准备工作,先来看使用 RestTemplate 发送 GET 请求。在 RestTemplate 中,和 GET 请求相关的方法有如下几个:
- 代码
public class TemplateGet {public static void main(String[] args) throws UnsupportedEncodingException {//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));HttpClient httpClient = builder.build();ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);RestTemplate restTemplate = new RestTemplate(requestFactory);//三种方式请求String forObject = restTemplate.getForObject("https://httpbin.org/get", String.class);ResponseEntity<String> forEntity = restTemplate.getForEntity("https://httpbin.org/get", String.class);ResponseEntity<String> responseEntity = restTemplate.exchange("https://httpbin.org/get", HttpMethod.GET, null, String.class);//遍历响应头System.out.println("响应头--start");forEntity.getHeaders().entrySet().forEach(System.out::println);System.out.println("响应头--end");assert forObject != null;byte[] bytes = forObject.getBytes(StandardCharsets.UTF_8);String res = new String(bytes, StandardCharsets.UTF_8);System.out.println(res);System.out.println(forEntity.getBody());System.out.println(responseEntity.getBody());}
}
- post请求
- json形式
public class TemplatePostJson {public static void main(String[] args) {//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));HttpClient httpClient = builder.build();ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);RestTemplate restTemplate = new RestTemplate(requestFactory);String url = "https://httpbin.org/post";String json = "{" +"\"name\":\"mkyong\"," +"\"notes\":\"hello\"" +"}";HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);//Content-Typeheaders.set("CustomHeader", "myCustom"); //Other headersHttpEntity<String> httpEntity = new HttpEntity<>(json, headers);//三种方式请求ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);String s = restTemplate.postForObject(url, httpEntity, String.class);ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);assert s != null;byte[] bytes = s.getBytes(StandardCharsets.UTF_8);String body=new String(bytes,StandardCharsets.UTF_8);System.out.println(body);System.out.println(responseEntity.getBody());System.out.println("响应头--start");stringResponseEntity.getHeaders().entrySet().forEach(System.out::println);System.out.println("响应头--end");}
}
- map(表单)形式
public class TemplatePostMap {public static void main(String[] args) {//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));HttpClient httpClient = builder.build();ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);RestTemplate restTemplate = new RestTemplate(requestFactory);String url = "https://httpbin.org/post";MultiValueMap<Object, Object> map = new LinkedMultiValueMap<>();map.add("aa", "aa");map.add("bb", "bb");map.add("cc", "cc");HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);//Content-Typeheaders.set("CustomHeader", "myCustom"); //Other headersHttpEntity<MultiValueMap<Object, Object>> httpEntity = new HttpEntity<>(map, headers);//三种方式请求ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);String s = restTemplate.postForObject(url, httpEntity, String.class);ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);assert s != null;byte[] bytes = s.getBytes(StandardCharsets.UTF_8);String body=new String(bytes,StandardCharsets.UTF_8);System.out.println(body);System.out.println(responseEntity.getBody());System.out.println("响应头--start");stringResponseEntity.getHeaders().entrySet().forEach(System.out::println);System.out.println("响应头--end");}
}
其实,如果参数是一个 MultiValueMap 的实例,则以 key/value 的形式发送,如果是一个普通对象,则会被转成 json 发送。
- postForLocation
postForLocation 方法的返回值是一个 URL 对象
,因为 POST 请求一般用来添加数据,有的时候需要将刚刚添加成功的数据的 URL 返回来,此时就可以使用这个方法,一个常见的使用场景如用户注册功能,用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。
- PUT 请求
只要将 GET 请求和 POST 请求搞定了,接下来 PUT 请求就会容易很多了,PUT 请求本身方法也比较少,只有三个,如下:
这三个重载的方法其参数其实和 POST 是一样的,可以用 key/value 的形式传参,也可以用 JSON 的形式传参,无论哪种方式,都是没有返回值的
- DELETE 请求
和 PUT 请求一样,DELETE 请求也是比较简单的,只有三个方法,如下:
不同于 POST 和 PUT ,DELETE 请求的参数只能在地址栏传送,可以是直接放在路径中,也可以用 key/value 的形式传递
,当然,这里也是没有返回值的。
public class TemplateDeleteJson {public static void main(String[] args) {//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));HttpClient httpClient = builder.build();ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);RestTemplate restTemplate = new RestTemplate(requestFactory);String url = "https://httpbin.org/delete";String json = "{" +"\"name\":\"mkyong\"," +"\"notes\":\"hello\"" +"}";HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);//Content-Typeheaders.set("CustomHeader", "myCustom"); //Other headersHttpEntity<String> httpEntity = new HttpEntity<>(json, headers);restTemplate.delete(url,httpEntity);}
}
参考文章
参考文章
参考文章
HttpClient、OKhttp、RestTemplate对比相关推荐
- webClient和restTemplate对比
通过一个小案例 将webClient 和 restTemplate 做一个简单的比较. 详细的WebFlux/WebClient 和 Spring MVC/RestTemplate 性能对比请参考:h ...
- 【REST】基于RESTful服务端的客户端实现(HttpClient、RestTemplate、HttpURLConnection)
最近一直在做针对webservice的接口协议对接,总结了一下基于restful服务端的客户端的实现方式,有以下三种: HTTPClient RestTemplate HttpURLConnectio ...
- HttpURLConnection与 HttpClient 区别/性能测试对比
ttpClient是个开源框架,封装了访问http的请求头,参数,内容体,响应等等, HttpURLConnection是java的标准类,什么都没封装,用起来太原始,不方便 HttpClient实际 ...
- Java常用http请求库
文章目录 常用http请求库 1. HttpClient 使用方法 使用示例 2. OKhttp 使用方法 使用示例 3. Retrofit 相关注解 使用方法 使用示例 4. RestTemplat ...
- 我终于决定要放弃 okhttp、httpClient,选择了这个牛逼的神仙工具!贼爽!
点击上方蓝色"方志朋",选择"设为星标"回复"666"获取独家整理的学习资料! 在SpringBoot项目直接使用okhttp.httpCl ...
- 我终于决定要放弃 okhttp、httpClient,选择了这个牛逼的神仙工具!贼爽
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 在SpringBoot项目直接使用okhttp.httpC ...
- 扔掉okhttp、httpClient,来试试这款轻量级HTTP客户端神器?
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者: 伍陆七 juejin.cn/post/689848 ...
- spring resttemplate 中文参数_SpringBoot使用RestTemplate访问第三方接口
养成习惯,先赞后看!!! 前言 相信大家都知道如何在自己的项目里面调用自己的接口,只需要调用自己项目里面接口特定的URL地址就行了,但是如果是调用其他项目的接口呢,这时候如果是直接调用的话,很明显我们 ...
- 框架--NoHttp和OkHttp哪个好用,Volley和NoHttp哪个好用?
NoHttp和OkHttp哪个好用,Volley和NoHttp哪个好用? NoHttp 源码及Demo托管在Github欢迎大家Star: https://github.com/Y0LANDA/NoH ...
最新文章
- STM32单片机外部中断配置讲解
- c实现三角形角度大于一个值_初中数学|高分必备!数学三角形相关知识点梳理汇总...
- PHP 8 确认支持 JIT
- 【小练习02】CSS--网易产品
- Git之深入解析48个经典操作场景的分析和处理,专治不会合并代码
- 【CodeForces - 527C】Glass Carving(线段树或者SBT或者set)
- 通过可视化来了解你的Spark应用程序
- 怎么入门Java?java拦截器怎么配置
- 艾伟:自己实现memcached客户端库
- python多重继承super父类参数_Python super()函数使用及多重继承
- 慎用"加速"一词,可以使用"早日"代替
- 兰州大学计算机英语分数线,兰州大学09MBA分数线A线140综合72英语42
- html5 模拟scrollview,horizontalScrollView
- 查看dll文件的两种办法
- 我37岁,从互联网大厂跳槽到国企后,发现没有一劳永逸的工作。。。
- JAVA前端————HTML—W3C—基本标签—超链接锚链接
- getElementById(‘divid‘).innerHTML赋值【js基础】
- c语言做搬山游戏,C语言实现搬山小游戏,适合新手的项目实战,超易上手!
- 安卓多媒体开发!Android高级工程师面试实战,系列篇
- js实现粒子特效,particles.js的使用