Android SharedPreferences源码分析

news2024/9/30 19:39:05

文章目录

  • Android SharedPreferences源码分析
    • 概述
    • 基本使用
    • 源码分析
      • 获取SP对象
      • 初始化和读取数据
      • 写入数据
        • MemoryCommitResult
        • commitToMemory()
        • commit()
        • apply()
        • enqueueDiskWrite()
        • writeToFile()
      • 主动等待写回任务结束
    • 总结

Android SharedPreferences源码分析

概述

SharedPreferences 是 Android 平台上轻量级的 K-V 存储框架。

SharedPreferences 采用 XML 文件格式持久化键值对数据,文件的存储位置位于应用沙盒的内部存储 /data/data/<包名>/shared_prefs/ 位置,每个 XML 文件对应于一个 SharedPreferences 对象。

一个sp文件(XML文件) 对应一个SharedPreferences对象。

基本使用

SharedPreferences sp = context.getSharedPreferences("app", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString("name", "小明")
        .putInt("age", 18)
        .putBoolean("sex", true)
        .apply();

String name = sp.getString("name", "");
int age = sp.getInt("age", 0);
boolean sex = sp.getBoolean("sex", false);

查看 /data/data/com.example.myapplication/shared_prefs/app.xml 文件:

在这里插入图片描述

源码分析

获取SP对象

ContextImpl 类是 Context 抽象类的实现类。

// ContextImpl.java

class ContextImpl extends Context {
    // sp文件的根目录
    private File mPreferencesDir;
    // 缓存name和file对象的对应关系
    private ArrayMap<String, File> mSharedPrefsPaths;
    // 缓存包名、file对象和SharedPreferencesImpl对象的对应关系
    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

    // 通过name获取sp对象
    public SharedPreferences getSharedPreferences(String name, int mode) {      
        File file;
        synchronized (ContextImpl.class) {
            // mSharedPrefsPaths缓存对象为null则创建
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            // 从mSharedPrefsPaths缓存中获取file对象
            file = mSharedPrefsPaths.get(name);
            // 如果file对象为null,则创建file对象
            if (file == null) {
                // 通过路径获取file对象
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

    // 创建file对象
    public File getSharedPreferencesPath(String name) {
        return makeFilename(getPreferencesDir(), name + ".xml");
    }

    // 获取sp文件的根目录
    private File getPreferencesDir() {
        synchronized (mSync) {
            if (mPreferencesDir == null) {
                mPreferencesDir = new File(getDataDir(), "shared_prefs");
            }
            return ensurePrivateDirExists(mPreferencesDir);
        }
    }

    // 通过file对象获取sp对象
    public SharedPreferences getSharedPreferences(File file, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            // 从sSharedPrefsCache缓存获取ArrayMap
            final ArrayMap<File, SharedPreferencesImpl> cache = 							 													getSharedPreferencesCacheLocked();
            // 从缓存中获取sp对象
            sp = cache.get(file);
            if (sp == null) {
                // 如果sp对象为null,则创建并缓存
                checkMode(mode);     
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        return sp;
    }

    // 从sSharedPrefsCache缓存获取sp对象
    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
        // sSharedPrefsCache缓存对象为null则创建
        if (sSharedPrefsCache == null) {
            sSharedPrefsCache = new ArrayMap<>();
        }
        // 获取包名
        final String packageName = getPackageName();
        // sSharedPrefsCache通过包名获取ArrayMap
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        // packagePrefs为null则创建
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }
        return packagePrefs;
    }
}

初始化和读取数据

// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {

    // 目标文件
    private final File mFile;
    // 锁
    private final Object mLock = new Object();
    // 文件是否加载
    private boolean mLoaded = false;

    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        mThrowable = null;
        // 开始加载文件
        startLoadFromDisk();
    }

    // 开启线程加载文件
    private void startLoadFromDisk() {
        synchronized (mLock) {
            mLoaded = false;
        }
        // 开启线程
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                loadFromDisk();
            }
        }.start();
    }

    // 加载文件
    private void loadFromDisk() {
        synchronized (mLock) {
            if (mLoaded) {
                return;
            }
            // 如果有备份文件,则恢复备份文件
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }

        Map<String, Object> map = null;  
        if (mFile.canRead()) {
            // 读取文件
            BufferedInputStream str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);
            // 解析XML文件并转为Map
            map = (Map<String, Object>) XmlUtils.readMapXml(str);            
        }

        synchronized (mLock) {
            mLoaded = true;
            if (map != null) {
                // 使用新Map
                mMap = map;
            } else {
                mMap = new HashMap<>();
            }
            // 解析完文件后唤醒锁
            mLoaded = true;
            mLock.notifyAll();
        }
    }

    // 获取数据
    public String getString(String key, @Nullable String defValue) {
        synchronized (mLock) {
            // 查询数据时可能会阻塞等待
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

    // 等待
    private void awaitLoadedLocked() {
        while (!mLoaded) {
            try {
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
    }
}    

写入数据

虽然 ContextImpl 中使用了内存缓存,但是最终数据还是需要执行磁盘 IO 持久化到磁盘文件中。如果每一次 “变更操作” 都对应一次磁盘 “写回操作” 的话,不仅效率低下,而且没有必要。所以 SharedPreferences 会使用 “事务” 机制,将多次变更操作聚合为一个 “事务”,一次事务最多只会执行一次磁盘写回操作。
SharedPreferences 的事务操作由 Editor 接口实现。

// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {

    public Editor edit() {
        // 等待文件加载完成
        synchronized (mLock) {
            awaitLoadedLocked();
        }
        // 创建编辑器
        return new EditorImpl();
    }

    // 编辑器
    public final class EditorImpl implements Editor {
        // 锁对象
        private final Object mEditorLock = new Object();
        // 修改记录
        private final Map<String, Object> mModified = new HashMap<>();
        // 清除全部数据的标记
        private boolean mClear = false;

        // 存放String类型数据
        @Override
        public Editor putString(String key, @Nullable String value) {
            synchronized (mEditorLock) {
                mModified.put(key, value);
                return this;
            }
        }

        // 存放int类型数据
        @Override
        public Editor putInt(String key, int value) {
            synchronized (mEditorLock) {
                mModified.put(key, value);
                return this;
            }
        }

        // 删除数据
        @Override
        public Editor remove(String key) {
            synchronized (mEditorLock) {
                mModified.put(key, this);
                return this;
            }
        }

        // 清除所有数据
        @Override
        public Editor clear() {
            synchronized (mEditorLock) {
                mClear = true;
                return this;
            }
        }

        // 异步提交
        @Override
        public void apply() {
            ...
        }

        // 同步提交,并返回操作结果是否成功
        @Override
        public boolean commit() {
            ...
        }
    }   
}
MemoryCommitResult

MemoryCommitResult 是一个事务对象,收集需要写入磁盘的数据。

// MemoryCommitResult.java

private static class MemoryCommitResult {
    // 内存版本号
    final long memoryStateGeneration;
    // 写入磁盘的数据
    final Map<String, Object> mapToWriteToDisk;
    // 同步计数器  
    final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

    @GuardedBy("mWritingToDiskLock")
    volatile boolean writeToDiskResult = false;
    boolean wasWritten = false;

    void setDiskWriteResult(boolean wasWritten, boolean result) {
        this.wasWritten = wasWritten;
        writeToDiskResult = result;
        // 唤醒等待锁
        writtenToDiskLatch.countDown();
    }
}
commitToMemory()
// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {
    // 每次提交数据自增 1,事务结束自减 1
    private int mDiskWritesInFlight = 0;
    // 内存版本
    private long mCurrentMemoryStateGeneration;
    // 磁盘版本
    private long mDiskStateGeneration;
    // 修改记录
    private final Map<String, Object> mModified = new HashMap<>();
    // 清除全部数据的标记
    private boolean mClear = false;

    // 全量提交到内存
    private MemoryCommitResult commitToMemory() {
        long memoryStateGeneration;
        boolean keysCleared = false;
        List<String> keysModified = null;
        Set<OnSharedPreferenceChangeListener> listeners = null;
        Map<String, Object> mapToWriteToDisk;

        synchronized (SharedPreferencesImpl.this.mLock) {
            // 表示有多个写入操作
            if (mDiskWritesInFlight > 0) {
                mMap = new HashMap<String, Object>(mMap);
            }
            // 需要写入磁盘的数据
            mapToWriteToDisk = mMap; // 全量Map
            // 自增加1
            mDiskWritesInFlight++;

            synchronized (mEditorLock) {
                // 是否发生有效修改
                boolean changesMade = false;

                // 清除全部数据
                if (mClear) {
                    if (!mapToWriteToDisk.isEmpty()) {
                        changesMade = true;
                        mapToWriteToDisk.clear();
                    }
                    keysCleared = true;
                    mClear = false;
                }

                // 将内存中的mModified都数据整合到mapToWriteToDisk中
                for (Map.Entry<String, Object> e : mModified.entrySet()) {
                    String k = e.getKey();
                    Object v = e.getValue();
                    if (v == this || v == null) {
                        // 如果value是this,表示删除这个key
                        if (!mapToWriteToDisk.containsKey(k)) {
                            continue;
                        }
                        mapToWriteToDisk.remove(k);
                    } else {
                        // 修改key-value值
                        if (mapToWriteToDisk.containsKey(k)) {
                            Object existingValue = mapToWriteToDisk.get(k);
                            if (existingValue != null && existingValue.equals(v)) {
                                continue;
                            }
                        }
                        mapToWriteToDisk.put(k, v);
                    }

                    // 标记修改完成
                    changesMade = true;
                    // 记录发生修改的key
                    if (hasListeners) {
                        keysModified.add(k);
                    }
                }

                // 重置内存中的mModified
                mModified.clear();

                // 修改完成,自增加1
                if (changesMade) {
                    mCurrentMemoryStateGeneration++;
                }
                // 修改内存版本号
                memoryStateGeneration = mCurrentMemoryStateGeneration;
            }
        }
        return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
                                      listeners, mapToWriteToDisk);
    }
}
commit()
// SharedPreferencesImpl.EditorImpl#commit()

public boolean commit() {
    // 写入到内存,并获取事务对象
    MemoryCommitResult mcr = commitToMemory();
    // 写入到磁盘
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
    // 写入等待
    mcr.writtenToDiskLatch.await();       
    // 触发回调监听
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}
apply()
// SharedPreferencesImpl.EditorImpl#apply()

public void apply() {
    // 写入到内存,并获取事务对象
    final MemoryCommitResult mcr = commitToMemory();
    // 创建一个Runnable
    final Runnable awaitCommit = new Runnable() {
        @Override
        public void run() {
            // 阻塞线程,直到磁盘操作执行完毕
            mcr.writtenToDiskLatch.await();
        }
    };
    // 将Runnable提交到QueuedWork中
    QueuedWork.addFinisher(awaitCommit);
    // 创建一个Runnable,写入成功后QueuedWork删除awaitCommit任务
    Runnable postWriteRunnable = new Runnable() {
        @Override
        public void run() {
            awaitCommit.run();
            QueuedWork.removeFinisher(awaitCommit);
        }
    };
    // 提交写入磁盘任务
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
    // 触发回调监听
    notifyListeners(mcr);
}
enqueueDiskWrite()
// SharedPreferencesImpl#enqueueDiskWrite()

// postWriteRunnable为null表示commit同步提交,不为null表示apply异步提交
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
    // 判断是否为同步操作
    final boolean isFromSyncCommit = (postWriteRunnable == null);
    // 写入磁盘任务
    final Runnable writeToDiskRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (mWritingToDiskLock) {
                // 写入到磁盘
                writeToFile(mcr, isFromSyncCommit);
            }
            synchronized (mLock) {
                // 自减1
                mDiskWritesInFlight--;
            }
            if (postWriteRunnable != null) {
                postWriteRunnable.run();
            }
        }
    };

    // 如果是同步操作
    if (isFromSyncCommit) {
        // 判断mDiskWritesInFlight变量,如果存在并发写入则交给QueuedWork
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        // wasEmpty为true,表示没有并发写入,则直接执行写入任务
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }

    // 如果是异步操作,提交到QueuedWork执行
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
writeToFile()
// SharedPreferencesImpl#writeToFile()

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    // 判断旧文件是否存在	
    boolean fileExists = mFile.exists();

    if (fileExists) {
        // 是否执行写入操作
        boolean needsWrite = false;
        // 如果磁盘版本小于内存版本,执行磁盘写入操作
        if (mDiskStateGeneration < mcr.memoryStateGeneration) {
            if (isFromSyncCommit) {
                // 如果是同步执行,一定执行写入操作
                needsWrite = true;
            } else {
                // 如果是异步执行,只有最新的内存版本菜执行写入操作
                synchronized (mLock) {
                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                        needsWrite = true;
                    }
                }
            }
        }

        // 无效的异步写回,直接结束
        if (!needsWrite) {
            mcr.setDiskWriteResult(false, true);
            return;
        }

        // 文件备份
        boolean backupFileExists = mBackupFile.exists();

        if (!backupFileExists) {
            // 如果没有备份文件,将旧文件改为备份文件
            if (!mFile.renameTo(mBackupFile)) {
                mcr.setDiskWriteResult(false, false);
                return;
            }
        } else {
            // 如果有备份文件,则删除旧文件
            mFile.delete();
        }
    }

    try {
        // 执行写入操作,写入到xml文件中
        FileOutputStream str = createFileOutputStream(mFile);     
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
        FileUtils.sync(str);
        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

        // 写入成功,直接删除备份文件
        mBackupFile.delete();
        // 同步磁盘版本号
        mDiskStateGeneration = mcr.memoryStateGeneration;
        // 写入成功
        mcr.setDiskWriteResult(true, true);

        return;
    } catch (XmlPullParserException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    } catch (IOException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    }

    // 写入失败会执行这里
    if (mFile.exists()) {
        if (!mFile.delete()) {
            Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false, false);
}

主动等待写回任务结束

在 apply() 方法中,在执行 enqueueDiskWrite() 前创建了 awaitCommit 任务并加入到 QueudWork 等待队列,直到磁盘写回结束才将 awaitCommit 移除。这个 awaitCommit 任务是做什么的呢?

mcr.writtenToDiskLatch.await();

可以看到,在主线程的 Activity#onPause、Activity#onStop、Service#onStop、Service#onStartCommand 等生命周期状态变更时,会调用 QueudeWork.waitToFinish():

// ActivityThread.java

public void handlePauseActivity(...) {
    performPauseActivity(r, finished, reason, pendingActions);
    if (r.isPreHoneycomb()) {
        QueuedWork.waitToFinish();
    }
}

private void handleStopService(IBinder token) {
    QueuedWork.waitToFinish();
    ActivityManager.getService().serviceDoneExecuting(token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
}

waitToFinish() 会执行所有 sFinishers 等待队列中的 aWaitCommit 任务,主动等待所有磁盘写回任务结束。在写回任务结束之前,主线程会阻塞在等待锁上,这里也有可能发生 ANR。

总结

SharedPreferences 是一个轻量级的 K-V 存储框架。从源码分析中,可以看到 SharedPreferences 在读写性能、可用性方面都有做一些优化,例如:锁细化、事务化、事务过滤、文件备份等。

建议:

  • 因为SP初始化时会加载全部sp文件和全量提交,因此大文件尽可能的拆分多个文件,修改多个数据时尽可能一起提交。
  • 因为 commit() 是同步操作,apply() 是异步操作,因此推荐使用 apply()。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1413499.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Vue3 基础知识》 Vue2+ElementUI 自动转 Vue3+ElementPlus(GoGoCode)

前言 GoGoCode 一个基于 AST 的 JavaScript/Typescript/HTML 代码转换工具。 AST abstract syntax code 抽象语法树。 实现 第一步&#xff1a;安装 GoGoCode 插件 全局安装最新的 gogocode-cli 即可 npm i gogocode-cli -g查看版本 gogocode-cli -V相关插件说明 插件描述…

qt 坦克大战游戏 GUI绘制

关于本章节中使用的图形绘制类&#xff0c;如QGraphicsView、QGraphicsScene等的详细使用说明请参见我的另一篇文章&#xff1a; 《图形绘制QGraphicsView、QGraphicsScene、QGraphicsItem、Qt GUI-CSDN博客》 本文将模仿坦克大战游戏&#xff0c;目前只绘制出一辆坦克&#…

看懂linux内核详解实现分解

一、linux的内核管理&#xff1a;对内核的基本认识 我们所谈到的操作系统主要指内核 以上功能据没有涉及实现文本编辑、实现字处理&#xff0c;也没有服务等等。 故&#xff0c;操作系统是一种通用软件&#xff0c;是平台类软件&#xff0c;自己并不做任何工作&#xff0c;只…

[嵌入式软件][启蒙篇][仿真平台] STM32F103实现IIC控制OLED屏幕

上一篇&#xff1a;[嵌入式软件][启蒙篇][仿真平台] STM32F103实现LED、按键 [嵌入式软件][启蒙篇][仿真平台] STM32F103实现串口输出输入、ADC采集 [嵌入式软件][启蒙篇][仿真平台]STM32F103实现定时器 [嵌入式软件][启蒙篇][仿真平台] STM32F103实现IIC控制OLED屏幕 文章目…

Unity中URP下额外灯角度衰减

文章目录 前言一、额外灯中聚光灯的角度衰减二、AngleAttenuation函数的传入参数1、参数&#xff1a;spotDirection.xyz2、_AdditionalLightsSpotDir3、参数&#xff1a;lightDirection4、参数&#xff1a;distanceAndSpotAttenuation.zw5、_AdditionalLightsAttenuation 三、A…

【DevOps】Jenkins Extended E-mail 邮件模板添加自定义变量

文章目录 1、配置Jenkins邮箱2、配置告警模板1、配置Jenkins邮箱 略 2、配置告警模板 自定义变量:DYSK_PYTEST_STATUS // Uses Declarative syntax to run commands inside a container. pipeline {agent {kubernetes {cloud "kubernetes" //选择名字是kuberne…

2024年上海高考数学最后四个多月的备考攻略,目标140+

亲爱的同学们&#xff0c;寒假已经来临&#xff0c;春节即将到来&#xff0c;距离2024年上海高考已经余额不足5个月了。作为让许多学子头疼&#xff0c;也是拉分大户的数学科目&#xff0c;你准备好了吗&#xff1f;今天&#xff0c;六分成长为您分享上海高考数学最后四个多月的…

武忠祥2025高等数学,基础阶段的百度网盘+视频及PDF

考研数学武忠祥基础主要学习以下几个方面的内容&#xff1a; 1.微积分:主要包括极限、连续、导数、积分等概念&#xff0c;以及它们的基本性质和运算方法。 2.线性代数:主要包括向量、向量空间、线性方程组、矩阵、行列式、特征值和特征向量等概念&#xff0c;以及它们的基本…

Bluetooth Device Address(BD_ADDR) - 2

蓝牙核心规范&#xff1a;Core v5.3中关于蓝牙地址的其他说明 Vol 3: Host, Part C: Generic Access Profile 3 User interface aspects 3.2 Representation of Bluetooth parameters 3.2.1 Bluetooth Device Address (BD_ADDR) BD_ADDR 是蓝牙设备使用的地址。在设备发现过…

Linux中断 -- 中断路由、优先级、数据和标识

目录 1.中断路由 2.中断优先级 3.中断平衡 4.Linux内核中重要的数据结构 5.中断标识 承前文&#xff0c;本文从中断路由、优先级、数据结构和标识意义等方面对Linux内核中断进行一步的解析。 1.中断路由 Aset affinity flow GIC文中有提到SPI类型中断的路由控制器寄存器为…

01 Redis的特性+下载安装启动+客户端连接

1.1 NoSQL NoSQL&#xff08;“non-relational”&#xff0c; “Not Only SQL”&#xff09;&#xff0c;泛指非关系型的数据库。 键值存储数据库 &#xff1a; 就像 Map 一样的 key-value 对。如Redis文档数据库 &#xff1a; NoSQL 与关系型数据的结合&#xff0c;最像关系…

南昌市青山湖、滕王阁、洛阳路隧道FM调频广播集群通信调度系统应用案例

一、用户需求 青山湖隧道&#xff0c;是南昌市一条东西走向的城市主干道&#xff0c;隧道为双向6车道&#xff0c;长1070米&#xff0c;其中湖底暗埋段为550米&#xff0c;净高5.45米&#xff0c;两孔每孔净宽12.4米。 滕王阁隧道是南昌市沿江北大道与沿江中大道连通工程&#…

智能分析网关V4智慧机房:视频AI智能安全监管方案

一、背景分析 随着互联网的迅猛发展&#xff0c;机房及其配套设施的数量持续攀升&#xff0c;它们的运行状况对于企业运营效率和服务质量的影响日益显著。作为企业信息化的基石&#xff0c;机房的安全监测与管理的重要性不容忽视。它不仅关乎企业的稳定运营&#xff0c;同时也直…

Redis6基础知识梳理~

初识NOSQL&#xff1a; NOSQL是为了解决性能问题而产生的技术&#xff0c;在最初&#xff0c;我们都是使用单体服务器架构&#xff0c;如下所示&#xff1a; 随着用户访问量大幅度提升&#xff0c;同时产生了大量的用户数据&#xff0c;单体服务器架构面对着巨大的压力 NOSQL解…

ctf-idea调试jar包

0.拿到jar包并解压 进入解压出来的目录,然后以该目录打开项目 1.设置maven 设不设置都行 2.添加依赖 添加两个依赖, boot-inf下的 classes和lib 3.配置调试器 添加 remote jvm debug 1.根据jdk版本选择调试参数 2.选择module classpath为解压后的文件夹名 如图,运行jar包的…

【Deeplabv3+】Ubutu18.04中使用pytorch复现Deeplabv3+第三步)-----CityscapesScripts生成自己的标签

本文是在前面两篇文章的基础上&#xff0c;讲解如何更改训练数据集颜色&#xff0c;需要与前面两篇文章连起来看。 本文用于修改cityscapes数据集的标签颜色与Semankitti数据集的标签一致&#xff0c;对修改后的数据集进行训练。需要下载两个开发工具包和一个数据集&#xff0…

免费交互式大模型在线图像去除水印.擦除.替换和增强照片项目代码(免费在线图像修复工具)

图像修复工具&#xff1a;基于SOTA人工智能模型的应用 原始图&#xff1a; 擦出不想要区域 结果展示 并继续擦除 怎么样神奇把&#xff0c;水印也同样神奇的效果&#xff01;&#xff01;&#xff01; 想要了解更多&#xff0c;请看下文&#xff01; &#xff01; 基于S…

安全小记-ngnix负载均衡

目录 一.配置ngnix环境二.nginx负载均衡 一.配置ngnix环境 本次实验使用的是centos7,首先默认yum源已经配置好&#xff0c;没有配置好的自行访问阿里云镜像站 https://developer.aliyun.com/mirror/ 接着进行安装工作 1.首先创建Nginx的目录并进入&#xff1a; mkdir /soft &…

Qt编写手机端视频播放器/推流工具/Onvif工具

一、视频播放器 同时支持多种解码内核&#xff0c;包括qmedia内核&#xff08;Qt4/Qt5/Qt6&#xff09;、ffmpeg内核&#xff08;ffmpeg2/ffmpeg3/ffmpeg4/ffmpeg5/ffmpeg6&#xff09;、vlc内核&#xff08;vlc2/vlc3&#xff09;、mpv内核&#xff08;mpv1/mp2&#xff09;、…

淘宝扭蛋机小程序:新时代的互动营销与娱乐体验

随着科技的快速发展&#xff0c;小程序已经成为人们日常生活中不可或缺的一部分。在众多的小程序中&#xff0c;淘宝扭蛋机小程序以其独特的互动性和趣味性&#xff0c;吸引了大量用户。本文将深入探讨淘宝扭蛋机小程序的特色、用户体验以及未来发展。 一、淘宝扭蛋机小程序的…