1.新增全局暂停加载和全局重新加载的能力。
Signed-off-by: zhoulisheng <635547767@qq.com>
This commit is contained in:
parent
9708de4ac0
commit
a766f210b4
|
@ -31,7 +31,26 @@ struct ManyPhotoShowPage {
|
|||
build() {
|
||||
Column() {
|
||||
|
||||
Button('点击暂停加载')
|
||||
.margin({top:10,bottom:5})
|
||||
.onClick(()=>{
|
||||
let imageKnife = ImageKnifeGlobal.getInstance().getImageKnife();
|
||||
if(imageKnife!= undefined){
|
||||
imageKnife.pauseRequests()
|
||||
}
|
||||
})
|
||||
|
||||
Button('点击重新加载')
|
||||
.margin({top:10,bottom:5})
|
||||
.onClick(()=>{
|
||||
let imageKnife = ImageKnifeGlobal.getInstance().getImageKnife();
|
||||
if(imageKnife!= undefined){
|
||||
imageKnife.resumeRequest()
|
||||
}
|
||||
})
|
||||
|
||||
Button('设置磁盘存储为50M')
|
||||
.margin({top:10,bottom:5})
|
||||
.onClick(()=>{
|
||||
if(ImageKnifeGlobal.getInstance().getImageKnife() != undefined) {
|
||||
let disk: DiskLruCache | undefined = (ImageKnifeGlobal.getInstance().getImageKnife())?.getDiskMemoryCache();
|
||||
|
|
|
@ -15,32 +15,35 @@
|
|||
|
||||
import { DiskLruCache } from "@ohos/disklrucache"
|
||||
import { LruCache } from "../cache/LruCache"
|
||||
import {EngineKeyFactories} from "../cache/key/EngineKeyFactories"
|
||||
import {EngineKeyInterface} from "../cache/key/EngineKeyInterface"
|
||||
import {RequestOption} from "../imageknife/RequestOption"
|
||||
import {AsyncCallback} from "../imageknife/interface/AsyncCallback"
|
||||
import {PlaceHolderManager} from "../imageknife/holder/PlaceHolderManager"
|
||||
import {RetryHolderManager} from "../imageknife/holder/RetryHolderManager"
|
||||
import {ErrorHolderManager} from "../imageknife/holder/ErrorHolderManager"
|
||||
import {RequestManager} from "../imageknife/requestmanage/RequestManager"
|
||||
import {NONE} from "../cache/diskstrategy/enum/NONE"
|
||||
import {FileTypeUtil} from '../imageknife/utils/FileTypeUtil'
|
||||
import {DownloadClient} from '../imageknife/networkmanage/DownloadClient'
|
||||
import {IDataFetch} from '../imageknife/networkmanage/IDataFetch'
|
||||
import {ParseResClient} from '../imageknife/resourcemanage/ParseResClient'
|
||||
import {IResourceFetch} from '../imageknife/resourcemanage/IResourceFetch'
|
||||
import {ImageKnifeData,ImageKnifeType} from '../imageknife/ImageKnifeData'
|
||||
import {ImageKnifeGlobal} from '../imageknife/ImageKnifeGlobal'
|
||||
import { EngineKeyFactories } from "../cache/key/EngineKeyFactories"
|
||||
import { EngineKeyInterface } from "../cache/key/EngineKeyInterface"
|
||||
import { RequestOption } from "../imageknife/RequestOption"
|
||||
import { AsyncCallback } from "../imageknife/interface/AsyncCallback"
|
||||
import { PlaceHolderManager } from "../imageknife/holder/PlaceHolderManager"
|
||||
import { RetryHolderManager } from "../imageknife/holder/RetryHolderManager"
|
||||
import { ErrorHolderManager } from "../imageknife/holder/ErrorHolderManager"
|
||||
import { RequestManager } from "../imageknife/requestmanage/RequestManager"
|
||||
import { NONE } from "../cache/diskstrategy/enum/NONE"
|
||||
import { FileTypeUtil } from '../imageknife/utils/FileTypeUtil'
|
||||
import { DownloadClient } from '../imageknife/networkmanage/DownloadClient'
|
||||
import { IDataFetch } from '../imageknife/networkmanage/IDataFetch'
|
||||
import { ParseResClient } from '../imageknife/resourcemanage/ParseResClient'
|
||||
import { IResourceFetch } from '../imageknife/resourcemanage/IResourceFetch'
|
||||
import { ImageKnifeData, ImageKnifeType } from '../imageknife/ImageKnifeData'
|
||||
import { ImageKnifeGlobal } from '../imageknife/ImageKnifeGlobal'
|
||||
import image from "@ohos.multimedia.image"
|
||||
import {CompressBuilder} from "../imageknife/compress/CompressBuilder"
|
||||
import { CompressBuilder } from "../imageknife/compress/CompressBuilder"
|
||||
import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle'
|
||||
import {LogUtil} from '../imageknife/utils/LogUtil'
|
||||
import { LogUtil } from '../imageknife/utils/LogUtil'
|
||||
import { EasyLinkedHashMap } from './utils/base/EasyLinkedHashMap'
|
||||
import { MethodMutex } from './utils/base/MethodMutex'
|
||||
import worker from '@ohos.worker'
|
||||
import common from '@ohos.app.ability.common'
|
||||
import HashMap from '@ohos.util.HashMap'
|
||||
import LinkedList from '@ohos.util.LinkedList'
|
||||
|
||||
export class ImageKnife {
|
||||
static readonly SEPARATOR: string = '/'
|
||||
|
||||
memoryCache: LruCache<string, ImageKnifeData>;
|
||||
diskMemoryCache: DiskLruCache;
|
||||
dataFetch: IDataFetch;
|
||||
|
@ -49,26 +52,31 @@ export class ImageKnife {
|
|||
|
||||
|
||||
placeholderCache: string = "placeholderCache"
|
||||
runningRequest: Array<RequestOption>;
|
||||
pendingRequest: Array<RequestOption>;
|
||||
runningMaps: EasyLinkedHashMap<string, RequestOption>;
|
||||
pendingMaps: EasyLinkedHashMap<string, RequestOption>;
|
||||
pausedMaps: EasyLinkedHashMap<string, RequestOption>;
|
||||
isPaused: boolean = false;
|
||||
mutex: MethodMutex = new MethodMutex();
|
||||
fileTypeUtil: FileTypeUtil; // 通用文件格式辨别
|
||||
diskCacheFolder: string = "ImageKnifeDiskCache"
|
||||
|
||||
|
||||
defaultListener: AsyncCallback<ImageKnifeData> = {
|
||||
callback:(err: string, data: ImageKnifeData)=>{return false}
|
||||
callback: (err: string, data: ImageKnifeData) => {
|
||||
return false
|
||||
}
|
||||
}; // 全局监听器
|
||||
|
||||
// gifWorker
|
||||
gifWorker: worker.ThreadWorker|undefined = undefined;
|
||||
|
||||
defaultLifeCycle: IDrawLifeCycle|undefined = undefined;
|
||||
|
||||
gifWorker: worker.ThreadWorker | undefined = undefined;
|
||||
defaultLifeCycle: IDrawLifeCycle | undefined = undefined;
|
||||
// 开发者可配置全局缓存
|
||||
engineKeyImpl: EngineKeyInterface;
|
||||
|
||||
private constructor() {
|
||||
|
||||
this.runningMaps = new EasyLinkedHashMap();
|
||||
this.pendingMaps = new EasyLinkedHashMap();
|
||||
this.pausedMaps = new EasyLinkedHashMap();
|
||||
|
||||
// 构造方法传入size 为保存文件个数
|
||||
this.memoryCache = new LruCache<string, ImageKnifeData>(100);
|
||||
|
||||
|
@ -84,22 +92,21 @@ export class ImageKnife {
|
|||
// 初始化本地 文件保存
|
||||
this.filesPath = (ImageKnifeGlobal.getInstance().getHapContext() as common.UIAbilityContext).filesDir as string;
|
||||
|
||||
this.runningRequest = new Array();
|
||||
this.pendingRequest = new Array();
|
||||
|
||||
// 通用文件格式识别初始化
|
||||
this.fileTypeUtil = new FileTypeUtil();
|
||||
|
||||
this.engineKeyImpl = new EngineKeyFactories();
|
||||
|
||||
|
||||
}
|
||||
|
||||
getMemoryCache(): LruCache<string, ImageKnifeData>{
|
||||
getMemoryCache(): LruCache<string, ImageKnifeData> {
|
||||
return this.memoryCache;
|
||||
}
|
||||
|
||||
public static with(context:Object): ImageKnifeGlobal{
|
||||
public static with(context: Object): ImageKnifeGlobal {
|
||||
// 存入hapContext;
|
||||
let global:ImageKnifeGlobal = ImageKnifeGlobal.getInstance();
|
||||
let global: ImageKnifeGlobal = ImageKnifeGlobal.getInstance();
|
||||
global.setHapContext(context)
|
||||
|
||||
// 初始化ImageKnife
|
||||
|
@ -113,7 +120,7 @@ export class ImageKnife {
|
|||
return global;
|
||||
}
|
||||
|
||||
getDiskMemoryCache(): DiskLruCache{
|
||||
getDiskMemoryCache(): DiskLruCache {
|
||||
return this.diskMemoryCache;
|
||||
};
|
||||
|
||||
|
@ -121,7 +128,7 @@ export class ImageKnife {
|
|||
this.diskMemoryCache = diskLruCache;
|
||||
};
|
||||
|
||||
getFileTypeUtil(): FileTypeUtil{
|
||||
getFileTypeUtil(): FileTypeUtil {
|
||||
return this.fileTypeUtil;
|
||||
}
|
||||
|
||||
|
@ -137,22 +144,23 @@ export class ImageKnife {
|
|||
return this.defaultListener;
|
||||
}
|
||||
|
||||
setGifWorker(worker:worker.ThreadWorker){
|
||||
setGifWorker(worker: worker.ThreadWorker) {
|
||||
this.gifWorker = worker
|
||||
}
|
||||
getGifWorker(){
|
||||
|
||||
getGifWorker() {
|
||||
return this.gifWorker;
|
||||
}
|
||||
|
||||
getDefaultLifeCycle(){
|
||||
getDefaultLifeCycle() {
|
||||
return this.defaultLifeCycle;
|
||||
}
|
||||
|
||||
setDefaultLifeCycle(viewLifeCycle:IDrawLifeCycle){
|
||||
setDefaultLifeCycle(viewLifeCycle: IDrawLifeCycle) {
|
||||
this.defaultLifeCycle = viewLifeCycle;
|
||||
}
|
||||
|
||||
setEngineKeyImpl(impl:EngineKeyInterface){
|
||||
setEngineKeyImpl(impl: EngineKeyInterface) {
|
||||
this.engineKeyImpl = impl;
|
||||
}
|
||||
|
||||
|
@ -162,34 +170,34 @@ export class ImageKnife {
|
|||
this.defaultListener = newDefaultListener;
|
||||
}
|
||||
|
||||
public compressBuilder(): CompressBuilder{
|
||||
public compressBuilder(): CompressBuilder {
|
||||
return new CompressBuilder();
|
||||
}
|
||||
|
||||
// 替代原来的LruCache
|
||||
public replaceLruCache(size:number){
|
||||
if(this.memoryCache.map.size() <= 0) {
|
||||
public replaceLruCache(size: number) {
|
||||
if (this.memoryCache.map.size() <= 0) {
|
||||
this.memoryCache = new LruCache<string, ImageKnifeData>(size);
|
||||
}else{
|
||||
} else {
|
||||
let newLruCache = new LruCache<string, ImageKnifeData>(size);
|
||||
this.memoryCache.foreachLruCache( (value:ImageKnifeData, key:string, map:Object)=> {
|
||||
this.memoryCache.foreachLruCache((value: ImageKnifeData, key: string, map: Object) => {
|
||||
newLruCache.put(key, value);
|
||||
})
|
||||
this.memoryCache = newLruCache;
|
||||
}
|
||||
}
|
||||
|
||||
public replaceDataFetch(fetch:IDataFetch){
|
||||
public replaceDataFetch(fetch: IDataFetch) {
|
||||
this.dataFetch = fetch;
|
||||
}
|
||||
|
||||
// 替代原来的DiskLruCache
|
||||
public replaceDiskLruCache(size:number) {
|
||||
public replaceDiskLruCache(size: number) {
|
||||
if (this.diskMemoryCache.getCacheMap().size() <= 0) {
|
||||
this.diskMemoryCache = DiskLruCache.create(ImageKnifeGlobal.getInstance().getHapContext(), size);
|
||||
} else {
|
||||
let newDiskLruCache = DiskLruCache.create(ImageKnifeGlobal.getInstance().getHapContext(), size);
|
||||
this.diskMemoryCache.foreachDiskLruCache( (value:string|ArrayBuffer, key:string, map:Object)=> {
|
||||
this.diskMemoryCache.foreachDiskLruCache((value: string | ArrayBuffer, key: string, map: Object) => {
|
||||
newDiskLruCache.set(key, value);
|
||||
})
|
||||
this.diskMemoryCache = newDiskLruCache;
|
||||
|
@ -197,17 +205,74 @@ export class ImageKnife {
|
|||
}
|
||||
|
||||
// 预加载 resource资源一级缓存,string资源实现二级缓存
|
||||
preload(request: RequestOption):void {
|
||||
preload(request: RequestOption): void {
|
||||
// 每个request 公共信息补充
|
||||
request.setFilesPath(this.filesPath);
|
||||
|
||||
return this.parseSource(request);
|
||||
}
|
||||
|
||||
// 暂停所有请求
|
||||
async pauseRequests(): Promise<void> {
|
||||
await this.mutex.lock(async () => {
|
||||
this.isPaused = true;
|
||||
|
||||
// 将未删除的所有request [run pend] 放入 [pause]
|
||||
this.pausedMaps.clear()
|
||||
console.log('dodo pauseRequests start1 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size())
|
||||
|
||||
// 将run存入pause
|
||||
let runNode = this.runningMaps.getHead();
|
||||
while (runNode) {
|
||||
let request = runNode.value;
|
||||
this.pausedMaps.put(request.uuid, request)
|
||||
runNode = runNode.next;
|
||||
}
|
||||
this.runningMaps.clear();
|
||||
console.log('dodo pauseRequests start2 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size())
|
||||
|
||||
let pendNode = this.pendingMaps.getHead();
|
||||
while (pendNode) {
|
||||
let request = pendNode.value;
|
||||
this.pausedMaps.put(request.uuid, request)
|
||||
pendNode = pendNode.next
|
||||
}
|
||||
this.pendingMaps.clear()
|
||||
console.log('dodo pauseRequests start3 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async resumeRequest(): Promise<void> {
|
||||
await this.mutex.lock(async () => {
|
||||
this.isPaused = false;
|
||||
console.log('dodo resumeRequest start1 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size())
|
||||
// 重启了之后需要把paused 里面的所有request重新发送
|
||||
let headNode = this.pausedMaps.getHead();
|
||||
while (headNode) {
|
||||
let request = headNode.value
|
||||
this.loadCacheManager(request)
|
||||
headNode = headNode.next
|
||||
}
|
||||
this.pausedMaps.clear()
|
||||
console.log('dodo resumeRequest start1 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size())
|
||||
})
|
||||
}
|
||||
|
||||
// 删除 请求
|
||||
remove(uuid: string) {
|
||||
if (this.isPaused) {
|
||||
this.pausedMaps.remove(uuid)
|
||||
} else {
|
||||
// TODO 哪些请求可以删除
|
||||
this.pendingMaps.remove(uuid)
|
||||
this.runningMaps.remove(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
// 正常加载
|
||||
call(request: RequestOption):void {
|
||||
call(request: RequestOption): void {
|
||||
// 添加全局监听
|
||||
if(this.defaultListener) {
|
||||
if (this.defaultListener) {
|
||||
request.addListener(this.defaultListener)
|
||||
}
|
||||
|
||||
|
@ -231,28 +296,28 @@ export class ImageKnife {
|
|||
}
|
||||
|
||||
loadResources(request: RequestOption) {
|
||||
let factories:EngineKeyInterface;
|
||||
let cacheKey:string;
|
||||
let transferKey:string;
|
||||
let dataKey:string;
|
||||
if(this.engineKeyImpl){
|
||||
let factories: EngineKeyInterface;
|
||||
let cacheKey: string;
|
||||
let transferKey: string;
|
||||
let dataKey: string;
|
||||
if (this.engineKeyImpl) {
|
||||
factories = this.engineKeyImpl;
|
||||
}else {
|
||||
} else {
|
||||
factories = new EngineKeyFactories();
|
||||
}
|
||||
// 生成内存缓存key 内存 变换后磁盘
|
||||
|
||||
let loadKey = '';
|
||||
if(typeof request.loadSrc == 'string'){
|
||||
if (typeof request.loadSrc == 'string') {
|
||||
loadKey = request.loadSrc;
|
||||
}else{
|
||||
} else {
|
||||
loadKey = JSON.stringify(request.loadSrc);
|
||||
}
|
||||
|
||||
let size = JSON.stringify(request.size);
|
||||
|
||||
let transformed = '';
|
||||
if(request && request.transformations) {
|
||||
if (request && request.transformations) {
|
||||
for (let i = 0; i < request.transformations.length; i++) {
|
||||
if (i == request.transformations.length - 1) {
|
||||
transformed += request.transformations[i].getName() + "";
|
||||
|
@ -289,92 +354,106 @@ export class ImageKnife {
|
|||
|
||||
// 删除执行结束的running
|
||||
removeRunning(request: RequestOption) {
|
||||
let index = -1;
|
||||
for (let i = 0; i < this.runningRequest.length; i++) {
|
||||
let tempRunning = this.runningRequest[i];
|
||||
if (this.keyEqual(request, tempRunning)) {
|
||||
// 如果key相同 说明找到执行的request,我们记录下当前request的index位置
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
let request = this.runningRequest.splice(index, 1)[0];
|
||||
this.loadNextPending(request);
|
||||
if (this.isPaused) {
|
||||
|
||||
} else {
|
||||
this.runningMaps.remove(request.uuid);
|
||||
console.log('dodo runningMaps length =' + this.runningMaps.size())
|
||||
let previousRequest = request;
|
||||
this.loadNextPending(previousRequest);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行相同key的pending队列请求
|
||||
keyEqualPendingToRun(index:number){
|
||||
let nextPending = this.pendingRequest.splice(index, 1)[0];
|
||||
this.runningRequest.push(nextPending)
|
||||
private keyEqualPendingToRun(nextPending: RequestOption) {
|
||||
// let nextPending = this.pendingRequest.splice(index, 1)[0];
|
||||
// this.runningRequest.push(nextPending)
|
||||
// RequestManager.execute((nextPending as RequestOption), this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch)
|
||||
|
||||
this.pendingMaps.remove(nextPending.uuid)
|
||||
this.runningMaps.put(nextPending.uuid, nextPending);
|
||||
|
||||
RequestManager.execute((nextPending as RequestOption), this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch)
|
||||
|
||||
}
|
||||
|
||||
searchNextKeyToRun(){
|
||||
private searchNextKeyToRun() {
|
||||
// 其次则寻找pending中第一个和running不重复的requestOrKey
|
||||
let index2 = -1;
|
||||
for (let i = 0; i < this.pendingRequest.length; i++) {
|
||||
let temppending = this.pendingRequest[i];
|
||||
let hasKeyEqual = false;
|
||||
for (let j = 0; j < this.runningRequest.length; j++) {
|
||||
let temprunning = this.runningRequest[j];
|
||||
if (this.requestOrKeyEqual(temppending, temprunning)) {
|
||||
hasKeyEqual = true;
|
||||
let pendingTailNode = this.pendingMaps.getTail();
|
||||
while (pendingTailNode) {
|
||||
let pendingRequest = pendingTailNode.value;
|
||||
let hasEqual = false;
|
||||
let runningTailNode = this.runningMaps.getTail();
|
||||
while (runningTailNode) {
|
||||
let runningRequest = runningTailNode.value;
|
||||
if (this.requestOrKeyEqual(pendingRequest, runningRequest)) {
|
||||
hasEqual = true;
|
||||
break
|
||||
}
|
||||
runningTailNode = runningTailNode.prev;
|
||||
}
|
||||
|
||||
if (hasEqual) {
|
||||
break;
|
||||
}
|
||||
pendingTailNode = pendingTailNode.prev;
|
||||
}
|
||||
if (!hasKeyEqual) {
|
||||
index2 = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index2 >= 0) {
|
||||
let nextPending = this.pendingRequest.splice(index2, 1)[0];
|
||||
this.runningRequest.push(nextPending)
|
||||
|
||||
if (pendingTailNode != null && pendingTailNode.value != null) {
|
||||
let nextPending = pendingTailNode.value;
|
||||
this.runningMaps.put(nextPending.uuid, nextPending)
|
||||
this.pendingMaps.remove(nextPending.uuid)
|
||||
RequestManager.execute((nextPending as RequestOption), this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch)
|
||||
} else {
|
||||
// 不执行
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 加载下一个key的请求
|
||||
loadNextPending(request:RequestOption) {
|
||||
// 首先寻找被移除key相同的request
|
||||
let index = -1;
|
||||
for (let i = 0; i < this.pendingRequest.length; i++) {
|
||||
let temppending = this.pendingRequest[i];
|
||||
if (this.requestOrKeyEqual(request, temppending)) {
|
||||
// 如果key相同 说明目前有任务正在执行,我们记录下当前request 放入pendingRunning
|
||||
index = i;
|
||||
private loadNextPending(request: RequestOption) {
|
||||
let hasEqualRunning = false;
|
||||
let tailNode = this.pendingMaps.getTail();
|
||||
while (tailNode) {
|
||||
if (this.requestOrKeyEqual(request, tailNode.value)) {
|
||||
hasEqualRunning = true;
|
||||
break;
|
||||
}
|
||||
tailNode = tailNode.prev;
|
||||
}
|
||||
if (index >= 0) {
|
||||
this.keyEqualPendingToRun(index);
|
||||
|
||||
if (hasEqualRunning) {
|
||||
this.keyEqualPendingToRun(tailNode.value);
|
||||
} else {
|
||||
this.searchNextKeyToRun();
|
||||
}
|
||||
}
|
||||
|
||||
// 启动新线程 去磁盘取 去网络取
|
||||
loadCacheManager(request: RequestOption) {
|
||||
private loadCacheManager(request: RequestOption) {
|
||||
if (this.isPaused) {
|
||||
// 将当前request存入pausedMaps
|
||||
this.pausedMaps.put(request.uuid, request);
|
||||
} else {
|
||||
// 正常逻辑
|
||||
if (this.keyNotEmpty(request)) {
|
||||
let hasRunningRequest = false;
|
||||
for (let i = 0; i < this.runningRequest.length; i++) {
|
||||
let tempRunning = this.runningRequest[i];
|
||||
if (this.requestOrKeyEqual(request, tempRunning)) {
|
||||
|
||||
// 如果requestOrKey相同 说明目前有任务正在执行,我们记录下当前request 放入pendingRunning
|
||||
// 遍历双向链表 从尾巴到头
|
||||
let tailNode = this.runningMaps.getTail();
|
||||
while (tailNode) {
|
||||
if (this.requestOrKeyEqual(request, tailNode.value)) {
|
||||
hasRunningRequest = true;
|
||||
break;
|
||||
}
|
||||
tailNode = tailNode.prev
|
||||
}
|
||||
|
||||
if (hasRunningRequest) {
|
||||
this.pendingRequest.push(request);
|
||||
this.pendingMaps.put(request.uuid, request);
|
||||
|
||||
// this.pendingRequest.push(request);
|
||||
} else {
|
||||
this.runningRequest.push(request);
|
||||
this.runningMaps.put(request.uuid, request)
|
||||
|
||||
// this.runningRequest.push(request);
|
||||
// 不存在相同key的 任务可以并行
|
||||
RequestManager.execute(request, this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch)
|
||||
}
|
||||
|
@ -382,11 +461,10 @@ export class ImageKnife {
|
|||
else {
|
||||
LogUtil.log("key没有生成无法进入存取!")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private keyNotEmpty(request: RequestOption): boolean{
|
||||
private keyNotEmpty(request: RequestOption): boolean {
|
||||
if (
|
||||
request.generateCacheKey != null && request.generateCacheKey.length > 0 &&
|
||||
request.generateDataKey != null && request.generateDataKey.length > 0 &&
|
||||
|
@ -397,7 +475,7 @@ export class ImageKnife {
|
|||
return false;
|
||||
}
|
||||
|
||||
private keyEqual(request1: RequestOption, request2: RequestOption): boolean{
|
||||
private keyEqual(request1: RequestOption, request2: RequestOption): boolean {
|
||||
// key 完全相等的情况
|
||||
if (
|
||||
request1.generateCacheKey == request2.generateCacheKey &&
|
||||
|
@ -411,7 +489,7 @@ export class ImageKnife {
|
|||
}
|
||||
|
||||
// 非严格校验模式,如果所有key相等我们认为一定相等, 如果请求类型是string 网络请求url或者uri相等 我们也认为该请求应该只发送一个即可,后续请求会去缓存或者磁盘读取
|
||||
private requestOrKeyEqual(request1: RequestOption, request2: RequestOption): boolean{
|
||||
private requestOrKeyEqual(request1: RequestOption, request2: RequestOption): boolean {
|
||||
// key 完全相等的情况
|
||||
if (
|
||||
request1.generateCacheKey == request2.generateCacheKey &&
|
||||
|
@ -422,7 +500,7 @@ export class ImageKnife {
|
|||
}
|
||||
|
||||
// 如果加载的是网络url或者是本地文件uri读取,那么loadSrc相同就认为是同一个请求
|
||||
if(
|
||||
if (
|
||||
typeof request1.loadSrc == 'string' && typeof request2.loadSrc == 'string' && request1.loadSrc == request2.loadSrc
|
||||
) {
|
||||
return true;
|
||||
|
@ -431,7 +509,7 @@ export class ImageKnife {
|
|||
return false;
|
||||
}
|
||||
|
||||
parseSource(request: RequestOption):void {
|
||||
private parseSource(request: RequestOption): void {
|
||||
if ((typeof (request.loadSrc as image.PixelMap).isEditable) == 'boolean') {
|
||||
let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, request.loadSrc as PixelMap)
|
||||
request.loadComplete(imageKnifeData);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
import { ImageKnifeOption } from '../imageknife/ImageKnifeOption'
|
||||
import { ImageKnifeGlobal } from '../imageknife/ImageKnifeGlobal'
|
||||
import { TransformType } from '../imageknife/transform/TransformType'
|
||||
import { RequestOption, Size } from '../imageknife/RequestOption'
|
||||
import { DetachFromLayout, RequestOption, Size } from '../imageknife/RequestOption'
|
||||
import { ImageKnifeData } from '../imageknife/ImageKnifeData'
|
||||
import { GIFFrame } from '../imageknife/utils/gif/GIFFrame'
|
||||
import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle'
|
||||
|
@ -82,6 +82,8 @@ export struct ImageKnifeComponent {
|
|||
private onReadyNext?: (data:ImageKnifeData|number|undefined) => void = undefined
|
||||
private onReadyNextData:ImageKnifeData|number|undefined = undefined
|
||||
|
||||
private detachFromLayout:DetachFromLayout;
|
||||
|
||||
build() {
|
||||
Canvas(this.context)
|
||||
.width('100%')
|
||||
|
@ -275,6 +277,7 @@ export struct ImageKnifeComponent {
|
|||
}
|
||||
this.resetGifData()
|
||||
let request = new RequestOption();
|
||||
this.detachFromLayout = request.detachFromLayout;
|
||||
this.configNecessary(request);
|
||||
this.configCacheStrategy(request);
|
||||
this.configDisplay(request);
|
||||
|
@ -584,6 +587,10 @@ export struct ImageKnifeComponent {
|
|||
|
||||
aboutToDisappear() {
|
||||
LogUtil.log('ImageKnifeComponent aboutToDisappear happened!')
|
||||
if(this.detachFromLayout){
|
||||
this.detachFromLayout.detach();
|
||||
}
|
||||
|
||||
this.resetGifData();
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,12 @@ export interface Size {
|
|||
width: number,
|
||||
height: number
|
||||
}
|
||||
export interface DetachFromLayout{
|
||||
detach:()=>void
|
||||
}
|
||||
|
||||
export class RequestOption {
|
||||
uuid:string ='' // 唯一标识
|
||||
loadSrc: string | PixelMap | Resource = '';
|
||||
strategy: DiskStrategy = new AUTOMATIC();
|
||||
dontAnimateFlag = false;
|
||||
|
@ -117,11 +122,30 @@ export class RequestOption {
|
|||
// 缩略图展示
|
||||
loadThumbnailReady = false;
|
||||
|
||||
|
||||
detachFromLayout:DetachFromLayout = {
|
||||
detach: ()=>{
|
||||
let imageKnife:ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife();
|
||||
if(imageKnife != undefined) {
|
||||
imageKnife.remove(this.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// 初始化全局监听
|
||||
this.requestListeners = new Array();
|
||||
// 初始化唯一标识,可以用这个标识找到ImageKnife中的对象
|
||||
this.uuid = this.generateUUID();
|
||||
}
|
||||
|
||||
generateUUID(): string {
|
||||
let d = new Date().getTime();
|
||||
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (d + Math.random() * 16) % 16 | 0;
|
||||
d = Math.floor(d / 16);
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,8 +173,6 @@ export class RequestOption {
|
|||
this.cachesPath = path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
load(src: string | PixelMap | Resource) {
|
||||
this.loadSrc = src;
|
||||
return this;
|
||||
|
@ -493,11 +515,11 @@ export class RequestOption {
|
|||
}
|
||||
}
|
||||
|
||||
// 加载成功之后
|
||||
let imageKnife:ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife();
|
||||
if(imageKnife != undefined) {
|
||||
imageKnife.removeRunning(this);
|
||||
}
|
||||
// // 加载成功之后
|
||||
// let imageKnife:ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife();
|
||||
// if(imageKnife != undefined) {
|
||||
// imageKnife.removeRunning(this);
|
||||
// }
|
||||
}
|
||||
|
||||
// 图片文件落盘之后会自动去寻找下一个数据加载
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
|
||||
import { IDataFetch } from '../networkmanage/IDataFetch'
|
||||
import { RequestOption } from '../RequestOption'
|
||||
import { NetworkDownloadClient } from './NetworkDownloadClient'
|
||||
|
||||
import { HttpDownloadClient } from './HttpDownloadClient'
|
||||
import { LoadLocalFileClient } from './LoadLocalFileClient'
|
||||
import { LoadDataShareFileClient } from './LoadDataShareFileClient'
|
||||
import loadRequest from '@ohos.request';
|
||||
|
@ -24,7 +25,7 @@ import common from '@ohos.app.ability.common'
|
|||
|
||||
// 数据加载器
|
||||
export class DownloadClient implements IDataFetch {
|
||||
private networkClient = new NetworkDownloadClient();
|
||||
private httpDownloadClient = new HttpDownloadClient();
|
||||
private localFileClient = new LoadLocalFileClient();
|
||||
private dataShareFileClient = new LoadDataShareFileClient();
|
||||
|
||||
|
@ -41,7 +42,7 @@ export class DownloadClient implements IDataFetch {
|
|||
this.dataShareFileClient.loadData(request, onCompleteFunction, onErrorFunction)
|
||||
} else {
|
||||
// 网络下载
|
||||
this.networkClient.loadData(request, onCompleteFunction, onErrorFunction)
|
||||
this.httpDownloadClient.loadData(request, onCompleteFunction, onErrorFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (C) 2021 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IDataFetch } from '../networkmanage/IDataFetch'
|
||||
import { RequestOption } from '../RequestOption'
|
||||
import { SparkMD5 } from '../../3rd_party/sparkmd5/spark-md5'
|
||||
import { FileUtils } from '../../cache/FileUtils'
|
||||
import loadRequest from '@ohos.request';
|
||||
import { LogUtil } from '../utils/LogUtil'
|
||||
import { ImageKnifeGlobal } from '../ImageKnifeGlobal'
|
||||
import common from '@ohos.app.ability.common'
|
||||
import { BusinessError } from '@ohos.base'
|
||||
import http from '@ohos.net.http'
|
||||
// 数据加载器
|
||||
class RequestData{
|
||||
receiveSize: number = 2000
|
||||
totalSize: number = 2000
|
||||
}
|
||||
|
||||
export class HttpDownloadClient implements IDataFetch {
|
||||
loadData(request: RequestOption, onComplete: (img: ArrayBuffer) => void, onError: (err: string) => void) {
|
||||
try {
|
||||
let httpRequest = http.createHttp()
|
||||
let arrayBuffers = new Array();
|
||||
httpRequest.on('headersReceive', (header: Object) => {
|
||||
// 跟服务器连接成功准备下载
|
||||
if (request.progressFunc) {
|
||||
// 进度条为0
|
||||
request.progressFunc.asyncSuccess(0)
|
||||
}
|
||||
})
|
||||
httpRequest.on('dataReceive', (data: ArrayBuffer) => {
|
||||
// 下载数据流多次返回
|
||||
arrayBuffers.push(data);
|
||||
})
|
||||
|
||||
httpRequest.on('dataReceiveProgress', (data: RequestData) => {
|
||||
// 下载进度
|
||||
let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100)
|
||||
if (request.progressFunc) {
|
||||
request.progressFunc.asyncSuccess(percent)
|
||||
}
|
||||
})
|
||||
|
||||
httpRequest.on('dataEnd', () => {
|
||||
// 下载完毕
|
||||
let combineArray = this.combineArrayBuffers(arrayBuffers);
|
||||
onComplete(combineArray)
|
||||
})
|
||||
|
||||
httpRequest.requestInStream(
|
||||
request.loadSrc as string,
|
||||
{
|
||||
method: http.RequestMethod.GET,
|
||||
expectDataType: http.HttpDataType.ARRAY_BUFFER,
|
||||
connectTimeout: 60000, // 可选 默认60000ms
|
||||
readTimeout: 0, //可选, 默认为60000ms
|
||||
usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定
|
||||
},
|
||||
(err: BusinessError, data: number) => {
|
||||
if (!err && data == 200) {
|
||||
|
||||
} else {
|
||||
httpRequest.off('headersReceive');
|
||||
httpRequest.off('dataReceive');
|
||||
httpRequest.off('dataReceiveProgress');
|
||||
httpRequest.off('dataEnd');
|
||||
httpRequest.destroy()
|
||||
onError('HttpDownloadClient err message =' + err.message)
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (err) {
|
||||
onError('HttpDownloadClient catch err request uuid ='+request.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
combineArrayBuffers(arrayBuffers: ArrayBuffer[]): ArrayBuffer {
|
||||
// 计算多个ArrayBuffer的总字节大小
|
||||
let totalByteLength = 0;
|
||||
for (const arrayBuffer of arrayBuffers) {
|
||||
totalByteLength += arrayBuffer.byteLength;
|
||||
}
|
||||
|
||||
// 创建一个新的ArrayBuffer
|
||||
const combinedArrayBuffer = new ArrayBuffer(totalByteLength);
|
||||
|
||||
// 创建一个Uint8Array来操作新的ArrayBuffer
|
||||
const combinedUint8Array = new Uint8Array(combinedArrayBuffer);
|
||||
|
||||
// 依次复制每个ArrayBuffer的内容到新的ArrayBuffer中
|
||||
let offset = 0;
|
||||
for (const arrayBuffer of arrayBuffers) {
|
||||
const sourceUint8Array = new Uint8Array(arrayBuffer);
|
||||
combinedUint8Array.set(sourceUint8Array, offset);
|
||||
offset += sourceUint8Array.length;
|
||||
}
|
||||
|
||||
return combinedArrayBuffer;
|
||||
}
|
||||
}
|
|
@ -292,6 +292,8 @@ export class RequestManager {
|
|||
// 处理磁盘加载 网络加载
|
||||
this.runWrapped(this.options, this.mRunReason, onComplete, onError)
|
||||
} else {
|
||||
// 需要清理状态
|
||||
cache.waitSaveDisk = false;
|
||||
onComplete(cache);
|
||||
return
|
||||
|
||||
|
|
|
@ -12,128 +12,36 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {LogUtil} from '../../imageknife/utils/LogUtil'
|
||||
import { LogUtil } from '../../imageknife/utils/LogUtil'
|
||||
|
||||
// jpg = 'jpg,0,FFD8',
|
||||
// png = 'png,0,89504E470D0A1A0A',
|
||||
// bmp = 'bmp,0,424D',
|
||||
// gif = 'gif,0,474946383961',
|
||||
// svg = 'svg,0,3C3F786D6C',
|
||||
// webp = 'webp,0,52494646',
|
||||
// tiff = 'tiff,0,492049|49492A00|4D4D002A|4D4D002B'
|
||||
|
||||
export class FileTypeUtil {
|
||||
private map:Map<string,string> = new Map<string,string>();
|
||||
private READ_MIN_LENGTH:number = 0;
|
||||
private SUPPORT_FORMATS = [
|
||||
PhotoFormat.jpg,
|
||||
PhotoFormat.png,
|
||||
PhotoFormat.tiff,
|
||||
PhotoFormat.bmp,
|
||||
PhotoFormat.webp,
|
||||
PhotoFormat.svg,
|
||||
PhotoFormat.gif
|
||||
]
|
||||
private fileSignatureMap: Record<string, Array<Uint8Array>> = {
|
||||
// 添加文件类型和对应的文件头部特征
|
||||
'jpg': [new Uint8Array([0xFF, 0xD8])],
|
||||
'png': [new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])],
|
||||
'gif': [new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61])],
|
||||
'bmp': [new Uint8Array([0x42, 0x4D])],
|
||||
'svg': [new Uint8Array([0x3C, 0x3F, 0x78, 0x6D, 0x6C])],
|
||||
'webp': [new Uint8Array([0x52, 0x49, 0x46, 0x46])],
|
||||
'tiff': [new Uint8Array([0x49, 0x20, 0x49]), new Uint8Array([0x49, 0x49, 0x2A, 0x00]), new Uint8Array([0x4D, 0x4D, 0x00, 0x2A]), new Uint8Array([0x4D, 0x4D, 0x00, 0x2B])],
|
||||
// 添加更多的文件类型和特征
|
||||
};
|
||||
|
||||
|
||||
constructor() {
|
||||
this.initImageType();
|
||||
|
||||
}
|
||||
|
||||
private getDataViewAt(dataView:DataView,index:number): string{
|
||||
return this.dec2Hex(dataView.getUint8(index))
|
||||
}
|
||||
|
||||
|
||||
|
||||
getFileType(arraybuffer: ArrayBuffer):string {
|
||||
let fileType:string = '';
|
||||
if (arraybuffer == null || arraybuffer == undefined || arraybuffer.byteLength <= this.READ_MIN_LENGTH) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let dataView = new DataView(arraybuffer);
|
||||
LogUtil.log('dataView +'+this.getDataViewAt(dataView,0)+this.getDataViewAt(dataView,1))
|
||||
let entries: IterableIterator<Object[]> = this.map.entries();
|
||||
for (let i = 0; i < this.map.size; i++) {
|
||||
let entry:Object[] = entries.next().value;
|
||||
let key = entry[0] as string
|
||||
let value = entry[1] as string
|
||||
let keySplit = key.split(',')
|
||||
if(keySplit.length == 2){
|
||||
let offset = Number(keySplit[0])
|
||||
let magicStringLength = keySplit[1].length;
|
||||
let readLength = magicStringLength/2;
|
||||
let start = 0;
|
||||
let fileMagic = ''
|
||||
while(start< readLength){
|
||||
fileMagic+=this.getDataViewAt(dataView,offset+start)
|
||||
start++;
|
||||
}
|
||||
if(fileMagic == keySplit[1]){
|
||||
LogUtil.log('匹配到了 fileType='+value)
|
||||
fileType = value
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileType;
|
||||
}
|
||||
|
||||
dec2Hex(uint8: number) {
|
||||
let hex = uint8.toString(16);
|
||||
if (hex.length <= 1) {
|
||||
hex = "0" + hex;
|
||||
}
|
||||
return hex.toUpperCase();
|
||||
}
|
||||
|
||||
private initImageType() {
|
||||
if(!this.map){
|
||||
this.map = new Map()
|
||||
}
|
||||
this.SUPPORT_FORMATS.forEach((value,index,arrs)=>{
|
||||
let values = value.split(',')
|
||||
if(values.length == 3){
|
||||
let magicSplits = values[2].split("|")
|
||||
if(magicSplits.length == 1){
|
||||
this.map.set(values[1]+','+values[2],values[0])
|
||||
}else if(magicSplits.length > 1){
|
||||
for(let i=0; i<magicSplits.length; i++){
|
||||
let magicStr = magicSplits[i];
|
||||
this.map.set(values[1]+','+magicStr,values[0])
|
||||
}
|
||||
}else{
|
||||
// 文件魔数不存在,不处理
|
||||
}
|
||||
}
|
||||
})
|
||||
this.initReadMinLength();
|
||||
|
||||
this.printMapContent();
|
||||
}
|
||||
|
||||
private printMapContent(){
|
||||
|
||||
let entries: IterableIterator<Object[]> = this.map.entries();
|
||||
for (let i = 0; i < this.map.size; i++) {
|
||||
let entry:Object[] = entries.next().value;
|
||||
let key = entry[0] as string
|
||||
let value = entry[1] as string
|
||||
LogUtil.log('key='+key+'---value='+value)
|
||||
}
|
||||
}
|
||||
|
||||
private initReadMinLength(){
|
||||
let max = 0;
|
||||
this.map.forEach((value,key,map)=>{
|
||||
let keySplit = key.split(',');
|
||||
if(keySplit.length == 2){
|
||||
let offset = Number(keySplit[0])
|
||||
let magicStringLength = keySplit[1].length;
|
||||
let tempMax = offset + magicStringLength/2;
|
||||
if(tempMax > max){
|
||||
max = tempMax;
|
||||
}
|
||||
}
|
||||
})
|
||||
this.READ_MIN_LENGTH =max;
|
||||
}
|
||||
|
||||
isImage(arraybuffer:ArrayBuffer){
|
||||
isImage(arraybuffer: ArrayBuffer) {
|
||||
let value = this.getFileType(arraybuffer);
|
||||
if(
|
||||
if (
|
||||
value == SupportFormat.jpg ||
|
||||
value == SupportFormat.png ||
|
||||
value == SupportFormat.tiff ||
|
||||
|
@ -141,14 +49,43 @@ export class FileTypeUtil {
|
|||
value == SupportFormat.bmp ||
|
||||
value == SupportFormat.gif ||
|
||||
value == SupportFormat.svg
|
||||
){
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getFileType(file: ArrayBuffer): string | null {
|
||||
const fileData = new Uint8Array(file);
|
||||
for (const fileType in this.fileSignatureMap) {
|
||||
const bufferList = this.fileSignatureMap[fileType];
|
||||
for (let i = 0; i < bufferList.length; i++) {
|
||||
let signature = bufferList[i];
|
||||
if(this.matchesSignature(fileData,signature)){
|
||||
return fileType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // 若无法识别文件类型,返回null
|
||||
}
|
||||
|
||||
|
||||
matchesSignature(fileData: Uint8Array, signature: Uint8Array): boolean {
|
||||
if (fileData.length < signature.length) {
|
||||
return false; // 文件长度不足,无法匹配魔数
|
||||
}
|
||||
|
||||
for (let i = 0; i < signature.length; i++) {
|
||||
if (fileData[i] !== signature[i]) {
|
||||
return false; // 魔数不匹配
|
||||
}
|
||||
}
|
||||
return true; // 文件头部魔数匹配
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export enum PhotoFormat {
|
||||
jpg = 'jpg,0,FFD8',
|
||||
png = 'png,0,89504E470D0A1A0A',
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
class Node<K, V> {
|
||||
key: K;
|
||||
value: V;
|
||||
next: Node<K, V> | null;
|
||||
prev: Node<K, V> | null;
|
||||
|
||||
constructor(key: K, value: V) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
}
|
||||
}
|
||||
// 链表的tail是最近访问,head是最晚访问,对顺序有要求需要访问tail
|
||||
export class EasyLinkedHashMap<K, V> {
|
||||
private map: Map<K, Node<K, V>>; // 存储键值对的哈希映射
|
||||
private head: Node<K, V> | null; // 链表头节点
|
||||
private tail: Node<K, V> | null; // 链表尾节点
|
||||
|
||||
constructor() {
|
||||
this.map = new Map<K, Node<K, V>>();
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
}
|
||||
|
||||
// 添加键值对到映射中,并在链表尾部添加新节点
|
||||
put(key: K, value: V) {
|
||||
if (this.map.has(key)) {
|
||||
// 如果键已存在,则更新对应的值
|
||||
const node = this.map.get(key)!;
|
||||
node.value = value;
|
||||
this.moveToTail(node);
|
||||
} else {
|
||||
// 创建新节点并加入链表尾部
|
||||
const newNode = new Node(key, value);
|
||||
this.map.set(key, newNode);
|
||||
if (this.tail) {
|
||||
this.tail.next = newNode;
|
||||
newNode.prev = this.tail;
|
||||
this.tail = newNode;
|
||||
} else {
|
||||
this.head = newNode;
|
||||
this.tail = newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从映射中移除键值对,并从链表中删除对应节点
|
||||
remove(key: K) {
|
||||
if (this.map.has(key)) {
|
||||
const node = this.map.get(key)!;
|
||||
this.map.delete(key);
|
||||
this.removeNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取键对应的值,并将对应节点移到链表尾部(表示最近访问)
|
||||
get(key: K): V | undefined {
|
||||
if (this.map.has(key)) {
|
||||
const node = this.map.get(key)!;
|
||||
this.moveToTail(node);
|
||||
return node.value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 将节点移动到链表尾部
|
||||
private moveToTail(node: Node<K, V>) {
|
||||
if (node === this.tail) {
|
||||
return; // 节点已在链表尾部,无需移动
|
||||
}
|
||||
|
||||
if (node === this.head) {
|
||||
this.head = node.next;
|
||||
} else {
|
||||
node.prev!.next = node.next;
|
||||
}
|
||||
|
||||
node.next!.prev = node.prev;
|
||||
this.tail!.next = node;
|
||||
node.prev = this.tail;
|
||||
node.next = null;
|
||||
this.tail = node;
|
||||
}
|
||||
|
||||
// 从链表中删除节点
|
||||
private removeNode(node: Node<K, V>) {
|
||||
if (node === this.head) {
|
||||
this.head = node.next;
|
||||
} else {
|
||||
node.prev!.next = node.next;
|
||||
}
|
||||
|
||||
if (node === this.tail) {
|
||||
this.tail = node.prev;
|
||||
} else {
|
||||
node.next!.prev = node.prev;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回映射中的键值对数量
|
||||
size(): number {
|
||||
return this.map.size;
|
||||
}
|
||||
// 返回顺序双向链表
|
||||
getTail(){
|
||||
return this.tail;
|
||||
}
|
||||
|
||||
getHead(){
|
||||
return this.head;
|
||||
}
|
||||
clear(){
|
||||
this.map.clear()
|
||||
this.head = null;
|
||||
this.tail=null;
|
||||
}
|
||||
}
|
||||
|
||||
function testLinkedHashMap(){
|
||||
// 创建一个新的 LinkedHashMap 实例
|
||||
let linkedHashMap = new EasyLinkedHashMap<string, number>();
|
||||
|
||||
// 添加键值对,并验证插入是否正确
|
||||
linkedHashMap.put('key1', 1);
|
||||
linkedHashMap.put('key2', 2);
|
||||
linkedHashMap.put('key3', 3);
|
||||
|
||||
console.log('dodo '+linkedHashMap.get('key1')); // 1
|
||||
console.log('dodo '+linkedHashMap.get('key2')); // 2
|
||||
console.log('dodo '+linkedHashMap.get('key3')); // 3
|
||||
|
||||
// 验证键值对更新是否正确
|
||||
linkedHashMap.put('key2', 20);
|
||||
console.log('dodo '+linkedHashMap.get('key2')); // 20
|
||||
|
||||
// 验证移除键值对是否正确
|
||||
linkedHashMap.remove('key1');
|
||||
console.log('dodo '+linkedHashMap.get('key1')); // undefined
|
||||
console.log('dodo '+linkedHashMap.size()); // 2
|
||||
|
||||
// 添加更多键值对
|
||||
linkedHashMap.put('key4', 4);
|
||||
linkedHashMap.put('key5', 5);
|
||||
linkedHashMap.put('key6', 6);
|
||||
|
||||
console.log('dodo '+linkedHashMap.size()); // 5
|
||||
|
||||
// 验证访问顺序是否正确,最近访问的键值对应该在链表尾部
|
||||
console.log('dodo '+linkedHashMap.get('key2')); // 20
|
||||
console.log('dodo '+linkedHashMap.get('key3')); // 3
|
||||
console.log('dodo '+linkedHashMap.get('key4')); // 4
|
||||
|
||||
// 添加更多键值对,超出容量限制(假设容量限制为 5)
|
||||
linkedHashMap.put('key7', 7);
|
||||
linkedHashMap.put('key8', 8);
|
||||
linkedHashMap.put('key9', 9);
|
||||
|
||||
console.log('dodo '+linkedHashMap.size()); // 5
|
||||
console.log('dodo '+linkedHashMap.get('key1')); // undefined,因为已经超过容量限制,最早访问的键值对被移除
|
||||
|
||||
// 验证移除最近访问的键值对后,访问顺序是否正确
|
||||
linkedHashMap.remove('key4');
|
||||
console.log('dodo '+linkedHashMap.get('key4')); // undefined
|
||||
console.log('dodo '+linkedHashMap.get('key2')); // 20
|
||||
console.log('dodo '+linkedHashMap.get('key3')); // 3
|
||||
|
||||
// 清空 LinkedHashMap
|
||||
linkedHashMap.remove('key2');
|
||||
linkedHashMap.remove('key3');
|
||||
linkedHashMap.remove('key5');
|
||||
linkedHashMap.remove('key6');
|
||||
linkedHashMap.remove('key7');
|
||||
linkedHashMap.remove('key8');
|
||||
linkedHashMap.remove('key9');
|
||||
|
||||
console.log('dodo '+linkedHashMap.size()); // 0
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Huawei Device Co., Ltd.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export class MethodMutex {
|
||||
private mutex: Promise<void>;
|
||||
private release: () => void;
|
||||
|
||||
constructor() {
|
||||
this.mutex = Promise.resolve();
|
||||
this.release = () => {};
|
||||
}
|
||||
|
||||
async lock<T>(fn: () => Promise<T>): Promise<T> {
|
||||
await this.mutex;
|
||||
let result: T;
|
||||
|
||||
try {
|
||||
this.mutex = new Promise<void>((resolve) => {
|
||||
this.release = resolve;
|
||||
});
|
||||
result = await fn();
|
||||
} finally {
|
||||
this.release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue