Android 五種數(shù)據(jù)存儲的方式分別為:
- SharedPreferences:以Map形式存放簡單的配置參數(shù);
- ContentProvider:將應(yīng)用的私有數(shù)據(jù)提供給其他應(yīng)用使用;
- 文件存儲:以IO流形式存放,可分為手機內(nèi)部和手機外部(sd卡等)存儲,可存放較大數(shù)據(jù);
- SQLite:輕量級、跨平臺數(shù)據(jù)庫,將所有數(shù)據(jù)都是存放在手機上的單一文件內(nèi),占用內(nèi)存小;
- 網(wǎng)絡(luò)存儲 :數(shù)據(jù)存儲在服務(wù)器上,通過連接網(wǎng)絡(luò)獲取數(shù)據(jù);
Sharedpreferences是Android平臺上一個輕量級的存儲類,用來保存應(yīng)用程序的各種配置信息,其本質(zhì)是一個以“鍵-值”對的方式保存數(shù)據(jù)的xml文件,其文件保存在/data/data/<package name>/shared_prefs目錄下。在全局變量上看,其優(yōu)點是不會產(chǎn)生Application 、 靜態(tài)變量的OOM(out of memory)和空指針問題,其缺點是效率沒有上面的兩種方法高。
1.獲取SharedPreferences
要想使用 SharedPreferences 來存儲數(shù)據(jù),首先需要獲取到 SharedPreferences 對象。Android中主要提供了三種方法用于得到 SharedPreferences 對象。
?1. Context 類中的 getSharedPreferences()方法:
?此方法接收兩個參數(shù),第一個參數(shù)用于指定 SharedPreferences 文件的名稱,如果指定的文件不存在則會創(chuàng)建一個,第二個參數(shù)用于指定操作模式,主要有以下幾種模式可以選擇。MODE_PRIVATE 是默認(rèn)的操作模式,和直接傳入 0 效果是相同的。
?MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 這兩種模式已在 Android 4.2 版本中被廢棄。
Context.MODE_PRIVATE: 指定該SharedPreferences數(shù)據(jù)只能被本應(yīng)用程序讀、寫;
Context.MODE_WORLD_READABLE: 指定該SharedPreferences數(shù)據(jù)能被其他應(yīng)用程序讀,但不能寫;
Context.MODE_WORLD_WRITEABLE: 指定該SharedPreferences數(shù)據(jù)能被其他應(yīng)用程序讀;
Context.MODE_APPEND:該模式會檢查文件是否存在,存在就往文件追加內(nèi)容,否則就創(chuàng)建新文件;
2. Activity 類中的 getPreferences()方法:
?這個方法和 Context 中的 getSharedPreferences()方法很相似,不過它只接收一個操作模式參數(shù),因為使用這個方法時會自動將當(dāng)前活動的類名作為 SharedPreferences 的文件名。
3. PreferenceManager 類中的 getDefaultSharedPreferences()方法:
?這是一個靜態(tài)方法,它接收一個 Context 參數(shù),并自動使用當(dāng)前應(yīng)用程序的包名作為前綴來命名 SharedPreferences 文件。
2.SharedPreferences的使用
SharedPreferences對象本身只能獲取數(shù)據(jù)而不支持存儲和修改,存儲修改是通過SharedPreferences.edit()獲取的內(nèi)部接口Editor對象實現(xiàn)。使用Preference來存取數(shù)據(jù),用到了SharedPreferences接口和SharedPreferences的一個內(nèi)部接口SharedPreferences.Editor,這兩個接口在android.content包中;
1)寫入數(shù)據(jù):
//步驟1:創(chuàng)建一個SharedPreferences對象
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
//步驟2: 實例化SharedPreferences.Editor對象
SharedPreferences.Editor editor = sharedPreferences.edit();
//步驟3:將獲取過來的值放入文件
editor.putString("name", “Tom”);
editor.putInt("age", 28);
editor.putBoolean("marrid",false);
//步驟4:提交
editor.commit();
2)讀取數(shù)據(jù):
SharedPreferences sharedPreferences= getSharedPreferences("data", Context .MODE_PRIVATE);
String userId=sharedPreferences.getString("name","");
3)刪除指定數(shù)據(jù)
editor.remove("name");
editor.commit();
4)清空數(shù)據(jù)
editor.clear();
editor.commit();
注意:如果在 Fragment 中使用SharedPreferences 時,需要放在onAttach(Activity activity)里面進(jìn)行SharedPreferences的初始化,否則會報空指針 即 getActivity()會可能返回null !
讀寫其他應(yīng)用的SharedPreferences 步驟如下(未實踐):
?1. 在創(chuàng)建SharedPreferences時,指定MODE_WORLD_READABLE模式,表明該SharedPreferences數(shù)據(jù)可以被其他程序讀取;
?2. 創(chuàng)建其他應(yīng)用程序?qū)?yīng)的Context;
?3. 使用其他程序的Context獲取對應(yīng)的SharedPreferences;
?4. 如果是寫入數(shù)據(jù),使用Editor接口即可,所有其他操作均和前面一致;
try {
//這里的com.example.mpreferences 就是應(yīng)用的包名
Context mcontext = createPackageContext("com.example.mpreferences", CONTEXT_IGNORE_SECURITY);
SharedPreferences msharedpreferences = mcontext.getSharedPreferences("name_preference", MODE_PRIVATE);
int count = msharedpreferences.getInt("count", 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
3. SharedPreferences 的源碼分析(API 25)
先從Context的getSharedPreferences開始:
public abstract SharedPreferences getSharedPreferences(String name, int mode);
我們知道Android中的Context類其實是使用了裝飾者模式,而被裝飾對象其實就是一個ContextImpl對象,ContextImpl的getSharedPreferences方法:
/**
* Map from preference name to generated path.
* 從preference名稱到生成路徑的映射;
*/
@GuardedBy("ContextImpl.class")
private ArrayMap<String, File> mSharedPrefsPaths;
/**
* Map from package name, to preference name, to cached preferences.
* 從包名映射到preferences,以緩存preferences,這是個靜態(tài)變量;
*/
@GuardedBy("ContextImpl.class")
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
@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);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
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 < android.os.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;
}
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;
}
從上面我們可以看出他們之間的關(guān)系:
mSharedPrefsPaths存放的是名稱與文件夾的映射(ArrayMap<String, File>),這里的名稱就是我們使用getSharedPreferences時傳入的name,如果mSharedPrefsPaths為null則初始化,如果file為null則新建一個File并將其加入mSharedPrefsPaths中;
( ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> )
sSharedPrefsCache 存放包名與ArrayMap鍵值對
初始化時會默認(rèn)以包名作為鍵值對中的Key,注意這是個static變量;
(ArrayMap<File, SharedPreferencesImpl>)sSharedPrefs
packagePrefs存放文件name與SharedPreferencesImpl鍵值對
注意:
- 對于一個相同的SharedPreferences name,獲取到的都是同一個SharedPreferences對象,它其實是SharedPreferencesImpl對象。
- sSharedPrefs在程序中是靜態(tài)的,如果退出了程序但Context沒有被清掉,那么下次進(jìn)入程序仍然可能取到本應(yīng)被刪除掉的值。而換了另一種清除SharedPreferences的方式:使用SharedPreferences.Editor的commit方法能夠起作用,調(diào)用后不退出程序都馬上生效。
SharedPreferencesImpl對象
SharedPreferencesImpl(File file, int mode) {
mFile = file;
// 備份的File
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
synchronized (SharedPreferencesImpl.this) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
// Debugging
if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
Map map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
/* ignore */
}
synchronized (SharedPreferencesImpl.this) {
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
notifyAll();
}
}
可以看到對于一個SharedPreferences文件name,第一次調(diào)用getSharedPreferences時會去創(chuàng)建一個SharedPreferencesImpl對象,它會開啟一個子線程,然后去把指定的SharedPreferences文件中的鍵值對全部讀取出來,存放在一個Map中。
調(diào)用getString時那個SharedPreferencesImpl構(gòu)造方法開啟的子線程可能還沒執(zhí)行完(比如文件比較大時全部讀取會比較久),這時getString當(dāng)然還不能獲取到相應(yīng)的值,必須阻塞到那個子線程讀取完為止,如getString方法:
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
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) {
}
}
}
顯然這個awaitLoadedLocked方法就是用來等this這個鎖的,在loadFromDiskLocked方法的最后我們也可以看到它調(diào)用了notifyAll方法,這時如果getString之前阻塞了就會被喚醒。那么這里會存在一個問題,我們的getString是寫在UI線程中,如果那個getString被阻塞太久了,比如60s,這時就會出現(xiàn)ANR,所以要根據(jù)具體情況考慮是否需要把SharedPreferences的讀寫放在子線程中。
關(guān)于mBackupFile,SharedPreferences在寫入時會先把之前的xml文件改成名成一個備份文件,然后再將要寫入的數(shù)據(jù)寫到一個新的文件中,如果這個過程執(zhí)行成功的話,就會把備份文件刪除。由此可見每次即使只是添加一個鍵值對,也會重新寫入整個文件的數(shù)據(jù),這也說明SharedPreferences只適合保存少量數(shù)據(jù),文件太大會有性能問題。
注意:
- 在UI線程中調(diào)用getXXX可能會導(dǎo)致ANR。
- 我們在初始化SharedPreferencesImpl對象時會加SharedPreferencesImpl對應(yīng)的xml文件中的所有數(shù)據(jù)都加載到內(nèi)存中,如果xml文件很大,將會占用大量的內(nèi)存,我們只想讀取xml文件中某個key的值,但我們獲取它的時候是會加載整個文件。
- 每添加一個鍵值對,都會重新寫入整個文件的數(shù)據(jù),不是增量寫入;
綜上原因能說明Sharedpreferences只適合做輕量級的存儲。
SharedPreferences的內(nèi)部類Editor
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("name", “Tom”);
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();
}
return new EditorImpl();
}
其實拿到的是一個EditorImpl對象,它是SharedPreferencesImpl的內(nèi)部類:
public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
.....
}
可以看到它有一個Map對象mModified,用來保存“修改的數(shù)據(jù)”,也就是你每次put的時候其實只是把那個鍵值對放到這個mModified 中,最后調(diào)用apply或者commit才會真正把數(shù)據(jù)寫入文件中,如上面的putString方法,其它putXXX代碼基本也是一樣的。
commit方法和apply方法的不同
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
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);
}
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
// Return value from EditorImpl#commitToMemory()
private static class MemoryCommitResult {
public boolean changesMade; // any keys different?
public List<String> keysModified; // may be null
public Set<OnSharedPreferenceChangeListener> listeners; // may be null
public Map<?, ?> mapToWriteToDisk;
public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
public volatile boolean writeToDiskResult = false;
public void setDiskWriteResult(boolean result) {
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
}
兩種方式首先都會先使用commitTomemory函數(shù)將修改的內(nèi)容寫入到SharedPreferencesImpl當(dāng)中,再調(diào)用enqueueDiskWrite寫磁盤操作,commitToMemory就是產(chǎn)生一個“合適”的MemoryCommitResult對象mcr,然后調(diào)用enqueueDiskWrite時需要把這個對象傳進(jìn)去,commitToMemory方法:
// Returns true if any changes were made
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;
}
這里需要弄清楚兩個對象mMap和mModified,mMap是存放當(dāng)前SharedPreferences文件中的鍵值對,而mModified是存放此時edit時put進(jìn)去的鍵值對。mDiskWritesInFlight表示正在等待寫的操作數(shù)量。
可以看到這個方法中首先處理了clear標(biāo)志,它調(diào)用的是mMap.clear(),然后再遍歷mModified將新的鍵值對put進(jìn)mMap,也就是說在一次commit事務(wù)中,如果同時put一些鍵值對和調(diào)用clear后再commit,那么clear掉的只是之前的鍵值對,這次put進(jìn)去的鍵值對還是會被寫入的。
遍歷mModified時,需要處理一個特殊情況,就是如果一個鍵值對的value是this(SharedPreferencesImpl)或者是null那么表示將此鍵值對刪除,這個在remove方法中可以看到,如果之前有同樣的key且value不同則用新的valu覆蓋舊的value,如果沒有存在同樣的key則完整寫入。需要注意的是這里使用了同步鎖住edtor對象,保證了當(dāng)前數(shù)據(jù)正確存入。
public Editor remove(String key) {
synchronized (this) {
mModified.put(key, this);
return this;
}
}
public Editor clear() {
synchronized (this) {
mClear = true;
return this;
}
}
commit接下來就是調(diào)用enqueueDiskWrite方法:
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr);
}
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);
}
定義一個Runnable任務(wù),在Runnable中先調(diào)用writeToFile進(jìn)行寫操作,寫操作需要先獲得mWritingToDiskLock,也就是寫鎖。然后執(zhí)行mDiskWritesInFlight–,表示正在等待寫的操作減少1。
判斷postWriteRunnable是否為null,調(diào)用commit時它為null,而調(diào)用apply時它不為null。isFromSyncCommit為true,而且有1個寫操作需要執(zhí)行,那么就調(diào)用writeToDiskRunnable.run(),注意這個調(diào)用是在當(dāng)前線程中進(jìn)行的。如果不是commit,那就是apply,這時調(diào)用QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable),這個QueuedWork類其實很簡單,里面有一個SingleThreadExecutor,用于異步執(zhí)行這個writeToDiskRunnable,commit的寫操作是在調(diào)用線程中執(zhí)行的,而apply內(nèi)部是用一個單線程的線程池實現(xiàn)的,因此寫操作是在子線程中執(zhí)行的。
commit和apply的總結(jié):
- apply沒有返回值而commit返回boolean表明修改是否提交成功 ;
- commit是把內(nèi)容同步提交到硬盤的,而apply先立即把修改提交到內(nèi)存,然后開啟一個異步的線程提交到硬盤,并且如果提交失敗,你不會收到任何通知。
- 所有commit提交是同步過程,效率會比apply異步提交的速度慢,在不關(guān)心提交結(jié)果是否成功的情況下,優(yōu)先考慮apply方法。
- apply是使用異步線程寫入磁盤,commit是同步寫入磁盤。所以我們在主線程使用的commit的時候,需要考慮是否會出現(xiàn)ANR問題。(不適合大量數(shù)據(jù)存儲)
4. 查看Sharedpreferencesd 保存數(shù)據(jù)的xml文件
要想查看data文件首先要獲取手機root權(quán)限,成功root后,修改data權(quán)限即可查看data里面的數(shù)據(jù)庫。由于在xml文件內(nèi)可以很清楚的查看到各個鍵-值”對數(shù)據(jù),所以用Sharedpreferencesd保存比較重要的數(shù)據(jù)的時候最好先加密再保存。成功查看如下圖所示:
data權(quán)限修改辦法:
1. 打開cmd;
2. 輸入’adb shell’;
3. 輸入su,回車 ;
4. 輸入chmod 777 /data/data/<package name>/shared_prefs 回車
該步驟設(shè)置data文件夾權(quán)限為777(drwxrwxrwx,也即administrators、power users和users組都有對該文件夾的讀、寫、運行權(quán)限) ;
當(dāng)你在Linux下用命令ll 或者ls -la的時候會看到類似drwxr-xr-x這樣標(biāo)識,具體代表什么意思呢?
?這段標(biāo)識總長度為10位(10個‘-’),第一位表示文件類型,如該文件是文件(用-表示),如該文件是文件夾(用d表示),如該文件是連接文件(用l表示),后面9個按照三個一組分,第一組:用戶權(quán)限,第二組:組權(quán)限,第三組:其他權(quán)限。 每一組是三位,分別是讀 r ,寫 w,執(zhí)行 x,這些權(quán)限都可以用數(shù)字來表示:r 4, w 2 , x 1。如果沒有其中的某個權(quán)限則用‘-’表示。例如:
?1. -rwxrwx---,第一位‘-’代表的是文件,第二位到第四位rwx代表此文件的擁有者有讀、寫、執(zhí)行的權(quán)限,同組用戶也有讀、寫、及執(zhí)行權(quán)限,其他用戶組沒任何權(quán)限。用數(shù)字來表示的話則是770.
?2. drwx------,第一位‘d’代表的是文件夾,第二位到第四位rwx代表此文件夾的擁有者有讀、寫、執(zhí)行的權(quán)限,第五位到第七位代表的是擁有者同組用戶的權(quán)限,同組用戶沒有任何權(quán)限,第八位到第十位代表的是其他用戶的權(quán)限,其他用戶也沒有任何權(quán)限。用數(shù)字來表示的話則是700.