GCD浅析
1.关于GCD
Grand Central Dispatch是异步执行任务的技术之一。我们先看一个简单的示例:
- (void)doSomethingInBackground {
[self performSelectorInBackground:@selector(startWork) withObject:nil];
} - (void)startWork {
//长时间处理操作
NSLog(@"进行长时间处理"); [self performSelectorOnMainThread:@selector(endWork) withObject:nil waitUntilDone:YES];
} - (void)endWork {
NSLog(@"完成工作并刷新页面");
}
这个示例实现的功能很简单:在后台线程中执行长时间的处理,处理结束后,在主线程中使用处理结果。上面实现方式是使用NSObject类的performSelectorInBackground和performSelectorOnMainThread来实现的。下面我们用GCD的方式来实现同样功能,做个对比:
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"进行长时间处理");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"完成工作并刷新页面");
});
});
通过上面的比较,我们可以知道,GCD更加的简洁,同样的功能,代码量减少了差不多一半。
2.多线程编程
什么是线程?线程(英语:thread)是操作系统能够进行运算调度的最小单位(来自维基百科)。iOS应用程序在启动后,首先会将包含在应用程序中的CPU指令配置到内存中,CPU从应用程序指定的地址开始,一个一个去执行CPU指令,一个CPU一次只能执行一个指令。由于现在的物理CPU芯片实际上有64个(64核)CPU,如果1个CPU核虚拟为两个CPU核工作,那么使用多个CPU核同时工作就是很正常的事情了。这种存在多个CPU核工作的即为“多线程”。
在实际开发中,多线程编程是一种容易发生问题的编程技术。我们常见的有这么几种:
- 数据竞争:多个线程更新相同的资源会导致数据的不一致;
- 死锁:多个线程相互持续等待;
- 内存消耗高:每个线程都是有内存开销的,太多线程会大量的消耗内存。
多线程存在上面的这些问题,那么我们是不是应该弃用呢?答案是否定的,因为使用多线程编程,可以让我们在执行长时间的处理时,保证用户界面的即时响应。
3.GCD的API
3.1Dispatch Queue
分发队列主要有两类:
- Serial Dispatch Queue:等待现在执行任务处理结束;
- Concurrent Dispatch Queue:不等待现在执行处理结束;
听概念是不是感觉很抽象?没关系,下面我们来看看代码。
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{
NSLog(@"blk");
}); dispatch_async(queue, ^{
NSLog(@"blk");
}); dispatch_async(queue, ^{
NSLog(@"blk");
}); dispatch_async(queue, ^{
NSLog(@"blk");
}); dispatch_async(queue, ^{
NSLog(@"blk");
});
可以看到,上面我们使用的是Serial Dispatch Queue,运行效果会怎么样呢?
可以看到:使用Serial Dispatch Queue,因为要等待正在执行的任务结束,并且同事执行的处理数只能有一个,所以会依次打印blk1、blk2、blk3、blk4、blk5。
现在将上面示例的DISPATCH_QUEUE_SERIAL改为DISPATCH_QUEUE_CONCURRENT,结果会怎么样呢?
结论是:使用Concurrent Dispatch Queue时,因为不用等待正在执行的任务结束,可以并行执行多个任务,也就是使用多个线程同时执行,但并行执行任务的数量取决于系统当前的状态。上面并行执行的线程情况截图如下:
3.2dispatch_queue_create
上面介绍了dispatch queue,那么怎么创建分发队列呢?我们可以使用GCD的API函数。
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_create函数的第一个参数指定队列的名称,这个名称会在XCode调试中显示,也会显示在应用程序崩溃时所生成的CrashLog中。因此我们命名最好时取一些有意义的,以方便我们来定位问题和代码。例如下面的代码:
dispatch_queue_t queue1 = dispatch_queue_create("com.gcd.test1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.gcd.test2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("com.gcd.test3", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue4 = dispatch_queue_create("com.gcd.test4", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue5 = dispatch_queue_create("com.gcd.test5", DISPATCH_QUEUE_SERIAL); dispatch_async(queue1, ^{
NSLog(@"blk1");
}); dispatch_async(queue2, ^{
NSArray *arr = @[@, @, @];
NSLog(@"%@", arr[]);
}); dispatch_async(queue3, ^{
NSLog(@"blk3");
}); dispatch_async(queue4, ^{
NSLog(@"blk4");
}); dispatch_async(queue5, ^{
NSLog(@"blk5");
});
从代码可以看到,queue2会崩溃,实际运行后的CrashLog如下:
dispatch_queue_create函数的第二个参数指定创建队列的类型。如果想创建Serial Dispatch Queue,那么指定类型为DISPATCH_QUEUE_SERIAL或NULL;如果想创建Concurrent Dispatch Queue,那么指定类型为DISPATCH_QUEUE_CONCURRENT。
【注意】:dispatch_queue_create函数可以创建任意个Dispatch Queue,当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue会并行执行,系统对于每一个Serial Dispatch Queue会生成并使用一个线程。如果生成大量的Serial Dispatch Queue,那么就会创建很多线程。过多使用线程,会消耗大量内存,降低系统的响应性能。
3.3Main Dispatch Queue/Global Dispatch Queue
上面说了用dispatch_queue_create函数生成Dispatch Queue,现在聊一下系统标准提供的几个Dispatch Queue。
名称 | Dispatch Queue类型 | 说明 |
Main Dispatch Queue | Serial Dispatch Queue | 主线程执行 |
Global Dispatch Queue(High Priority) | Concurrent Dispatch Queue | 执行优先级:高 |
Global Dispatch Queue(Default Priority) | Concurrent Dispatch Queue | 执行优先级:默认 |
Global Dispatch Queue(Low Priority) | Concurrent Dispatch Queue | 执行优先级:低 |
Global Dispatch Queue(Background Priority) | Concurrent Dispatch Queue | 执行优先级:后台 |
怎么创建?
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, );
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_queue_t lowQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, );
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, );
3.4dispatch_after
从这个函数名称,我们也能猜到它的大概作用:在xx时间后,再做某件事情。
NSLog(@"Before:%@", [NSDate date]);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"After:%@", [NSDate date]);
});
【注意】:dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间后追加处理(也就是Block)到Dispatch Queue。
3.5Dispatch_Group
假设现在有个新的需求:需要在 Dispatch Queue中的多个任务完成之后,再做某件事情。当然大家会马上反应过来,用Serial Dispatch Queue即可。但如果应用场景是Concurrent Dispatch Queue或者多个Dispatch Queue呢?
这个时候就需要Dispatch Group了。
dispatch_queue_t queue1 = dispatch_queue_create("com.gcd.test1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.gcd.test2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("com.gcd.test3", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue4 = dispatch_queue_create("com.gcd.test4", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue5 = dispatch_queue_create("com.gcd.test5", DISPATCH_QUEUE_SERIAL); dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue1, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue2, ^{
NSLog(@"blk2");
});
dispatch_group_async(group, queue3, ^{
NSLog(@"blk3");
});
dispatch_group_async(group, queue4, ^{
NSLog(@"blk4");
});
dispatch_group_async(group, queue5, ^{
NSLog(@"blk5");
}); dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
多次运行,我们发现打印done总是在最后:
3.6dispatch_barrier_async
闭上眼睛,来想象这么一个场景:假设我们有一个本地数据库,会频繁的去读取数据和写入数据;写入是一个特殊的操作,如果在写入的时候有其他的读取操作,那么很有可能会读到脏数据,甚至也可能会造成程序的崩溃;如果只是单纯的读取操作,那么多个并行就不会发生问题。
针对这种场景,我们该怎么办呢?
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{
NSLog(@"blk1");
});
dispatch_async(queue, ^{
NSLog(@"blk2");
});
dispatch_async(queue, ^{
NSLog(@"blk3");
});
dispatch_barrier_async(queue, ^{
NSLog(@"blk0");
});
dispatch_async(queue, ^{
NSLog(@"blk4");
});
dispatch_async(queue, ^{
NSLog(@"blk5");
});
dispatch_async(queue, ^{
NSLog(@"blk6");
});
多次运行看结果:
通过多次运行,我们发现,不管怎么变化,blk0的打印永远在blk1、blk2、blk3的后面,blk4、blk5、blk6的前面。
3.7dispatch_sync
前面一直在说的都是“async”,意思就是非同步的将Block追加到指定的Dispatch Queue,dispatch_async函数不做任何等待;
对应的就有“sync”,意思就是同步的将Block追加到指定的Dispatch Queue。在追加Block结束之前,dispatch_sync函数会一直等待。
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"blk1");
});
dispatch_sync(queue, ^{
NSLog(@"blk2");
});
dispatch_sync(queue, ^{
NSLog(@"blk3");
});
dispatch_sync(queue, ^{
NSLog(@"blk4");
});
dispatch_sync(queue, ^{
NSLog(@"blk5");
}); NSLog(@"主线程");
多次运行看看结果,发现结果总是固定的,另外查看线程,执行都是在主线程。
如果使用dispatch_async呢?结果怎样?多次运行,可以发现顺序是随机的。
学到这里,我们已经接触到了这么4个概念:同步、异步、串行、并行。下面总结一下:
- 队列分为串行和并行,任务的执行分为同步和异步,异步是多线程的代名词,异步在实际应用中会开启新的线程,执行耗时操作;
- 队列只是负责任务的调度,而不负责任务的执行,任务是在线程中执行。
- 同步意味着在当前线程中执行,异步意味着会开启新的线程。
下面我们继续来细细品味一下这4个概念:串行同步、串行异步、并行同步、并行异步。
3.7.1串行同步
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"blk1 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk2 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk3 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk4 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk5 thread:%@", [NSThread currentThread]);
}); NSLog(@"主线程 thread:%@", [NSThread currentThread]);
运行结果:
结论:取出一个任务不放进别的线程,阻塞当前线程(不开辟新的线程,顺序执行),等任务执行完成,开始下一个任务。
3.7.2串行异步
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"blk1 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk2 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk3 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk4 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk5 thread:%@", [NSThread currentThread]);
}); NSLog(@"主线程 thread:%@", [NSThread currentThread]);
运行结果:
结论:取出一个任务放进别的线程,不阻塞当前线程,等任务执行完成,开始下一个任务。
3.7.3并行同步
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"blk1 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk2 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk3 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk4 thread:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"blk5 thread:%@", [NSThread currentThread]);
}); NSLog(@"主线程 thread:%@", [NSThread currentThread]);
运行结果:
结论:取出一个任务不放进别的线程,阻塞当前线程(不开辟新的线程,顺序执行),等任务执行完成,开始下一个任务。
3.7.4并行异步
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"blk1 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk2 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk3 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk4 thread:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"blk5 thread:%@", [NSThread currentThread]);
}); NSLog(@"主线程 thread:%@", [NSThread currentThread]);
运行结果:
结论:取出一个任务放进别的线程,不阻塞当前线程,不等任务执行完成,开始下一个任务(会开启多个线程)。
3.7.5总结
串行(Serial Dispatch Queue) | 并行(Concurrent Dispatch Queue) | 主线程(Main Queue) | |
同步(sync) |
没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
异步(async) |
开启一个新线程 串行执行任务 |
开启多个新线程 并行执行任务 |
没有开启新线程 串行执行任务 |
3.7.6死锁
死锁是同步(sync)线程操作中比较常见的一个问题,要理解死锁,我们要深刻的理解这么一句话:串行与并行说的是队列,队列是用来调度和存放任务的;而同步与异步,针对的是线程。区别在于,同步会阻塞当前线程,必须要等待同步线程中的任务执行完成以后,才能继续执行下一任务;而异步线程则是不用等待。下面我们来分析几个示例,更细的理解死锁是怎么来的,又是怎么没的。
示例1:
NSLog(@"任务1 thread:%@", [NSThread currentThread]); //任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务2 thread:%@", [NSThread currentThread]); //任务2
});
NSLog(@"任务3 thread:%@", [NSThread currentThread]); // 任务3
结果:
发现后面的任务2和任务3都没有打印。
3.8dispatch_once
从函数名就知道,这个函数用于保证在应用程序执行中只执行一次指定处理。通常用于实现单例。
+ (HDFNetConfig *)sharedConfig {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
3.9dispatch_semaphore_t
当我们在处理一系列线程的时候,当线程数量达到一定量时,在以前我们会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是dispatch_semaphore。与它相关的共有三个函数,分别是dispatch_semaphore_create,dispatch_semaphore_signal,dispatch_semaphore_wait。
3.9.1dispatch_semaphore_create
该函数会创建一个信号量,定义如下:
dispatch_semaphore_t
dispatch_semaphore_create(long value);
传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
3.9.2dispatch_semaphore_signal
发送一个信号量,定义如下:
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
这个函数会使传入的信号量dsema的值加1。
返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
3.9.3dispatch_semaphore_wait
等待信号,定义如下:
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
这个函数会使传入的信号量dsema的值减1。
这个函数的作用是这样的:
- 如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
- 如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
当其返回值为0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。
举个例子来说明一下信号量的工作原理:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
3.9.4示例
dispatch_semaphore_t semaphore = dispatch_semaphore_create();
dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
for (int i = ; i < ; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"%i %@",i, [NSThread currentThread]);
sleep();
dispatch_semaphore_signal(semaphore);
});
}
GCD浅析的更多相关文章
- IOS GCD 浅析
一.简单介绍 1.队列的类型: 1.1主队列:main queue 主线程队列,更新UI的操作.是一个串行的队列,串行队列每次只处理一个任务. 1.2系统创建的并发队列:glob ...
- ios之gcd浅析
A.普通的GCD异步运行与主线程更新写法: dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^ ...
- SDWebImage之SDWebImageDownloaderOperation
上篇讲了SDWebImageDownloader,从源码分析的过程中,我们知道,实际执行下载任务的是SDWebImageDownloaderOperation,本篇我们来看看SDWebImageDow ...
- iOS应用卡顿分析
1.屏幕显示图像的原理 显示器按照从上到下的方式,一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描.为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬 ...
- iOS 并发概念浅析
在进行iOS开发过程中,我们常会遇到网络请求.复杂计算.数据存取等比较耗时的操作,如果处理不合理,将对APP的流畅度产生较大影响.除了优化APP架构,并发(concurrency)是一个常用且较好的解 ...
- 内存管理 & 内存优化技巧 浅析
内存管理 浅析 下列行为都会增加一个app的内存占用: 1.创建一个OC对象: 2.定义一个变量: 3.调用一个函数或者方法. 如果app占用内存过大,系统可能会强制关闭app,造成闪退现象,影响用户 ...
- EXCEL函数常用技巧浅析
EXCEL函数常用技巧浅析 EXCEL函数是一门趣味性非常大的游戏,此贴内容基本上为总结前人经验而来.废话不多说,我们现在走入正题. 一:判断数值奇偶性 1.1 ISODD(number) 判断一个 ...
- SQL Server on Linux 理由浅析
SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
随机推荐
- 常见JS挂马方法及如何防止网站被黑客挂马?
最近有朋友说自己的网站平时并未作弊,文章也都是原创的,更新很稳定.可不知道为什么网站突然就被各大搜索引擎降权了,一直找不到原因.最后发现是网站被挂马了,导致网站被连累了.在此,借助马海祥博客的平台,给 ...
- Atom 编辑器系列视频课程
此课程为 Atom 编辑器系列课程,主要介绍了 Atom 的高效开发技巧以及必备插件. 课程列表 Atom编辑器系列课程 #1 - Atom简介 Atom编辑器系列课程 #2 - 设置简介 Atom编 ...
- HoloLens开发手记 - 构建2D应用 Building 2D apps
HoloLens可以让我们在真实世界中看到全息图像内容.但是它本质上还是一台Windows 10设备,这意味着HoloLens可以以2D应用形式运行Windows Store里的大部分UWP应用. 目 ...
- Jsoup后台解析html、jsp网页
在一些网络爬虫或者从第三方网站抓取信息的程序都面临1个问题,如何从网页中把所需的信息提取出来,Jsoup是个比较好的选择,它能把网站内容解析成Document,再从document中取element就 ...
- java算法 蓝桥杯 乘法运算
问题描述 编制一个乘法运算的程序. 从键盘读入2个100以内的正整数,进行乘法运算并以竖式输出. 输入格式 输入只有一行,是两个用空格隔开的数字,均在1~99之间(含1和99). 输出格式 输出为4行 ...
- Jquery实现的几款漂亮的时间轴
引言 最近项目中使用了很多前端的东西,对于我一个做后台开发的人员,这是一个很好的锻炼的机会.经过这段时间的学习,感觉前端的东西太多了,太强大了,做出来的东西太炫酷了.现在有很多开源的前端框架,做的都非 ...
- 使用OpenFiler来模拟存储配置RAC中ASM共享盘及多路径(multipath)的测试
第一章 本篇总览 之前发布了一篇<Oracle_lhr_RAC 12cR1安装>,但是其中的存储并没有使用多路径,而是使用了VMware自身提供的存储.所以,年前最后一件事就是把多路径学习 ...
- opp(Object Oriented Programming)
嗯,昨天忙了一天没来及发,过年啊,打扫啊,什么搽窗户啊,拖地啊,整理柜子啊,什么乱七八糟的都有,就是一个字,忙. 好了,废话也不多说,把自己学到的放上来吧.嗯,说什么好呢,就说原型链啊 原型对象 每个 ...
- Java如何判断字符串中包含有全角,半角符号
首先介绍下全角跟半角之间的区别: 在计算机屏幕上,一个汉字要占两个英文字符的位置,人们把一个英文字符所占的位置称为"半角",相对地把一个汉字所占的位置称为"全角" ...
- Android SQLite总结
SQLite在Android一般应用中还是比较常用,早期的时候碰到过不少坑,其中最烦的就是多线程并发读写问题,今天正好整理一下,做个笔记,也欢迎指正.讨论和补充. 一.查询优化 1.wal模式 开启w ...