关于SDCC下XRAM变量初始值可能不正确的问题
__xdata volatile int var0; __xdata volatile int var1 = 1; void main(void) { // 功能代码 }以上这个简单到发指的程序，在STC8A8K64U中遇到个奇怪问题：var0的初始值有时候不是0，而var1的初始值也不是1，是一些奇怪的随机值。第一感觉就是XRAM的初始化可能存在BUG，于是稍微了解了一下SDCC中XRAM初始化的原理，没想到还真发现了一些好玩的东西，给大家做个分享。一些前置知识。无论是Keil还是SDCC，在真正执行main函数之前，会自动插入一些初始化代码，这里面就包括RAM、IRAM和XRAM的初始化等。在从C编译成汇编的过程中，编译器会按照功能和特性的不同，会生成很多辅助的段或者叫做小节。与本问题相关的小节主要有：;-------------------------------------------------------- ; uninitialized external ram data ;-------------------------------------------------------- .area XSEG (XDATA) _var0:: .ds 2 ;-------------------------------------------------------- ; initialized external ram data ;-------------------------------------------------------- .area XISEG (XDATA) _var1:: .ds 2 ;-------------------------------------------------------- ; external ram initial data ;-------------------------------------------------------- .area XINIT (CODE) __xinit__var1: .byte #0x01, #0x00;未初始化外部变量存储区XSEG，var0就属于这个区，因为它定义在外部扩展RAM中(__xdata)，且代码中没有给它赋初始值；已初始化外部变量存储区XISEG，var1就属于这个区，因为它定义在外部扩展RAM中(__xdata)，且代码中有给它赋初始值；已初始化外部变量初始值区XINIT，注意这个实际上是在代码区里面，是ROM不是RAM，最终会烧录在flash的特定位置。另外，在编译过程中会生成一些辅助变量，s_是特定区的起始位置，l_是特定区的长度。如s_XSEG就是XSEG区的起始位置。按照C语言规范，未初始化的全局变量(如var0)应当默认给初始值0，这个SDCC帮我们做了，代码在crtxclear.asm中 .globl __XPAGE __mcs51_genXRAMCLEAR:: mov r0,#l_PSEG mov a,r0 orl a,#(l_PSEG >> 8) jz 00006$ mov r1,#s_PSEG mov __XPAGE,#(s_PSEG >> 8) clr a 00005$: movx @r1,a inc r1 djnz r0,00005$ 00006$: mov r0,#l_XSEG mov a,r0 orl a,#(l_XSEG >> 8) jz 00008$ mov r1,#((l_XSEG + 255) >> 8) mov dptr,#s_XSEG clr a 00007$: movx @dptr,a inc dptr djnz r0,00007$ djnz r1,00007$ 00008$:已初始化的全局变量(如var1)，也需要执行一些操作才能有初始值，这个SDCC帮我们做了，代码在crtxinit.asm中 .globl __XPAGE __mcs51_genXINIT:: mov r1,#l_XINIT mov a,r1 orl a,#(l_XINIT >> 8) jz 00003$ mov r2,#((l_XINIT+255) >> 8) mov dptr,#s_XINIT mov r0,#s_XISEG mov __XPAGE,#(s_XISEG >> 8) 00001$: clr a movc a,@a+dptr movx @r0,a inc dptr inc r0 cjne r0,#0,00002$ inc __XPAGE 00002$: djnz r1,00001$ djnz r2,00001$ mov __XPAGE,#0xFF 00003$:其实这里就已经能发现问题了，当给XRAM内存赋值时，使用的地址寄存是R0和R1。而R0和R1只是一个8位的寄存器，还需要其它方式组合出16位地址，才能完全访问STC8A8K64U完整8KB的XRAM。从代码里面看到使用__XPAGE做了高8位地址，那么就继续追查__XPAGE是个什么东西。最终是在crtpagesfr.asm里面__XPAGE == 0xa0 ; 0xa0 is P2 on the original 8051这个__XPAGE不就是P2端口么？所有问题豁然开朗。在传统8052单片机中，如果要扩展RAM，是需要实实在在外接RAM芯片的。P2接RAM芯片的地址高8位，P0口接RAM芯片地址低8位和数据位。SDCC默认的实现正是基于这一点，用__XPAGE(也就是P2)锁存了XRAM地址高8位，然后只需要操作地址低8位和数据即可(都是P0)。这在当时的硬件下应该能减少端口操作提升效率，但这并不适合现在STC系列的所有单片机，因为STC的XRAM其实也是内置的，和P2、P0没有丝毫关系，再按当年的方式来操作必然不能正确生效了。到这里解决方案其实已经呼之欲出了，重写SDCC内置的方法就能解决问题。已知需要重写的有crtxclear.asm、crtxinit.asm和crtxstack.asm，这个等以后有空了再说吧！
执行激活脚本slmbr.vbs /xpr slmgr.vbs /upk slmgr.vgs /ipk {GVLK} slmgr.vbs /skms {ADDR} slmgr.vbs /ato说明：将脚本中的{GVLK}按系统版本替换成KMS密钥将脚本中的{ADDR}替换成KMS服务器地址通用批量许可证密钥 (GVLK)在下表中，可找到 Windows 每个版本的 GVLK。 LTSC 是长期服务渠道，而 LTSB 是 Long-Term Servicing Branch 。Windows Server（LTSC 版本）Windows Server 2022操作系统版本KMS 客户端产品密钥Windows Server 2022 DatacenterWX4NM-KYWYW-QJJR4-XV3QB-6VM33Windows Server 2022 Datacenter Azure EditionNTBV8-9K7Q8-V27C6-M2BTV-KHMXVWindows Server 2022 StandardVDYBN-27WPP-V4HQT-9VMD4-VMK7HWindows Server 2019操作系统版本KMS 客户端产品密钥Windows Server 2019 DatacenterWMDGN-G9PQG-XVVXX-R3X43-63DFGWindows Server 2019 StandardN69G4-B89J2-4G8F4-WWYCC-J464CWindows Server 2019 EssentialsWVDHN-86M7X-466P6-VHXV7-YY726Windows Server 2016操作系统版本KMS 客户端产品密钥Windows Server 2016 DatacenterCB7KF-BWN84-R7R2Y-793K2-8XDDGWindows Server 2016 StandardWC2BQ-8NRM3-FDDYY-2BFGV-KHKQYWindows Server 2016 EssentialsJCKRF-N37P4-C2D82-9YXRT-4M63BWindows 11 和 Windows 10（半年频道版本）有关受支持的版本和服务终止日期的信息，请参阅 Windows 生命周期情况说明书。操作系统版本KMS 客户端产品密钥Windows 11 专业版 Windows 10 专业版W269N-WFGWX-YVC9B-4J6C9-T83GXWindows 11 专业版 N Windows 10 专业版 NMH37W-N47XK-V7XM9-C7227-GCQG9Windows 11 专业工作站版 Windows 10 专业工作站版NRG8B-VKK3Q-CXVCJ-9G2XF-6Q84JWindows 11 专业工作站版 N Windows 10 专业工作站版 N9FNHH-K3HBT-3W4TD-6383H-6XYWFWindows 11 专业教育版 Windows 10 专业教育版6TP4R-GNPTD-KYYHQ-7B7DP-J447YWindows 11 专业教育版 N Windows 10 专业教育版 NYVWGF-BXNMC-HTQYQ-CPQ99-66QFCWindows 11 教育版 Windows 10 教育版NW6C2-QMPVW-D7KKK-3GKT6-VCFB2Windows 11 教育版 N Windows 10 教育版 N2WH4N-8QGBV-H22JP-CT43Q-MDWWJWindows 11 企业版 Windows 10 企业版NPPR9-FWDCX-D2C8J-H872K-2YT43Windows 11 企业版 N Windows 10 企业版 NDPH2V-TTNVB-4X9Q3-TJR4H-KHJW4Windows 11 企业版 G Windows 10 企业版 GYYVX9-NTFWV-6MDM3-9PT4T-4M68BWindows 11 企业版 G N Windows 10 企业版 G N44RPN-FTY23-9VTTB-MP9BX-T84FVWindows 10（LTSC/LTSB 版本）Windows 10 LTSC 2021 和 2019操作系统版本KMS 客户端产品密钥Windows 10 企业版 LTSC 2021 Windows 10 企业版 LTSC 2019M7XTQ-FN8P6-TTKYV-9D4CC-J462DWindows 10 企业版 N LTSC 2021 Windows 10 企业版 N LTSC 201992NFX-8DJQP-P6BBQ-THF9C-7CG2HWindows 10 LTSB 2016操作系统版本KMS 客户端产品密钥Windows 10 企业版 LTSB 2016DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJWindows 10 企业版 N LTSB 2016QFFDN-GRT3P-VKWWX-X7T3R-8B639Windows 10 LTSB 2015操作系统版本KMS 客户端产品密钥Windows 10 企业版 2015 LTSBWNMTR-4C88C-JK8YV-HQ7T2-76DF9Windows 10 企业版 2015 LTSB N2F77B-TNFGY-69QQF-B8YKP-D69TJ
{ "Dhcp4": { "server-tag": "DHCPv4-A", "interfaces-config": { "interfaces": [ "enpXsY" ], "dhcp-socket-type": "raw" }, "control-socket": { "socket-type": "unix", "socket-name": "/tmp/kea-dhcp4-ctrl.sock" }, "hooks-libraries": [ { "library": "/usr/lib64/kea/hooks/libdhcp_mysql_cb.so" } ], "config-control": { "config-databases": [ { "type": "mysql", "name": "kea", "user": "kea", "password": "******", "host": "host", "port": 3306 } ], "config-fetch-wait-time": 20 }, "lease-database": { "type": "mysql", "name": "kea", "user": "kea", "password": "******", "host": "host", "port": 3306 }, "hosts-database": { "type": "mysql", "name": "kea", "user": "kea", "password": "******", "host": "host", "port": 3306 }, "loggers": [ { "name": "kea-dhcp4", "output_options": [ { "output": "/var/log/kea/dhcp4.log" } ], "severity": "INFO", "debuglevel": 0 } ] } }
dnf install epel-release dnf config-manager --set-enabled crb dnf install cmake automake autoconf g++ libtool flex dnf install openssl-devel libdnet-devel hwloc-devel luajit-devel libpcap-devel pcre-devel xz-devel zlib-devel git clone https://github.com/snort3/libdaq.git ./bootstrap ./configure make install ldconfig git clone https://github.com/snort3/snort3.git ./configure_cmake.sh cd build make -j $(nproc) make install
抛开USB协议本身的结构数据之外，通用USB键盘鼠标的数据包相当简单。USB键盘每次发送八个字节，而USB数据每次发送四个字节。USB键盘数据包BYTE0 ├─ bit0： Left Control是否按下，按下为1 ├─ bit1： Left Shift 是否按下，按下为1 ├─ bit2： Left Alt 是否按下，按下为1 ├─ bit3： Left Win 是否按下，按下为1 ├─ bit4： Right Control是否按下，按下为1 ├─ bit5： Right Shift 是否按下，按下为1 ├─ bit6： Right Alt 是否按下，按下为1 └─ bit7： Right Win 是否按下，按下为1 BYTE1 ─ 保留位 BYTE2 ~ BYTE7 ─ 这六个为普通按键键码USB鼠标数据报BYTE0 ├─ bit7: 表示Y坐标的变化量超出-256~255的范围，0表示没有溢出 ├─ bit6: 表示X坐标的变化量超出-256~255的范围，0表示没有溢出 ├─ bit5: Y坐标变化的符号位，1表示负数，即鼠标向下移动 ├─ bit4: X坐标变化的符号位，1表示负数，即鼠标向左移动 ├─ bit3: 恒为1 ├─ bit2: 1表示中键按下 ├─ bit1: 1表示右键按下 └─ bit0: 1表示左键按下 BYTE2 ─ X坐标变化量，与byte的bit4组成9位符号数，负数表示向左移，正数表右移。用补码表示变化量 BYTE3 ─ Y坐标变化量，与byte的bit5组成9位符号数，负数表示向下移，正数表上移。用补码表示变化量 BYTE4 ─ 滚轮变化。
颜色是通过眼、脑和我们的生活经验所产生的一种对光的视觉效应，我们肉眼所见到的光线，是由频率范围很窄的电磁波产生的，不同频率的电磁波表现为不同的颜色，对色彩的辨认是肉眼受到电磁波辐射能刺激后所引起的一种视觉神经的感觉。但色彩在电脑里面是如何表示的呢？围绕色彩又有哪些常用的算法呢？请看下文：颜色表示方法LMS色彩人类眼睛有对于长(L, 560-580nm)、中(M, 530-540nm)、短(S, 420-440nm)波长的光感受器(视锥细胞)，因此根据三种视锥细胞的刺激比例，便能描述任一种颜色的感觉，此称为LMS空间需要注意的是，人类还有一种单色的夜视光感测器(视杆细胞)，其最敏感的感知频谱范围约在490-495nm，约在绿色范围内CIE色彩CIE(International Commission on Illumination, 法文Commission internationale de l'éclairage)基于生理、物理实验等的颜色模型，理论上包括了所有人眼可见光，包括有CIE-RGB、CIE-LAB、CIE-XYZ、CIE-xyY等多种模型，更偏向于色彩理论研究一些。大写的Y通常都表示感观亮度YUV/YCrCb色彩YUV主要用于模拟电视信号，YCrCb主要用于数字视频信号。但大致含义基本是相同的，Y表示明度信息，UVCrCb都是用于描述色度信息，mpeg、jpeg仍然在使用这种色彩表示方式。当初这么设计的原因有两点：人眼对亮度变化更加敏感一些，每一个亮度信息Y都应该单独保存；而人眼对色度变化不那么敏感，相邻的2个或4个像素点可以共用色度信息，可以减少数据大小。可以方便兼容黑白电视，只需要取出Y信号就可以了。换算规则：$$ \begin{aligned} Y &=& 0.299R + 0.587G + 0.114B \\ U &=& -0.147R - 0.289G + 0.436B \\ V &=& 0.615R - 0.515G - 0.100B \end{aligned} $$RGB色彩(Red、Green、Blue)一个物体如果自身发光的话，它发出什么颜色我们就看到什么颜色。如果是多个色光同时发光，我们看到的是所有色光叠加后的颜色。使用加法三原色的组合来表示一个点的具体色彩其中每一个三原色通道的取值范围是[0, 255]理论上可以表示$255\times255\times255=2^{8+8+8}=16777216$种颜色LinearRGB理论上完全按照物理规律的RGB表示法，通常使用[0, 1]范围内的值来表示sRGB由于人眼特性，人对暗色变化更加敏感，因此肉眼感觉的50度灰实际线性值只有0.214左右。为了使计算机表示色彩递进关系和人眼感受到的基本一致，微软联合爱普生、HP等制定了sRGB，它的通道值127就对应人眼感受到的中灰（传说中的伽玛校正）。++通常情况下如果没有特殊说明，一般所说的“RGB”均指sRGB++AdobeRGB比sRGB全，包含了所有CMYK色彩CMYK色彩(Cyan、Magenta、Yellow、Black)一个物体如果自身不发光的话，我们看到的颜色是它吸收后剩余的颜色。显然它的三原色就是发光三原色的互补色（为什么防蓝光眼镜看起来偏黄）使用减法三原色的组合来表示一人点的具体色彩通常CMYK每一个色彩通道的取值范围是[0, 100]理论上可以表示$100\times100\times100=1000000$种颜色换算规则：$$ \left\{ \begin{aligned} C^{'} &=& \frac{255-R}{255} \\ M^{'} &=& \frac{255-G}{255} \\ Y^{'} &=& \frac{255-B}{255} \end{aligned} \right. $$$$ K = \min(C^{'}, M^{'}, Y^{'}) $$$$ \left\{ \begin{aligned} C &=& C^{'} - K \\ M &=& M^{'} - K \\ Y &=& Y^{'} - K \end{aligned} \right. $$HS?色彩色相(Hue): 决定是什么颜色色系，一般情况下H取值范围是[0, 360)$$ \left\{ \begin{aligned} R{'} &=& R \times \frac{1}{255} \\ G{'} &=& G \times \frac{1}{255} \\ B{'} &=& B \times \frac{1}{255} \end{aligned} \right. $$$$ \left\{ \begin{aligned} C_{max} &=& \max(R{'}, G{'}, B{'}) \\ C_{min} &=& \min(R{'}, G{'}, B{'}) \\ \Delta C &=& C_{max} - C_{min} \end{aligned} \right. $$$$ H = \begin{cases} 0^\circ && \Delta C = 0 \\ 60^\circ \times \frac{G{'} - B{'}}{\Delta C} + 0^\circ && C_{max} = R{'} \\ 60^\circ \times \frac{B{'} - R{'}}{\Delta C} + 120^\circ && C_{max} = G{'} \\ 60^\circ \times \frac{R{'} - G{'}}{\Delta C} + 240^\circ && C_{max} = B{'} \\ \end{cases} $$HSL/HSI色彩(Hue、Saturation、Light/Intensity)饱和度(Saturation): 决定颜色的浓淡，取值范围一般[0, 100]明度(Light): 决定颜色有多明亮，取值范围一般[0, 100]理论上能表示$360\times100\times100=360000$种颜色，但实际会少很多。换算规则：$$ S = \begin{cases} 0 && \Delta C = 0 \\ 100 \times \frac{\Delta C}{1 - \vert 2L-1 \vert} && \Delta C \ne 0 \end{cases} $$$$ L = 100 \times \frac{C_{max} + C_{min}}{2} $$举例：纯绿(#00FF00)：H=120, S=100, L=50浅绿(#80FF80)：H=120, S=50, L=50深绿(#008000)：H=120, S=100, L=25纯黑(#000000)：H=~, S=0, L=0纯白(#FFFFFF)：H=~, S=~, L=100HSV/HSB色彩(Hue、Saturation、Value/Brightness)饱和度(Saturation): 混入白色的程度，取值范围一般[0, 100]明度(Brightness): 混入黑色的程度，取值范围一般[0, 100]理论上能表示$360\times100\times100=360000$种颜色，但实际会少很多。换算规则：$$ S = \begin{cases} 0 && C_{max} = 0 \\ 100 \times (1 - \frac{C_{min}}{C_{max}}) && C_{max} \ne 0 \end{cases} $$$$ V = 100 \times C_{max} $$举例：纯绿(#00FF00)：H=120, S=100, V=100浅绿(#80FF80)：H=120, S=50, V=100深绿(#008000)：H=120, S=100, V=50纯黑(#000000)：H=~, S=~, V=0纯白(#FFFFFF)：H=~, S=0, V=100常用基本算法透明叠加算法假设前景色透明度为$\alpha_{f}$，背景色透明度为$\alpha_{b}$，则计算方式如下：最终总透明度与层叠顺序无关。参见方法ColorUtils#compositeAlphaRGB三个通道计算方式完全相同。参见方法ColorUtils#compositeComponent$$ \alpha = 1 - (1 - \alpha_{f}) (1 - \alpha_{b}) $$$$ C = \frac{ \alpha_{f} C{_f} + (1 - \alpha_{f})\alpha_{b} C_{b} } {\alpha} $$颜色渐变算法特殊范围渐变若渐变起始值和结束值的RGB三个通道有两个完全相等，则对别一通道做线性处理即可(注意需要伽玛校正) $$ \left\{ \begin{aligned} C_{ls} = C_{ss}^{2.2} \\ C_{le} = C_{se}^{2.2} \end{aligned} \right. $$$$ C_{m} = C_{ls} + ratio \times (C_{le} - C_{ls}) $$$$ C^{'} = \sqrt[2.2]{C_{m}} $$若渐变起始值和结束值的HSV三个通道有两个完全相等，则对别一通道做线性处理即可(注意无需伽玛校正)通用渐变处理将渐变起始值和结束值的RGB三个通道分别按照特殊范围渐变方法1处理即可(若有透明通道变化则完全按照线性处理，无需伽玛校正)。参见安卓ArgbEvaluator类对比度亮度算法图片的亮度是指将所有像素点取平均值后的数值大小。但因为有RGB三个颜色通道，一般会按照三个通道分别计算后再转成具有亮度值的颜色表示方法。另外由于HSL或HSV的亮度或明度值是完全按照机器值计算的，和人眼主观感受并不相同，所以通常图片的最终亮度是换算成YUV或XYZ表示法后的Y值。\如果要增加图片的亮度，只需按照一定规则使像素点平均值增加即可图片的对比度是指最亮的点和最暗的点的的比值。为了防止偶尔一个极亮或极暗像素点的偶发影响，一般会取最亮的5%和最暗的5%来参与计算。\如果要增加图片的对比度，只需要按照一定规则使各像素点与平均值差值变大即可假设一幅图片有N个像素点，$p$为调整后的对比度(小于1减小对比度，大于1增加对比度)，$l$为调整后的亮度(小于1减小亮度，大于1增加亮度)，则调整图片的对比度和亮度的算法如下(RGB三个通道计算方式完全相同，只取一个通道说明)：$$ \overline{C} = \frac{1}{n} \sum_{n=1}^{N}C_n $$$$ C_n^{'} = p \times (C_n - \overline{C}) + l \times \overline{C} $$主色提取算法将原始图片进行缩放(安卓中默认是$112\times112$)缩减图片总颜色数量(RGB888转换成RGB555)将所有颜色作为一个统计块放到一个有序队列中(队列顺序按照RGB三个通道各自差值的乘积降序排列)从有序队列中取出并移除第一个位置的统计块获取取出的统计块RGB三个通道中的差值最大的项按照RGB最大差值通道对颜色进行重新排序按照颜色统计数量找到位于一半的分割点将取出的统计块按照分割点拆分两个统计块将新生成的两个统计块加到队列中重复步骤4～9，直到队列元素数量不小于最小分类数量(默认16)计算每一个统计块的加权平均颜色值过滤掉平均值结果中一些特定的颜色特别暗的(即HSL的L小于5，接近黑色)特别亮的(即HSL的L大于95，接近白色)一个红色区间($10\le H \le 37, S\le82$)???计算对应文本颜色将临时色彩设为纯白，透明度初始设为全不透明不段增加临时色彩透明度在一定次数限制内迭代将带有透明度的纯白色与统计块的平均色进行合成直到合成色与统计块平均色对比度恰好接近某一对比度将此时的合成色当作匹配的的文本颜色若未找到合适的，则将临时色设成黑色重复1～5步优先使标题色和文本色色调一致(即初始临时色相同)根据过滤条件获取目标统计结果如getDominantSwatch是获取数量最多的颜色如getVibrantSwatch饱和度在35以上，亮度在30～70之间的颜色如getDarkVibrantSwatch饱和度在35以上，亮度在45以下的颜色如getDarkVibrantSwatch饱和度在35以上，亮度在55以上的颜色颜色变换算法通过对原图片每一个像素的$RGBA$进行数学变换后，能够得到新颜色$R{'}G{'}B{'}A{'}$，可以得到一些特殊的效果(相机滤镜)$$ \begin{bmatrix} R^{'} \\ G^{'} \\ B^{'} \\ A^{'} \end{bmatrix} = \begin{bmatrix} R \\ G \\ B \\ A \end{bmatrix} \bigodot \begin{bmatrix} a & b & c & d \\ e & f & g & h \\ i & j & k & l \\ m & n & o & p \end{bmatrix} + \begin{bmatrix} q \\ r \\ s \\ t \end{bmatrix} $$$$ \left\{ \begin{aligned} R^{'} &=& aR + bG + cB + dA + q \\ G^{'} &=& eR + fG + gB + hA + r \\ B^{'} &=& iR + jG + kB + lA + s \\ A^{'} &=& mR + nG + oB + pA + t \end{aligned} \right. $$常用矩阵：原样矩阵 $$ \begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 \end{bmatrix} $$黑白矩阵 $$ \begin{bmatrix} 0.299 & 0.587 & 0.114 & 0 & 0 \\ 0.299 & 0.587 & 0.114 & 0 & 0 \\ 0.299 & 0.587 & 0.114 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 \end{bmatrix} $$负片(底片)矩阵 $$ \begin{bmatrix} -1 & 0 & 0 & 0 & 255 \\ 0 & -1 & 0 & 0 & 255 \\ 0 & 0 & -1 & 0 & 255 \\ 0 & 0 & 0 & 1 & 0 \end{bmatrix} $$常用模糊算法什么是模糊？当相邻的多个像素点色彩互相影响，使原本清晰可辨的边界无法辨别时就是模糊。为了使图片模糊，需要指定一个像素点需要与周围多少个像素点进行相关运算，这个值叫做模糊半径。当模糊半径为1时，即相当于需要像素点和它周围8个点按照一定权值比例进行运算简单算法如下(RGB三个通道计算方式完全相同，只取一个通道说明)：$$ \begin{matrix} \boxed{C_{tl}} \boxed{C_{tc}} \boxed{C_{tr}} \\ \boxed{C_{ml}} \boxed{C} \boxed{C_{mr}} \\ \boxed{C_{bl}} \boxed{C_{bc}} \boxed{C_{br}} \end{matrix} $$$$ \begin{bmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \\ \end{bmatrix} $$$$ C^{'} = \frac{1}{24}C_{tl} + \frac{2}{24}C_{tc} + \frac{1}{24}C_{tr} + \frac{2}{24}C_{ml} + \frac{4}{24}C + \frac{2}{24}C_{mr} + \frac{1}{24}C_{bl} + \frac{2}{24}C_{bc} + \frac{1}{24}C_{br} $$很显然，模糊矩阵的取值很重要，直接会影响到模糊的效果，是否符合生活中直观感受完全取觉得于它。正太分布就十分符合这种场景，但一般正太分布是一维的，我们需要一个二维正太分布。正态分布的密码函数叫作“高斯函数”，因此这种模糊算法叫做高斯模糊简易图片哈希算法将图片缩小到$8 \times 8$大小将缩小后的图转成灰度图计算灰度图片的平均值统计像素点，小于平均值记为0，否则记为1将64位二进制数据转成8位十六进制值，即为图片哈希计算两个哈希间的汉明距离即为相似度
Kotlin作为一种JVM语言，与传统的Java相比，增加了很多有意思的语法糖，可以显著降低开发难度，提升开发效率，增加代码健壮性。基础篇变量和类型Kotlin中定义变量使用如下结构定义常量/变量val 常量名: 类型 = 常量值 var 变量名: 类型 = 初始值空安全Java中最常见的一个问题就是NullPointException，在Kotlin中为了减少此问题推出了optional语法。在定义一个变量时，若在常规类型后加一个?，表示此变量可以为null。/** str1不能为null */ var str1: String = "" /** str2可以为null */ var str2: String? = null在调用可空对象的方法时，需要加一个?以进行安全的调用。/** str1一定不为null，因此直接访问它的属性就是安全的 */ val len1 = str1.length /** str2可能为null，必须加?以安全访问它的属性，当str2为null时，len2也为null */ val len2 = str2?.length如果一个对象定义为可空，但在使用时确信一定不为null，则可以使用!!进行强转，但强烈建议不要这么做// 当str2为null时会抛NPE val len2 = str2!!.length结合?:操作符，可以省掉很多null判断（A ?: B的含义为，只有当A为null时才会执行B）例一fun joinRoom(roomId: Long?, password: String?): Boolean { roomId ?: return false password ?: return false …… }例二public String recordingFaceUrl() { if (mediaBean != null) { if (mediaBean.recording != null) { if (mediaBean.recording.face_cover_url != null) { return mediaBean.recording.face_cover_url; } else { return mediaBean.recording.cover_image; } } else { return ""; } } else { return ""; } }fun recordingFaceUrl(): String { return mediaBean?.recording?.face_cover_url ?: mediaBean?.recording?.cover_image ?: "" }类型推导当定义变量时初始值类型明确时，可以省略类型声明，Kotlin会自动推导出来。// 自动推导出str1的类型为String var str1 = "abcde" // 会自动推导出len的类型为Int var len = "ABDEF".length当已经经过了类型检测时，可以直接当作检测后的类型使用，而无需强转。void forbar(Object obj) { if (obj instanceof String) { ((String) obj).chatAt(0); } }fun forbar(any: Any) { if (any is String) { // obj已经被自动转成了String类型 obj.chatAt(0) } } fun forbar(any: Any) { if (any !is String) return // obj已经被自动转成了String类型 obj.chatAt(0) }在泛型相关调用中，多数泛型参数也能够被自动推导填充（注：目前Java8也支持大部分泛型的类型推导）与Java进行互调时，自动推导出的类型不是空安全的，并且会失去空安全检测。// 自动推导出str1类型是 String! ，即不是 String 也不是 String? val str1 = context.getString(R.string.title) // 编译不会报错并可运行，但有NPE风险 var len1 = str1.length // 编译不会报错并可运行，但有NPE风险 val str2: String = context.getString(R.string.title)解决办法：在Java的方法中使用@Nullable和@NotNull注解明确返回值是否可空特殊类型Kotlin中数字类型处理方式与Java很不同，表面上全部都是封装的类类型，但实际可能根据上下文章自动装箱或拆箱。\例外情况是可空类型一定对应封装类型，如Kotlin的Int?一定对应Java的IntegerKotlin中不存在数字的隐式转换，所有不同的类型必须显式调用相应的转换方法。对于Int和Long类型的的位操作，在Kotlin中必须使用位操作函数或中缀表达式，不能使用操作符。Java操作Kotlin函数形式Kotlin中缀形式a << ba.shl(b)a shl ba >> ba.shr(b)a shr ba >>> ba.ushr(b)a ushr ba & ba.and(b)a and b`ab`a.or(b)a or b~aa.inv() 另外在Kotlin中定义数组的方式也和在Java中不同，尤其基本类型有其自己的数组定义方式。Java类型Kotlin类型int[]IntArrayInteger[]Array<Int> 或 Array<Int?>String[]Array<String> 或 Array<String?>方法和属性方法常规方法Kotlin中使用如下格式定义方法：fun 方法名(参数列表) = 一句话表达式 fun 方法名(参数列表): 返回类型 { 方法体 }当返回类型是Unit时（相当于Java中的void），可以省略返回类型当方法体只有一句话时，可以使用简约格式fun square(x: Int) = x * x包级方法在Kotlin文件中可以像C++一样直接定义方法，这样的方法在其它Kotlin中看起来是属于包的，不属于任何一个具体的类，在Java中各种Utils类的静态方法都可以使用包级方法实现。例如在IO.kt中有如下方法时，在Java中可以用IOKt.closeQuietly(is)调用。fun closeQuietly(closable: Closable) {}扩展方法在Kotlin中可以按照如下格式给类增加方法，新增加的方法和类原本的方法调用形式完全相同。fun 类型.方法名(参数列表): 返回类型 { 方法体 }如果定义的扩展方法是包级方法，则任何使用该类型的地方均可调用。也可以在某个类型内部定义扩展方法，此时扩展方法只能在外部类型内使用，并且可以访问外部类型的成员变量。abstract class BaseFragment : Fragment() { private val composite = CompositeDisposable() // 只有在BaseFragment及其子类中的Disposable才可调用autoDispose方法 fun Disposable.autoDispose() { /* * 在扩展方法内部的this指代的是被扩展的对象，此例中指Disposable对象 * 若要访问外部对象则需要用@明确标识 */ this@BaseFragment.composite.add(this) } }对应于Java中如下代码：class BaseFragment extends Fragment { CompositeDisposable composite = CompositeDisposable(); public void autoDispose(Disposable disposable) { this.composite.add(disposable); } }特殊参数默认参数定义方法时可以给参数设置默认值，在调用时可以省略最右侧的参数fun foobar(arg1: String, arg2: String = "", arg3: String? = null) { // Do something } /* 以下三个调用完全相同 */ foobar("8023") foobar("8023", "") foobar("8023", "", null)Kotlin实现此功能时，实际是将被省略的参数自动补全，若要在Java中调用也能省略参数，则需要给方法增加@JvmOverloads注解。命名参数在调用方法时，可以指定参数名称，这样就只给指定名称的参数赋值。fun foobar(arg1: String = "", arg2: String = "", arg3: String? = null) { // Do something } /* 以下三个调用完全相同 */ foobar(arg2 = "8023") foobar("", "8023") foobar("", "8023", null)变长参数和Java中使用...不同，Kotlin中使用varargs标记变长参数。void foobar(String... args) {}fun foobar(vararg args: String) {}若需要将变长参数作为其它变长参数的参数，则需要用*进行展开。fun foo(vararg args: Any) {} fun bar1(vararg args: Any) = foo(args) fun bar2(vararg args: Any) = foo(*args) // 在foo中args[0][0]为“abc” bar1("abc") // 在foo中args[0]为“abc” bar2("abc")构造方法Kotlin中任意类的构造方法名称都是constructor与类定义写在一起的构造方法是默认构造方法，其它构造方法都必须调用它。如果默认构造没有其它特殊要求（如设置显隐性等），constructor关键字可以省略如果默认构造方法的参数有val/var关键字修饰，则对应的参数自动变成成员常量/成员变量构造函数参数列表可以有默认值，如果加上@JvmOverloads注解则会自动生成多个构造方法以供Java调用init{}代码块默认构造函数代码块/** * 由于有JvmOverloads注解，会自动生成RatioLayout(context)和RatioLayout(context, attrs)两个构造方法 * 由于context有var标记，则context自动变为成员变量 * 另：Kotlin中定义class默认是final的，只有标为open才能被继承 */ open class RatioLayout @JvmOverloads constructor(var context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs, defStyle) { /** 非默认构造，必须调用默认构造，即this(context, attrs) */ protected constructor(context: Context, attrs: AttributeSet?, defStyle: Int): this(context, attrs) /** 初始化代码块，相当于默认构造方法的方法体 */ init { /** 在初始化代码块中，可以访问默认构造的所有参数，即使没有标记val/var */ Log.e("RatioLayout", attrs.length) } }属性在传统Java开发中经常会使用到JavaBean。但JavaBean通常需要手写很多余的getter和setter方法。而在Kotlin中，所有非private变量默认都会自动生成bean方法。如果不需要自动生成bean方法，可以使用@JvmField注解如果需要在getter或setter中进行额外操作，可以覆盖get()和/或set(value)方法，其中get的返回类型和set的参数类型必须与属性兼容属性有字段属性和计算属性两类，带有初始值的为字段属性，字段属性的方法中中可以用field访问原始的字段值用val标识的属性为只读属性，只读属性只有get()方法在kotlin中访问属性时与在java访问public成员变量形式完全相同public boolean isFavored() { return this.recordings != null && this.recordings.recording != null && this.recordings.recording.has_favored; } public void setFavored(boolean favored) { if (recordings != null && recordings.recording != null) { recordings.recording.has_favored = favored; } }/** 计算属性 */ var isFavored: Boolean get() = mediaBean?.recording?.has_favored ?: false set(value) { mediaBean?.recording?.has_favored = value } /** 字段属性 */ var favoredCount: Int = 0 private set(value) { field = Math.max(0, value) }伴生对象Kotlin没有静态变量、静态方法的概念，如果要完成相应的功能可以使用伴生对象。\如果需要生成纯static变量或方法，可以增加@JvmStatic注解。（注意此时变量不能使用const关键字）class SomeFragment: Fragment { companion object { private const val EXTRA_NAME = "extra_name" @JvmStatic fun newInstance(name: String): SomeFragment { val args = Bundle() args.put(EXTRA_NAME, name) val fragment = SomeFragment() fragment.arguments = args return fragment } } }Lambda函数当一个方法（包括构造方法）的参数为有且仅有一个方法的Java接口类型时，可以使用更为简约的Lambda形式。Thread { // 异步线程 }.start()new Thread(new Runnable { @Override public void run() { // 异步线程 } }).start();当接口有一个参数时，可以不用显示标明，会自动将参数命名为it，另外一般默认会将最后一个语句作为返回值// 按钮只能点击一次，点击一次后禁用掉 button.setOnClickListener { it.enabled = false // it指onClick(view)的参数view，即button }当方法的最后一个参数为Java单方法接口时，也可以使用Lambda表达式，此时可以将Lambda写在圆括号之外当接口有多个参数时，必须明确标示出来，没有用到的参数可以用下划线_占位dialog.setPositiveButton("确定") { it, _ -> it.dismiss() }在Kotlin中定义的接口不能使用Lambda表达式简写在Kotlin中可以直接定义lambda函数，并且lambda可以作为其它函数的参数使用。fun setCallback(callback: (Int, Int) -> Int): Int { return callback(1, 2) } // result的值为3，类型为Int val result = setCallback { arg1, arg2 -> return arg1 + arg2 }当lambda函数可以为null，定义时需要使用optional语法，调用时需要使用invoke操作符。fun setCallback(callback: ((Int, Int) -> Int)?): Int { return callback?.invoke(1, 2) ?: 0 } // result的值为3 val result = setCallback { arg1, arg2 -> return arg1 + arg2 } // result的值为0 val result = setCallback(null)lambda函数在定义时可以为扩展方法，也可以和泛型结合以达到更高级的用法，此时在lambda函数体内this指代的是被扩展的对象。fun <T, R> with(receiver: T, block: T.() -> R) { return receiver.block() } // result的结果是“这是” val result = with("这是测试代码") { // 此时this指代的是“这是测试代码”这个字符串对象 return this.substring(0, 2) }数组和集合集合分为可修改集合和只读集合，带有Mutable的集合为可修改集合，只读集合没有add/set/remove等方法若数组/集合的初始值已经确定，则可以使用arrayOf、listOf、setOf、mapOf等扩展方法val a = intArrayOf(1, 2, 3, 4) // a的类型为IntArray val b = arrayOf(1, 2, 3, 4) // b的类型为Array<Int> val c = listOf(1, 2, 3, 4) // c的表面类型为List<Int>，实际承载类型是ArrayList，但不能调用add等方法 val d = mutableListOf(1, 2, 3, 4) // d的表面类型为MutableList<Int>，实际承载类型是ArrayList val e = mapOf("name" to "Qi", // e的表面类型为Map<String, Any>，实际承载类型是HashMap "sex" to true) // key to value 语法表示一个k-v键值对，参见infix表达式在Kotlin中集合也可以完全按照数组的样式访问，并且对于Map类型的“下标”可以是非数字形式。（另：参见操作符重载之get/set）val list = listOf("Android", "iOS", "Web") val it1 = list[0] // it1类型为String，值为Android val it2 = list[4] // it2类型为String，但会抛出IndexOutOfBoundsException val it3 = list.getOrNull(4) // it3类型为String?，值为null，不抛异常 val it4 = list.getOrElse(4, "RD") // it4类型为String，值为RD，不抛异常 val map = HashMap() map["user_id"] = UserManager.INSTANCE.getCurrentUserID() map["song_id"] = "1234567890"Kotlin为数组和集合提供了很多扩展方法，可以很方便的进行过滤、排序、循环、映射等诸多操作。\每一个filter、map、forEach等都会产生一个循环调用，一般不应该出现多次连续调用的情况。\（附注：部分文档中说Kotlin在编译时会有优化，无需太过于考虑性能浪费问题，未能验证）// 打印所有身高超过165的18岁女性用户姓名 // 糟糕 users.filter { it.height > 165 } .filter { it.age == 18 } .filter { it.sex = Sex.FEMALE } .map { it.name } .forEach { println(it) } // 较好 users.filter { it.height > 165 && it.age == 18 && it.sex = Sex.FEMALE } .forEach { println(it.name) }数组和集合操作实际就是for循环，但在for循环中可以通过continue跳过本次循环，但在数组和集合操作中则需要使用return@语法，因为每一次调用都相当于调用了一次函数。fun main(args: Array<String>) { val list = listOf(1, 2, 3, 4, 5) list.map { // 若存在此语句，则不会有任何输出，因为map是一个inline函数，实际编译时会被展开 // 此处的return返回的是main方法，map尚未执行完时main就已经结束了，后续不会执行 // if (it % 2 == 0) return // map函数必须有返回值，并且有多少个传入参数就必须有多少个返回值 if (it % 2 == 0) return@map 0 it * it // map全部执行完成后的临时集合为[1, 0, 9, 0, 25] }.forEach { // forEach不要求有返回值，此时就相当于continue功能了 if (it % 3 == 0) return@forEach print(it) } // 最终输出125 }区间在Java中使用for循环时一般使用for (int i = A; i < B; i += C)格式，其中限制条件A、B、C在Kotlin中被封装成XXXRange的区间形式。（参见infix和操作符重载）Java 代码Kotlin 代码for (int i = A; i <= B; i++)for (i in A .. B)for (int i = A; i <= B; i += C)for (i in A .. B step C)for (int i = A; i <= B; i += C)for (i in A rangeTo B step C)for (int i = A; i < B; i += C)for (i in A until B step C)for (int i = A; i >= B; i--)for (i in A downTo B)for (int i = A; i >= B; i -= C)for (i in A downTo B step C)异常与Java相同，Kotlin中所有异常均是Throwable的子类，try……catch……finally也与Java基本相同。\但在Java中强制捕获的异常在Kotlin中并不强制！进阶篇字符串字符串模板fun info(User: User) { print("姓名：${user.name}，性别：${user.sex}，年龄：${user.age}") }void info(User user) { StringBuilder sb = new StringBuilder(); sb.append("姓名："); sb.append(user.name); sb.append("，性别："); sb.append(user.sex); sb.append("，年龄："); sb.append(user.age); System.out.print(sb.toString()); }原生字符串 与Python、PHP等类似，可以用3个双引号来包裹字符串，这样的字符串内容没有转义，也可以直接使用双引号。另外，使用原生字符串时，为了保持页面的缩进样式可能在每行行首会产生多余缩进，此时可以使用trinIndent进行处理。val text0 = """ for(c in str) { println(c) } """ val text1 = """ for(c in str) { println(c) } """.trimIndent() val text2 = """for(c in str) { println(c) }""" text0 == text1 // false text1 == text2 // true多分支与Java中的switch……case相似，Kotlin中也提供了多分支操作when，但远比switch强大的多，另外在when中按照从上到下的顺序来进行匹配，当某个分支的条件满足时整个when就退出（==因此else不能作为第一个条件==），每个分支不需要用break强制中断。when(any) { 1, "ONE" -> {/*当any的值是数字1或者字符串ONE时执行此处操作*/} -1 -> {/*当any的值为-1时执行此处操作*/} "TWO" -> {/*当any的值为"TWO"时执行此处操作*/} is String -> {/*当any是字符串类型时执行此处操作，不能和上一条件调换顺序*/} else -> {/*以上条件都不满足时执行此处操作*/} }也可以when { any == 1 || any == "ONE" -> {/*当any的值是数字1或者字符串ONE时执行此处操作*/} any == -1 -> {/*当any的值为-1时执行此处操作*/} any == "TWO" -> {/*当any的值为"TWO"时执行此处操作*/} any is String -> {/*当any是字符串类型时执行此处操作，不能和上一条件调换顺序*/} else -> {/*以上条件都不满足时执行此处操作*/} }泛型在Kotlin中，泛型用法与Java大致相同，但Kotlin的泛型更严格一点，每一个泛型类都必须明确指定泛型类型，或可以正确推导出泛型类型。val list1 = listOf<Any>() // 正确，list1的类型是List<Any>，由明确指定得到 val list2 = listOf("", 1) // 正确，list2的类型是List<Any>，由自动推导得到 val list3 = listOf() // 错误，无法确定list3的泛型类型不变不变即泛型参数是一个十分明确的类型，而这个泛型类型即可以作为输入类型也能作为输类型。例如常见的ArrayList<String>就是不变泛型。逆变（in）逆变泛型使用in关键字，表示传入的参数至少是指定类型，因此逆变泛型只能作为输入类型，与Java中Foo<? super T>语法相似。class Foo<in T> { fun set(item: T) { } // 合法，逆变可以作为参数 fun get(): T { } // 非法，逆变不能作为返回值 } class Bar class Child: Bar val foo = Foo<Bar> foo.set(Child()) // 合法，Child对象也至少是一个Bar类型的对象协变（out）协变泛型使用out关键字，表示传出的参数最多是指定类型，因此逆变泛型只能作为输入类型，与Java中Foo<? extends T>语法相似。class Foo<out T> { fun set(item: T) { } // 非法，协变不能作为参数 fun get(): T { } // 合法，协变可以作为返回值 }星投影（*）星投影有点类似在Java中的省略泛型参数，但在Java中省略泛型参数后泛型类型为Object，但Kotlin中星投影只能表示泛型类型传递的不确定性，不能当作Any泛型，因此不能创建星投影泛型对象。val list1 = ArrayList<Any>() // 合法，泛型类型为Any val list2 = ArrayList<*>() // 非法，不能创建星投影泛型对象 class MainAdapter: Adapter { fun setItems(items: List<*>) { …… notifyDataSetChanged() } } val adapter = MainAdapter() class items1 = ArrayList<Int>() adapter.setItems(items1) // 合法，可以接受任意泛型类型 class items2 = ArrayList<String>() adapter.setItems(items1) // 合法，可以接受任意泛型类型具化（reified）JVM虚拟机下的泛型全部是假泛型，在实现编译时会抹去所有泛型信息，若要想知道具体的泛型信息则需要通过一些复杂的操作才能实现，但在Kotlin中还可能通过具化关键字reified来简化泛型操作。\具体实现上来说，实际是在编译时生成相应的方法重载来达到具化目的的，因此具化只能用于泛型函数不能用于泛型类，并且这个泛型函数必须是inline的。inline fun <reified T> foobar(value: T): String { return "“${value}”的类型是${T::class.simpleName}" } // 会自动生成foobar(value: String)重载 val value1 = foobar("字符") // 如果有下面的调用会再生成foobar(value: Int)重载 // val value2 = foobar(123)数据类如果是一个纯Bean类，可以使用data class，data class其实就是JavaBean的简化语法糖，因此它需要至少一个字段，即默认构造需要至少一个val/var参数。data class默认实现了equals、hashCode、toString等JavaBean必备的方法，除这些常规方法外，它还实现了一系列componentN解构方法。data class BlockEvent(@JvmField var userId: String, val isBlocked: Boolean) val event = BlockEvent("951753", false) // 语句段1 val (userId, isBlocked) = event // 语句段2 val userId = event.component1() val isBlocked = event.component2() // 语句段3 val (_, isBlocked) = event // 语句段4 val isBlocked = event.component2() // 以上四个语句段，1和2完全等同，3和4完全等同单例模式object Singletonpublic class Singleton { public static final volatile Singleton INSTANCE; static { synchronized(Singleton.class) { INSTANCE = new Singleton(); } } private Singleton() {} }多数文档中说明Kotlin的单例是线程安全并且懒加载的，但从反编译的代码上来看使用的是线程不安全的饿汉模式，暂时还没有搞明白原因。操作符常规操作符Java中的一些操作符如instanceof等在Kotlin中也有与之对应的形式，如下表所示：Java 代码Kotlin 代码备注a instanceof Aa is A!(a instanceof A)a !is AB b = (B)aval b = a as B?B b = (a instanceof B) ? (B)a : nullval b = a as? Ba.equlas(b)a == ba?.equals(b) ?: (b === null)a == ba === b==注意：a as? B和a as B?含义是完全不一样的！==操作符重载在Java中只有数字类型支持++、--、+=、-=、*=、/=、%=等操作，只有数组可以进行arr[0]形式的下标访问，只有函数可以进行foobar()形式的调用，但在Kotlin中也可以让我们自己的类型支持这些操作符，此时就要用到操作符重载。\（特别注意：滥用操作符重载会严重影响代码可读性，因此重载操作符一定要慎重！）class Demo { // +this operator fun unaryPlus(): Demo { } // ++this、this++ operator fun inc(): Demo { } // 注意不要直接修改this值 // !this operator fun not(): Demo { } // this + other operator fun plus(other: Demo): Demo { } // this += other operator fun plusAssign(other: Demo) { } // this .. other operator fun rangeTo(other: Demo): ClosedRange<Demo> { } // other in this operator fun contains(other: Demo): Boolean { } // this(other) operator fun invoke(other: Any): Any { } // this[index] operator fun get(index: Int): Any { } // this[index] = value operator fun set(index: Int, value: Any) { } // this[index1, index2] operator fun get(index1: Any, index2: Any): Any { } // this[index1, index2] = value operator fun set(index1: Any, index2: Any, value: Any) { } // this == other、this != other operator fun equals(other: Any?): Boolean { } // this > other、this >= other、this < other、this <= other operator fun compareTo(other: Demo): Int { } }别名在一些场景下可以给类型指定另外的名称，以解决名字冲突或缩减代码。import android.twitter.util.Constants as ConstT import com.facebook.util.Constants as ConstF ConstT.SIZE_PB // 相当于调用android.twitter.util.Constants.SIZE_PB ConstF.SIZE_PB // 相当于调用com.facebook.util.Constants.SIZE_PB typealias StringList = ArrayList<String> typealias StringMap<T> = HashMap<String, T> typealias LongClick = (View) -> Boolean // val list = ArrayList<String>(10) val list = StringList(10) // val map = HashMap<String, Double>() val map = StringMap<Double> val lc: LongClick = { it -> false } view.setOnLongClickListener(lc)反射*类引用最基本的反射功能是获取 Kotlin 类的运行时引用。val kclazz: KClass<UserModel> = UserModel::class val jclazz: Class<UserModel> = kclazz.java // 声明Intent需要用到java class val intent = Intent(context, MainActivity::class.java)方法引用在Kotlin中可以将方法赋值给一个变量，然后就可以把这个变量当作该方法调用。\（实现上Kotlin是将这个引用封装成了一系列叫作Function的inline函数）类方法引用val foobar = String::substring val sub1: String = foobar("这是字符串", 0, 1) // sub1的结果为“这” val sub2: String = foobar("这是字符串", 1, 2) // sub2的结果为“是”约等于以下Java代码，但一般情况下foobar会被当作inline函数展开。String foobar(String ref, int start, int end) { return ref.substring(start, end); } String sub1 = foobar("这是字符串", 0, 1); String sub2 = foobar("这是字符串", 1, 2);由于双冒号::右侧的方法不带有参数，当这个方法存在重载时，需要显式声明我们希望使用的是哪一个// foo会引用substring(int) val foo: Function2<String, Int, String> = String::substring // bar会引用substring(int, int) val bar: Function3<String, Int, Int, String> = String::substring==注意：如果要显式声明，则必须依赖kotlin-reflect库，Function后的数字表示参数个数，泛型最后一个是返回值类型==实例方法引用val foobar = "这是字符串"::substring val sub1: String = foobar(0, 1) // sub1的结果为“这” val sub2: String = foobar(1, 2) // sub2的结果为“是”约等于以下Java代码，但一般情况下foobar会被当作inline函数展开。String ref = "这是字符串"; String foobar(int start, int end) { return ref.substring(start, end); } String sub1 = foobar(0, 1); String sub2 = foobar(1, 2);方法引用的作用 方法引用可在一些场景下简化方法的调用。RxTextView.textChanges(EditText(context!!)) .map(CharSequence::toString) .subscribe { }属性引用与方法引用相似，我们也可以用::来引用一个属性。（实现上Kotlin会假想将val属性封装成KProperty对象，将var封装成KMutableProperty对象，实际编译后并不存在这样的对象）类属性引用val user = UserModel() val id = UserModel::id id.set(user, 123456)约等于以下Java代码，但实际比这要复杂些，并且一般不会展开。void id(UserModel user, int id) { user.id = id; } UserModel user = new UserModel(); id(user, 123456);实例属性引用val user = UserModel() val id = user::id id.set(123456)属性引用的作用 通过属性引用可以检查属性的当前状态信息。id.isLateinit id.isInitialized新语法中缀中缀即用infix修饰有且仅有一个参数的函数（包括扩展函数），后续调用此函数时可以使用中缀表达式的形式。这个语法糖主要是在某些场景下可以使函数更美观一些，或者更符合一般常规的上的习惯。若有多个中缀表达式连续调用，则一律采取从左往右的执行顺序。class Foobar { infix fun go(step: Int)…… } val foobar = Foobar() // 与foobar.go(1)功能含义完全相同 foobar go 1例如DBFlow的Kotlin扩展，使得在Kotlin中的DBFlow操作拥有于SQL语句90%的相似度。select(name).from(Android.class).where(name.is("Nexus 5x")).and(version.is(6.0))select name from Android::class where (name eq "Nexus 5x") and (version eq 6.0)委托委托模式是软件设计模式中的一项基本技巧。在委托模式中，有两个以上的对象参与处理同一个请求，接受请求的对象将请求委托给另一个对象来处理。类委托类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。interface IButtons { fun clickLeft() fun clickRight() } interface IWheel { fun clickWheel() fun rollUp() fun rollDown() } class Mouse(val buttons: IButtons, val wheel: IWheel) : IButtons by buttons, IWheel by wheel { // 正常的委托需要有以下方法，但在类委托下此方法自动实现，其它4个方法类似 fun clickLeft() { buttons.clickLeft() } }属性委托属性委托指的是一个类的某个属性值不是在类中直接进行定义，而是将其托付给一个代理类，从而实现对该类的属性统一管理。格式为val/var <属性名>: <类型> by <表达式>，by关键字之后的表达式就是委托, 属性的 getter （以及setter）将被委托给这个对象的 getValue() 和 setValue() 方法（因此被委托的属性不能使用@JvmField注解）。属性委托不必实现任何接口（因此实际是通过编译时替换实现）, 但必须提供 getValue() 函数（对于 var属性,还需要 setValue()函数）。class Foobar { var prop: String by Delegate() } /** * 具体处理委托操作的类 * 1. 委托的getValue和setValue都属于操作符，因此必须有operator关键字 * 2. 两个方法的thisRef表示属性所在的对象，此例中即Foobar对象 * 3. property是对被委托属性的反射引用，参见反射→属性引用部分 */ class Delegate { // val/var属性均必须有getValue方法 operator fun getValue(thisRef: Any, property: KProperty<*>): String { return "$thisRef, 这里委托了 ${property.name} 属性" } // 只有var属性才需要setValue方法 operator fun setValue(thisRef: Any, property: KProperty<*>, value: String) { println("$thisRef 的 ${property.name} 属性赋值为 $value") } }常用委托懒加载// 只有在首次调用text的时候才会执行lazy的lambda体初始化，由于lazy只初始化一次并且中途不能进行修改，所以必须是val类型 val text by lazy { File("/sdcard/log.txt").readText() }映射器class User(val map: MutableMap<String, Any>) { var name: String by map val age: Int by map } val user = User(mapOf("name" to "张珊", age to 18))观察器var name: String by Delegates.observable("初始值") { property, old, new -> println("旧值：$old -> 新值：$new") }拦截器var name: String by Delegates.vetoable("初始值") { property, old, new -> println("旧值：$old -> 新值：$new") new.length > 0 // 当返回false时，不允许修改属性的值，相当于拦截了本次修改 }预占位var name: String by Delegates.notNull<String>()此委托适用于那些无法在初始化阶段就确定属性值的场合。但与延迟lateinit功能基本相同，一般不建议使用。延迟Kotlin一般要求在定义变量时有初始值（类的成员属性可以在默认构造时赋值），但典型如Fragment传递的参数，明确知道参数是不可null的，但又无法在构造函数时获取，常规方式下只能定义成可空类型然后再在onCreate时获取，但将实际不可空的属性定义成可空类型会增加后续操作的复杂度。延迟加载就恰恰适用于这些无法在初始化阶段就确定属性值的场合，由于延迟加载需要稍后修改属性，因此必须定义为var可修改类型。class SomeFragment: Fragment { lateinit var name: String override fun onCreate(state: Bundle?) { super.onCreate(state) name = arguments.get(EXTRA_NAME) } companion object { private const val EXTRA_NAME = "extra_name" @JvmStatic fun newInstance(name: String): SomeFragment { val args = Bundle() args.put(EXTRA_NAME, name) val fragment = SomeFragment() fragment.arguments = args return fragment } } }==注意：在首次赋值之前访问lateinit属性会抛出UninitializedPropertyAccessException异常==内联inline 众所周知函数的调用是需要有栈操作的，一些简单又频繁调用的功能如果用函数实现，可能会有一些性能上的损失，此时可以使用内联函数实现。与C/C++中的内联函数相似，在编译期间内联函数会被展开，即实际的字节码中并不存在这个函数。inline fun mix(a: Int, b: Int) = if (a < b) a else bnoinline默认情况下所有内联函数的lambda参数也都是内联的，如果不希望内联则需要使用noinline关键字。inline fun foo(value: Int, noinline callback: (Int) -> String): String { return callback(value) } foo(2) { return "${it * it}" }Sting foo(int value, Function1<Integer, String> callback) { return callback.invoke(value); } class Inner0 extends Function1<Integer, String> { public String invoke(Integer it) { return String.valueOf(it * it); } } foo(2, new Inner0());crossinlineinline fun inner(lambda: () -> Int) { lambda() } fun outer() { inner { // (1) 由于inline和它的lambda参数都将被展开，处的return实际最终将是outer的return return // (2) 有了@inner标记才是inner的return，此处必须按照inner的lambda要求一个Int return@inner 0 } println("结束") // 若(1)处代码存在，则此行不会执行 }要解决以上问题，可以在定义inner时使用crossinline关键字。// 此时(1)处代码编译时报错 inline fun inner(crossinline lambda: () -> Int) { lambda() }辅助函数letinline fun <T, R> T.let(block: (T) -> R): R val result: Int = user.let { it -> // it指代user对象，并默认以最后一个语句作为返回值 it.age }runinline fun <T, R> T.run(block: T.() -> R): R val result: Int = user.run { // this指代user对象，并默认以最后一个语句作为返回值 this.age }alsoinline fun <T> T.also(block: (T) -> Unit): T val result = user.also { it -> // it指代user对象，此lambda无返回值，also会返回对象本身 it.age = 16 }applyinline fun <T> T.apply(block: T.() -> Unit): T val result = user.apply { // this指代user对象，此lambda无返回值，apply会返回对象本身 this.age = 16 }takeIfinline fun <T> T.takeIf(predicate: (T) -> Boolean): T? // 当user的年龄大于16时，result = user，否则result = null val result = user.takeIf { it -> // it指代user对象，并默认以最后一个语句作为返回值 // 若返回值为true，则takeIf整体返回user对象本身 // 若返回值为false，则takeIf整体返回null it.age > 16 }takeUnlessinline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? // 当user的年龄不大于16时，result = user，否则result = null val result = user.takeUnless { it -> // it指代user对象，并默认以最后一个语句作为返回值 // 若返回值为false，则takeIf整体返回user对象本身 // 若返回值为true，则takeIf整体返回null it.age > 16 }withinline fun <T, R> with(receiver: T, block: T.() -> R): R val result: Int = with(user) { // this指代user对象，并默认以最后一个语句作为返回值 this.age }repeatinline fun repeat(times: Int, action: (Int) -> Unit) // 无返回值，以下语句输出10次带换行的你好 repeat(10) { println("你好") }补漏篇Kotlin调JavaGetter 和 Setter遵循 Java 约定的 getter 和 setter 的方法（名称以 get 开头的无参数方法和以 set 开头的单参数方法）在 Kotlin 中表示为属性。 Boolean 访问器方法（其中 getter 的名称以 is 开头而 setter 的名称以 set 开头）会表示为与 getter 方法具有相同名称的属性。void类型处理如果一个 Java 方法返回 void，那么从 Kotlin 调用时中返回 Unit。 万一有人使用其返回值，它将由 Kotlin 编译器在调用处赋值， 因为该值本身是预先知道的（是 Unit）。特殊关键字一些 Kotlin 关键字在 Java 中是有效标识符：in、 object、 is 等等。 如果一个 Java 库使用了 Kotlin 关键字作为方法，你仍然可以通过反引号（`）字符转义它来调用该方法foo.`is`(bar)平台类型空安全Java 中的任何引用都可能是 null，这使得 Kotlin 对来自 Java 的对象要求严格空安全是不现实的。 Java 声明的类型在 Kotlin 中会被特别对待并称为平台类型。对这种类型的空检查会放宽， 因此它们的安全保证与在 Java 中相同。T! 表示“T 或者 T?”(Mutable)Collection! 表示“可以可变或不可变、可空或不可空的 T 的 Java 集合”Array<(out) T>! 表示“可空或者不可空的 T（或 T 的子类型）的 Java 数组”强烈建议在Java使用@NotNull或@Nullable注解，以使Kotlin能明确知道空安全性。Kotlin支持多种框架下空安全性注解。JetBrains（org.jetbrains.annotations 包中的 @Nullable 和 @NotNull）Eclipse（org.eclipse.jdt.annotation）Android（com.android.annotations 和 android.support.annotations）FindBugs（edu.umd.cs.findbugs.annotations）Lombok（lombok.NonNull）Java调KotlinGetter 和 SetterKotlin属性的getter方法会通过名称加get前缀得到，setter方法会通过名称加set前缀得到。一个例外是如果属性的名称以is开头，则getter的名称与属性相同，setter的名称通过将is替换成get得到，并且不仅限于Boolean类型包级函数在org.foo.bar包内的Example.kt文件中声明的所有的函数和属性，包括扩展函数，都编译成一个名为org.foo.bar.ExampleKt的Java类的静态方法。 可以使用@file:JvmName注解修改生成的Java类的类名，若期望将多个kt文件合并生成一个Java类，可以在相关文件中使用@JvmMultifileClass注解。实例字段如果需要在Java中将Kotlin属性作为字段暴露，那就需要使用@JvmField注解对其标注，该字段将具有与底层属性相同的可见性。静态字段在命名对象或伴生对象中声明的Kotlin属性，会在该命名对象或包含伴生对象的类中具有静态幕后字段。通常这些字段是私有的，但可以通过@JvmField注解、lateinit修饰符和const修饰符将它暴露出来。静态方法在命名对象或伴生对象中声明的Kotlin方法，会在相应的对象中生成实例方法。如果使用@JvmStatic注解，编译器既会在相应对象的类中生成静态方法，也会在对象自身中生成实例方法。可见性Kotlin的可见性以下列方式映射到Java：private成员编译成private成员private的顶层声明编译成包级局部声明protected保持protected（注意 Java 允许访问同一个包中其他类的受保护成员， 而Kotlin不能，所以 Java 类会访问更广泛的代码internal声明会成为Java中的public。internal类的成员会通过名字修饰，使其更难以在Java中意外使用到，并且根据Kotlin规则使其允许重载相同签名的成员而互不可见public保持public重载通常有默认参数值的Kotlin函数在Java中只会有一个所有参数都存在的完整参数签名的方法可见，如果希望向Java调用者暴露多个重载，可以使用@JvmOverloads注解。受检异常Kotlin没有受检异常，如果我们想要在Java中捕捉这个异常，要在Kotlin中使用@Throws注解。
OID对象标识符Object Identifier就是通信中对“对象”的唯一编号。OID广泛使用于SNMP、PKI、LDAP等系统中。按照ISO/IEC9834-1(GB/T17969.1)标准的定义，“对象”是指“通信和信息处理世界中的任何事物，它是可标识(可以命名)的，同时它可被注册”。OID是与对象相关联的用来无歧义地标识对象的全局唯一的值，可保证对象在通信权威信息处理中正确地定位和管理。组织形式与域名结构层级关系类似，OID编码也是一种树状结构，并且没有层级数量的限制。OID名称包括数字和字符串两种形式：数字名称形式下，每个层次的值是一个大于0小于16000000的正整数，不同层次之间用点号“.”分隔；字符串名称形式下，每个层次的值是一个不少于1个字符并且不大于100个字符的可变长度字符串，同时该值在注册机构范围内是唯一的，不同层次之间用点号“/”分隔。每一个OID必须具有数字名称，而字符串名称则是可有可无的。同样与域名的管理方式类似，国家(地区)级OID由各国(地区)相应机构自行管理，例如我国就自行管理了1.2.156和2.16.156分支等，其余OID则由互联网数字分配机构(IANA)统一管理，若国家(地区)不具备管理的能力则也由IANA代管。若有注册新OID的需求，可以访问[http://www.china-oid.org.cn/]()以了解更多信息。示例说明由于OID包含条目众多，并且随时都有可能新增，因此若想获得最新信息可以查阅[http://oid-info.com]()。以下只摘录了极小一部分作演示用：. OID ├─ 0 (ITU-T) ├─ 1 (ISO) │ ├─ 0 (ISO and IEC standard) │ ├─ 1 (Registration Authorities) │ ├─ 2 (Member-Body) │ │ ├─ 156 (cn, China) │ │ │ ├─ 1 (cesi, China Electronics Standardization Institute) │ │ │ └─ 2 (cn-ora, China "OID Registration Authority") │ │ └─ 344 (hk, Hong Kong) │ │ └─ 840 (us, United States) │ └─ 3 (Identified-Organization) │ └─ 6 (dod, Department of Defense) │ └─ 1(internet) │ ├─ 2 (mgmt, Management) │ │ └─ 1 (mib-2, Management Information Bases Version 2) │ │ ├─ 1 (system) │ │ │ ├─ 1 (sysDescr) │ │ │ └─ 3 (sysUpTime) │ │ │ └─ 0 (sysUpTimeInstance) │ │ └─ 2 (pib, Policy Information Base) │ └─ 4 (private, Private projects) │ └─ 1 (enterprise) │ └─ 1466 (LDAPv3 OIDs) │ └─ 115 (syntax definitions of LDAPv3 schema framework) │ └─ 121 (attribute syntax definitions of LDAPv3) │ └─ 1 (attribute type syntax definitions of LDAPv3) │ ├─ 7 (boolean) │ └─ 12 (utf-8 string) └─ 2(joint-ISO-ITU-T) └─ 5(ds, Directory Services) ├─ 4(attributeType) │ ├─ 0(objectClass) │ └─ 4(commonName) └─ 29(certificateExtension) └─ 17(subjectAltName)查询以上OID树可以得到如下定义：OID(数字名)OID(字符串名)含义1.2.156.1ISO/Member-Body/CN/1中国电子技术标准化研究所1.2.156.2ISO/Member-Body/CN/2中国OID注册中心1.3.6.1.2.1.1.1ISO/Identified-Organization/6/1/2/1/1/1SNMP系统基本信息1.3.6.1.2.1.1.3.0ISO/Identified-Organization/6/1/2/1/1/3/0SNMP系统开机时间1.3.6.1.4.1.1466.115.121.1.7ISO/Identified-Organization/6/1/4/1/1466/115/121/1/7LDAPv3 布尔类型2.5.4.3joint-ISO-ITU-T/5/4/3目录服务通用名称2.5.29.17joint-ISO-ITU-T/5/29/17SSL证书主题备用名称
IP地址Internet Protocol Address是指互联网协议地址，又译为网际协议地址。IP地址是IP协议提供的一种统一的地址格式，它为互联网上的每一个网络和每一台主机分配一个逻辑地址，以此来屏蔽物理地址的差异。IP地址就像是我们的家庭住址一样，如果你要写信给一个人，你就要知道他（她）的地址，这样邮递员才能把信送到。计算机发送信息就好比是邮递员，它必须知道唯一的“家庭地址”才能不至于把信送错人家。只不过我们的地址是用文字来表示的，计算机的地址用二进制数字表示。IPv4地址IPv4地址由四个字节即32位组成，但实际中一般书写成点分十进制格式，即一个字节为一组，每组有效范围0~255，组间用.号进行连接。一般情况下IPv4地址首字节不能为0，即合法有效的最小IPv4地址是1.0.0.0。IPv4地址包含网络号和主机号两个部分，网络号表示其属于哪一个具体网络，主机号表示其属于该网络中的哪一台主机。在同一个硬件能够互联互通的网络环境下，网络号相同时一般可以认为属于同一个网络。子网掩码仅单纯给定一个IPv4地址时，是无法得知网络号或主机号的。如果要知道网络号或主机号，必须要结合子网掩码运算才行。子网掩码通常有两种常见表示方法：一是网络号在IP地址中所占的位数，例如192.161.147.73/12；二是将网络号所在位全部置为1，主机号所在位全部置为0，并转换点分十进制表示，例如192.161.147.73/255.240.0.0。二者含义完全一样，都表如下的网络号和主机号：$$ \begin{matrix} \underbrace{ 11000000.1010 } \\ 网络号 \end{matrix} \begin{matrix} \underbrace{ 0001.10010011.01001001 } \\ 主机号 \end{matrix} $$其网络号部分为11000000.1010(即192.160.0.0)，主机号部分为0001.10010011.01001001(即0.1.147.73)。注意： ①主机号全为0时有特殊用途，用于表示该网络的完整网络号，不能分配给主机使用。 ②主机号全为1时有特殊用途，用于表示该网络的广播地址，不能分配给主机使用。 ③网络号不能全为0，因为此时完整网络号将变成0.0.0.0，该地址有其它特殊用途。 地址分类地址类别地址范围二进制表示默认掩码A类1.0.0.0 到 127.255.255.2550xxxxxxxx . z . z . z255.0.0.0(8)B类128.0.0.0 到 191.255.255.25510xxxxxxx . z . z . z255.255.0.0(16)C类192.0.0.0 到 223.255.255.255110xxxxxx . z . z . z255.255.255.0(24)D类224.0.0.0 到 239.255.255.2551110xxxxx . z . z . z E类240.0.0.0 到 255.255.255.2551111xxxxx . z . z . z A类地址网络号部分为8位，最高位固定为0，所以网络号有效范围是1~127，每个网络可以有16777214个主机号(2^24^-2)。B类地址网络号部分为16位，最高两位固定为10，所以网络号有效范围是128~191，每个网络可以有65534个主机号(2^16^-2)。C类地址网络号部分为24位，最高三位固定为110，所以网络号有效范围是192~223，每个网络可以有254个主机号(2^8^-2)。D类地址最高四位固定为1110，此类地址不区分网络号和主机号，只在组播中使用。例如224.0.0.2表示所有路由的组播地址。E类地址最高四位固定为1111，此类地址不区分网络号和主机号，仅用于实验用途。此五类地址划分未将特殊IPv4地址/地址段去除，使用时要注意不要轻易使用这些特殊的地址/地址段。特殊地址0.0.0.0 此地址通常用于表示一个无效的、未知的或者不可用的目标； 在服务器上可用于表示本机上的所有IPv4地址，通常用于简化服务器拥有多个IPv4时的配置； 在路由中可用于表示默认路由，即当路由表中没有找到匹配的路由的时候所对应的路由。100.64.0.0/10 用于在电信级NAT环境中服务提供商与其用户通信。127.0.0.0/8 此段地址全部表示回环地址，所有发往该地址段的数据包都应当被送回。但在实际中除127.0.0.1外，发往本段内其它地址的数据可能会被丢弃。127.0.0.1/24 回环地址中的一个，其特殊之处在于，所有网络设备一定在此地址上遵循了“回环地址”的约定，即发往该地址的数据会被立即发回它自身。10.0.0.0/8 A类地址中的私有地址，只能出现在局域网中，不能出现在互联网中，只拥有一个网络号，可容纳16777214个主机。172.16.0.0/16 B类地址中的私有地址，只能出现在局域网中，不能出现在互联网中，拥有16个网络号，每个网络可容纳65534个主机。192.168.0.0/24 C类地址中的私有地址，只能出现在局域网中，不能出现在互联网中，拥有256个网络号，每个网络可容纳254个主机，此类地址在家庭网络中最为常见。169.254.0.0/16 本地链路地址，通常用于主机没能由DHCP分配到IP地址时的自动分配。255.255.255.255/32 广播地址，可指代同一广播域内的所有主机。192.0.0.0/24 用于IANA的IPv4特殊用途地址表。192.0.2.0/24 分配为用于文档和示例中的“TEST-NET”。198.51.100.0/24 分配为用于文档和示例中的“TEST-NET-2”。203.0.113.0/24 分配为用于文档和示例中的“TEST-NET-3”。233.252.0.0/24 分配为用于文档和示例中的“MCAST-TEST-NET”。192.88.99.0/24 用于6to4任播中继。198.18.0.0/15 用于测试两个不同的子网的网间通信。IPv6地址IPv4地址由16个字节即128位组成，但实际中一般书写成冒分十六进制格式，即两个字节为一组，每组有效范围0000~FFFF，组间用:号进行连接。在这种表示法中，每一组中的前导0是可以省略的，并且连续多组的0可以压缩为::，但为了保证地址解析的唯一性::只允许出现一次。例如2001:0000:0000:0000:0008:417A:0000:0000，即能简写为2001::8:417A:0:0，也能简写为2001:0:0:0:8:417A::，但不能简写为2001::8:417A::。子网前缀IPv6的子网前缀与IPv4的子网掩码功能基本相同，都是为了区分地址中哪部分是网络号哪部分是主机号。但与IPv4不同的是由于IPv6地址要长很多，所以IPv6的子网前缀只有一种表示方法，即网络号在IPv6地址中所占的位数。由于IPv6自动配置需要使用64位主机号，因此强烈建议子网前缀最大不要超过64，以便能够为主机号留下足够的长度。另外由于IPv6地址的前48位用于路由前缀标识(该标识由IANA负责分配和管理)，因此除了运营商级别的骨干网络外，普通网络中的子网前缀最小不应小于48。例如2409:8A00:606E:D390:1CC6:977C:59DB:30F8/64，网络号和主机号如下：$$ \begin{matrix} \underbrace{ 2409:8A00:606E } \\ 路由前缀 \end{matrix} \begin{matrix} \underbrace{ :1CC6 } \\ 子网号 \end{matrix} \begin{matrix} \underbrace{ :1CC6:977C:59DB:30F8 } \\ 主机号 \end{matrix} $$其中路由前缀部分还可以继续细化，以上地址前缀部分查询IANA后细分如下：$$ \begin{matrix} \underbrace{ 001 } \\ 类型 \end{matrix} \begin{matrix} \underbrace{ 00100\ 00001001:1000 } \\ 一级运营商 \end{matrix} \begin{matrix} \underbrace{ 1010\ 00000000 } \\ 二级运营商 \end{matrix} \begin{matrix} \underbrace{ :01100000\ 01101110 } \\ 三级子网号 \end{matrix} $$2000::/3前缀表示这是一个全局路由单播地址2409:8000::/20前缀表示这是一个属于中国移动的全局单播地址 2409:8a00::/32前缀表示这是一个属于中国移动(北京)的全局单播地址 2409:8A00:606E::/48可能是中国移动(北京)按照基站再细化后的前缀 地址分类0:0:0:0:0:0:0:0 (::) 和IPv4中的0.0.0.0作用基本相同。0:0:0:0:0:0:0:1 (::1) 和IPv4中的127.0.0.1作用完全相同。2000::/3 全局单播地址，相当于IPv4中公网IP的概念，即除特殊地址后剩下的A、B、C类地址。FE80::/10 链路本地单播地址，与IPv4地址中的特殊段169.254.0.0/16概念相似，但与10.0.0.0/8、172.16.0.0/16~172.32.0.0/16、192.168.0.0/24~192.168.255.0/24功能上更加接近。FF00::/8 组播地址，与IPv4中D类地址功能完全相同。