一、概述

  SharedPreferences(简称SP)是Android中很常用的数据存储方式,SP采用key-value(键值对)形式,主要用于轻量级的数据存储,尤其适合保存应用的配置参数,但不建议使用SP来存储大规模的数据,可能会降低性能。
  SP采用xml文件格式来保存数据,改文件所在目录位于/data/data/shared_prefs/。

二、使用

1.得到SharedPreferences对象

private SharedPreferences mSharedPreferences;
private final static String PREFRENCE_FILE_KEY = "com.zhangmiao.shared_preferences";
mSharedPreferences = getSharedPreferences(PREFRENCE_FILE_KEY, MODE_PRIVATE);

2.添加数据

        final SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putInt("id",1);
editor.putString("name","小熊");
editor.putInt("age",24);
editor.commit();

3.获取数据

        TextView textView = (TextView)findViewById(R.id.text);
String message = "id = " + mSharedPreferences.getInt("id",-1)
+ ",name = " + mSharedPreferences.getString("name","无")
+ ",age = " + mSharedPreferences.getInt("age",-1)+"。";
textView.setText(message);

4.查看生成的sharedpreferences.xml文件

我使用的是adb命令查看的文件,命令如下:(系统是window10)
adb shell
run-as com.zhangmiao.myapplication(应用包名)
ls(查看xml文件的名称)
cat com.zhangmiao.shared_preferences.xml(查看xml文件)

三、解析SharedPreferences

1.获取方式

1.1.getSharedPreferences(String name, int mode)

    @Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}

这里的mBase就是Context类,Context的具体实现类是ContextImpl类,所以直接查看ContextImpl类中的getSharedPreferences方法。

@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
} File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);【1.3】
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);【1.2】
}

  第一步:判断版本号是否小于17,如果是,判断name是否为null,如果是,则设置name为“null”。
  第二步:同步ContextImpl.class,如果mSharedPrefsPaths为null,则初始化mSharedPrefsPath,判断name对应的file是否为null,则调用getSharedPreferencesPath(name)初始化file,并加入mSharedPrefsPaths中。
  第三步:返回getSharedPreferences(file, mode)方法。

1.2.public SharedPreferences getSharedPreferences(File file, int mode)

@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);【1.4】
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();【1.5】
sp = cache.get(file);
if (sp == null) {
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}

  第一步:检查mode的类型,如果没有MODE_WORLD_READABLE与MODE_WORLD_WRITEABLE,则抛出异常。
  第二步:使用getSharedPreferencesCacheLocked()方法,得到cache数组映射,得到file对应的sp,如果为空,初始化并家务cache中。
  第三步:调用sp的startReloadIfChangedUnexpectedly()方法。

1.3.public File getSharedPreferencesPath(String name)

    @Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}

继续查看makeFileName()方法

    private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException(
"File " + name + " contains a path separator");
}

如果name包含文件分隔符则生成xml文件,否则抛出异常。

1.4.private void checkMode(int mode)

    private void checkMode(int mode) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
if ((mode & MODE_WORLD_READABLE) != 0) {
throw new SecurityException("MODE_WORLD_READABLE no longer supported");
}
if ((mode & MODE_WORLD_WRITEABLE) != 0) {
throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
}
}
}

  如果sdk的版本大于等于24,则mode包含MODE_WORLD_READABLE与MODE_WORLD_WRITEABLE则抛出异常。意思是从版本24开始,创建的SP文件模式,不允许MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE模块。
  当设置MODE_MULTI_PROCESS模式,则每次getSharedPreferences过程,会检查SP文件上次修改时间和文件大小,一旦所有修改则会重新从磁盘加载文件。

1.5.private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked()

    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
} final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
} return packagePrefs;
}

  第一步:如果sSharedPrefsCache为null,则初始化它。
  第二步:调用getPackageName()方法得到packageName。
  第三步:得到sSharedPrefsCache中packageName对应的ArrayMap对象packagePrefs。
  第四步:如果packagePrefs为null,则初始化它,并且添加到sSharedPrefsCache中。
  总结:获取SharedPreference只要实现在ContextImpl中,mSharedPrefsPaths包含name对应的File,sSharedPrefsCache中包含packageName对应的packagePrefs,packagePrefs中包含File对应的SharedPreferencesImpl。

  而获取过程主要时生成File文件,初始化上面的三个ArrayMap,将本应用相关的数据添加进去,以及检测版本与mode,并抛出异常。

2.添加数据

  添加数据使用到SharedPreferences.Editor类,SharedPreferences的实现是在ShredPreferencesImpl中,SharedPreferences.Editor的具体实现在ShredPreferencesImpl.EditorImpl中。

2.1.public Editor edit()

    public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (this) {
awaitLoadedLocked();【2.2】
} return new EditorImpl();
}

  同步this,调用awaitLoadedLocked()方法,返回EditorImpl对象。

2.2.private void awaitLoadedLocked()

    private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
wait();
} catch (InterruptedException unused) {
}
}
}

  第一步:如果mLoaded为false,则调用BlockGuard.getThreadPolicy().onReadFromDisk()方法,忽略无用的线程。
  第二步:如果mLoaded为false,没有加载完成,则等待。

2.3.put***(String key, *** value)系列方法

  put***()方法基本相同,这里以putInt(String key, int value)为例介绍:

        public Editor putInt(String key, int value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}

  同步this,将数据添加到mModified中,this是Editor对象。

2.4.public Editor remove(String key)

        public Editor remove(String key) {
synchronized (this) {
mModified.put(key, this);
return this;
}
}

  同步this,添加到mModified的数据为key与this。

2.5.public Editor clear()

        public Editor clear() {
synchronized (this) {
mClear = true;
return this;
}
}

  设置mClear为true。

2.6.public boolean commit()

        public boolean commit() {
MemoryCommitResult mcr = commitToMemory();【2.7】
SharedPreferencesImpl.this.enqueueDiskWrite(【2.9】
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);【2.8】
return mcr.writeToDiskResult;
}

  第一步:调用commitToMemory()方法,得到mcr对象。
  第二步:调用SharedPreferencesImpl的enqueueDiskWrite()方法。
  第三步:调用mcr,writtenToDiskLatch.await()方法。
  第四步:调用notifyListeners(mcr)方法。

2.7.private MemoryCommitResult commitToMemory()

        private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
mcr.keysModified = new ArrayList<String>();
mcr.listeners =
new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
} synchronized (this) {
if (mClear) {
if (!mMap.isEmpty()) {
mcr.changesMade = true;
mMap.clear();
}
mClear = false;
} for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
} mcr.changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
} mModified.clear();
}
}
return mcr;
}

  第一步:同步SharedPreferencesImpl.this。
  第二步:如果mDiskWritesInFlinght大于0,则初始化mMap。
  第三步:设置mcr.mapToWriteToDisk为mMap,mDiskWritesInFlight加1。
  第四步:如果存在listener,初始化mcr的keysModified和listeners。
  第五步:同步this,如果mClear为true,调用mMap.clear();遍历mModified,如果value为this,则调用mMap.remove(k)方法,移除关键字;如果不是this,先判断mMap是否包含k与value,不包含,则调用mMap.put(k,v),在mcr.keysModified中添加k。
  第六步:调用mModified.clear()方法。

2.8.private void notifyListeners(final MemoryCommitResult mcr)

        private void notifyListeners(final MemoryCommitResult mcr) {
if (mcr.listeners == null || mcr.keysModified == null ||
mcr.keysModified.size() == 0) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
}
}
}
} else {
// Run this function on the main thread.
ActivityThread.sMainThreadHandler.post(new Runnable() {
public void run() {
notifyListeners(mcr);
}
});
}
}

  第一步:如果mcr.listeners为null或者mcr.keysModified为null或者mcr.keysModified.size()等于0,则直接返回。
  第二步:如果当前looper是mainLooper,遍历mcr.keysModified,得到mcr.keysModified.get(i)为key,遍历scr.Listeners,调用listener.onSharedPreferenceChanged()方法。
  第三步:如果不是mainLooper,则在主线程中调用notifyListeners(mcr)方法。

2.9.private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable)

    private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr);【2.10】
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
}; final boolean isFromSyncCommit = (postWriteRunnable == null); // Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
} QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}

  第一步:创建writeToDiskRunnable线程,同步mWritingToDiskLock,调用writeFile(mcr)执行文件写入操作,同步SharedPreferencesImpl.this,将mDiskWritesInFlight减一,如果postWriteRunnable不为null,则运行postWriteRunnable。

  第二步:如果isFromSyncCommit为true(commit方法会进入),同步ShreadPreferencesImpl.this,设置wasEmpty的值(因为commitToMemory过程会加1,则wasEmpty为true);如果wasEmpty为true,运行wirteToDiskRunnable线程,然后返回。
  第三步:调用QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable)方法。

2.10.private void writeToFile(MemoryCommitResult mcr)

    // Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr) {
// Rename the current file so it may be used as a backup during the next read
if (mFile.exists()) {
if (!mcr.changesMade) {
// If the file already exists, but no changes were
// made to the underlying map, it's wasteful to
// re-write the file. Return as if we wrote it
// out.
mcr.setDiskWriteResult(true);
return;
}
if (!mBackupFile.exists()) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
mcr.setDiskWriteResult(false);
return;
}
} else {
mFile.delete();
}
} // Attempt to write the file, delete the backup and return true as atomically as
// possible. If any exception occurs, delete the new file; next time we will restore
// from the backup.
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false);
}

  第一步:如果mFile存在,如果mcr.changesMade为false(没有key发生改变,则直接饭后),设置mcr的DiskWriteResult为true,返回。如果mBackupFile不存在,则将mFile重命名为mBackupFile,设置mcr的DiskWriteResult为false,如果mBackupFile存在,则直接删除mFile。
  第二步:将mMap全部信息写去文件中,调用Context.setFilePermissionsFromMode()方法。
  第三步:得到Os.stat(mFile.getPath())的stat,设置mStatTimestamp与mStatSize。
  第四步:写入成功后,删除mBackupFile备份文件。
  第五步:设置mcr的DiskWriteResult为true,返回写入成功,唤醒等待线程。
  第六步:如果文件mFile存在,则删除mFile,如果写入文件的操作失败,则删除未成功写入的文件。
  第七步:设置mcr的DiskWriteResult为true,返回写入失败,唤醒等待线程。

2.11.public void apply()

        public void apply() {
final MemoryCommitResult mcr = commitToMemory();【2.7】
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
}; QueuedWork.add(awaitCommit); Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.remove(awaitCommit);
}
}; SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr);【2.8】
}

  第一步:调用commitToMemory()方法。
  第二步:创建线程awaitCommit调用mcr.writtenToDiskLatch.await()方法。
  第三步:在QueuedWork添加awaitCommit线程。
  第四步:创建线程postWriteRunnable调用awaitCommit.run()方法,从QueuedWork中移除awaitCommit。
  第五步:调用SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable)方法。
  第六步:调用notifyListeners(mcr)方法。
  总结:所有的put、clear、remove都加了同步锁,如果有多次调用putString,建议使用putStringSet方法,减少锁的消耗。
  edit()每次都是创建新的EditorImpl对象,getSharedPreferences()是从ContextImpl.sSharedPrefsCache唯一的SPI对象。
  commit与apply的区别:
    (1)apply没有返回值;commit有返回值能知道修改是否提交成功。
    (2)apply是将修改提交到内存中,再异步提交到磁盘文件;commit是同步地提交到磁盘文件。
    (3)多并发的提交commit是,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,从而降低效率;而apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率。

3.获取数据

3.1.public Map<String, ?> getAll()

    public Map<String, ?> getAll() {
synchronized (this) {
awaitLoadedLocked();
//noinspection unchecked
return new HashMap<String, Object>(mMap);
}
}

  第一步:调用awaitLoadedLocked()方法。
  第二步:返回mMap的HashMap。

3.2.public String get***(String key, @Nullable *** defValue)

  get***方法的实现基本相同,这里以getString()方法为例介绍。

    @Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}

  第一步:同步this。
  第二步:调用awaitLoadedLocked()方法。
  第三步,从mMap中获取key对应的value值,如果为空,则返回defValue,不为空。则返回从mMap中得到的值。
  总结:所有的get都加了同步锁,

四、优化建议

1.强烈建议不要在sp里面存储特别大的key/value,有助于减少卡顿。
2.请不要高频地使用apply,尽可能地批量提交,commit直接在主线程操作,也需要主要不要高频的使用。
3.不要使用MODE_MULTI_PROCESS。
4.高频写操作的key与高频读操作的key可以适当地拆分文件,用于减少同步锁竞争。
5.不要一上来就执行getSharedPreferences().edit(),应当分成量大步骤来做,中间可以执行其他代码。
6.不要连续多次edit(),应该一次获取edit(),然后多次执行putXXX(),减少内存波动。
7.每次commit时会把所有的数据更新到文件,所以整个文件是不应该过大的,影响整体性能。

参考文章:http://gityuan.com/2017/06/18/SharedPreferences/

SharedPreferences解析的更多相关文章

  1. 史上最全面,清晰的SharedPreferences解析

    基础用法获取Sp:getput监听器原理分析获取SharedPreferences构造SharedPreferencesgetX原理分析putX原理分析创建editorputStringapplyap ...

  2. 无废话Android之android下junit测试框架配置、保存文件到手机内存、android下文件访问的权限、保存文件到SD卡、获取SD卡大小、使用SharedPreferences进行数据存储、使用Pull解析器操作XML文件、android下操作sqlite数据库和事务(2)

    1.android下junit测试框架配置 单元测试需要在手机中进行安装测试 (1).在清单文件中manifest节点下配置如下节点 <instrumentation android:name= ...

  3. SharedPreferences共享优先存储的详细解析和原理

    共享优先存储: publicvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setCont ...

  4. Android之SharedPreferences数据存储

    一.SharedPreferences保存数据介绍 如果有想要保存的相对较小键值集合,应使用SharedPreferences API.SharedPreferences对象指向包含键值对的文件并提供 ...

  5. Android 一个对sharedpreferences 数据进行加密的开源库

    1.项目地址 https://github.com/iamMehedi/Secured-Preference-Store 2.使用方法 2.1.存数据 //存数据 SecuredPreferenceS ...

  6. SharedPreferences 详解(多进程,存取数组解决方案)

    一.SharedPreferences基本概念 文件保存路径:/data/data/<包名>/shared_prefs目录下目录下生成了一个SP.xml文件 SharedPreferenc ...

  7. Android之解析GML并显示

    本例主要实现在APP中解析GML数据并显示 GML,地理标记语言(外语全称:Geography MarkupLanguage.外语缩写:GML),它由开放式地理信息系统协会(外语缩写:OGC)于199 ...

  8. Android数据存储之sharedpreferences与Content Provider

    android中对数据操作包含有: file, sqlite3, Preferences, ContectResolver与ContentProvider前三种数据操作方式都只是针对本应用内数据,程序 ...

  9. android数据存储之SharedPreferences

    一.SharedPreferences简介      (1)SharedPreferences是Android平台上一个轻量级的存储类,用来保存应用的一些常用配置,比如Activity状态,Activ ...

随机推荐

  1. PL/SQL链接Oracle出现乱码

    1.用Pl/sql时,中文注释是乱码,需要查看下oracle server端的字符集. SQL语句:select userenv('language') from dual 结果:SIMPLIFIED ...

  2. 增加cookie和表单提交的安全

    设置COOKIE setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path ...

  3. c#dev gridview 设置隔行换色等

    1:禁止gridview编辑 2:隔行换色 介绍一些常用的gridcontrol设置. 1.设置隔行变色.首先设置显示隔行变色,步骤:OptionsView-->EnableAppearance ...

  4. js处理日期

    /Date(-62135596800000)/ 如何用js转化为日期时间格式 2015-11-20 14:33:20像这样 var a = '/Date(-62135596800000)/' Date ...

  5. 面试简单整理之mybatis

    125.mybatis 中 #{}和 ${}的区别是什么? 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解 ...

  6. 微信小程序开发之搞懂flex布局3——Flex Item

    Flex Item flex容器的子元素就是这个容器的flex item. The direct children of a Flex Container (elements with display ...

  7. Redis详细讲解(Redis原理,Redis安装,Redis配置,Redis使用,Redis命令)

    一.Redis介绍 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发 ...

  8. 常用API2 正则表达式

    正则表达式 正则表达式的概念 正则表达式(Regular Expression ,在代码中常简写为regex) 正则表达式是一个字符串,使用单个字符串来描述.用来定义匹配规则,匹配一系列符合某个句法规 ...

  9. 解决redis连接错误:MISCONF Redis is configured to save RDB snapshots, but it is currently not able to...

    今天Redis服务器在连接redis数据库时突然报错:MISCONF Redis is configured to save RDB snapshots, but it is currently no ...

  10. redis学习-集合set常用命令

    redis学习-集合set常用命令   1.sadd:添加一个元素到集合中(集合中的元素无序的并且唯一) 2.smembers:查看集合中所有的元素(上图事例) 3.srem:删除结合中指定的元素 4 ...