iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >关于Android的 DiskLruCache磁盘缓存机制原理
  • 242
分享到

关于Android的 DiskLruCache磁盘缓存机制原理

2024-04-02 19:04:59 242人浏览 泡泡鱼
摘要

目录一、为什么用DiskLruCache1、LruCache和DiskLruCache2、为何使用DiskLruCache二、DiskLruCache使用1、添加依赖2、创建Disk

一、为什么用DiskLruCache

1、LruCache和DiskLruCache

LruCacheDiskLruCache两者都是利用到LRU算法,通过LRU算法对缓存进行管理,以最近最少使用作为管理的依据,删除最近最少使用的数据,保留最近最常用的数据;

LruCache运用于内存缓存,而DiskLruCache是存储设备缓存;

2、为何使用DiskLruCache

离线数据存在的意义,当无网络或者是网络状况不好时,APP依然具备部分功能是一种很好的用户体验;

假设网易新闻这类新闻客户端,数据完全存储在缓存中而不使用DiskLruCache技术存储,那么当客户端被销毁,缓存被释放,意味着再次打开APP将是一片空白;

另外DiskLruCache技术也可为app“离线阅读”这一功能做技术支持;

DiskLruCache的存储路径是可以自定义的,不过也可以是默认的存储路径,而默认的存储路径一般是这样的:/sdcard/Android/data/包名/cache,包名是指APP的包名。我们可以在手机上打开,浏览这一路径;

二、DiskLruCache使用

1、添加依赖


// add dependence 
implementation 'com.jakewharton:disklrucache:2.0.2' 


2、创建DiskLruCache对象


 
DiskLruCache diskLruCache = DiskLruCache.open(directory, 1, 1, 1024 * 1024 * 10); 

3、添加 / 获取 缓存(一对一)


 
public void aDDDiskCache(String key, String value) throws IOException { 
    File cacheDir = context.getCacheDir(); 
    DiskLruCache diskLruCache = DiskLruCache.open(cacheDir, 1, 1, 1024 * 1024 * 10); 
    DiskLruCache.Editor editor = diskLruCache.edit(key); 
    // index与valueCount对应,分别为0,1,2...valueCount-1 
    editor.newOutputStream(0).write(value.getBytes());  
    editor.commit(); 
    diskLruCache.close(); 
} 
 
public void getDiskCache(String key) throws IOException { 
    File directory = context.getCacheDir(); 
    DiskLruCache diskLruCache = DiskLruCache.open(directory, 1, 1, 1024 * 1024 * 10); 
    String value = diskLruCache.get(key).getString(0); 
    diskLruCache.close(); 
} 

4、添加 / 获取 缓存(一对多)


 
public void addDiskCache(String key, String value1, String value2) throws IOException { 
    File directory = context.getCacheDir(); 
    DiskLruCache diskLruCache = DiskLruCache.open(directory, 1, 2, 1024 * 1024 * 10); 
    DiskLruCache.Editor editor = diskLruCache.edit(key); 
    editor.newOutputStream(0).write(value1.getBytes()); 
    editor.newOutputStream(1).write(value2.getBytes()); 
    editor.commit(); 
    diskLruCache.close(); 
} 
 
public void getDiskCache(String key) throws IOException { 
    File directory = context.getCacheDir(); 
    DiskLruCache diskLruCache = DiskLruCache.open(directory, 1, 2, 1024); 
    DiskLruCache.Snapshot snapshot = diskLruCache.get(key); 
    String value1 = snapshot.getString(0); 
    String value2 = snapshot.getString(1); 
    diskLruCache.close(); 
} 

三、源码分析

1、open()

DiskLruCache的构造方法是private修饰,这也就是告诉我们,不能通过new DiskLruCache来获取实例,构造方法如下:


private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { 
    this.directory = directory; 
    this.appVersion = appVersion; 
    this.journalFile = new File(directory, JOURNAL_FILE); 
    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); 
    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); 
    this.valueCount = valueCount; 
    this.maxSize = maxSize; 
} 


但是提供了open()方法,供我们获取DiskLruCache的实例,open方法如下:


 
  public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 
      throws IOException { 
    if (maxSize <= 0) { 
      throw new IllegalArgumentException("maxSize <= 0"); 
    } 
    if (valueCount <= 0) { 
      throw new IllegalArgumentException("valueCount <= 0"); 
    } 
    // If a bkp file exists, use it instead. 
    //看备份文件是否存在 
    File backupFile = new File(directory, JOURNAL_FILE_BACKUP); 
   //如果备份文件存在,并且日志文件也存在,就把备份文件删除 
    //如果备份文件存在,日志文件不存在,就把备份文件重命名为日志文件 
     if (backupFile.exists()) { 
      File journalFile = new File(directory, JOURNAL_FILE); 
      // If journal file also exists just delete backup file. 
        // 
      if (journalFile.exists()) { 
        backupFile.delete(); 
      } else { 
        renameTo(backupFile, journalFile, false); 
      } 
    } 
    // Prefer to pick up where we left off. 
    //初始化DiskLruCache,包括,大小,版本,路径,key对应多少value 
    DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 
    //如果日志文件存在,就开始赌文件信息,并返回 
    //主要就是构建entry列表 
    if (cache.journalFile.exists()) { 
      try { 
        cache.readJournal(); 
        cache.processJournal(); 
        return cache; 
      } catch (IOException journalIsCorrupt) { 
        System.out 
            .println("DiskLruCache " 
                + directory 
                + " is corrupt: " 
                + journalIsCorrupt.getMessage() 
                + ", removing"); 
        cache.delete(); 
      } 
    } 
    //不存在就新建一个 
    // Create a new empty cache. 
    directory.mkdirs(); 
    cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 
    cache.rebuildJournal(); 
    return cache; 
  } 
open函数:如果日志文件存在,直接去构建entry列表;如果不存在,就构建日志文件;

2、rebuildJournal()


构建文件: 
  //这个就是我们可以直接在disk里面看到的journal文件 主要就是对他的操作 
 private final File journalFile; 
 //journal文件的temp 缓存文件,一般都是先构建这个缓存文件,等待构建完成以后将这个缓存文件重新命名为journal 
 private final File journalFileTmp; 
 
  private synchronized void rebuildJournal() throws IOException { 
    if (journalWriter != null) { 
      journalWriter.close(); 
    } 
    //指向journalFileTmp这个日志文件的缓存 
    Writer writer = new BufferedWriter( 
        new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII)); 
    try { 
      writer.write(MAGIC); 
      writer.write("\n"); 
      writer.write(VERSION_1); 
      writer.write("\n"); 
      writer.write(Integer.toString(appVersion)); 
      writer.write("\n"); 
      writer.write(Integer.toString(valueCount)); 
      writer.write("\n"); 
      writer.write("\n"); 
      for (Entry entry : lruEntries.values()) { 
        if (entry.currentEditor != null) { 
          writer.write(DIRTY + ' ' + entry.key + '\n'); 
        } else { 
          writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 
        } 
      } 
    } finally { 
      writer.close(); 
    } 
    if (journalFile.exists()) { 
      renameTo(journalFile, journalFileBackup, true); 
    } 
     //所以这个地方 构建日志文件的流程主要就是先构建出日志文件的缓存文件,如果缓存构建成功 那就直接重命名这个缓存文件,这样做好处在哪里? 
    renameTo(journalFileTmp, journalFile, false); 
    journalFileBackup.delete(); 
    //这里也是把写入日志文件的writer初始化 
    journalWriter = new BufferedWriter( 
        new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); 
  } 


来看当日志文件存在的时候,做了什么

3、readJournal()


private void readJournal() throws IOException { 
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); 
try { 
//读日志文件的头信息 
  String magic = reader.readLine(); 
  String version = reader.readLine(); 
  String appVersionString = reader.readLine(); 
  String valueCountString = reader.readLine(); 
  String blank = reader.readLine(); 
  if (!MAGIC.equals(magic) 
      || !VERSION_1.equals(version) 
      || !Integer.toString(appVersion).equals(appVersionString) 
      || !Integer.toString(valueCount).equals(valueCountString) 
      || !"".equals(blank)) { 
    throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " 
        + valueCountString + ", " + blank + "]"); 
  } 
//这里开始,就开始读取日志信息 
  int lineCount = 0; 
  while (true) { 
    try { 
    //构建LruEntries entry列表 
      readJournalLine(reader.readLine()); 
      lineCount++; 
    } catch (EOFException endOfJournal) { 
      break; 
    } 
  } 
  redundantOpCount = lineCount - lruEntries.size(); 
  // If we ended on a truncated line, rebuild the journal before appending to it. 
  if (reader.hasUnterminatedLine()) { 
    rebuildJournal(); 
  } else { 
    //初始化写入文件的writer 
    journalWriter = new BufferedWriter(new OutputStreamWriter( 
        new FileOutputStream(journalFile, true), Util.US_ASCII)); 
  } 
} finally { 
  Util.closeQuietly(reader); 
} 
} 


然后看下这个函数里面的几个主要变量:


//每个entry对应的缓存文件的格式 一般为1,也就是一个key,对应几个缓存,一般设为1,key-value一一对应的关系 
private final int valueCount; 
private long size = 0; 
//这个是专门用于写入日志文件的writer 
private Writer journalWriter; 
//这个集合应该不陌生了, 
private final LinkedHashMap<String, Entry> lruEntries = 
        new LinkedHashMap<String, Entry>(0, 0.75f, true); 
//这个值大于一定数目时 就会触发对journal文件的清理了 
private int redundantOpCount; 


下面就看下entry这个实体类的内部结构


private final class Entry { 
        private final String key; 
         
        private final long[] lengths; 
         
        private boolean readable; 
         
        private Editor currentEditor; 
        @Override 
        public String toString() { 
            return "Entry{" + 
                    "key='" + key + '\'' + 
                    ", lengths=" + Arrays.toString(lengths) + 
                    ", readable=" + readable + 
                    ", currentEditor=" + currentEditor + 
                    ", sequenceNumber=" + sequenceNumber + 
                    '}'; 
        } 
         
        private long sequenceNumber; 
        private Entry(String key) { 
            this.key = key; 
            this.lengths = new long[valueCount]; 
        } 
        public String getLengths() throws IOException { 
            StringBuilder result = new StringBuilder(); 
            for (long size : lengths) { 
                result.append(' ').append(size); 
            } 
            return result.toString(); 
        } 
         
        private void setLengths(String[] strings) throws IOException { 
            if (strings.length != valueCount) { 
                throw invalidLengths(strings); 
            } 
            try { 
                for (int i = 0; i < strings.length; i++) { 
                    lengths[i] = Long.parseLong(strings[i]); 
                } 
            } catch (NumberFORMatException e) { 
                throw invalidLengths(strings); 
            } 
        } 
        private IOException invalidLengths(String[] strings) throws IOException { 
            throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); 
        } 
        //臨時文件創建成功了以後 就會重命名為正式文件了 
        public File getCleanFile(int i) { 
            Log.v("getCleanFile","getCleanFile path=="+new File(directory, key + "." + i).getAbsolutePath()); 
            return new File(directory, key + "." + i); 
        } 
        //tmp开头的都是临时文件 
        public File getDirtyFile(int i) { 
            Log.v("getDirtyFile","getDirtyFile path=="+new File(directory, key + "." + i + ".tmp").getAbsolutePath()); 
            return new File(directory, key + "." + i + ".tmp"); 
        } 
} 


DiskLruCacheopen函数的主要流程就基本走完了;

4、get()


 
  public synchronized Snapshot get(String key) throws IOException { 
    checkNotClosed(); 
    validateKey(key); 
    Entry entry = lruEntries.get(key); 
    if (entry == null) { 
      return null; 
    } 
    if (!entry.readable) { 
      return null; 
    } 
    // Open all streams eagerly to guarantee that we see a single published 
    // snapshot. If we opened streams lazily then the streams could come 
    // from different edits. 
    InputStream[] ins = new InputStream[valueCount]; 
    try { 
      for (int i = 0; i < valueCount; i++) { 
        ins[i] = new FileInputStream(entry.getCleanFile(i)); 
      } 
    } catch (FileNotFoundException e) { 
      // A file must have been deleted manually! 
      for (int i = 0; i < valueCount; i++) { 
        if (ins[i] != null) { 
          Util.closeQuietly(ins[i]); 
        } else { 
          break; 
        } 
      } 
      return null; 
    } 
    redundantOpCount++; 
    //在取得需要的文件以后 记得在日志文件里增加一条记录 并检查是否需要重新构建日志文件 
    journalWriter.append(READ + ' ' + key + '\n'); 
    if (journalRebuildRequired()) { 
      executorService.submit(cleanupCallable); 
    } 
    return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); 
  } 


5、validateKey


private void validateKey(String key) { 
        Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); 
        if (!matcher.matches()) { 
          throw new IllegalArgumentException("keys must match regex " 
                  + STRING_KEY_PATTERN + ": \"" + key + "\""); 
        } 
  } 


这里是对存储entrymap的key做了正则验证,所以key一定要用md5加密,因为有些特殊字符验证不能通过;

然后看这句代码对应的:


if (journalRebuildRequired()) { 
      executorService.submit(cleanupCallable); 
    } 


对应的回调函数是:


 
  final ThreadPoolExecutor executorService = 
      new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); 
  private final Callable<Void> cleanupCallable = new Callable<Void>() { 
    public Void call() throws Exception { 
      synchronized (DiskLruCache.this) { 
        if (journalWriter == null) { 
          return null; // Closed. 
        } 
        trimToSize(); 
        if (journalRebuildRequired()) { 
          rebuildJournal(); 
          redundantOpCount = 0; 
        } 
      } 
      return null; 
    } 
  }; 


其中再来看看trimTOSize()的状态

6、trimTOSize()


private void trimToSize() throws IOException { 
    while (size > maxSize) { 
      Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); 
      remove(toEvict.geTKEy()); 
    } 
  } 


就是检测总缓存是否超过了限制数量,

再来看journalRebuildRequired函数

7、journalRebuildRequired()


 
  private boolean journalRebuildRequired() { 
    final int redundantOpCompactThreshold = 2000; 
    return redundantOpCount >= redundantOpCompactThreshold // 
        && redundantOpCount >= lruEntries.size(); 
  } 


就是校验redundantOpCount是否超出了范围,如果是,就重构日志文件;

最后看get函数的返回值 new Snapshot()


 
//这个类持有该entry中每个文件的inputStream 通过这个inputStream 可以读取他的内容 
  public final class Snapshot implements Closeable { 
    private final String key; 
    private final long sequenceNumber; 
    private final InputStream[] ins; 
    private final long[] lengths; 
    private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { 
      this.key = key; 
      this.sequenceNumber = sequenceNumber; 
      this.ins = ins; 
      this.lengths = lengths; 
    } 
     
    public Editor edit() throws IOException { 
      return DiskLruCache.this.edit(key, sequenceNumber); 
    } 
     
    public InputStream getInputStream(int index) { 
      return ins[index]; 
    } 
     
    public String getString(int index) throws IOException { 
      return inputStreamToString(getInputStream(index)); 
    } 
     
    public long getLength(int index) { 
      return lengths[index]; 
    } 
    public void close() { 
      for (InputStream in : ins) { 
        Util.closeQuietly(in); 
      } 
    } 
  } 


到这里就明白了get最终返回的其实就是entry根据key 来取的snapshot对象,这个对象直接把inputStream暴露给外面;

8、save的过程


public Editor edit(String key) throws IOException { 
    return edit(key, ANY_SEQUENCE_NUMBER); 
} 
//根据传进去的key 创建一个entry 并且将这个key加入到entry的那个map里 然后创建一个对应的editor 
//同时在日志文件里加入一条对该key的dirty记录 
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { 
    //因为这里涉及到写文件 所以要先校验一下写日志文件的writer 是否被正确的初始化 
    checkNotClosed(); 
    //这个地方是校验 我们的key的,通常来说 假设我们要用这个缓存来存一张图片的话,我们的key 通常是用这个图片的 
    //网络地址 进行md5加密,而对这个key的格式在这里是有要求的 所以这一步就是验证key是否符合规范 
    validateKey(key); 
    Entry entry = lruEntries.get(key); 
    if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null 
            || entry.sequenceNumber != expectedSequenceNumber)) { 
        return null; // Snapshot is stale. 
    } 
    if (entry == null) { 
        entry = new Entry(key); 
        lruEntries.put(key, entry); 
    } else if (entry.currentEditor != null) { 
        return null; // Another edit is in progress. 
    } 
    Editor editor = new Editor(entry); 
    entry.currentEditor = editor; 
    // Flush the journal before creating files to prevent file leaks. 
    journalWriter.write(DIRTY + ' ' + key + '\n'); 
    journalWriter.flush(); 
    return editor; 
} 


然后取得输出流


public OutputStream newOutputStream(int index) throws IOException { 
        if (index < 0 || index >= valueCount) { 
            throw new IllegalArgumentException("Expected index " + index + " to " 
                    + "be greater than 0 and less than the maximum value count " 
                    + "of " + valueCount); 
        } 
        synchronized (DiskLruCache.this) { 
            if (entry.currentEditor != this) { 
                throw new IllegalStateException(); 
            } 
            if (!entry.readable) { 
                written[index] = true; 
            } 
            File dirtyFile = entry.getDirtyFile(index); 
            FileOutputStream outputStream; 
            try { 
                outputStream = new FileOutputStream(dirtyFile); 
            } catch (FileNotFoundException e) { 
                // Attempt to recreate the cache directory. 
                directory.mkdirs(); 
                try { 
                    outputStream = new FileOutputStream(dirtyFile); 
                } catch (FileNotFoundException e2) { 
                    // We are unable to recover. Silently eat the writes. 
                    return NULL_OUTPUT_STREAM; 
                } 
            } 
            return new FaultHidinGoutputStream(outputStream); 
        } 
    } 


注意这个index 其实一般传0 就可以了,DiskLruCache 认为 一个key 下面可以对应多个文件,这些文件 用一个数组来存储,所以正常情况下,我们都是

一个key 对应一个缓存文件 所以传0


//tmp开头的都是临时文件 
     public File getDirtyFile(int i) { 
         return new File(directory, key + "." + i + ".tmp"); 
     } 


然后你这边就能看到,这个输出流,实际上是tmp 也就是缓存文件的 .tmp 也就是缓存文件的 缓存文件 输出流;

这个流 我们写完毕以后 就要commit;


public void commit() throws IOException { 
        if (hasErrors) { 
            completeEdit(this, false); 
            remove(entry.key); // The previous entry is stale. 
        } else { 
            completeEdit(this, true); 
        } 
        committed = true; 
    } 


这个就是根据缓存文件的大小 更新disklrucache的总大小 然后再日志文件里对该key加入cleanlog


//最后判断是否超过最大的maxSize 以便对缓存进行清理 
private synchronized void completeEdit(Editor editor, boolean success) throws IOException { 
    Entry entry = editor.entry; 
    if (entry.currentEditor != editor) { 
        throw new IllegalStateException(); 
    } 
    // If this edit is creating the entry for the first time, every index must have a value. 
    if (success && !entry.readable) { 
        for (int i = 0; i < valueCount; i++) { 
            if (!editor.written[i]) { 
                editor.abort(); 
                throw new IllegalStateException("Newly created entry didn't create value for index " + i); 
            } 
            if (!entry.getDirtyFile(i).exists()) { 
                editor.abort(); 
                return; 
            } 
        } 
    } 
    for (int i = 0; i < valueCount; i++) { 
        File dirty = entry.getDirtyFile(i); 
        if (success) { 
            if (dirty.exists()) { 
                File clean = entry.getCleanFile(i); 
                dirty.renameTo(clean); 
                long oldLength = entry.lengths[i]; 
                long newLength = clean.length(); 
                entry.lengths[i] = newLength; 
                size = size - oldLength + newLength; 
            } 
        } else { 
            deleteIfExists(dirty); 
        } 
    } 
    redundantOpCount++; 
    entry.currentEditor = null; 
    if (entry.readable | success) { 
        entry.readable = true; 
        journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 
        if (success) { 
            entry.sequenceNumber = nextSequenceNumber++; 
        } 
    } else { 
        lruEntries.remove(entry.key); 
        journalWriter.write(REMOVE + ' ' + entry.key + '\n'); 
    } 
    journalWriter.flush(); 
    if (size > maxSize || journalRebuildRequired()) { 
        executorService.submit(cleanupCallable); 
    } 
} 


commit以后 就会把tmp文件转正 ,重命名为 真正的缓存文件了;

这个里面的流程和日志文件的rebuild 是差不多的,都是为了防止写文件的出问题。所以做了这样的冗余处理;

总结:

DiskLruCache,利用一个journal文件,保证了保证了cache实体的可用性(只有CLEAN的可用),且获取文件的长度的时候可以通过在该文件的记录中读取。

利用FaultHidingOutputStreamFileOutPutStream很好的对写入文件过程中是否发生错误进行捕获,而不是让用户手动去调用出错后的处理方法;

到此这篇关于关于Android DiskLruCache的磁盘缓存机制原理的文章就介绍到这了,更多相关Android DiskLruCache磁盘缓存机制原理内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 关于Android的 DiskLruCache磁盘缓存机制原理

本文链接: https://www.lsjlt.com/news/135596.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 关于Android的 DiskLruCache磁盘缓存机制原理
    目录一、为什么用DiskLruCache1、LruCache和DiskLruCache2、为何使用DiskLruCache二、DiskLruCache使用1、添加依赖2、创建Disk...
    99+
    2024-04-02
  • HTTP缓存机制的原理
    这篇文章主要介绍“HTTP缓存机制的原理”,在日常操作中,相信很多人在HTTP缓存机制的原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”HTTP缓存机制的原理”的疑惑有所帮...
    99+
    2024-04-02
  • Hibernate缓存机制的原理
    本篇内容主要讲解“Hibernate缓存机制的原理”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Hibernate缓存机制的原理”吧!1. 为什么要用 Hibe...
    99+
    2024-04-02
  • redis缓存存储Session原理机制
    目录基于 Redis 存储 Session首先安装 redis 存储引擎的包设置session过期时间分布式获取Session:(redis)基于 Redis 存储 Session ...
    99+
    2024-04-02
  • shiro中缓存机制的原理是什么
    本篇文章给大家分享的是有关shiro中缓存机制的原理是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Shiro提供了类似于Spring的Cache抽象,即Shiro本身不实...
    99+
    2023-05-31
    shiro
  • 如何理解Linux下的磁盘缓存机制与SSD的写入放大问题
    本篇内容介绍了“如何理解Linux下的磁盘缓存机制与SSD的写入放大问题”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前段时间在开发一个使用...
    99+
    2023-06-12
  • redis缓存存储Session原理机制是什么
    这篇文章主要讲解了“redis缓存存储Session原理机制是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“redis缓存存储Session原理机制是什么”吧!基于 Redis 存储 S...
    99+
    2023-06-25
  • 二级缓存的更新机制原理和实施方式
    二级缓存更新机制的原理及实现方式 一、引言随着计算机技术的发展,数据处理和存储需求的增加,对于系统性能的要求也越来越高。为了提高系统的运行效率,缓存技术应运而生。而在缓存技术中,二级缓存是一种重要的组成部分。本文将介绍二级缓存更...
    99+
    2024-01-30
    缓存机制 实现方式 更新机制 数据访问
  • 详解Android ViewPager2中的缓存和复用机制
    目录1. 前言 2. 回顾RecyclerView缓存机制 3. offscreenPageLimit原理 4. FragmentStateAdapter原理以及缓存机制 4.1 简...
    99+
    2024-04-02
  • Android 图片的三级缓存机制实例分析
    Android 图片的三级缓存机制实例分析当我们获取图片的时候,如果不加以协调好图片的缓存,就会造成大流量,费流量应用,用户体验不好,影响后期发展。为此,我特地分享Android图片的三级缓存机制之从网络中获取图片,来优化应用,具体分三步进...
    99+
    2023-05-31
    android 图片 三级缓存
  • android Handler机制的原理是什么
    Android中的Handler机制是用来实现线程之间的通信的一种机制。它的原理是基于消息队列和消息循环。每个线程都有自己的消息队列...
    99+
    2023-09-20
    android
  • 关于Java的动态代理机制
    目录静态代理功能接口功能提供者功能代理者探索动态代理实现机制静态代理 常规的代理模式有以下三个部分组成: 功能接口 interface IFunction { void doATh...
    99+
    2023-05-19
    Java 代理 Java 动态代理
  • Android图片三级缓存的原理及其实现
    为什么要使用三级缓存 如今的 Android App 经常会需要网络交互,通过网络获取图片是再正常不过的事了 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流...
    99+
    2023-05-30
    android 图片 三级缓存
  • android handler的机制和原理是什么
    Android中的Handler机制是用于在不同线程之间进行消息传递和任务调度的一种机制。它的原理是基于消息队列和Looper。1....
    99+
    2023-08-24
    android handler
  • Android ViewPager2中缓存和复用机制的示例分析
    这篇文章主要为大家展示了“Android ViewPager2中缓存和复用机制的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Android ViewPager2中缓存和复用机制的示例分...
    99+
    2023-06-25
  • 关于Android触摸事件分发的原理详析
    目录一:前言二:说在前面的知识三:整体流程1:activity2:window就是PhoneWindow3:view group4:view四:一些关键点五:从源码看触摸事件分发总结...
    99+
    2024-04-02
  • ZooKeeper的Watcher机制是基于什么原理的
    ZooKeeper的Watcher机制是基于发布/订阅模式的原理。在ZooKeeper中,客户端可以注册Watcher来监听指定节点...
    99+
    2024-03-08
    ZooKeeper
  • Python内存管理机制的原理是什么
    今天就跟大家聊聊有关Python内存管理机制的原理是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。小块空间的内存池在Python中,许多时候申请的内存都是小块的内存,这些小块内存...
    99+
    2023-06-17
  • 关于docker清理Overlay2占用磁盘空间的问题(亲测有效)
    使用Docker过程中,长时间运行服务容器,导致不能进行上传文件等操作,通过命令df -h 发现overlay占用较高。通过命令docker system prune -a 清理无用...
    99+
    2024-04-02
  • kafka的消息存储机制和原理分析
    目录消息的保存路径数据分片log分段日志和索引文件内容分析在 partition 中通过 offset 查找 message过程日志的清除策略以及压缩策略日志的清理策略有两个日志压缩...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作