深度剖析linux内核万能--双向链表,Hash链表模版
我们都知道,链表是数据结构中用得最广泛的一种数据结构,对于数据结构,有顺序存储,数组就是一种。有链式存储,链表算一种。当然还有索引式的,散列式的,各种风格的说法,叫法层出不穷,但是万变不离其中,只要知道什么场合用什么样的数据结构,那就行了。
那么,标题说的内核万能链表,其实就是内核链表,它到底和我们平常大学学的数据结构的链表有什么不同呢??内核链表,是在linux内核里的一种普遍存在的数据结构,比如内核调度算法中有这样的结构,摄像头驱动程序,wifi模块,G_sensor等等,用到链表的东西实在实在是太多了,看内核的人务必要懂得这样的数据结构的用法,否则你根本不知道它是怎么产生的。
内核链表,在我们学完数据结构的人看来,它很奇怪,为什么说它奇怪?因为它没有数据域,这就是内核链表和普通链表的最大区别,那有人会问,没有数据域那怎么插入数据到链表呢?还有其它的什么遍历啊,反序啊等等的操作。。。别急,内核链表最大的优势就是内核已经提供了对链表进行常见操作的函数接口,也就是说给你接口你就会用,但是会用也不是那么简单,其中就有两个非常重要的宏,这两个宏能够理解,内核链表不过如此而已,没什么难度。
废话不多说,我们来看看,到底是怎么回事?
上代码...
以下的代码经过我本人亲自裁剪的常用接口,其实还有一些的,但是都大同小异,会用一个,其它也一样!
在代码里面,我做了详细的剖析,我做成了一个.h文件,当然写了一个使用的main函数:
最重要的,我对container_of和offsetof两个宏进行了分析:
<span style="font-size:18px;">#ifndef __list_H
#define __list_H
//在linux内核中提供了一个用来创建双向循环链表的结构list_head。
//list_head只有指针域,没有数据域,不是拿来单独使用,一般被嵌入到其它的结构中
struct list_head {
struct list_head *next, *prev;
};
//需要注意的是,头结点的head是不使用的
#define LIST_HEAD_INIT(name) { &(name), &(name) }
//我们知道0地址内容时不允许被访问的,但是0地址我们还是可以访问的
//这里用一个取址运算符(TYPE *)0表示将0地址强制转换为TYPE *类型
//((TYPE *)0) -> MEMBER 也就是从0地址找到TYPE的成员MEMBER
//将main.c中的结构体代入
//offset(struct list , list);----->展开后((size_t) & (struct list *)0 -> list)
//写清楚一点时这样:
//struct list *p = NULL ; &p->list ; 即是求p这个结构体指针的成员list的地址,只不过p是0
//地址,从0地址开始计算list成员的地址,也就是成员list在结构体struct list中的偏移量
//这个宏的作用就是求解MEMBER成员在TYPE中的偏移量
//编译器不报错的原因是在编译阶段就已经知道结构体里每个成员的属性的相对偏移量了 ,
//在代码中对结构体成员的访问其实最终 会被编译器转化为对其相对地址的访问
//在代码运行期间,其实根本就没有变量名还有属性成员,有的也就只有地址。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
//container_of最终结果返回的是第二个表达式的值,这里所谓的__mptr是一个中间的指针变量,其实就是list_head指针类型,被初始化为ptr
//而我所说的这个ptr,就是我们要求的list_head中的节点地址,定义这样一个中间的指针变量其实考虑了很多因素
//如果传参进来的是ptr++,会有副作用,就类似于(p++)+(p++)这样
//而(char *)__mptr之所以要强制转换为char实质上是因为地址是以字节为单位的,而char的长度是一个字节
//所以contain_of实质是两个地址相减的结果
//__mptr是结构体中list_head节点的地址,offset宏求的是list_head节点在MEMBER在结构体中TYPE中的偏移量,那么
//__mptr减去它所在结构体中的偏移量,就是结构体的地址了。
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define LIST_HEAD_INIT(name) { &(name), &(name) }
//生成名字为name的双向链表的头节点
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
//初始化链表头
static inline void INIT_LIST_HEAD(struct list_head *list)
{</span>
<span style="font-size:18px;"><span style="white-space:pre"> </span>//链表的前一个节点和后一个节点都指向它自己
list->next = list;
list->prev = list;
}
static inline void __list_add(struct list_head *new, struct list_head *prev,struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
//双向链表插入节点的操作---在头节点head的下一个节点,即是头插 ,即使将new这个节点插入
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
//双向链表插入节点的操作---向尾部插入new节点
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
//双向链表中节点删除 ,并把被删除的位置清0
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = NULL;
entry->prev = NULL;
}
//将上面entry被删除的节点重新初始化
static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
//将老的节点old换成新的节点new
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
//将上面的过程进行重新初始化
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
//将链表中的list节点移动到链表的头部
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
//判断当前链表list节点是不是链表的最后一个节点
static inline int list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
//判断链表是不是空的,如果空返回真,否则返回假
//也就是判断表头的节点的下一个节点是不是它本身
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
//判断链表是不是空的,如果空返回真,否则返回假 ,稍微和上面有区别的是
//判断表头的前一个节点和后一个节点是否为本身,是返回真,不是,返回假
//也就是prev和next
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
//将链表进行翻转,也就是将head的next和head的本身做交换
static inline void list_rotate_left(struct list_head *head)
{
struct list_head *first;
if (!list_empty(head)) {
first = head->next;
list_move_tail(first, head);
}
}
//判断链表中是否只有一个节点
static inline int list_is_singular(const struct list_head *head)
{
return !list_empty(head) && (head->next == head->prev);
}
static inline void __list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
struct list_head *new_first = entry->next;
list->next = head->next;
list->next->prev = list;
list->prev = entry;
entry->next = list;
head->next = new_first;
new_first->prev = head;
}
//分割链表,以entry为界限进行分割,分割完后的另一半的那条链表被list所指
static inline void list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
if (list_empty(head))
return;
if (list_is_singular(head) &&
(head->next != entry && head != entry))
return;
if (entry == head)
INIT_LIST_HEAD(list);
else
__list_cut_position(list, head, entry);
}
static inline void __list_splice(const struct list_head *list,
struct list_head *prev,
struct list_head *next)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
//将list和head这两条链表合并,注意,链表不能为空
static inline void list_splice(const struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
//将list和head这两条链表合并后并初始化
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
#define prefetch(x) __builtin_prefetch(x)
//双向链表的正向遍历,从前往后
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
//双向链表的遍历,跟上面那个有所不同的是,下面这个添加了通过list_entry,方便很多
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
//如果遍历链表不是从头开始,而是从某个已知的节点pos开始, 从当前的位置往后遍历,注意和头的情况区分
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
//双向链表的反向遍历,从后往前
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
//双向链表遍历的时候可以同时删除pos节点
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
//提供一个和pos一样的指针n,在for循环中暂时保留pos的下一个节点的地址,避免因pos节点被释放而造成断链的结果
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
//当我们知道list_head的地址时,可以通过list_entry这个宏获取它的父结构的地址。
//所以泽火革宏的功能就是由结构体成员的地址求结构体的地址。其中ptr是所求结构体中
//list_head成员的指针,type是所求结构体的类型,member是结构体list_head成员名
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
//哈希链表相关的数据结构
//单指针双向循环链表
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif
#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
//对定义的struct hlist_head 哈希链表变量进行初始化为空
#define HLIST_HEAD_INIT { .first = NULL }
//初始化哈希链表头
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
//这个宏用在删除节点之后对节点的操作当中
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
//判断h->prev是不是为空,如果pprev的指向是空的话,表示这个节点没有添加到这个链表当中来,如果是空,返回true,否则返回false*/
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}
//判断哈希链表是否为空,空返回true,否则返回false
static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;
if (next)
next->pprev = pprev;
}
//对哈希链表的节点进行删除
//对于删除操作的话,要注意n是不是末尾节点,如果是末尾节点的话,
//next就是NULL,所以就没有指向的pprev,就更不能进行相应的修改了,否则进行修改。
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}
//删除哈希链表的节点并对其进行初始化
static inline void hlist_del_init(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
INIT_HLIST_NODE(n);
}
}
//为哈希链表添加一个新的节点
//n:要添加的新的节点。
//h:hlist链表的表头节点。
//这个函数是给h的下一个和first节点中添加一个新的hlist_node节点,类似于上面的双向链表的头插
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
if (first)
first->pprev = &n->next;
h->first = n;
n->pprev = &h->first;
}
//在next的前面添加一个新节点n
static inline void hlist_add_before(struct hlist_node *n,
struct hlist_node *next)
{
n->pprev = next->pprev;
n->next = next;
next->pprev = &n->next;
*(n->pprev) = n;
}
//在next的后面添加一个新节点n
static inline void hlist_add_after(struct hlist_node *n,
struct hlist_node *next)
{
next->next = n->next;
n->next = next;
next->pprev = &n->next;
if(next->next)
next->next->pprev = &next->next;
}
//将新的节点new替换老的节点old
static inline void hlist_move_list(struct hlist_head *old,
struct hlist_head *new)
{
new->first = old->first;
if (new->first)
new->first->pprev = &new->first;
old->first = NULL;
}
//哈希链表得到ptr所指地址的这个结构体的首地址
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
//哈希链表的遍历操作
//pos:struct hlist_node类型的一个指针;
//head:struct hlist_head类型的一个指针,表示hlist链表的头结点。
//这个实际上就是一个for循环,从头到尾遍历链表。
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos ; pos = pos->next)
//以下的遍历于双向循环链表遍历操作大同小异,在此不做注释
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
pos = n)
#define hlist_for_each_entry(tpos, pos, head, member) \
for (pos = (head)->first; \
pos && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
#define hlist_for_each_entry_continue(tpos, pos, member) \
for (pos = (pos)->next; \
pos && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
#define hlist_for_each_entry_from(tpos, pos, member) \
for (; pos && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
for (pos = (head)->first; \
pos && ({ n = pos->next; 1; }) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = n)
#endif</span>
以上就是裁剪过后的list.h的分析,下面我们来看看如何使用内核链表:同样的,我写成了两个函数接口,一个是双向链表,一个是哈希链表,其实都差不多。
以下是main.c文件:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
#define SIZE 100
#define hname "yangyuanxin"
#define true 1
#define false 0
//创建一个双向链表结构
struct list
{
char name[11] ;
char sex[5] ;
char company_name[10];
char seq_number[32];
//嵌套在struct list这个自己创建的结构体里面
//list_head就是它的父结构的一个成员,
struct list_head list;
};
//创建一个哈希链表结构
struct hlist
{
int id ;
char name[20] ;
struct hlist_node hlist ;
};
//内核链表中哈希链表的使用
int hlist_fuction(void) ;
//内核链表中基本的双向链表的使用
int double_list(void) ;
int main(int argc, char *argv[])
{
hlist_fuction();
double_list();
return 0;
}
//定义一个和哈希链表操作的相关函数
int hlist_fuction(void)
{
printf("欢迎进入哈希链表模块!\n");
//初始化哈希链表头
HLIST_HEAD(hhead);
//定义一个哈希链表成员
struct hlist people ;
//定义一个哈希链表节点
struct hlist *entry ;
//定义一个已知的某个哈希链表的节点
struct hlist_node *p ;
printf("请输入成员id:\n");
scanf("%d",&people.id);
printf("请输入成员name:\n");
scanf("%s",&people.name);
//将成员添加到哈希链表的头部
hlist_add_head(&people.hlist , &hhead);
//遍历哈希链表
hlist_for_each(p,&hhead)
{
//获取哈希链表的地址
entry = hlist_entry(p , struct hlist , hlist);
printf("成员id为:\n");
printf("%d\n",entry->id);
printf("成员name:\n");
printf("%s\n",entry->name);
}
int errcount = 0 ;
repest :
printf("需要删除链表吗?需要y,否则n\n");
//刷新输入缓冲区
fflush(stdin);
char ch ;
scanf("%c",&ch) ;
switch(ch)
{
case 'y' : printf("正在准备删除链表节点!\n");
hlist_del(&people.hlist);
printf("链表节点已删除!\n");
break ;
case 'n' : printf("链表节点没有被删除!\n"); break ;
default:
printf("你的输入有误请重新输入!\n");
errcount ++ ;
if(errcount == 3){
printf("输入错误次数太多,已退出程序!\n");
exit(1);
}
system("cls");
goto repest ;
}
switch(hlist_empty(&hhead))
{
case true : printf("哈希链表为空\n"); break ;
case false :printf("哈希链表不为空\n"); break ;
}
sleep(2);
system("cls");
return 0 ;
}
int double_list(void)
{
printf("欢迎进入双向链表模块!\n");
//链表头初始化的两种方式
//1.
// struct list_head head;
// INIT_LIST_HEAD(&head);
//2.
LIST_HEAD(head);
//创建一个结构体成员变量
struct list people;
//创建一个链表节点
struct list *entry;
//已知的某个链表的节点
struct list_head *p;
printf("请输入员工姓名:\n");
scanf("%s",&people.name);
printf("请输入公司名称:\n");
scanf("%s",&people.company_name);
printf("请输入工号:\n");
scanf("%s",&people.seq_number);
list_add(&people.list,&head);
#if 0
printf("\n------------正向遍历-------------------\n\n");
printf("遍历的第一种方式:\n");
list_for_each(p,&head)
{
entry=list_entry(p,struct list,list);
printf("姓名: %s\n",entry->name);
printf("公司名: %s\n",entry->company_name);
printf("工号: %s\n",entry->seq_number);
}
#endif
printf("遍历的第二种方式:\n");
list_for_each_entry(entry,&head,list)
{
printf("name:%s\n",entry->name);
printf("company_name:%s\n",entry->company_name);
printf("seq_number:%s\n",entry->seq_number);
}
#if 1
printf("\n------------反向遍历------------------\n\n");
list_for_each_prev(p,&head)
{
entry=list_entry(p,struct list , list);
printf("name:%s\n",entry->name);
printf("company_name:%s\n",entry->company_name);
printf("seq_number:%s\n",entry->seq_number);
}
#endif
static int errcount ;
fflush(stdin);
char ch ;
respest:
printf("要删除双向链表节点吗?要输入y,否则输入n\n");
scanf("%c",&ch);
switch(ch)
{
case 'y' : printf("准备删除双向链表所有节点!\n");
sleep(2);
list_del(&people.list);
printf("删除成功!\n");
switch(list_empty(&head))
{
case true : printf("双向链表为空!\n"); break ;
case false : printf("双向链表不为空!\n"); break ;
}
break ;
case 'n' : printf("双向链表节点没有被删除!\n");
switch(list_empty(&head))
{
case true : printf("双向链表为空!\n"); break ;
case false : printf("双向链表不为空!\n"); break ;
}
break ;
default :
if(errcount == 3){
printf("你的输入错误次数太多,程序退出!");
exit(1);
}
printf("你的输入有误,请重新输入!\n");
errcount ++ ;
system("cls");
goto respest ;
}
return 0 ;
}
以上就是内核链表的使用方法。
我将内核链表裁剪后到win32的dev C++的gcc上进行了编译,没有错误也没有警告。
看看运行结果:
这个是哈希链表模块的运行结果:
来看看不为空的情况:
接下来是双向链表的运行结果:
接下来看看不为空的情况:
出错的情况:
至此,内核链表的剖析到此结束,下一次,我会继续剖析内核中红黑树的使用,以及哈夫曼树等等,还有图的遍历,只要是有的,我都尽量给挖出来。
花了一天时间进行整理,真心不容易...
深度剖析linux内核万能--双向链表,Hash链表模版的更多相关文章
- Linux内核中双向链表的经典实现
概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...
- Linux内核之数据双链表
导读 Linux 内核中自己实现了双向链表,可以在 include/linux/list.h 找到定义.我们将会首先从双向链表数据结构开始介绍内核里的数据结构.为什么?因为它在内核里使用的很广泛,你只 ...
- 深度剖析Linux与Windows系统的区别
当我们每个人接触Linux之前,应该先接触的都是windows吧?但我们一般接触Linux后,习惯linux的管理和使用方法后,我们再回过头再来使用windows的时候,内心其实是拒绝的.我们会觉得图 ...
- 深度剖析Linux与Windows系统的区别,新手必读!
当我们每个人接触Linux之前,应该先接触的都是windows吧?但我们一般接触Linux后,习惯linux的管理和使用方法后,我们再回过头再来使用windows的时候,内心其实是拒绝的.我们会觉得图 ...
- KSM剖析——Linux 内核中的内存去耦合
简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging) 允许这个系统管理程序通 ...
- 剖析linux内核中的宏---------container_of
#define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); ...
- 深度介绍Linux内核是如何工作的
本文发表于Linux Format magazine杂志,作者从技术深度上解释了Linux Kernel是如何工作的.相信对Linux开发者来说有不小的帮助. 牛津字典中对"kernel&q ...
- 剖析linux内核中的宏-----------offsetof
offsetof用于计算TYPE结构体中MEMBER成员的偏移位置. #ifndef offsetof#define offsetof(TYPE, MEMBER) ((size_t) &((T ...
- linux内核系列(二)内核数据结构之链表
双向链表 传统链表与linu内核链表的区别图: 图一 图二 从上图中看出在传统链表中各种不同链表间没有通用性,因为各个数据域不同,而在linux内核中巧妙将链表结构内嵌到数据域结构中使得不同结构之间能 ...
随机推荐
- OpenResty修改Nginx默认autoindex页面
Nginx的autoindex 命令可以自动列出目录下的文件,一些网站用这个功能做文件下载,但是Nginx又没有提供这个页面的 自定义的功能,后来看到别人提及 ngx_openresty,才想到 bo ...
- Android适配难题全面总结
支持多种屏幕 Android 可在各种具有不同屏幕尺寸和密度的设备上运行.对于 应用,Android 系统在不同设备中提供一致的开发环境, 可以处理大多数工作,将每个应用的用户界面调整为适应其显示的 ...
- Android开发学习之路--Drawable mutations
时间过得很快,明天终于可以拿到房子了,交完这次房租,也可以成为房东了,看看博客也好久没有更新了,最近一直在整机器人,也没有太多时间整理博客. 今天下午和同事一起遇到了一个问题,就是明明没有改变 ...
- socket系列之客户端socket——Socket类
假设TCP套接字服务器端已经建立好并正在监听客户端的连接了,那么客户端就可以通过Socket类来发起连接.客户端发起一个连接请求后,就被动地在等待服务器的响应.这个类同样位于java.net包中,包含 ...
- 【并发编程】ThreadPoolExecutor参数详解
ThreadPoolExecutor executor = new ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long ke ...
- linux C++多线程操作文件&输出加锁
下述demo将指定目录下文件存入vector,然后一分为二交给两个线程(threadTask1,threadTask2)去分别处理,对输出函数printDirent加锁防止紊乱. #include & ...
- Android初级教程对大量数据的做分页处理理论知识
有时候要加载的数据上千条时,页面加载数据就会很慢(数据加载也属于耗时操作).因此就要考虑分页甚至分批显示.先介绍一些分页的理论知识.对于具体用在哪里,会在后续博客中更新. 分页信息 1,一共多少条数据 ...
- UNIX网络编程——常用服务器模型总结
下面有9种服务器模型分别是: 迭代服务器. 并发服务器,为每个客户fork一个进程. 预先派生子进程,每个子进程都调用accept,accept无上锁保护. 预先派生子进程,以文件锁的方式保护acce ...
- iOS开发之七:常用控件--UISlider、UISegmentedControl、UIPageControl的使用
一.UISlider的使用 其实UISlider在iOS开发中用的似乎不是很多,我们看到的用到的地方多是音乐播放器的音量控制,以及视频播放器中的音量控制. 还是记录一下吧! 1.常用属性 // 设置获 ...
- Swift基础之OC文件调用Swift代码(在上次的基础上写的)
前两天刚写过Swift调用OC,今天在原来的基础上,实现OC调用Swift. 首先,创建一个OneSwiftFile.swift文件,创建一个继承于NSObject的类(这个地方你可以自己选择继承的父 ...