1.Python 爬虫进阶篇——diskcahce缓存(二)
2.JDK源码分析-Queue, Deque
3.OkHttp3源码详解之 okhttp连接池复用机制(一)
4.LinkedBlockingQueue
5.死磕 java集合之ArrayDeque源码分析
Python 爬虫进阶篇——diskcahce缓存(二)
上一篇文章为大家介绍了diskcache的基础用法,本文将继续深入探讨diskcache的更多高级功能。
关于diskcache,它是一种基于SQLite数据库的缓存对象管理方式。SQLite是一个轻量级的基于磁盘的数据库,它不需要单独的66商业源码服务器进程,并支持SQL查询。在上篇文章的源码截图上,你可以看到一些SQL语句的使用。
diskcache支持使用diskcache.FanoutCache自动分片基础数据库。分片是对数据进行水平分区,可以减少写入时的阻塞。尽管读和写不会互相阻碍,但写入会阻碍其他写入。分片的默认值为8。
以下是一个示例代码:
# 示例代码
在示例中,我们在diskcache_2文件夹中创建了一个具有四个分片和一秒超时的缓存。如果操作耗时超过一秒,它们将尝试中止操作。
那么每一个分片的大小是多少呢?分片的大小都是平均分配的,占总空间的四分之一。diskcache的默认大小为1GB。
diskcache提供了一个collections.deque兼容的帝国时代源码双端队列diskcache.Deque。双端队列是堆栈和队列的一般化,可以在前后都可以进行快速访问和编辑,且可持久化,操作比较便捷。
以下是一个示例代码:
# 示例代码
运行结果如下:
Popleft 获取队列最前面的一个元素,pop获取末尾的一个元素;
Appendleft 在队列最开始添加一个元素,append 在末尾添加一个元素;
Extendleft 在队列最开始添加一个数组,extend在末尾添加一个数组;
那么deque的存储位置在哪里?可以使用以下命令查看队列的存储位置:
包括cache也是一个可以使用directory属性查看默认存储的位置;
那么如何指定directory呢?Deque的第二个参数指定存储位置,第一个参数为队列的初始值。或者可以直接指定参数名称,如下:
Index 类似于dict(字典),可持久化,使用也比较便捷。以下是一个示例:
# 示例代码
popitem表示获取最后一个键对值,并且删除,结果如下:
peekitem() 可传递last 参数是否获取最后一个,不会删除原始值;
setdefault(key,default) 可以给指定的key值设置默认值,在查找时可以预先设置一个默认值,防止key值不存在而抛出异常。
keys()与values()可以查找所有Index中的key值与value值,然而没有提供判断key值是否存在的方法,但是jdk 源码注解汉语可以使用setdefault的方法自行封装。
Lock还记得上篇文章中提到的Lock锁吗?先看一下源码:
可以看到这里的锁用的就是cache的add方法的特性,源码中也给出了使用的方式:
从源码上看有一个while 死循环,直到add成功时才会被释放,这里处理的方式不是很好,可能会造成死锁,所以一般情况下,都会添加一个过期的时间,如下:
RLock还有一种RLock锁,与Lock锁的区别是RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。以下是一个示例:
结果是执行成功。
看一下源码:
从源码中可以看出,判断锁的条件是os.getpid()(进程pid)与threading.get_ident()(线程标识符),如果每次acquire时的pid与ident都相同的时,即可成功。那么就可以在相同的进程中无限次数的acquire,但是多少次acquire就得多少次的release,防止死锁。
那么是使用Lock还是RLock呢?这个要具体的看实际情况,并不是谁就一定好。
总结本次推文中介绍了diskcache中FanoutCache缓存分片、双端队列deque、Index、Lock以及RLock。宝宝个人网站源码
大家可以在实际中灵活运用,diskcache缓存的优势还是很大的,无需安装其他的模块,并且在文件管理器中能直接查看,还可以利用缓存的一些特性使用多线程的去实现业务等。
但是也是有缺点的,即受制于本地文件系统的限制。每个磁盘每个目录下的文件数量是有限制的,所以需要结合实际情况使用。
JDK源码分析-Queue, Deque
Queue 和 Deque 是 Java 中的两个接口,分别代表队列和双端队列。
Queue 接口提供了基本的队列操作:入队(enqueue)和出队(dequeue)。同时,Queue 接口有 6 个方法,分为入队、出队和遍历三类。与之不同的是,当队列为空时,element() 方法会抛出异常,而 peek() 方法则会返回 null。
Deque 接口继承自 Queue 接口,表示双端队列,java字符动画源码具备「队列」和「栈」的特性。双端队列可以分别从两端插入和移除元素,而一般队列只能从尾部插入元素、头部移除元素。Deque 接口定义了入队、出队、遍历以及独有的一些操作方法。Deque 作为双端队列,不仅继承了 Queue 的方法,还提供了额外的双端操作。
综上,Queue 提供了基本的队列功能,而 Deque 在 Queue 的基础上增加了双端操作,使其兼具队列和栈的特性。在实际应用中,根据需求选择合适的接口可以提高代码的灵活性和效率。
OkHttp3源码详解之 okhttp连接池复用机制(一)
提高网络性能优化,关键在于降低延迟和提升响应速度。
在浏览器中发起请求时,header部分通常如下所示:
keep-alive是指浏览器与服务端之间保持长连接,这种连接可以复用。在HTTP1.1中,它默认是开启的。
连接复用为何能提高性能?通常,在发起http请求时,我们需要完成TCP的三次握手、传输数据,最后释放连接。三次握手的过程可以参考这里:TCP三次握手详解及释放连接过程。
一次响应的过程:
在高并发的请求连接情况下或同一客户端多次频繁的请求操作中,无限制地创建连接会导致性能低下。
如果使用keep-alive,在timeout空闲时间内,连接不会关闭,相同的重复请求将复用原有的connection,减少握手的次数,大幅提高效率。
并非keep-alive的timeout设置时间越长,性能就越好。长时间不关闭会导致过多的僵尸连接和泄露连接出现。
那么,OkHttp3在客户端是如何实现类似keep-alive的机制的?
连接池的类位于okhttp3.ConnectionPool。我们的目标是了解如何在timeout时间内复用connection,并有效地对其进行回收清理操作。
其成员变量代码片段:
excutor:线程池,用于检测闲置socket并进行清理。
connections:connection缓存池。Deque是一个双端列表,支持在头尾插入元素,这里用作LIFO(后进先出)堆栈,多用于缓存数据。
routeDatabase:用于记录连接失败的router。
2.1 缓存操作:
ConnectionPool提供对Deque进行操作的方法,包括put、get、connectionBecameIdle、evictAll等操作,分别对应放入连接、获取连接、移除连接、移除所有连接操作。
2.2 连接池的清理和回收:
在观察ConnectionPool的成员变量时,我们了解到一个Executor线程池用于清理闲置的连接。注释中这样解释:
Background threads are used to cleanup expired connections
我们在put新连接到队列时,会先执行清理闲置连接的线程。调用的正是executor.execute(cleanupRunnable);方法。观察cleanupRunnable:
线程中不停调用Cleanup清理的动作并立即返回下次清理的间隔时间。继而进入wait等待之后释放锁,继续执行下一次的清理。所以可能理解成它是个监测时间并释放连接的后台线程。
了解cleanup动作的过程。这里就是如何清理所谓闲置连接的流程。怎么找到闲置的连接是主要解决的问题。
在遍历缓存列表的过程中,使用连接数目inUseConnectionCount和闲置连接数目idleConnectionCount的计数累加值都是通过pruneAndGetAllocationCount()是否大于0来控制的。那么很显然,pruneAndGetAllocationCount()方法就是用来识别对应连接是否闲置的。>0则不闲置,否则就是闲置的连接。
进入观察:
好了,原先存放在RealConnection中的allocations派上用场了。遍历StreamAllocation弱引用链表,移除为空的引用,遍历结束后返回链表中弱引用的数量。所以可以看出List>就是一个记录connection活跃情况的List。>0表示活跃,=0表示空闲。StreamAllocation在列表中的数量就是物理socket被引用的次数。
解释:StreamAllocation被高层反复执行aquire与release。这两个函数在执行过程中其实是在一直在改变Connection中的List大小。
搞定了查找闲置的connection操作,我们回到cleanup的操作。计算了inUseConnectionCount和idleConnectionCount之后,程序又根据闲置时间对connection进行了一个选择排序,选择排序的核心是:
通过对比最大闲置时间选择排序可以方便地查找出闲置时间最长的一个connection。如此一来,我们就可以移除这个没用的connection了!
总结:清理闲置连接的核心主要是引用计数器List>和选择排序算法以及excutor的清理线程池。
LinkedBlockingQueue
LinkedBlockingDequeå¨ç»æä¸æå«äºä¹å讲解è¿çé»å¡éåï¼å®ä¸æ¯Queueèæ¯Dequeï¼ä¸æç¿»è¯æå端éåï¼å端éåæå¯ä»¥ä»ä»»æä¸ç«¯å ¥éæè åºéå ç´ çéåï¼å®ç°äºå¨éå头åéåå°¾çé«ææå ¥å移é¤LinkedBlockingDequeæ¯é¾è¡¨å®ç°ç线ç¨å®å ¨çæ ççåæ¶æ¯æFIFOãLIFOçå端é»å¡éåï¼å¯ä»¥å顾ä¸ä¹åçLinkedBlockingQueueé»å¡éåç¹ç¹ï¼æ¬è´¨ä¸æ¯ç±»ä¼¼çï¼ä½æ¯åæäºä¸åï¼
QueueåDequeçå ³ç³»æç¹ç±»ä¼¼äºåé¾è¡¨åååé¾è¡¨ï¼LinkedBlockingQueueåLinkedBlockingDequeçå é¨ç»ç¹å®ç°å°±æ¯åé¾è¡¨åååé¾è¡¨çåºå«ï¼å ·ä½å¯åèæºç ã
å¨ç¬¬äºç¹ä¸å¯è½æäºäººæäºçé®ï¼ä¸¤ä¸ªäºæ¥éåä¸ä¸ªäºæ¥éçåºå«å¨åªéï¼æ们å¯ä»¥èè以ä¸åºæ¯ï¼
A线ç¨å è¿è¡å ¥éæä½ï¼B线ç¨éåè¿è¡åºéæä½ï¼å¦ææ¯LinkedBlockingQueueï¼A线ç¨å ¥éè¿ç¨è¿æªç»æï¼å·²è·å¾éè¿æªéæ¾ï¼ï¼B线ç¨åºéæä½ä¸ä¼è¢«é»å¡çå¾ ï¼éä¸åï¼ï¼å¦ææ¯LinkedBlockingDequeåB线ç¨ä¼è¢«é»å¡çå¾ ï¼åä¸æéï¼A线ç¨å®ææä½æ继ç»æ§è¡
LinkedBlockingQueueä¸è¬çæä½æ¯è·åä¸æéå°±å¯ä»¥ï¼ä½æäºæä½ä¾å¦removeæä½ï¼åéè¦åæ¶è·å两æéï¼ä¹åçLinkedBlockingQueue讲解æ¾ç»è¯´æè¿
LinkedBlockingQueue ç±äºæ¯åé¾è¡¨ç»æï¼åªè½ä¸ç«¯æä½ï¼è¯»åªè½å¨å¤´ï¼ååªè½å¨å°¾ï¼å æ¤ä¸¤æéæçæ´é«ãLinkedBlockingDeque ç±äºæ¯åé¾è¡¨ç»æï¼ä¸¤ç«¯å¤´å°¾é½è½è¯»åï¼å æ¤åªè½ç¨ä¸æéä¿è¯ååæ§ã å½ç¶æçä¹å°±æ´ä½
ArrayBlockingQueue
LinkedBlockingQueue
é®é¢ï¼ä¸ºä»ä¹ArrayBlockingQueue ä¸è½ç¨ä¸¤æé
å 为ååºåï¼ArrayBlockingQueue çå ç´ éè¦åå移å¨ã
LinkedBlockingQueueå é¨ç±åé¾è¡¨å®ç°ï¼åªè½ä»headåå ç´ ï¼ä»tailæ·»å å ç´ ãæ·»å å ç´ åè·åå ç´ é½æç¬ç«çéï¼ä¹å°±æ¯è¯´LinkedBlockingQueueæ¯è¯»åå离çï¼è¯»åæä½å¯ä»¥å¹¶è¡æ§è¡ãLinkedBlockingQueueéç¨å¯éå ¥é(ReentrantLock)æ¥ä¿è¯å¨å¹¶åæ åµä¸ç线ç¨å®å ¨ã
LinkedBlockingQueueä¸å ±æä¸ä¸ªæé å¨ï¼åå«æ¯æ åæé å¨ãå¯ä»¥æå®å®¹éçæé å¨ãå¯ä»¥ç©¿å ¥ä¸ä¸ªå®¹å¨çæé å¨ãå¦æå¨å建å®ä¾çæ¶åè°ç¨çæ¯æ åæé å¨ï¼LinkedBlockingQueueçé»è®¤å®¹éæ¯Integer.MAX_VALUEï¼è¿æ ·åå¾å¯è½ä¼å¯¼è´éåè¿æ²¡æ满ï¼ä½æ¯å åå´å·²ç»æ»¡äºçæ åµï¼å å溢åºï¼ã
size()æ¹æ³ä¼éåæ´ä¸ªéåï¼æ¶é´å¤æ度为O(n),æ以æ好éç¨isEmtpy
1.å¤æå ç´ æ¯å¦ä¸ºnullï¼ä¸ºnullæåºå¼å¸¸
2.å é(å¯ä¸æé)
3.å¤æéåé¿åº¦æ¯å¦å°è¾¾å®¹éï¼å¦æå°è¾¾ä¸ç´çå¾
4.å¦æ没æé满ï¼enqueue()å¨éå°¾å å ¥å ç´
5.éåé¿åº¦å 1ï¼æ¤æ¶å¦æéåè¿æ²¡æ满ï¼è°ç¨signalå¤éå ¶ä»å µå¡éå
1.å é(ä¾æ§æ¯ReentrantLock)ï¼æ³¨æè¿éçéååå ¥æ¯ä¸åç两æé
2.å¤æéåæ¯å¦ä¸ºç©ºï¼å¦æ为空就ä¸ç´çå¾
3.éè¿dequeueæ¹æ³åå¾æ°æ®
3.åèµ°å ç´ åéåæ¯å¦ä¸ºç©ºï¼å¦æä¸ä¸ºç©ºå¤éå ¶ä»çå¾ ä¸çéå
åçï¼å¨éå°¾æå ¥ä¸ä¸ªå ç´ ï¼ å¦æéå没满ï¼ç«å³è¿åtrueï¼ å¦æéå满äºï¼ç«å³è¿åfalseã
åçï¼å¦æ没æå ç´ ï¼ç´æ¥è¿ånullï¼å¦ææå ç´ ï¼åºé
1ãå ·ä½å ¥éä¸åºéçåçå¾ï¼
å¾ä¸æ¯ä¸ä¸ªèç¹ååé¨å表示å°è£ çæ°æ®xï¼åè¾¹ç表示æåçä¸ä¸ä¸ªå¼ç¨ã
1.1ãåå§å
åå§åä¹åï¼åå§åä¸ä¸ªæ°æ®ä¸ºnullï¼ä¸headålastèç¹é½æ¯è¿ä¸ªèç¹ã
1.2ãå ¥é两个å ç´ è¿å
1.3ãåºéä¸ä¸ªå ç´ å
表é¢ä¸çï¼åªæ¯å°å¤´èç¹çnextæéæåäºè¦å é¤çx1.nextï¼äºå®ä¸è¿æ ·æè§çå°±å®å ¨å¯ä»¥ï¼ä½æ¯jdkå®é ä¸æ¯å°åæ¥çheadèç¹å é¤äºï¼èä¸è¾¹çå°çè¿ä¸ªheadèç¹ï¼æ£æ¯åååºéçx1èç¹ï¼åªæ¯å ¶å¼è¢«ç½®ç©ºäºã
2ãä¸ç§å ¥é对æ¯ï¼
3ãä¸ç§åºé对æ¯ï¼
死磕 java集合之ArrayDeque源码分析
双端队列是一种特殊的队列,两端皆可操作元素。ArrayDeque以数组方式实现,非线程安全。Deque接口继承自Queue,新增操作两端元素、类栈方法。
ArrayDeque属性使用数组存储,头尾指针标识,最小容量为8。默认初始容量,最小8。入队方法包括从头addFirst(e)和尾addLast(e)。容量不足直接扩容两倍,通过取模循环头尾指针。出队方法pollFirst()和pollLast(),同样取模循环。ArrayDeque可直接作为栈使用,操作队列头即可实现。
总结:ArrayDeque采用数组实现双端队列,通过头尾指针循环数组操作。容量不足时扩容,每次增加一倍容量。作为栈使用,只需操作队列头。不支持线程安全。