diff --git "a/src/31.\345\255\227\347\254\246\344\270\262\347\232\204intern\346\234\272\345\210\266\346\230\257\346\200\216\344\271\210\344\270\200\345\233\236\344\272\213\357\274\237.md" "b/src/31.\345\255\227\347\254\246\344\270\262\347\232\204intern\346\234\272\345\210\266\346\230\257\346\200\216\344\271\210\344\270\200\345\233\236\344\272\213\357\274\237.md"
new file mode 100644
index 0000000..e377296
--- /dev/null
+++ "b/src/31.\345\255\227\347\254\246\344\270\262\347\232\204intern\346\234\272\345\210\266\346\230\257\346\200\216\344\271\210\344\270\200\345\233\236\344\272\213\357\274\237.md"
@@ -0,0 +1,173 @@
+上一篇文章我们介绍了字符串的底层结构,看到里面有一个 state 字段,该字段也是一个结构体,内部定义了很多的标志位。
+
+![](./images/94.png)
+
+如果字符串的 interned 标志位大于 0,那么虚拟机将为其开启 intern 机制。那什么是 intern 机制呢?在 Python 中,某些字符串也可以像小整数对象池里的整数一样,共享给所有变量使用,从而通过避免重复创建来降低内存使用、减少性能开销,这便是 intern 机制。
+
+Python 的做法是在虚拟机内部维护一个全局字典,所有开启 intern 机制的字符串均会保存在这里,后续如果需要使用的话,会尝试在全局字典中获取,从而实现避免重复创建的功能。
+
+另外 intern 机制也分为多种。
+
+~~~C
+// Include/cpython/unicode.h
+#define SSTATE_NOT_INTERNED 0
+#define SSTATE_INTERNED_MORTAL 1
+#define SSTATE_INTERNED_IMMORTAL 2
+~~~
+
+解释一下这几个字段:
+
+- SSTATE_NOT_INTERNED:字符串未开启 intern 机制;
+- SSTATE_INTERNED_MORTAL:字符串开启了 intern 机制,但它不是永久驻留的,在某些情况下可能会被回收;
+- SSTATE_INTERNED_IMMORTAL:字符串开启了 intern 机制,会永远存活于内存中;
+
+这些字段定义了字符串在内存管理中的不同驻留状态,从未驻留、短暂驻留到永久驻留,帮助优化字符串的内存使用和管理。
+
+而当一个字符串要开启 intern 机制时,会调用 PyUnicode_InternInPlace 函数,看一下它的逻辑。
+
+~~~c
+// Objects/unicodeobject.c
+
+void
+PyUnicode_InternInPlace(PyObject **p)
+{
+ PyObject *s = *p;
+ PyObject *t;
+ // 类型检查,因为 intern 共享机制只能用在字符串对象上
+ // PyUnicode_Check(s) -> isinstance(s, str)
+ // PyUnicode_CheckExact(s) -> type(s) is str
+ if (s == NULL || !PyUnicode_Check(s))
+ return;
+ if (!PyUnicode_CheckExact(s))
+ return;
+ // 执行到这儿,说明 s 一定指向字符串,那么检测它是否已经开启了 intern 机制
+ // 这个函数的逻辑很简单,内部会获取 state.interned,看它是否大于 0
+ // 如果已经被 intern 机制处理了,那么直接返回
+ if (PyUnicode_CHECK_INTERNED(s))
+ return;
+ // 所有开启 intern 机制的字符串,都会保存在 interned 字典中
+ // 如果 interned 字典为空,那么创建
+ if (interned == NULL) {
+ interned = PyDict_New();
+ if (interned == NULL) {
+ PyErr_Clear(); /* Don't leave an exception */
+ return;
+ }
+ }
+ // 将字符串同时作为 key 和 value 保存在 interned 字典中,即开启 intern 机制
+ // PyDict_SetDefault 对应字典的 setdefault 方法
+ t = PyDict_SetDefault(interned, s, s);
+ if (t == NULL) {
+ PyErr_Clear();
+ return;
+ }
+ // 注意这里有一个让人混淆的地方,首先 t 和 s 都是 C 指针
+ // 如果 t != s,说明它们指向了不同的字符串,如果 t == s,说明它们指向的是同一个字符串
+ // 由于 s 同时作为 key 和 value,那么不管 s 指向的字符串是否在字典中已存在
+ // PyObject_RichCompare(t, s, Py_EQ) 永远为真,也就是 t 和 s 指向的字符串的值是相等的
+ // 只是当 t != s 时,说明它们指向的不是同一个字符串,但值相等
+ // 这也意味着字典中已经存在某个 key,它指向的字符串维护了相同的文本数据
+ // 那么增加 t 指向的字符串的引用计数,减少 s 指向的字符串的引用计数
+ if (t != s) {
+ Py_INCREF(t);
+ Py_SETREF(*p, t);
+ return;
+ }
+ // 否则说明 t == s,即 s 在字典中不存在,那么开启 interned 机制
+ Py_REFCNT(s) -= 2;
+ _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL;
+}
+~~~
+
+估计很多人都以为 Python 在创建字符串时,会先检测该字符串是否已经存在,如果有,就不用创建新的,这样可以节省空间。但其实不是这样的,事实上节省内存空间是没错的,可 Python 并不是在创建字符串的时候就通过 intern 机制实现了节省空间的目的。
+
+对于任何一个字符串,Python 总是会为它申请内存,尽管创建出来的字符串在 interned 字典中已经存在了(有另外的字符串对象维护了相同的文本)。而这正是关键所在,通常 Python 在运行时创建了一个字符串对象(假设叫 temp)之后,基本上都会调用 PyUnicode_InternInPlace 对 temp 进行处理。
+
+如果维护的值已经存在于 interned 字典中,那么 temp 指向的对象的引用计数就会减 1,然后会因引用计数为 0 而被销毁,只是昙花一现,然后归于湮灭。
+
+> 所以现在我们就明白了 intern 机制,并不是说先判断是否存在,如果存在,就不创建。而是先创建,然后发现已经有其它的字符串维护了一个与之相同的文本数据,于是 intern 机制再将引用计数减一,导致引用计数为 0,最终被回收。
+
+然后关于字符串对象的 intern 机制,还有一点需要注意。实际上,被 intern 机制处理过后的字符串分为两类,一类处于 SSTATE_INTERNED_IMMORTAL 状态,另一类处于 SSTATE_INTERNED_MORTAL 状态,这两种状态的区别在 unicode_dealloc 中可以清晰的看到。SSTATE_INTERNED_IMMORTAL 状态的字符串是永远不会被销毁的,它与解释器共存亡。
+
+而 PyUnicode_InternInPlace 只能创建 SSTATE_INTERNED_MORTAL 的字符串对象,如果想创建 SSTATE_INTERNED_IMMORTAL 对象,必须通过另外的接口来强制改变 intern 状态。
+
+~~~C
+void
+PyUnicode_InternImmortal(PyObject **p)
+{
+ PyUnicode_InternInPlace(p);
+ if (PyUnicode_CHECK_INTERNED(*p) != SSTATE_INTERNED_IMMORTAL) {
+ _PyUnicode_STATE(*p).interned = SSTATE_INTERNED_IMMORTAL;
+ Py_INCREF(*p);
+ }
+}
+~~~
+
+但是问题来了,什么样的字符串才会开启 intern 机制呢?
+
+**1)如果字符串为 ASCII 字符串,并且长度不超过 4096,那么会开启 intern 机制。**
+
+~~~python
+>>> s1 = "a" * 4096
+>>> s2 = "a" * 4096
+# 会开启 intern 机制,s1 和 s2 指向同一个字符串
+>>> s1 is s2
+True
+
+# 长度超过了 4096,所以不会开启 intern 机制
+>>> s1 = "a" * 4097
+>>> s2 = "a" * 4097
+>>> s1 is s2
+False
+~~~
+
+**2)如果一个字符串只有一个字符,并且码点小于 256(一个字节可以表示),那么也会开启 intern 机制。**
+
+~~~python
+>>> hex(128)
+'0x80'
+# s1 和 s2 指向同一个字符串,因为开启了 intern 机制
+>>> s1 = chr(128)
+>>> s2 = "\x80"
+>>> s1 is s2
+True
+
+# ASCII 字符指的是码点小于 128 的字符,显然 s1 和 s2 不是 ASCII 字符串
+# 虽然码点小于 256,但长度不等于 1,所以不会开启 intern 机制
+>>> s1 = chr(128) + "x"
+>>> s2 = chr(128) + "x"
+>>> s1 is s2
+False
+~~~
+
+实际上,存储单个字符这种方式有点类似于 bytes 对象的缓存池。是的,正如整数有小整数对象池、bytes 对象有字符缓存池一样,字符串也有其对应的缓存池。
+
+总之 intern 机制并不是大家想的那样:先检测字符串是否已经存在,如果有,就不用创建新的,从而节省内存。但其实不是这样的,节省内存空间是没错的,可 Python 并不是在创建字符串的时候就通过 intern 机制实现了节省空间的目的。对于任何一个字符串,解释器总是会为它创建对应的结构体实例,但如果发现创建出来的实例在 intern 字典中已经存在了,那么再将它销毁。
+
+最后关于 intern 机制,在 Python 里面可以通过 sys.intern 函数强制开启。
+
+~~~Python
+>>> s1 = "憨pi-_-||"
+>>> s2 = "憨pi-_-||"
+>>> s1 is s2
+False
+>>>
+>>> s1 = sys.intern("憨pi-_-||")
+>>> s2 = sys.intern("憨pi-_-||")
+>>> s1 is s2
+True
+~~~
+
+以上就是字符串的 intern 机制,下一篇文章来介绍字符串的相关操作是怎么实现的。
+
+-------------
+
+
+
+**欢迎大家关注我的公众号:古明地觉的编程教室。**
+
+![](./images/qrcode_for_gh.jpg)
+
+**如果觉得文章对你有所帮助,也可以请作者吃个馒头,Thanks♪(・ω・)ノ。**
+
+![](./images/supports.png)
\ No newline at end of file
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 1ed424e..1082d65 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -30,4 +30,5 @@
+ [27. 详解 bytearray 对象的底层实现](27.详解bytearray对象的底层实现.md)
+ [28. 字符集和字符编码](28.字符集和字符编码.md)
+ [29. Python 是怎么存储字符串的?](29.Python是怎么存储字符串的?.md)
-+ [30. 解密字符串的底层结构,它是怎么实现的?](30.解密字符串的底层结构,它是怎么实现的?.md)
\ No newline at end of file
++ [30. 解密字符串的底层结构,它是怎么实现的?](30.解密字符串的底层结构,它是怎么实现的?.md)
++ [31. 字符串的 intern 机制是怎么一回事?](31.字符串的intern机制是怎么一回事?.md)
\ No newline at end of file
diff --git a/src/images/94.png b/src/images/94.png
new file mode 100644
index 0000000..ab174da
Binary files /dev/null and b/src/images/94.png differ