那些传男不传女的技艺,最终都消失了

其实我并不是想说重男轻女的问题,而是想用“传男不传女”这种说法来指代哪些神神秘秘的传统技艺,毕竟这种博人眼球的标题总是能吸引关注。

问题源于我看过一个揭秘川剧变脸的小视频,结果底下清一色的评论“这种视频应该封杀,中国传统技艺不要让外国人偷师了”,无一不是在逼迫原作者删除这个视频。初看之下我还不以为意,结果发现抱有这种观点的人真是不在少数,也不知道是应该感叹拳拳华夏爱国之心,还是感叹素质教育任重道远。

应该怎样评价这种观点呢?首先得看“传男不传女”这种规矩是怎么来的?“传男不传女”的一般都是一些赖以谋生的独特手艺,比如“变脸”,比如“唐朝鼓乐”,再比如沸沸扬扬的“漆线雕”,当初先人立下此规矩原因无非有二:

  1. 认为女儿终究是外人
  2. 古代的“专利保护”,其实第一条也是依赖于这一条的,因为“外人”终究会成为潜在的竞争者。

Read More

Regex温故知新1

《正则表达式必知必会》——Ben Forta,回顾笔记。

正则表达式的两种基本用途:搜索和替换。给定一个正则表达式,它要么匹配一些文本(进行一次搜索),要么匹配并替换一些文本(进行一次替换)。

正则表达式的核心难点:验证某个模式能不能获得预期的匹配结果并不困难,但如何验证它不会匹配到你不想要的东西可就没那么简单了。

Read More

Cache-Control扩展

rfc5861 定义了两个 Cache-Control 的扩展:

  1. stale-while-revalidate
  2. stale-if-error

这是两个扩展都是用来定义缓存过期(stale)后的处理策略,旨在提高用户体验,不过两者是独立使用,并无关联的。

stale-while-revalidate

我们在 http 里已经使用缓存很多年了,不过有个问题很常见:如果缓存过期了会发生什么?
如果从缓存中可以立即取得响应,但是从服务器获取响应需要几百毫秒或者更久,那用户会很容易注意到这个细节差异。

一个理所当然的解决方案是“在缓存过期之前预拉取最新的内容”,这个听上去很合理,不过这引发了另外一个头疼的问题:“如何决定何时预拉取呢?”。如果没有正确实现预拉取策略,那就有可能加重缓存,网络以及后台服务器等等的负载。

退而求其次,另一个可以采取的方案是,对于那些”稍微“过期的缓存,允许先直接使用,然后在后台静默的更新缓存内容。

1

Read More

aar依赖

问题记录。

今天添加 aar 依赖之后,一直提示找不到 aar 中的定义类,然后发现甚至都没有生成 build/intermediates/exploded-aar 文件夹,原因就是新的 as 版本添加了 aar 缓存,在 .android/build-cache 中,手工添加或者更新 aar 文件之后,缓存并没有刷新,因此导致找不到类或者其他类似问题。

简单的解决办法就是禁止掉 aar 缓存,在 gradle.properties 中添加一行内容:

android.enableBuildCache=false

重新 build 一下,就可以看到 exploded-aar 出现了,也更新了。

Pool in Android (0)

在应用开发中,在某些场景下,需要频繁重复创建某种对象来传递消息或者执行操作,在使用完之后又随即废弃。在这种情况下,会对系统性能造成一定的影响,特别是存在以下特征时:

  1. 对象频繁创建但是只需要短暂使用
  2. 对象的创建代价比较高。

比较典型的例子比如 Android 里面的 MotionEvent, 每次触发屏幕移动事件都会产生一个新的 MotionEvent 对象,但是这个 MotionEvent 对象却只使用一次就废弃了。
再比如数据库连接,连接数据库是非常耗时的,如果每次查询都创建一个连接,会产生显著的响应延迟。
在以上类似的场景中,我们引入“对象池化”技术,特别是在移动设备这种硬件资源紧张的系统中,池化技术更是无处不在。

对象池是一个保存可复用对象的容器,用户可以从池子中取得对象,对其进行操作处理,并在使用完毕之后将这个对象归还给池子,而不是任由系统销毁这个对象。我们简略区分一下,Java 对象的生命周期大致包括三个阶段:

  1. 对象的创建
  2. 对象的使用
  3. 对象的销毁

池化通过减少“对象的创建”和”对象的销毁”这两步的开销,进而降低系统负担从而减少了响应时间,是一种以空间换时间的策略。

不过同时,池化也引入了新的风险,既然池中的对象是复用的,那么这些对象先前的状态如何处理。此时,又需要将池化对象分两种:

  1. 无状态对象。这种对象可以直接复用,因为每次使用的状态都是一样的。
  2. 有状态对象。这种对象在取出(或者归还)的时候,都应该先将所有的对象重置,所以你在很多对象上能看到 recycle() 方法,这个对象很大程度上有可能就使用了池化技术。

对象池的实现

对象池的实现有很多,最基本的只需要实现“取出”和“归还”功能即可,一个简单的实现:

1
2
3
4
interface Pool<T> {
T acquire()
boolean release(T instance)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static class SimplePool<T> implements Pool<T> {
private final Object[] mPool;

private int mPoolSize;

public SimplePool(int maxPoolSize) {
mPool = new Object[maxPoolSize];
}

@Override
@SuppressWarnings("unchecked")
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;
mPoolSize--;
return instance;
}
return null;
}

@Override
public boolean release(T instance) {
if (isInPool(instance)) {
throw new IllegalStateException("Already in the pool!");
}
if (mPoolSize < mPool.length) {
mPool[mPoolSize] = instance;
mPoolSize++;
return true;
}
return false;
}

private boolean isInPool(T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
}
}
return false;
}
}

上面其实是 Android v4 包里面的实现,整体就是用一个数组来保存复用对象,然后依次轮询。

什么时候不适合池化?

  1. 过于轻量级对象不适合池化,因为创建的开销可能比在池中取出归还的消耗还低。不过这是一个很主观的标准,在 android 这种移动系统中,每一次创建对象都要在堆上进行分配,每一次销毁都要等待垃圾回收处理,就算单次创建的消耗低,次数多了一样会拖累系统。
  2. 对象创建之后,每次使用都会持有较长时间,此时相比“对象的使用”的时间来说,“对象的创建”和“对象的销毁”的开销基本可以忽略了,创建一次对象也算是物超所值了。同时,长时间的持有对象能造成两种结果:1. 对象池越来越大 2. 客户无法从对象池获取到对象了,因为都在使用中。不过这同样也是一个很主观的标准,比如,线程池就是一个很好的例子。你可以对每一个用户请求都新开一个线程来处理,但是当达到负载峰值时,根本就不可能为每一个用户请求创建一个线程,因此,用户请求只能排队等候了。
  3. 对象的状态难以重置,不然下一次使用的时候会引入上次使用的干扰。

池化需要注意的问题?

  1. 对象池也是一种缓存,缓存不宜过大,需要根据具体的情况来设定合适的池大小。
  2. 对象池为空的时候要有适当的处理,此时可以新建一个对象返回给客户,不能让客户无限等待。
  3. 多线程控制,这个时候可以选择线程同步,不过更适合的是 ThreadLocal,直接规避多线程问题。

缓存设计(1)

在程序设计中,设计良好的缓存可以避免无意义的重复计算,节省可观的计算资源,从而提高性能。
不过同时,缓存一样要消耗资源,过大的内存一样会造成空间资源的浪费。这些方方面面都是在设计缓存的时候需要考虑的事情。

本文主要关注如何设计一个良好运行,线程安全的缓存方案。

简单实现

一个简单缓存方案的实现如下:

1
2
3
4
5
6
7
IF value in cached THEN
return value from cache
ELSE
compute value
save value in cache
return value
END IF

Read More