用了这么多年的泛型,你对它到底有多了解?

现代程序员写代码没有人敢说自己没用过泛型,这个泛型模板T可以被任何你想要的类型替代,确实很魔法很神奇,很多人也习以为常了,但就是这么有趣的泛型T底层到底是怎么帮你实现的,不知道有多少人清楚底层玩法,这篇我就试着来分享一下,不一定全对哈。。。

一:没有泛型前

现在的netcore 3.1和最新的.netframework8早已经没有当初那个被人诟病的ArrayList了,但很巧这玩意不得不说,因为它决定了C#团队痛改前非,抛弃过往重新上路,上一段ArrayList案例代码。

    public class ArrayList{private object[] items;private int index = 0;public ArrayList(){items = new object[10];}public void Add(object item){items[index++] = item;}}

上面这段代码,为了保证在Add中可以塞入各种类型 eg: int,double,class, 就想到了一个绝招用祖宗类object接收,这就引入了两大问题,装箱拆箱和类型安全。

1. 装箱拆箱

这个很好理解,因为你使用了祖宗类,所以当你 Add 的时候塞入的是值类型的话,自然就有装箱操作,比如下面代码:

            ArrayList arrayList = new ArrayList();arrayList.Add(3);

<1> 占用更大的空间

这个问题我准备用windbg看一下,相信大家知道一个int类型占用4个字节,那装箱到堆上是几个字节呢,好奇吧????。

原始代码和IL代码如下:

        public static void Main(string[] args){var num = 10;var obj = (object)num;Console.Read();}IL_0000: nopIL_0001: ldc.i4.s 10IL_0003: stloc.0IL_0004: ldloc.0IL_0005: box [mscorlib]System.Int32IL_000a: stloc.1IL_000b: call int32 [mscorlib]System.Console::Read()IL_0010: popIL_0011: ret

可以清楚的看到IL_0005 中有一个box指令,装箱没有问题,然后抓一下dump文件。

~0s -> !clrstack -l -> !do 0x0000018300002d48


0:000> ~0s
ntdll!ZwReadFile+0x14:
00007ff9`fc7baa64 c3              ret
0:000> !clrstack -l
OS Thread Id: 0xfc (0)Child SP               IP Call Site
0000002c397fedf0 00007ff985c808f3 ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 28]LOCALS:0x0000002c397fee2c = 0x000000000000000a0x0000002c397fee20 = 0x0000018300002d480000002c397ff038 00007ff9e51b6c93 [GCFrame: 0000002c397ff038]
0:000> !do 0x0000018300002d48
Name:        System.Int32
MethodTable: 00007ff9e33285a0
EEClass:     00007ff9e34958a8
Size:        24(0x18) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:MT    Field   Offset                 Type VT     Attr            Value Name
00007ff9e33285a0  40005a0        8         System.Int32  1 instance               10 m_value

倒数第5行 Size: 24(0x18) bytes, 可以清楚的看到是24字节。为什么是24个字节,8(同步块指针) + 8(方法表指针) + 4(对象大小)=20,但因为是x64位,内存是按8对齐,也就是要按8的倍数计算,所以占用是 8+8+8 =24 字节,原来只有4字节的大小因为装箱已被爆到24字节,如果是10000个值类型的装箱那空间占用是不是挺可怕的?

<2> 栈到堆的装箱搬运到运输到售后到无害化处理都需要付出重大的人力和机器成本

2. 类型不安全

很简单,因为是祖宗类型object,所以无法避免程序员使用乱七八糟的类型,当然这可能是无意的,但是编译器确无法规避,代码如下:

ArrayList arrayList = new ArrayList();arrayList.Add(3);arrayList.Add(new Action<int>((num) => { }));arrayList.Add(new object());

面对这两大尴尬的问题,C#团队决定重新设计一个类型,实现一定终身,这就有了泛型。

二:泛型的出现

1. 救世主

首先可以明确的说,泛型就是为了解决这两个问题而生的,你可以在底层提供的List<T>中使用List<int>,List<double>。。。等等你看得上的类型,而这种技术的底层实现原理才是本篇关注的重点。

public static void Main(string[] args){List<double> list1 = new List<double>();List<string> list3 = new List<string>();...}

三:泛型原理探究

这个问题的探索其实就是 List<T> -> List<int>在何处实现了 T -> int 的替换,反观java,它的泛型实现其实在底层还是用object来替换的,C#肯定不是这么做的,不然也没这篇文章啦,要知道在哪个阶段被替换了,你起码要知道C#代码编译的几个阶段,为了理解方便,我画一张图吧。

流程大家也看到了,要么在MSIL中被替换,要么在JIT编译中被替换。。。

public static void Main(string[] args){List<double> list1 = new List<double>();List<int> list2 = new List<int>();List<string> list3 = new List<string>();List<int[]> list4 = new List<int[]>();Console.ReadLine();}

1. 在第一阶段探究

因为第一阶段是MSIL代码,所以用ILSpy看一下中间代码即可。

        IL_0000: nopIL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<float64>::.ctor()IL_0006: stloc.0IL_0007: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()IL_000c: stloc.1IL_000d: newobj instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor()IL_0012: stloc.2IL_0013: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32[]>::.ctor()IL_0018: stloc.3IL_0019: call string [mscorlib]System.Console::ReadLine()IL_001e: popIL_001f: ret.class public auto ansi serializable beforefieldinit System.Collections.Generic.List`1<T>extends System.Objectimplements class System.Collections.Generic.IList`1<!T>,class System.Collections.Generic.ICollection`1<!T>,class System.Collections.Generic.IEnumerable`1<!T>,System.Collections.IEnumerable,System.Collections.IList,System.Collections.ICollection,class System.Collections.Generic.IReadOnlyList`1<!T>,class System.Collections.Generic.IReadOnlyCollection`1<!T>

从上面的IL代码中可以看到,最终的类定义还是 System.Collections.Generic.List1\<T>,说明在中间代码阶段还是没有实现 T -> int 的替换。

2. 在第二阶段探究

想看到JIT编译后的代码,这个说难也不难,其实每个对象头上都有一个方法表指针,而这个指针指向的就是方法表,方法表中有该类型的所有最终生成方法,如果不好理解,我就画个图。

!dumpheap -stat 寻找托管堆上的四个List对象。


0:000> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
00007ff9e3314320        1           32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
00007ff9e339b4b8        1           40 System.Collections.Generic.List`1[[System.Double, mscorlib]]
00007ff9e333a068        1           40 System.Collections.Generic.List`1[[System.Int32, mscorlib]]
00007ff9e3330d58        1           40 System.Collections.Generic.List`1[[System.String, mscorlib]]
00007ff9e3314a58        1           40 System.IO.Stream+NullStream
00007ff9e3314510        1           40 Microsoft.Win32.Win32Native+InputRecord
00007ff9e3314218        1           40 System.Text.InternalEncoderBestFitFallback
00007ff985b442c0        1           40 System.Collections.Generic.List`1[[System.Int32[], mscorlib]]
00007ff9e338fd28        1           48 System.Text.DBCSCodePageEncoding+DBCSDecoder
00007ff9e3325ef0        1           48 System.SharedStatics

可以看到从托管堆中找到了4个list对象,现在我就挑一个最简单的 System.Collections.Generic.List1[[System.Int32, mscorlib]] ,前面的 00007ff9e333a068 就是方法表地址。

!dumpmt -md 00007ff9e333a068


0:000> !dumpmt -md 00007ff9e333a068
EEClass:         00007ff9e349b008
Module:          00007ff9e3301000
Name:            System.Collections.Generic.List`1[[System.Int32, mscorlib]]
mdToken:         00000000020004af
File:            C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
BaseSize:        0x28
ComponentSize:   0x0
Slots in VTable: 77
Number of IFaces in IFaceMap: 8
--------------------------------------
MethodDesc TableEntry       MethodDesc    JIT Name
00007ff9e3882450 00007ff9e3308de8 PreJIT System.Object.ToString()
00007ff9e389cc60 00007ff9e34cb9b0 PreJIT System.Object.Equals(System.Object)
00007ff9e3882090 00007ff9e34cb9d8 PreJIT System.Object.GetHashCode()
00007ff9e387f420 00007ff9e34cb9e0 PreJIT System.Object.Finalize()
00007ff9e38a3650 00007ff9e34dc6e8 PreJIT System.Collections.Generic.List`1[[System.Int32, mscorlib]].Add(Int32)
00007ff9e4202dc0 00007ff9e34dc7f8 PreJIT System.Collections.Generic.List`1[[System.Int32, mscorlib]].Insert(Int32, Int32)

上面方法表中的方法过多,我做了一下删减,可以清楚的看到,此时Add方法已经接受(Int32)类型的数据了,说明在JIT编译之后,终于实现了 T -> int 的替换,然后再把 List<double> 打出来看一下。


0:000> !dumpmt -md 00007ff9e339b4b8
MethodDesc TableEntry       MethodDesc    JIT Name
00007ff9e3882450 00007ff9e3308de8 PreJIT System.Object.ToString()
00007ff9e389cc60 00007ff9e34cb9b0 PreJIT System.Object.Equals(System.Object)
00007ff9e3882090 00007ff9e34cb9d8 PreJIT System.Object.GetHashCode()
00007ff9e387f420 00007ff9e34cb9e0 PreJIT System.Object.Finalize()
00007ff9e4428730 00007ff9e34e4170 PreJIT System.Collections.Generic.List`1[[System.Double, mscorlib]].Add(Double)
00007ff9e3867a00 00007ff9e34e4280 PreJIT System.Collections.Generic.List`1[[System.Double, mscorlib]].Insert(Int32, Double)

上面看的都是值类型,接下来再看一下如果 T 是引用类型会是怎么样呢?


0:000> !dumpmt -md 00007ff9e3330d58
MethodDesc TableEntry       MethodDesc    JIT Name
00007ff9e3890060 00007ff9e34eb058 PreJIT System.Collections.Generic.List`1[[System.__Canon, mscorlib]].Add(System.__Canon)0:000> !dumpmt -md 00007ff985b442c0
MethodDesc TableEntry       MethodDesc    JIT Name
00007ff9e3890060 00007ff9e34eb058 PreJIT System.Collections.Generic.List`1[[System.__Canon, mscorlib]].Add(System.__Canon)

可以看到当是List<int[]> 和 List<string> 的时候,JIT使用了 System.__Canon 这么一个类型作为替代,有可能人家是摄影爱好者吧,为什么用__Canon替代引用类型,这是因为它想让能共享代码区域的方法都共享来节省空间和内存吧,不信的话可以看看它们的Entry列都是同一个内存地址:00007ff9e3890060, 打印出来就是这么一段汇编。


0:000> !u 00007ff9e3890060
preJIT generated code
System.Collections.Generic.List`1[[System.__Canon, mscorlib]].Add(System.__Canon)
Begin 00007ff9e3890060, size 4a
>>> 00007ff9`e3890060 57              push    rdi
00007ff9`e3890061 56              push    rsi
00007ff9`e3890062 4883ec28        sub     rsp,28h
00007ff9`e3890066 488bf1          mov     rsi,rcx
00007ff9`e3890069 488bfa          mov     rdi,rdx
00007ff9`e389006c 8b4e18          mov     ecx,dword ptr [rsi+18h]
00007ff9`e389006f 488b5608        mov     rdx,qword ptr [rsi+8]
00007ff9`e3890073 3b4a08          cmp     ecx,dword ptr [rdx+8]
00007ff9`e3890076 7422            je      mscorlib_ni+0x59009a (00007ff9`e389009a)
00007ff9`e3890078 488b4e08        mov     rcx,qword ptr [rsi+8]
00007ff9`e389007c 8b5618          mov     edx,dword ptr [rsi+18h]
00007ff9`e389007f 448d4201        lea     r8d,[rdx+1]
00007ff9`e3890083 44894618        mov     dword ptr [rsi+18h],r8d
00007ff9`e3890087 4c8bc7          mov     r8,rdi
00007ff9`e389008a ff152088faff    call    qword ptr [mscorlib_ni+0x5388b0 (00007ff9`e38388b0)] (JitHelp: CORINFO_HELP_ARRADDR_ST)
00007ff9`e3890090 ff461c          inc     dword ptr [rsi+1Ch]
00007ff9`e3890093 4883c428        add     rsp,28h
00007ff9`e3890097 5e              pop     rsi
00007ff9`e3890098 5f              pop     rdi
00007ff9`e3890099 c3              ret
00007ff9`e389009a 8b5618          mov     edx,dword ptr [rsi+18h]
00007ff9`e389009d ffc2            inc     edx
00007ff9`e389009f 488bce          mov     rcx,rsi
00007ff9`e38900a2 90              nop
00007ff9`e38900a3 e8c877feff      call    mscorlib_ni+0x577870 (00007ff9`e3877870) (System.Collections.Generic.List`1[[System.__Canon, mscorlib]].EnsureCapacity(Int32), mdToken: 00000000060039e5)
00007ff9`e38900a8 ebce            jmp     mscorlib_ni+0x590078 (00007ff9`e3890078)

然后再回过头看List<int> 和 List<double> ,从Entry列中看确实不是一个地址,说明List<int> 和 List<double> 是两个完全不一样的Add方法,看得懂汇编的可以自己看一下哈。。。

MethodDesc TableEntry       MethodDesc    JIT Name
00007ff9e38a3650 00007ff9e34dc6e8 PreJIT System.Collections.Generic.List`1[[System.Int32, mscorlib]].Add(Int32)
00007ff9e4428730 00007ff9e34e4170 PreJIT System.Collections.Generic.List`1[[System.Double, mscorlib]].Add(Double)0:000> !u 00007ff9e38a3650
preJIT generated code
System.Collections.Generic.List`1[[System.Int32, mscorlib]].Add(Int32)
Begin 00007ff9e38a3650, size 50
>>> 00007ff9`e38a3650 57              push    rdi
00007ff9`e38a3651 56              push    rsi
00007ff9`e38a3652 4883ec28        sub     rsp,28h
00007ff9`e38a3656 488bf1          mov     rsi,rcx
00007ff9`e38a3659 8bfa            mov     edi,edx
00007ff9`e38a365b 8b5618          mov     edx,dword ptr [rsi+18h]
00007ff9`e38a365e 488b4e08        mov     rcx,qword ptr [rsi+8]
00007ff9`e38a3662 3b5108          cmp     edx,dword ptr [rcx+8]
00007ff9`e38a3665 7423            je      mscorlib_ni+0x5a368a (00007ff9`e38a368a)
00007ff9`e38a3667 488b5608        mov     rdx,qword ptr [rsi+8]
00007ff9`e38a366b 8b4e18          mov     ecx,dword ptr [rsi+18h]
00007ff9`e38a366e 8d4101          lea     eax,[rcx+1]
00007ff9`e38a3671 894618          mov     dword ptr [rsi+18h],eax
00007ff9`e38a3674 3b4a08          cmp     ecx,dword ptr [rdx+8]
00007ff9`e38a3677 7321            jae     mscorlib_ni+0x5a369a (00007ff9`e38a369a)
00007ff9`e38a3679 4863c9          movsxd  rcx,ecx
00007ff9`e38a367c 897c8a10        mov     dword ptr [rdx+rcx*4+10h],edi
00007ff9`e38a3680 ff461c          inc     dword ptr [rsi+1Ch]
00007ff9`e38a3683 4883c428        add     rsp,28h
00007ff9`e38a3687 5e              pop     rsi
00007ff9`e38a3688 5f              pop     rdi
00007ff9`e38a3689 c3              ret
00007ff9`e38a368a 8b5618          mov     edx,dword ptr [rsi+18h]
00007ff9`e38a368d ffc2            inc     edx
00007ff9`e38a368f 488bce          mov     rcx,rsi
00007ff9`e38a3692 90              nop
00007ff9`e38a3693 e8a8e60700      call    mscorlib_ni+0x621d40 (00007ff9`e3921d40) (System.Collections.Generic.List`1[[System.Int32, mscorlib]].EnsureCapacity(Int32), mdToken: 00000000060039e5)
00007ff9`e38a3698 ebcd            jmp     mscorlib_ni+0x5a3667 (00007ff9`e38a3667)
00007ff9`e38a369a e8bf60f9ff      call    mscorlib_ni+0x53975e (00007ff9`e383975e) (mscorlib_ni)
00007ff9`e38a369f cc              int     30:000> !u 00007ff9e4428730
preJIT generated code
System.Collections.Generic.List`1[[System.Double, mscorlib]].Add(Double)
Begin 00007ff9e4428730, size 5a
>>> 00007ff9`e4428730 56              push    rsi
00007ff9`e4428731 4883ec20        sub     rsp,20h
00007ff9`e4428735 488bf1          mov     rsi,rcx
00007ff9`e4428738 8b5618          mov     edx,dword ptr [rsi+18h]
00007ff9`e442873b 488b4e08        mov     rcx,qword ptr [rsi+8]
00007ff9`e442873f 3b5108          cmp     edx,dword ptr [rcx+8]
00007ff9`e4428742 7424            je      mscorlib_ni+0x1128768 (00007ff9`e4428768)
00007ff9`e4428744 488b5608        mov     rdx,qword ptr [rsi+8]
00007ff9`e4428748 8b4e18          mov     ecx,dword ptr [rsi+18h]
00007ff9`e442874b 8d4101          lea     eax,[rcx+1]
00007ff9`e442874e 894618          mov     dword ptr [rsi+18h],eax
00007ff9`e4428751 3b4a08          cmp     ecx,dword ptr [rdx+8]
00007ff9`e4428754 732e            jae     mscorlib_ni+0x1128784 (00007ff9`e4428784)
00007ff9`e4428756 4863c9          movsxd  rcx,ecx
00007ff9`e4428759 f20f114cca10    movsd   mmword ptr [rdx+rcx*8+10h],xmm1
00007ff9`e442875f ff461c          inc     dword ptr [rsi+1Ch]
00007ff9`e4428762 4883c420        add     rsp,20h
00007ff9`e4428766 5e              pop     rsi
00007ff9`e4428767 c3              ret
00007ff9`e4428768 f20f114c2438    movsd   mmword ptr [rsp+38h],xmm1
00007ff9`e442876e 8b5618          mov     edx,dword ptr [rsi+18h]
00007ff9`e4428771 ffc2            inc     edx
00007ff9`e4428773 488bce          mov     rcx,rsi
00007ff9`e4428776 90              nop
00007ff9`e4428777 e854fbffff      call    mscorlib_ni+0x11282d0 (00007ff9`e44282d0) (System.Collections.Generic.List`1[[System.Double, mscorlib]].EnsureCapacity(Int32), mdToken: 00000000060039e5)
00007ff9`e442877c f20f104c2438    movsd   xmm1,mmword ptr [rsp+38h]
00007ff9`e4428782 ebc0            jmp     mscorlib_ni+0x1128744 (00007ff9`e4428744)
00007ff9`e4428784 e8d50f41ff      call    mscorlib_ni+0x53975e (00007ff9`e383975e) (mscorlib_ni)
00007ff9`e4428789 cc              int     3

可能你有点蒙,我画一张图吧。

四:总结

泛型T真正的被代替是在 JIT编译时才实现的,四个List<T> 会生成四个具有相应具体类型的类对象,所以就不存在拆箱和装箱的问题,而类型的限定visualstudio编译器工具提前就帮我们约束好啦。

夜深了,先休息啦!希望本篇对你有帮助。

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

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

相关文章

数据结构与算法--两个链表中第一个公共节点

链表中第一个公共节点 公节点定义&#xff1a;同一个节点在两个链表中&#xff0c;并不是节点值相同题目&#xff1a;输入两个节点&#xff0c;找出他们的第一个公共节点&#xff0c;节点定义如需 /*** 链表元素节点** author liaojiamin* Date:Created in 12:17 2021/3/5*/ …

ASP.NET Core技术研究-全面认识Web服务器Kestrel

因为IIS不支持跨平台的原因&#xff0c;我们在升级到ASP.NET Core后&#xff0c;会接触到一个新的Web服务器Kestrel。相信大家刚接触这个Kestrel时&#xff0c;会有各种各样的疑问。今天我们全面认识一下ASP.NET Core的默认Web服务器Kestrel。一、初识Kestrel首先&#xff0c;K…

数据结构与算法--二叉堆(最大堆,最小堆)实现及原理

二叉堆&#xff08;最大堆&#xff0c;最小堆&#xff09;实现及原理 二叉堆与二叉查找树一样&#xff0c;堆也有两个性质&#xff0c;即结构性质和堆性质。和AVL树一样&#xff0c;对堆的一次操作必须到堆的所有性质都被满足才能终止&#xff0c;也就是我们每次对堆的操作都必…

Blazor WebAssembly 3.2.0 已在塔架就位 将发射新一代前端SPA框架

最美人间四月天&#xff0c;春光不负赶路人。在充满无限希望的明媚春天里&#xff0c;一路风雨兼程的.NET团队正奋力实现新的突破。根据计划&#xff0c;新一代基于WebAssembly 技术研发的前端SPA框架Blazor 将于5月19日在微软Build大会升空。目前&#xff0c;Blazor 的测试工作…

如何将 Azure 上的 Ubuntu 19.10 服务器升级到 20.04

点击上方蓝字关注“汪宇杰博客”导语Ubuntu 20.04 LTS 已经正式推出了。作为一名软粉&#xff0c;看到新版鲍叔毒瘤&#xff0c;我当然是激动万分&#xff0c;抱着批判的态度&#xff0c;第一时间很不情愿的更新了我的服务器。4月23日发布的 Ubuntu 20.04 是个 LTS 版。其 Linu…

我想快速给WPF程序添加托盘菜单

我想...1 简单要求&#xff1a;使用开源控件库在XAML中声明托盘菜单&#xff0c;就像给控件添加ContextMenu一样封装了常用命令&#xff0c;比如&#xff1a;打开主窗体、退出应用程序等TerminalMACS我在TerminalMACS中添加了托盘菜单&#xff0c;最终实现的托盘菜单效果&#…

【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务

在我的上一篇文章《在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度》&#xff0c;我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计划运行后台任务。不幸的是&#xff0c;由于Quartz.NET API的工作方式&#xff0c;在Quartz作业中使用Scoped依…

mysql技术分享-- 视图是什么

视图 最近遇到mysql锁相关问题&#xff0c;在查阅资料时候&#xff0c;经常能看到在锁的解释中总有视图的概念出现&#xff0c;因此我觉得有必要先去了解一下视图相关的详细信息&#xff0c;有助于我对mysql锁相关的理解。视图&#xff08;View&#xff09;是一个命名的虚拟表…

在 Visual Studio 2019 中为 .NET Core WinForm App 启用窗体设计器

当我们在使用 Visual Studio 2019 非预览版本开发 Windows Forms App (.NET Core) 应用程序时是不能使用窗体设计器的。即使在窗体文件上右击选择“显示设计器”菜单&#xff0c;仍旧只能看到代码&#xff0c;无法打开窗体设计器。根据微软开发者博客的描述&#xff0c;我们可以…

mysql技术分享--表分区实现

分区表 分区概念 分区功能并不是在存储引擎层完成的&#xff0c;因此不止有InnoDB存储引擎支持分区&#xff0c;常见的存储引擎MyISAM&#xff0c;NDB等都支持。但是也并不是所有存储引擎都支持&#xff0c;比如CSV&#xff0c;FEDERATED&#xff0c;MERGE等就不支持&#xf…

视频号,张小龙的星辰大海

阅读本文大概需要 4.1分钟。前段时间&#xff0c;微信开通了视频号。本想第一时间写一篇文章&#xff0c;分析下视频号。发现理解还不深入&#xff0c;于是这段时间一直在思考视频号对微信的战略意义和它的前景。思考了接近1个月&#xff0c;想明白了一些事情&#xff0c;有时候…

[Java基础]List集合子类特点

ArrayList练习: package test19;import java.util.ArrayList; import java.util.Iterator;public class ArrayListDemo {public static void main(String[] args){ArrayList<String> array new ArrayList<String>();array.add("hello");array.add(&quo…

[半翻] 设计面向DDD的微服务

这篇文章行文结构对照微软博客&#xff0c; 结合本人意译和多年实践的回顾性思考形成此次读书笔记。Domian-driven Design领域-驱动-设计&#xff08;DDD&#xff09;提倡基于(用例相关的现实业务)进行建模。1. DDD的视角DDD将现实问题视为领域;DDD将独立的问题描述为有界限的上…

【值得收藏】首次披露Facebook移动端软件的持续部署 | IDCF

&#xff08;图片来源于网络&#xff09;摘要持续部署是指软件更新一旦准备好就立即发布的实践方法&#xff0c;在业界越来越多地被采用。移动端软件的更新频率普遍落后于基于云端的服务&#xff0c;原因有很多。比如&#xff0c;移动端软件只能定期发布版本&#xff1b;用户可…