Block 分析
来源:互联网 发布:公需课挂机软件2017 编辑:程序博客网 时间:2024/06/08 07:05
摘录:http://www.cnblogs.com/goahead-yingjun/articles/4478425.html
参考:http://www.cocoachina.com/bbs/read.php?tid=152222
前言
Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是一回事。需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。
可以这样理解,Block其实包含两个部分内容
Block执行的代码,这是在编译的时候已经生成好的;
一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。
Block与函数另一个不同是,Block类似ObjC的对象,可以使用自动释放池管理内存(但Block并不完全等同于ObjC对象)
Block的类型与内存管理
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。
NSGlobalBlock:类似函数,位于text段(不在堆栈中);
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。
1、NSGlobalBlock
block在自己的环路(代码块)中不会抓取任何外部变量,它不需要在在运行的引用其他外包变量,便不会在运行的时候将block压入栈中,而会作为一个NSGlobalBlock编译。它既不是栈也不是堆,而是代码片段的一部分。所以它不管在什么环境下(MRC、ARC)都能正常运行。
如下,我们可以通过是否引用外部变量识别,未引用外部变量即为NSGlobalBlock。
void exampleA() { //create a NSGlobalBlock float (^sum)(float, float) = ^(float a, float b){ return a + b; }; NSLog(@"block is %@", sum); //block is <__NSGlobalBlock__: 0x47d0>}
2.NSStackBlock和NSMallocBlock
如果block有引用外部的局部变量,那么该block便会被压入调用它的函数的栈中,编译器会将其编译为NSStackBlock类型。当其调用它的函数结束时,栈会被清空,同时在栈中的block也会被清除,如果此时有其他函数调用此block便会野指针报错。要解决这个问题,需要手动对block进行copy,通过copy后,并设为自动释放,会将copy分配到堆中,并交由最近释放池负责释放,如:
[[stackBlock copy] autorelease]
但是,在ARC环境下,block会默认分配到堆中,作为一个自动释放的NSMallocBlock(即不受栈被清空的影响)。
代码如下:
typedef void (^dBlock)();dBlock exampleB_getBlock(){ char b = 'B'; void (^block)() = ^{ printf("%c\n", b); }; NSLog(@"block:%@",block); dBlock block_copy=[[block copy] autorelease]; NSLog(@"block_copy:%@",block_copy);// return block_copy; return block;}void exampleB() { dBlock block=exampleB_getBlock(); block();}
在MRC环境中,运行后,打印为
block:<__NSStackBlock__: 0xbffbff68>block_copy:<__NSMallocBlock__: 0x78e66430>
说明block经过copy后,被分配到了堆上。
当返回block时,因为是NSStackBlock,所以block();
野指针报错。
当返回block_copy时,因为是NSMallocBlock,运行正常。
如果在ARC环境下,block不经过copy,便默认是NSMallocBlock,直接返回block就可以了。
PS
NSMallocBlock只需要对NSStackBlock进行copy操作就可以获取,但是retain操作就不行,会在下面说明
Block的copy、retain、release操作
不同于NSObjec的copy、retain、release操作:
- Block_copy与copy等效,Block_release与release等效;
- 对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
- NSGlobalBlock:retain、copy、release操作都无效;
- NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry
addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock
copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy]
autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。 - NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
- 尽量不要对Block使用retain操作。
Block对外部变量的存取管理
如果block对外部变量做了引用,那他们之间会有什么影响呢?
基本数据类型 的引用
1、局部变量
void exampleC() { char b = 'B'; NSLog(@"%p,%c",&b,b); void (^block)() = ^{ NSLog(@"%c",b); }; block();//修改前 b='C'; NSLog(@"%p,%c",&b,b); block();//修改后}
打印:
0xbffa8f8b,B0xbffa8f6c,B0xbffa8f8b,C0xbffa8f6c,B
执行后block两次打印结果为两个B,说明,对于基本数据类型的局部变量,当block对其引用的时候,会创建下该变量的一份快照(新建一个同类型的变量,并保存相同值),变量后面值的改变不会影响快照的值,在创建block的时候本质上已经分为两个独立的变量了。
注意:局部变量(无__block修饰)的快照,是只读的,不能在block中修改其值,包括指针变量也不能修改其引用地址。
2.静态变量 全局变量
void exampleC() { static int num=1; NSLog(@"%p,%d",&num,num); void (^block)() = ^{ NSLog(@"%p,%d",&num,num); }; block();//修改前 num=2; NSLog(@"%p,%d",&num,num); block();//修改后}
执行后打印了:
0x647f0,10x647f0,10x647f0,20x647f0,2
变量地址都是一样,说明没有创建快照,而是直接引用,因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,所以获取到的是最新值。
3、__block修饰的变量
被__block修饰的变量称作Block变量。 Block变量等效于全局变量、或静态变量。当block位于栈区时,block变量使用的都是相同变量,而当block位于堆区时,block变量一样会创建快照,使用快照代替原变量,具体看下面会讲到。
OC对象 的引用
iOS中,oc对象本质上是一个指向结构体的指针变量。 block对oc对象的引用,其本质是对其指针地址的引用,因此,block引用时,实际上是对指针变量做了一份快照(新创建一个指针变量,指向相同的地址)。所以,获取到的oc对象取决于快照(指针变量)指向的值,如果其指向的值发生变化,block引用到的值自然也跟着变了。下面看例子:
void exampleC() { NSString *str=[NSString stringWithFormat:@"%@",@"str"]; NSLog(@"%p,%p,%@",&str,str,str); void (^block)() = ^{ NSLog(@"%p,%p,%@",&str,str,str); NSLog(@"-----------------"); }; block();//修改前 str=[NSString stringWithFormat:@"%@",@"newStr"]; NSLog(@"%p,%p,%@",&str,str,str); block();//修改后}
执行后,打印
0xbffd7f84,0x7b7942b0,str0xbffd7f74,0x7b7942b0,str-----------------0xbffd7f84,0x7b896950,newStr0xbffd7f74,0x7b7942b0,str-----------------
从结果看,创建的指针变量str与在block内引用的str快照的变量地址是不同的0xbffd7f84和0xbffd7f74,说明,在block在引用该对象的时候,创建了一个相同类型的oc对象快照(指针变量),保存相同的值,因为是指针,所以值为引用地址0x7b7942b0。当修改str时,其指针指向新对象地址0x7b896950,但快照依然指向了0x7b7942b0,因此还是打印出了str字符串。
如果你硬要问,oc对象的处理方式不是用指针变量来理解,那我就验证一下给你看:
void exampleC() { char b = 'B'; char *pb=&b; NSLog(@"%p,%p,%c",&pb,pb,*pb); void (^block)() = ^{ NSLog(@"%p,%p,%c",&pb,pb,*pb); NSLog(@"-----------------"); }; block();//修改前 *pb='C'; NSLog(@"%p,%p,%c",&pb,pb,*pb); block();//修改后}
打印结果:
0xbffe0f84,0xbffe0f8b,B0xbffe0f6c,0xbffe0f8b,B-----------------0xbffe0f84,0xbffe0f8b,C0xbffe0f6c,0xbffe0f8b,C-----------------
验证以上oc对象引用的说法。block的快照就是变量类型相同的是两个变量,保存相同的值,如果是oc对象就引用相同对象(地址)。
依托上面的论证,oc对象变化的时候,如果改变引用对象的地址,block返回到的就一直是最新的值。这种情况在使用oc可变对象的时候会出现,如NSMutableArray、NSMutableString等等。代码如下:
void exampleC() { NSMutableArray *list=[NSMutableArray arrayWithArray:@[@"a",@"b",@"c"]];// NSMutableString *mutStr=[NSMutableString stringWithFormat:@"abc"]; NSLog(@"%p,%p,%@",&list,list,list); void (^block)() = ^{ NSLog(@"%p,%p,%@",&list,list,list); NSLog(@"-----------------"); }; block();//修改前 //[mutStr appendString:@"123"]; [list removeObjectAtIndex:0]; NSLog(@"%p,%p,%@",&list,list,list); block();//修改后}
执行后结果为
0xbffcff7c,0x7b063c10,( a, b, c)0xbffcff6c,0x7b063c10,( a, b, c)-----------------0xbffcff7c,0x7b063c10,( b, c)0xbffcff6c,0x7b063c10,( b, c)-----------------
从打印结果可看出,虽然其快照与外部变量不同,但可变对象在修改后,其引用地址是不会改变,一直都是0x7b063c10,因此,block一直都能引用到最新的值。因为不会改变快照的引用地址,所以,是可以在block中修改可变oc对象的
Block 对 OC对象 的持有
block对于objc对象的内存管理较为复杂,当block被分配到堆区的时候,其对引用到的外部变量的持有也会发生变化,如果处理不好,就会造成循环引用等问题。 这里要分static静态变量、 global全局变量 、local局部变量、 block变量分析,还要区分非arc和arc环境的影响。
- MRC环境下
代码如下:
@interface ViewController : UIViewController{ NSObject *instanceObj;}@endNSString* __globalObj = nil;@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; [self exampleD];}- (void)exampleD{ static NSString *__staticObj; __globalObj = [[NSString alloc] initWithFormat:@"global"]; instanceObj = [[NSString alloc] initWithFormat:@"instance"]; __staticObj = [[NSString alloc] initWithFormat:@"static"]; NSString* localObj = [[NSString alloc] initWithFormat:@"local"]; __block NSString* blockObj =[[NSString alloc] initWithFormat:@"block"]; NSLog(@"-----------原变量-------------"); NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj); NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj); NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); NSLog(@"%p,%p,%@",&localObj,localObj, localObj); NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj); NSLog(@"---------------------------------"); NSLog(@"self:%d",[self retainCount]); dBlock aBlock = ^{ NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj); NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj); NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); NSLog(@"%p,%p,%@",&localObj,localObj, localObj); NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj); }; NSLog(@"----------被block引用后的变量---------"); aBlock(); NSLog(@"--------------------------------"); NSLog(@"self:%d",[self retainCount]); aBlock = [[aBlock copy] autorelease]; NSLog(@"self:%d",[self retainCount]); NSLog(@"-------block被分配堆区后引用的变量---------"); aBlock(); NSLog(@"--------------------------------"); NSLog(@"\n-------block被分配堆区后引用的变量计数-------------"); NSLog(@"globalObj:%d", [__globalObj retainCount]); NSLog(@"staticObj:%d", [__staticObj retainCount]); NSLog(@"instanceObj:%d", [instanceObj retainCount]); NSLog(@"localObj:%d", [localObj retainCount]); NSLog(@"blockObj:%d", [blockObj retainCount]);}@end
运行后,打印
-----------原变量-------------0x2f9d4,0x79066450,global0x2f9d8,0x79062eb0,static0x790e256c,0x79062e90,instance0xbffd2f94,0x790628f0,local0xbffd2f90,0x790e3d20,block---------------------------------self:25----------被block引用后的变量---------0x2f9d4,0x79066450,global0x2f9d8,0x79062eb0,static0x790e256c,0x79062e90,instance0xbffd2f60,0x790628f0,local0xbffd2f90,0x790e3d20,block--------------------------------self:25self:26-------block被分配堆区后引用的变量---------2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x2f9d4,0x79066450,global2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x2f9d8,0x79062eb0,static2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x790e256c,0x79062e90,instance2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x78f692e8,0x790628f0,local2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x78f68ab8,0x790e3d20,block---------------------------------------block被分配堆区后引用的变量计数-------------globalObj:1staticObj:1instanceObj:1localObj:2blockObj:1
从结果分析可看出,
- globalObj和staticObj在内存中的位置是确定的,所以Block
copy时不会retain对象。而blockObj的作用类型跟globalObj和staticObj相似,也不会retain。 - localObj在Block copy时,系统自动retain对象,增加其引用计数,所以局部变量,记得release一次。
- _instanceObj在Block copy时也没有直接retain _instanceObj对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。
从内存上讲,在block copy的时候,是重新在堆区开辟一块内存空间,将block保存进去,将NSStackBlock变为NSMallocBlock。
我们都知道,在block引用外包oc的时候,会创建一份oc变量(指针变量)快照,其中只对localObj创建快照,而其他类型是直接使用同一个变量。
为了保证堆区block中的快照(指针变量)指针引用到的localObj和instanceObj有效,避免因栈区清空导致localObj和instanceObj被销毁造成野指针,因此,编译器在copy block的时候,会对其引用到的localObj和instanceObj的self作一次retain。而globalObj、staticObj和blockObj则不存在这种问题。
PS:
blockObj与globalObj、staticObj不同的是,在block未被copy到堆区的时候,在block里blockObj是直接使用的,但是当block被copy时,blockObj其实也会另外做一份快照,blockObj在block copy前后,blockObj变量地址是不同的,只是引用地址相同而已,但是跟局部变量的快照不同,它可以修改其值(引用地址)的。所以其作用是跟globalObj、staticObj一样的。
- ARC环境下
在arc环境下,引用外部变量的block会被自动分配到堆区(相当于默认帮我们做了copy)。由于arc中没有retain,retainCount的概念。只有强引用和弱引用的概念。当一个变量没有__strong的指针指向它时,就会被系统释放。因此我们可以通过为变量设置一个week弱引用来检测引用对象,一旦引用对象被释放,week就会为nil, 下面代码来测试:
__weak NSString *globalObj_week;__weak NSString *instanceObj_week;__weak NSString *staticObj_week;__weak NSString *localObj_week;__weak NSString *blockObj_week;- (void)exampleD{ static NSString *__staticObj; __globalObj = [[NSString alloc] initWithFormat:@"global"]; instanceObj = [[NSString alloc] initWithFormat:@"instance"]; __staticObj = [[NSString alloc] initWithFormat:@"static"]; NSString* localObj = [[NSString alloc] initWithFormat:@"local"]; __block NSString* blockObj =[[NSString alloc] initWithFormat:@"block"]; NSLog(@"-----------原变量-------------"); NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj); NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj); NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); NSLog(@"%p,%p,%@",&localObj,localObj, localObj); NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj); NSLog(@"---------------------------------"); globalObj_week=__globalObj; instanceObj_week=instanceObj; staticObj_week=__staticObj; localObj_week=localObj; blockObj_week=blockObj; dBlock aBlock = ^{ NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj); NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj); NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); NSLog(@"%p,%p,%@",&localObj,localObj, localObj); NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj); NSLog(@"--------------week--------------"); NSLog(@"globalObj_week:%@",globalObj_week); NSLog(@"instanceObj_week:%@",instanceObj_week); NSLog(@"staticObj_week:%@",staticObj_week); NSLog(@"localObj_week:%@",localObj_week); NSLog(@"blockObj_week:%@",blockObj_week); }; __globalObj=nil; instanceObj=nil; __staticObj=nil; localObj=nil; blockObj=nil; NSLog(@"-------block被分配堆区后引用的变量---------"); aBlock(); NSLog(@"--------------------------------");}
执行结果:
-----------原变量-------------0xee9d8,0x7d06f2d0,global0xee9f0,0x7d06e680,static0x7be7221c,0x7d0706e0,instance0xbff13f90,0x7d06e690,local0xbff13f88,0x7d06f410,block----------------------------------------block被分配堆区后引用的变量---------0xee9d8,0x0,(null)0xee9f0,0x0,(null)0x7be7221c,0x0,(null)0x7be74ed8,0x7d06e690,local0x7be74ef8,0x0,(null)--------------week--------------globalObj_week:(null)instanceObj_week:(null)staticObj_week:(null)localObj_week:localblockObj_week:(null)--------------------------------
从结果可看出,最后只有localObj未被释放掉,说明在block被分配堆区的时候,局部变量的快照变量会对引用对象再做一次强引用。
成员变量instanceObj不做快照不再做强引用,是因为block已经对self做了一次强引用(与MRC同理),这会引发另外一个问题“循环引用”。
PS:
在arc环境下的blockObj,在block分配到堆区的时候跟MRC环境一样会创建一个可修改的快照,但该快照会对blockObj引用的对象作强引用,原变量blockObj销毁(block分配到堆区后,你所使用的变量blockObj其实是新建的快照,其变量地址与原blockObj变量地址是不一样的),所以,在创建block后,blockObj=nil
其实操作的是快照,结果就把引用对象销毁了blockObj_week:(null)
注意:
这里对oc对象赋值的时候可不能使用objec=@"objec";
的方法赋值,因为@"objec"
是常量(不在堆栈),常量是不遵循arc原则的,即创建后就一直存在,不管有多少强引用,它都不会被销毁。如下代码:
NSString* couObj =@"1"; __weak NSString *wobj=couObj; couObj=nil; NSLog(@"%@",wobj);
执行后还是会打印1
Block的循环引用
根据上面的结论, 因为block在拷贝到堆上的时候,会retain(强引用)其引用的外部变量,那么如果block中如果引用了他的宿主对象self(使用其成员变量或者显式使用了self),如果这时self的成员变量或者属性又对block做持有,那就会引起循环引用,如下:
//ARC:myblock为strong//MRC:myblock为copyself.myblock = ^{ [self doSomething]; //或者 NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); };
或者
//ARC_myblock = ^{ [self doSomething]; //或者 NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); };
- 在MRC环境下
如果self的成员变量或者copy属性对block做了持有,如
@property (nonatomic,copy) dBlock myBlock;dBlock aBlock = ^{ [self doSomething]; NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); }; self.myBlock=aBlock; //或者_myBlock=[aBlock copy];
这里,copy属性的myBlock 对block做了持有。说到这,要搞清楚,只有在堆区的block即NSMallocBlock才会对里面的外部对象做持有,对外部对象做持有才有可能出现循环引用的问题,如果这里block是没经过copy的,只是NSStackBlock,NSStackBlock会因栈清空而释放掉,则不存在循环引用问题。所以,这里讨论的block是NSMallocBlock。
这里self的myBlock属性对在堆区的aBlock做了持有,而aBlock中有引用了self或者成员变量,所以aBlock又对self做了持有,造成循环。
要解决这个问题就必须打破循环,我们可以从aBlock中对self或者成员变量的引用入手,让aBlock间接使用self,而不对其做retain,代码做如下修改:
__block typeof(instanceObj) week_instanceObj=instanceObj;__block typeof(self) weekself=self; dBlock aBlock = ^{ [weekself doSomething]; NSLog(@"%p,%p,%@",&week_instanceObj,week_instanceObj, week_instanceObj); };
使用__block来间接引用,上面“Block 对 OC对象 的持有”MRC环境分析提过,__block变量在block copy中,使用的是week_instanceObj的快照,该快照引用对象与instanceObj一样,但不做retain,不改变引用计数。
- ARC环境下
以下代码会造成循环引用:
@property (nonatomic,strong) dBlock myBlock;dBlock aBlock = ^{ [self doSomething]; NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj); }; self.myBlock=aBlock; //或者_myBlock=aBlock;
因为arc下,成员变量默认都是strong,引用外部对象的block会自动分配到堆区,为NSMallocBlock。所以,当成员变量或strong属性对blcok做强引用,便形成循环。
要解决循环引用可使用arc中的弱引用weak来间距引用,代码修改:
__weak NSString* week_instanceObj=instanceObj; __weak typeof(self) weekself = self; NSLog(@"week_instanceObj:%p,%p,%@",&week_instanceObj,week_instanceObj, week_instanceObj); dBlock aBlock = ^{ [weekself doSomething]; NSLog(@"week_instanceObj:%p,%p,%@",&week_instanceObj,week_instanceObj, week_instanceObj); };
这时可不能跟MRC一样时候__block来间距引用,因为__block对象在分配到堆区时,创建出来取代原变量的快照依然会对self做强引用。__weak对象会在block分配到堆区时创建一份快照,该快照对引用对象依然是弱引用。
总结
block类型:取决于block是否引用了外部变量。未引用外部变量的为NSGlobalBlock
NSGlobalBlock:未引用外部变量,不在堆栈中,独立的程序块。
NSStackBlock:引用外部变量 ,位于栈内存,函数返回后Block将无效,存在于MRC环境,通过copy可转为NSMallocBlock分配到堆区,在ARC下自动转换;
NSMallocBlock:NSStackBlock copy得来, 位于堆内存。
对外部变量的引用,除了静态变量 、全局变量、NSStackBlock中的block变量外,会对引用的变量做一份快照,保存创建快照时相同的值,oc对象(指针变量)保存相同引用地址。
关于将block copy到堆区后(或者arc自动copy),对引用的变量的持有变化:
在MRC下,只有局部变量的引用对象计数会+1(快照retain),对成员变量或self的引用也会使self计数+1,__block变量在copy后会取代原变量,但不会再对引用对象做retain。
在ARC下,局部变量会再强引用一次(快照也是强引用),对__block变量不改变强引用个数(快照变量会取代原变量),对成员变量或self的直接引用会使对self再强引用一次。
不论是MRC还是ARC下,当block分配到堆区后,其创建出来的快照的释放转为自动释放,只要block还在,其快照的引用就在。
- Block 分析
- Block分析
- block cleanout分析(一)
- block cleanout分析(二)
- inline-block 属性分析
- Block代码块分析
- 全面分析 block
- iOS---Block分析
- block全面分析
- Block全面分析
- Block全面分析
- block全面分析
- Block全面分析
- Block全面分析
- Block全面分析
- Block底层分析
- Block的详细分析
- Hadoop-Block的相关分析
- Special equations(数学筛)
- RMQ 算法
- Android stdio Ndk HelloWorld
- 我们也说说Android.mk(5) - 案例教程
- 第一次正式java web开发项目的总结
- Block 分析
- myeclipse快捷键大全(转载)
- 使用宏避免多次包含
- RDB redis
- JavaScript 正则表达式——基本语法
- [struts2]单文件与多文件上传
- 结构体中的递归定义
- 前端的那些事
- 飞入购物车特效