Android常用库源码解析
图片加载框架比较
共同优点
- 都对多级缓存、线程池、缓存算法做了处理
- 自适应程度高,根据系统性能初始化缓存配置、系统信息变更后动态调整策略。比如根据 CPU 核数确定最大并发数,根据可用内存确定内存缓存大小,网络状态变化时调整最大并发数等。
- 支持多种数据源支持多种数据源,网络、本地、资源、Assets 等
不同点
- Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多。
- Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。Glide 支持加载 Gif 动态图,而 Picasso 不支持该特性
- Fresco在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算Fresco>Glide>Picasso
- UIL可以算是老牌最火的图片加载库了,该作者在项目中说明已经停止了对该项目的维护。这就意味着以后任何的 bug 都不会修复,任何的新特性都不会再继续开发,所以毫无疑问 UIL 不推荐在项目中使用了。
图片框架的缓存
- MemorycCache图片内存缓存。默认使用了 LRU 算法。
- DiskCache图片磁盘缓存,默认使用LruDiskCache算法,在缓存满时删除最近最少使用的图片
glide源码
一般看源码先看他的使用方法,通过使用的方法看对应的代码。
Glide.with(MainActivity.this).load(url).into(headerImage);
- with方法把context传进去,返回RequestManager 。并且调用GlideBuilder.build(做初始化的),在这里做一些初始化操作,比如构建线程池(包括sourceExecutor ,diskCacheExecutor ),缓存大小和缓存器,默认的连接监听工厂(connectivityMonitorFactory ),Engine对象(怎么使用缓存的)和RequestManagerRetriever 对象、DecodeJob(解析InputStream生成图片)。
- RequestManager监听了ActivityFragmentLifecycle ,会在onStop pauseRequests();onStart 恢复resumeRequests()。
- load(URL)Glide.with(context)已经返回了RequestManager,其实就是RequestManager.load(""),主要就是把URL传进去,获取RequestBuilder对象。
- 主要的操作都在into方法里(在这里会取lru缓存还是本地缓存,还是没有,告诉RequestBuilder)。RequestBuilder的into方法里开启了线程池进行加载资源。网络请求是通过url打开连接,返回一个HttpURLConnection对象,进行网络请求的。加载得资源后转换到主线程并进行回调设置给imageview。
- glide为什么有lru还会内存溢出。因为直接把整个大图片的整个内存加载进去了。对于大图可以下载下来,asdrawale来加载,drawable更省内存,Drawable应该不属于常驻内存的对象,不然的话,不可能不会出现OOM的~~
public synchronized void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
//获取GlideExecutor 线程池
GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
//开始执行decodeJob这个Runnable
executor.execute(decodeJob);
}
private static final Executor MAIN_THREAD_EXECUTOR =
new Executor() {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
handler.post(command);
}
};
- Glide支持图片的二级缓存(并不是三级缓存,因为从网络加载并不属于缓存),即内存缓存和磁盘缓存。
- Glide内部处理了网络图片加载的错位或者闪烁(tag)。
public Request getRequest() {
//本质还是getTag
Object tag = getTag();
Request request = null;
if (tag != null) {
if (tag instanceof Request) {
request = (Request) tag;
} else {
throw new IllegalArgumentException("You must not call setTag() on a view
Glide is targeting");
}
}
return request;
}
@Override
public void setRequest(Request request) {
//本质是setTag
setTag(request);
}
对图片加载用到了LruCache(最少最近使用)算法
他会把内存控制在一定大小内,超过最大值时会自动回收,这个最大值可以自己定,一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。一般使用最大可用内存的1/8作为缓存的大小。LruCache的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap (频繁增删、不需要排序)中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
public class BitmapCache implements ImageCache {
private LruCache<String, Bitmap> mCache;
public BitmapCache() {
int maxSize = 10 * 1024 * 1024;
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
网络框架比较
常用网络库使用方法
public interface netApi {
@GET("repos/{owner}/{repo}/contributors")
Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
volleyStringRequest();
volleyJsonRequest();
retrofitHttpRequest();
try {
okhttpAsyGet();
OkHttpSyncGet();
} catch (Exception e) {
e.printStackTrace();
}
}
//volley第一步
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
private void volleyStringRequest() {
//volley第二步
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
//volley第三步
mQueue.add(stringRequest);
}
private void volleyJsonRequest() {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://www.sina.com/sports/101010100.html", null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.d("TAG", response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(jsonObjectRequest);
}
//okhttp第一步
private final OkHttpClient client = new OkHttpClient();
public void okhttpAsyGet() throws Exception {
//okhttp第二步
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
//okhttp第三步
okhttp3.Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
public void OkHttpSyncGet() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());//只能获取一次,可以用string保存
}
});
}
public void retrofitHttpRequest() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
netApi repo = retrofit.create(netApi.class);
retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("userName", "path");
call.enqueue(new retrofit2.Callback<ResponseBody>() {
@Override
public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
//response
}
@Override
public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) {
}
});
}
}
String post(String url, String json) throws IOException {
RequestBody formBody = new FormEncodingBuilder()
.add("platform", "android")
.add("name", "bug")
.add("subject", "XXXXXXXXXXXXXXX")
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
- HttpURLConnection和HttpClient。这两种方式都支持HTTPS协议、以流的形式进行上传和下载、配置超时时间、IPv6、以及连接池等功能。
- 在Android 2.2版本之前,HttpClient是最好的选择。因为HttpURLConnection有一些bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能。
- 在Android 2.3版本及以后,HttpClientHttpURLConnection则是最佳的选择,HttpURLConnection的API提供的比较简单,可以更加容易地去使用和扩展它。而且速度快、节省电量。
- OkHttp 处理了很多网络问题:自动重连、会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。
- volley的设计目标就是非常适合数据量小,通信量大的客户端,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。Volley停止了更新,而OkHttp得到了官方的认可,并在不断优化。因此我最终替换为了OkHttp
volley原理
主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。
为什么说Volley适合数据量小,通信频繁的网络操作
volley中为了提高请求处理的速度,采用了ByteArrayPool进行内存中的数据存储的,如果下载大量的数据,这个存储空间就会溢出,所以不适合大量的数据,但是由于他的这个存储空间是内存中分配的,当存储的时候优是从ByteArrayPool中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数 ,所以他比较适合据量小,通信量大网络数据交互情况。
Retrofit原理
- Retrofit 2.0底层依赖OkHttp实现,也就是说Retrofit本质上就是对OkHttp的更进一步封装,还支持Rxjava。Retrofit和其它Http库最大区别在于通过大范围使用注解简化Http请求(请求方式、请求参数)。
- 网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。
- Retrofit主要是在create方法中采用动态代理模式实现接口方法
,这个过程构建了一个ServiceMethod对象。(扩展apiservice的功能)
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
- loadServiceMethod(method)方法:解析注解获取请求方式,参数类型和参数注解拼接请求的链接,当一切都准备好之后会把数据添加到Retrofit的RequestBuilder中。然后当我们主动发起网络请求的时候会调用okhttp发起网络请求,okhttp的配置包括请求方式,URL等。
new OkHttpCall<>(requestFactory, args, callFactory,responseConverter)
自己写网络请求框架
- volley,okHttp等,这类优秀的框架其底层的实现大部分也是基于系统的 线程池 和 httpClient 或 HttpUrlConnection的网络请求类框架,Android中是不能在主线程中(又称UI线程)进行网络操作的,那么框架中必不可少地要使用到子线程,可以使用简单的 Thread + Runnable + Handler或者重量级点的AsyncTask。
- 处理好并发操作,一个应用中往往要进行多线程操作,而Java虚拟机对于一个线程的内存分配大约在1M左右,具体多少要看它执行的任务而定。所有就要使用线程池,例如newFixdThreadPool 可以控制并发数量,且在整个APP运行过程中有几个常驻线程在,避免使用时反复地new,退出时再销毁,而 newCacheThreadPool 则会在任务完成后,自动回收线程,它会帮你释放线程内存,也就不会有常驻线程。
- 还要注意使接口分离,降低耦合,而且接口能够我们带来很大的方便。
okhttp源码
- 在构造器中利用建造者模式来构建 OkHttpClient 的对象,OkHttpClient 的构造器中主要是默认的配置。例如:
- dispatcher :调度器,⽤于调度多线程发起⽹络请求,有总请求数和单主机总请求数的控制(64,5)
- List protocols :⽀持的应⽤层协议,即 HTTP/1.1、HTTP/2 等
- Cache cache :Cache 存储的配置。默认是没有,如果需要⽤,得⾃⼰配置出 Cache 存储的⽂件位置以及存储空间上限。
- boolean followRedirects :遇到重定向的要求是,是否⾃动follow。
- 连接、读取、写入超时
- 在newCall(Request request) (request是请求参数和URL)的时候,其实是里面创建了一个 RealCall 的对象,里面有execute() 方法。里面getResponseWithInterceptorChain() ,添加了很多Interceptor,并返回 Response 对象的。
- RealCall.enqueue() 被调⽤的时候⼤同⼩异,区别在于enqueue() 会使⽤ Dispatcher 的线程池来把请求放在后台线程进⾏,但实质上使⽤的同样也是getResponseWithInterceptorChain() ⽅法。
- getResponseWithInterceptorChain() ⽅法做的事:把所有配置好的Interceptor 放在⼀个 List ⾥,然后作为参数,创建⼀个RealInterceptorChain 对象,并调用chain.proceed(request) 来
发起请求和获取响应。 - Interceptor 是 OkHttp 最核心的一个东西,它负责拦截请求进行一些额外的处理。Interceptor有:
- RetryAndFollowlnterceptor:负责失败重试、重定向
- Bridgelnterceptor负责向服务器发送请求数据,例如头消息、cookie等等
- Cachelnterceptor: 读取缓存、更新缓存
- Connectlnterceptor:负责和服务器建立连接的。在这⾥,OkHttp 会创建出⽹络请求所需要的 TCP 连接(如果是 HTTP),或者是建⽴在 TCP 连接之上的 TLS 连接(如果是 HTTPS),并且会创建出对应的 HttpCodec 对象(⽤于编码解码 HTTP 请求)
- Networklnterceptor:从服务器读取响应数据
- 每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终完成一次网络请求。
- 然后是开发者使⽤ addNetworkInterceptor(Interceptor) 所设置的,它们的⾏为逻辑和使⽤ addInterceptor(Interceptor) 创建的⼀样,但由于位置不同,所以这⾥创建的 Interceptor 会看到每个请求和响应的数据(包括重定向以及重试的⼀些中间请求和响应),并且看到的是完整原始数据,⽽不是没有加 Content-Length 的请求数据,或者 Body还没有被 gzip 解压的响应数据。多数情况,这个⽅法不需要被使⽤;
- 同步请求通过Call.execute()直接返回当前的Response,而异步请求会把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback)的方式来获取最后结果。
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
- 复用机制:Http中添加了一种KeepAlive机制,当数据传输完毕后仍然保持连接,等待下一次请求时直接复用该连接。
ConnectionPool :取到的话复用,没有取到放到连接池中。
ConnectionPool关键代码: - OkHttp 默认最大并发数 64,单域名最大并发 5,为了实现请求的并发,Dispatcher 配置了一个线程池,
//线程池,核心线程数为0,最大线程数为最大整数,线程空闲存活时间60s,//SynchronousQueue 直接提交策略
private static final Executor executor = new ThreadPoolExecutor(0,
Integer.MAX_VALUE , 60L , TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
//空闲连接的最大连接数
private final int maxIdleConnections;
//保持连接的周期
private final long keepAliveDurationNs;
//双端队列,存放具体的连接
private final Deque<RealConnection> connections = new ArrayDeque<>();
//用于记录连接失败的route
final RouteDatabase routeDatabase = new RouteDatabase();
//构造函数//从这里可以知道,空闲连接的最大连接数为5,保持连接的周期是5分钟
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
自定义一个拦截器
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
其他库
LeakCanary原理解析
- lifecycleCallbacks监听Activity,onActivityDestroyed方法最终会调用RefWatcher.watch方法:
- 通过将Activity包装到WeakReference(弱引用)中,弱引⽤在引⽤对象被垃圾回收之前,会将引⽤放⼊它关联的队列中。所以可以通过队列中是否有对应的引⽤来判断对象是否被垃圾回收了。(有的话被回收了,没有的话就没有被回收)
- 如果Activity没有被回收,调用GcTigger.runGc方法运行GC,如果这时候还没有被回收,那就说明Activity可能已经泄露。
- 生成dunp文件,对他进行分析,开启一个前台服务
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
Evenbus是做什么的?和RXjava有什么区别?
- 采用EventBus作为事件管理,可以跨线程,跨组件通信。 以前我们做组件间的消息分发更新,一般会采用观察者模式,或者接口数据回调的相关方式。但是这样的做法虽然可以解决问题,但是组件之间的耦合比较严重,而且代码也不易阅读和相关维护。为了解决这样的问题我们可以使用消息总线EventBus框架。
- EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
- RxJava要比EventBus的应用更广泛,RxJava里面几乎可以做任何事情。做异步、网络的数据处理,写出来的代码比较优雅。
黏性事件
简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似,但是它只能收到最新的一次消息,比如说在未订阅之前已经发送了多条黏性消息了,然后再订阅只能收到最近的一条消息。
EventBus源码
register(this)就是去当前类,遍历所有的方法,找到onEvent开头的然后进行存储(把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ),eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切),然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用。数据传递是通过handler。
Android常用库源码解析的更多相关文章
- Android 开源项目源码解析(第二期)
Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations ...
- andorid jar/库源码解析之EventBus
目录:andorid jar/库源码解析 EventBus: 作用: 用于不同Activity,Service等之间传递消息(数据). 栗子: A页面:onCreate定义 EventBus.ge ...
- andorid jar/库源码解析之Butterknife
目录:andorid jar/库源码解析 Butterknife: 作用: 用于初始化界面控件,控件方法,通过注释进行绑定控件和控件方法 栗子: public class MainActivity e ...
- andorid jar/库源码解析之错误提示
目录:andorid jar/库源码解析 错误: 错误1: Error: Static interface methods are only supported starting with Andro ...
- Generator函数执行器-co函数库源码解析
一.co函数是什么 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行.短小精悍只有短短200余行,就可以免去手动编写G ...
- Android MIFARE NFCA源码解析
Android MIFARE NFCA源码解析TagTechnology定义了所有标签的共有接口类BasicTagTechnology 实现了TagTechnology的一些接口 再有具体的标签协议继 ...
- andorid jar/库源码解析之Bolts
目录:andorid jar/库源码解析 Bolts: 作用: 用于链式执行跨线程代码,且传递数据 栗子: Task.call(new Callable<Boolean>() { @Ove ...
- andorid jar/库源码解析之Dagger/Dagger2
目录:andorid jar/库源码解析 Dagger.Dagger2: 作用: 1.用于解耦Activity和业务逻辑 2.在使用业务的时候,不需要重复编写new代码. 3.当业务变化的时候,不需要 ...
- andorid jar/库源码解析之okhttp3
目录:andorid jar/库源码解析 Okhttp3: 作用: 用于网络编程(http,https)的快速开发. 栗子: // okHttpClient定义成全局静态,或者单例,不然重复new可能 ...
随机推荐
- C# Socket 简单的控制台案例
一.服务器端 1. 实例化并设置socket实例对象 a.创建ip地址和端口 b.绑定监听地址 c.设置一下允许同时访问数 2. 监听连接 a.通过启动一个新的线程执行,这样主线程不会假死(启动线程, ...
- Bzoj 2013 [Ceoi2010] A huge tower 题解
2013: [Ceoi2010]A huge tower Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 471 Solved: 321[Submit ...
- MediatR-进程内的消息通信框架
MediatR是一款进程内的消息订阅.发布框架,提供了Send方法用于发布到单个处理程序.Publish方法发布到多个处理程序,使用起来非常方便.目前支持 .NET Framework4.5..NET ...
- LeetCode第2题
// 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字.//// 如果,我们将这两个数相加起来,则会返回一个新的链表 ...
- DAX 第三篇:过滤器函数
过滤器函数允许你操纵筛选上下文以创建动态的计算. 一,筛选上下文的构成 DAX中的筛选上下文由三部分构成:交叉过滤构成的过滤,查询上下文中每行的列值构成的过滤,外部切片器构成的显式过滤. 1,交叉过滤 ...
- 俩台服务器搭建redis集群5.0.4
俩台服务器搭建redis集群 1.俩服务器分别新建目录:usr/local/redis-cluster 2.下载源码并解压编译(使用redis版本5.0.4) 3.tar xzf redis-5.0. ...
- Python_我的学习笔记 (博客停更------)
贡献一张PyCharm快捷键图(图片是借用他人的)----------建议最大化查看,因为这样不会破坏布局 注:部分内容引用小甲鱼,其他等网页,网站内容.如有冒犯,请联系我. 2019.07.21 ...
- SQL SERVER中生僻字问题存储与查询问题
以下仅记录碰到的几个问题 1.首先字段设置为varchar的时候存储后无法进行正常的显示 显示为? 此状态下匹配查询或者Like模糊查询都没问题 2.将字段设置为nvarchar,在进行插入或者跟新时 ...
- git rebase 理解
摘录自:https://blog.csdn.net/wangnan9279/article/details/79287631
- javascript获取指定区间范围随机数
//获取指定区间范围随机数,包括lowerValue和upperValuefunction randomFrom(lowerValue,upperValue){ return Math.floo ...