1. Introducción
LRU es el algoritmo menos utilizado recientemente menos utilizado recientemente.
Una vez, cuando los principales marcos de fotos en caché no eran populares. Existe una tecnología de memoria caché muy común: SoftReference y WeakReference
(referencia suave y referencia débil). Pero en la era de Android 2.3 (Nivel 9), el mecanismo de recolección de basura está más inclinado a reciclar SoftReference
u WeakReference
objetos. Más tarde, llegué a Android3.0, y la imagen estaba almacenada en caché en el contenido, porque no sabía cuándo liberar la memoria, no había estrategia, y no había uso en una ocasión previsible para liberarla. Esto causó un desbordamiento de memoria.
2. Cómo usar
Se puede usar como un mapa, pero solo implementa la estrategia de caché LRU.
Solo recuerda algunos puntos cuando uses:
1.(必填)
Debe proporcionar una capacidad de caché como parámetro de construcción.2.(必填)
Tamaño de sobrescritura Del método, diseñe a medida una pieza de datos para calcular la capacidad. Si no sobrescribe, no puede predecir la capacidad de datos y no puede garantizar que la capacidad de caché se limite a la capacidad máxima.3.(选填)
entryRemoved
Método de sobrescritura , puede conocer los datos cuando se borra el caché menos utilizado (desalojado, clave, oldValue, newVaule).4.(记住)
LruCache es seguro para subprocesos, y los métodos internos get, put, remove, incluyendo trimToSize, son todos seguros (porque están bloqueados).5.(选填)
También existe elcreate
método de sobrescritura .
Generalmente, 1, 2, 3, 4 son suficientes, 5 pueden ignorarse.
El siguiente es un Bitmap
caso entryRemoved
en el que LruCache implementa un pequeño caché . La lógica personalizada puede ser ignorada. Si desea verlo, puede ir a mi demo de demostración para ver la entryRemoved
lógica personalizada .
private static final float ONE_MIB = 1024 * 1024;
// 7MB
private static final int CACHE_SIZE = (int) (7 * ONE_MIB);
private LruCache<String, Bitmap> bitmapCache;
this.bitmapCache = new LruCache<String, Bitmap>(CACHE_SIZE) {
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
...
}
};
3. Análisis del código fuente
3.1 Análisis principal de LruCache
LruCache
Es LinkedHashMap
una característica ( accessOrder=true
basada en la secuencia de acceso) más LinkedHashMap
una estrategia de almacenamiento en caché para bloquear operaciones de datos.
El caché de datos de LruCache está en la memoria.
-
1. Primero configure los
LinkedHashMap
parámetros de la estructura internaaccessOrder=true
para lograr la clasificación de datos de acuerdo con el orden de acceso. -
2. Luego, cada vez que
LruCache.get(K key)`` 方法里都会调用
LinkedHashMap.get (clave de objeto) `. -
3.
accessOrder=true
Después de la configuración anterior , seLinkedHashMap.get(Object key)
realizará cada vezLinkedHashMap.makeTail(LinkedEntry<K, V> e)
. -
4. LinkedHashMap es una lista enlazada doblemente circular, y cada vez que los datos de LruCache.get-> LinkedHashMap.get se ponen al final.
-
5. Bajo la implementación de put y trimToSize, si se elimina el volumen de datos, los primeros datos se eliminarán primero (porque los datos a los que se accedió más recientemente están al final).
El análisis específico es: 3.2, 3.3, 3.4, 3.5.
3.2 La única forma de construir LruCache
/**
* LruCache的构造方法:需要传入最大缓存个数
*/
public LruCache(int maxSize) {
...
this.maxSize = maxSize;
/*
* 初始化LinkedHashMap
* 第一个参数:initialCapacity,初始大小
* 第二个参数:loadFactor,负载因子=0.75f
* 第三个参数:accessOrder=true,基于访问顺序;accessOrder=false,基于插入顺序
*/
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
El primer parámetro se initialCapacity
usa para inicializar el LinkedHashMap
tamaño.
Primero, introduzca brevemente el segundo parámetro loadFactor
. El parámetro de construcción real en HashMap implica el problema de expansión. Por ejemplo, la capacidad máxima de HashMap es 100. Si se establece aquí 0.75f
, la capacidad se expandirá cuando alcance la capacidad de 75.
Principalmente es el tercer parámetro accessOrder=true
, por lo que la LinkedHashMap
ordenación de los datos se basará en el orden de acceso a los datos, y así se realizará el LruCache
principio de funcionamiento central.
3.3 LruCache.get (tecla K)
/**
* 根据 key 查询缓存,如果存在于缓存或者被 create 方法创建了。
* 如果值返回了,那么它将被移动到双向循环链表的的尾部。
* 如果如果没有缓存的值,则返回 null。
*/
public final V get(K key) {
...
V mapValue;
synchronized (this) {
// 关键点:LinkedHashMap每次get都会基于访问顺序来重整数据顺序
mapValue = map.get(key);
// 计算 命中次数
if (mapValue != null) {
hitCount++;
return mapValue;
}
// 计算 丢失次数
missCount++;
}
/*
* 官方解释:
* 尝试创建一个值,这可能需要很长时间,并且Map可能在create()返回的值时有所不同。如果在create()执行的时
* 候,一个冲突的值被添加到Map,我们在Map中删除这个值,释放被创造的值。
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
/***************************
* 不覆写create方法走不到下面 *
***************************/
/*
* 正常情况走不到这里
* 走到这里的话 说明 实现了自定义的 create(K key) 逻辑
* 因为默认的 create(K key) 逻辑为null
*/
synchronized (this) {
// 记录 create 的次数
createCount++;
// 将自定义create创建的值,放入LinkedHashMap中,如果key已经存在,会返回 之前相同key 的值
mapValue = map.put(key, createdValue);
// 如果之前存在相同key的value,即有冲突。
if (mapValue != null) {
/*
* 有冲突
* 所以 撤销 刚才的 操作
* 将 之前相同key 的值 重新放回去
*/
map.put(key, mapValue);
} else {
// 拿到键值对,计算出在容量中的相对长度,然后加上
size += safeSizeOf(key, createdValue);
}
}
// 如果上面 判断出了 将要放入的值发生冲突
if (mapValue != null) {
/*
* 刚才create的值被删除了,原来的 之前相同key 的值被重新添加回去了
* 告诉 自定义 的 entryRemoved 方法
*/
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
// 上面 进行了 size += 操作 所以这里要重整长度
trimToSize(maxSize);
return createdValue;
}
}
El método get anterior no parece ver dónde hay una estrategia de caché que implementa LRU. Principalmente en mapValue = map.get(key);
, LinkedHashMap
el get
método llamado , más la configuración predeterminada LinkedHashMap
en la estructura LruCache accessOrder=true
.
3.4 LinkedHashMap.get (clave de objeto)
/**
* Returns the value of the mapping with the specified key.
*
* @param key
* the key.
* @return the value of the mapping with the specified key, or {@code null}
* if no mapping for the specified key is found.
*/
@Override public V get(Object key) {
/*
* This method is overridden to eliminate the need for a polymorphic
* invocation in superclass at the expense of code duplication.
*/
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null)
return null;
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
}
return null;
}
De hecho, solo mire if (accessOrder)
la lógica cuidadosamente : si accessOrder=true
se ejecuta N veces cada vez makeTail(LinkedEntry<K, V> e)
.
Próximo vistazo:
3.5 LinkedHashMap.makeTail (LinkedEntry <K, V> e)
/**
* Relinks the given entry to the tail of the list. Under access ordering,
* this method is invoked whenever the value of a pre-existing entry is
* read by Map.get or modified by Map.put.
*/
private void makeTail(LinkedEntry<K, V> e) {
// Unlink e
e.prv.nxt = e.nxt;
e.nxt.prv = e.prv;
// Relink e as tail
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
e.nxt = header;
e.prv = oldTail;
oldTail.nxt = header.prv = e;
modCount++;
}
// Desvincular e
// Vuelva a vincular e como cola
LinkedHashMap es una lista enlazada doblemente circular, y luego los datos de LruCache.get-> LinkedHashMap.get se ponen al final.
Lo anterior es el principio de funcionamiento central de LruCache.
A continuación, presentamos la estrategia de desbordamiento de capacidad de LruCache.
4.6 LruCache.put (tecla K, valor V)
public final V put(K key, V value) {
...
synchronized (this) {
...
// 拿到键值对,计算出在容量中的相对长度,然后加上
size += safeSizeOf(key, value);
...
}
...
trimToSize(maxSize);
return previous;
}
Recuerda algunos puntos:
1.``put
Al principio, el valor se pone efectivamenteLinkedHashMap
, sin importar si excede la capacidad de búfer establecida.2.
Luego, según elsafeSizeOf
método, calcule la capacidad de los datos agregados esta vez y agréguelossize
.3.
Cuando se trata de safeSizeOf, se dice que sizeOf (clave K, valor V) calculará el tamaño de los datos agregados.4.
Hasta que finalice la colocación, setrimToSize
juzgasize
si es mayormaxSize
y luego se realiza la eliminación de los datos a los que rara vez se ha accedido recientemente.
4.7 LruCache.trimToSize (int maxSize)
public void trimToSize(int maxSize) {
/*
* 这是一个死循环,
* 1.只有 扩容 的情况下能立即跳出
* 2.非扩容的情况下,map的数据会一个一个删除,直到map里没有值了,就会跳出
*/
while (true) {
K key;
V value;
synchronized (this) {
// 在重新调整容量大小前,本身容量就为空的话,会出异常的。
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
// 如果是 扩容 或者 map为空了,就会中断,因为扩容不会涉及到丢弃数据的情况
if (size <= maxSize || map.isEmpty()) {
break;
}
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
// 拿到键值对,计算出在容量中的相对长度,然后减去。
size -= safeSizeOf(key, value);
// 添加一次收回次数
evictionCount++;
}
/*
* 将最后一次删除的最少访问数据回调出去
*/
entryRemoved(true, key, value, null);
}
}
Breve descripción: juzgará si size
es mayor que antes maxSize
. En caso afirmativo, no haga nada después de saltar. Si no, demuestra que la capacidad se ha desbordado. De la makeTail
figura se sabe que los datos a los que se accede con mayor frecuencia están al final. Obtenga un conjunto que almacene la clave y luego elimínelo desde el principio, elimine uno para determinar si se desborda, hasta que no haya desbordamiento.
Última mirada:
4.8 Anulación del efecto de entrada eliminado
entryRemoved
LruCache
Escena que se llama:
-
1. (put)
put
se llama cuando la llaveevicted=false
de conflictos ,key
= estoput
eskey
,oldValue
= conflictos están cubiertosvalue
,newValue
= estoput
esvalue
. -
2.
(trimToSize
)trimToSize
, solo se llamará una vez, es decir, se recuperarán los datos de menor acceso que se eliminaron la última vez.evicted=true
, Clave = clave eliminada por última vez, valor antiguo = última eliminaciónvalue
,newValue=null
(no hay conflicto esta vez, solo elimine). -
3.
(remove)
Al eliminar, la clave correspondiente existe y se llama después de que se elimina con éxito.evicted=false
, Clave = clave de este put,oldValue
= valor de esta eliminación,newValue=null
(no hay conflicto esta vez, solo elimine). -
4. (En la segunda mitad de get, el escenario de procesamiento se pierde después de que se pierde la consulta, pero se recomienda ignorarlo) Cuando get, si la creación personalizada no se implementa normalmente, el método get solo irá a la mitad en el código. Si implementa el
create(K key)
método personalizado , Y se llamará cuando el valor creado por usted se ponga en LruCache cuando haya un conflicto clave. unakey-value
.
El cuarto punto explicarlo: <1>.第四点是这样的,先 get(key),然后没拿到,丢失。<2>.如果你提供了 自定义的 create(key) 方法,那么 LruCache 会根据你的逻辑自造一个 value,但是当放入的时候发现冲突了,但是已经放入了。<3>.此时,会将那个冲突的值再让回去覆盖,此时调用上述4.的 entryRemoved
.
Debido a que la cantidad de datos en el caso HashMap, toman los datos pueden perderse, lo que resulta en la primera mitad de averiguar, su encargo create(key)
fue encontrado en cuando se enteraron (有冲突)
. Luego, apresuradamente, devuelva el valor original, en este momento creará un viaje en vano, no hará nada y volverá a ir entryRemoved
.
En resumen, es como el comentario:
/**
* 1.当被回收或者删掉时调用。该方法当value被回收释放存储空间时被remove调用
* 或者替换条目值时put调用,默认实现什么都没做。
* 2.该方法没用同步调用,如果其他线程访问缓存时,该方法也会执行。
* 3.evicted=true:如果该条目被删除空间 (表示 进行了trimToSize or remove) evicted=false:put冲突后 或 get里成功create后
* 导致
* 4.newValue!=null,那么则被put()或get()调用。
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
}
Puedo hacer referencia a demo
la entryRemoved
.
4.9 Bloqueo de sincronización local de LruCache
En get
, put
, trimToSize
, remove
cuatro métodos en los entryRemoved
métodos no son en bloques de sincronización en. Como los entryRemoved
parámetros de devolución de llamada son todos parámetros de dominio de método, no son seguros para subprocesos.
La pila de métodos locales y el contador de programas son áreas de datos aisladas de hilos
4. Resuma los puntos importantes de LruCache
-
1.LruCache
A través delLinkedHashMap
tercer constructor sin argumentosaccessOrder=true
se dio cuentaLinkedHashMap
basa en el orden de acceso (los datos se ha accedido recientemente, aparecerá una lista de la cola), cuando la capacidad de la abundancia, la cabeza de la lista enlazada de pedidos de datos de eliminación de datos. Por lo tanto, se implementa el mecanismo de caché de datos LRU. -
2.
LruCache
internamenteget
,put
,remove
que incluyetrimToSize
todos los seguros (porque todo cerrado).
LruCache
No libera la memoria en sí,LinkedHashMap
los datos se eliminan, si los datos aún se mencionan en otro lugar, todavía hay un problema de fuga, debe liberar manualmente la memoria.
-
4. El
entryRemoved
método de sobrescritura puede saber si existe un conflicto en la eliminación de datos de LruCache, o puede liberar manualmente los recursos. -
5
maxSize
ysizeOf(K key, V value)
método de reemplazo está estrechamente relacionada con, la unidad debe ser el mismo. (Tales comomaxSize
Shi7MB
, la costumbre desizeOf
calcular el tamaño de cada uno de datos debe ser capaz de calcular el tiempo asociado con unidades de MB)
Comparta especialmente el análisis de las preguntas reales de la entrevista de byte beating, agregue VX: q1607947758 para obtenerlo gratis