一.项目设计知识点——接上一篇
5.为什么选择库函数?什么时候用库函数操作文件?
通过库函数我们可以更好地使用其功能,节省了我们自己写函数的过程,并且对于已经运用成熟的库函数,我们很难写出更加高效的新算法。
库函数对文件的操作
1.文件分类:
通过流进行输入输出:文本文件(存放ASCII码) 和 二进制文件(二进制编码方式)
从用户角度: 普通文件 和 设备文件
文件内容: 源文件,目标文件,可执行文件,头文件,数据文件等
基本操作
文件指针:一个指向文件有关信息的指针,这些信息包括文件名,状态和当前位置,他们保存在一个结构体变量中。该类型为FILE 型。编写程序时可直接使用。
如: FILE *fp; 即fp是指向FILE类型的指针变量。
2.各种函数的使用
打开文件: fopen
函数原型 FILE * fopen(const char *path,cost char *mode)
作用:打开一个文件,返回指向该文件的指针
参数说明:第一个参数为欲打开文件的文件路径及文件名,第二个参数表示对文件的打开方式
注:mode有以下值:
r: 只读 ,文件必须存在
r+: 可读写,必须存在
rb+: 打开二进制文件,可以读写
rt+: 打开文本文件,可读写
w: 只写,文件存在则文件长度清0,文件不存在则建立该文件
w+: 可读写,文件存在则文件长度清0,文件不存在则建立该文件
a: (追加)附加方式打开只写,不存在建立该文件,存在写入的数据加到文件尾, EOF符保留
a+: 附加方式打开可读写,不存在建立该文件,存在写入的数据加到文件尾,EOF符不 保留
wb: 打开二进制文件,只写
wb+: 打开或建立二进制文件,可读写
wt+: 打开或建立文本文件,可读写
at+: 打开文本文件,可读写,写的数据加在文本末尾
ab+: 打开二进制文件,可读写,写的数据加在文件末尾
由mode字符可知,上述如r、w、a在其后都可以加一个b,表示以二进制形式打开文件
返回值:文件打开了,返回一个指向该打开文件的指针(FILE结构);文件打开失败,错误上存error code(错误代码)
注意:在fopen操作后要进行判断,是否文件打开,文件真正打开了才能进行后面的读或写操作,如有错误要进行错误处理
例:FILE *pfile=fopen(const char *filename,"rb");
文件的关闭:
fclose(文件指针)
功能:关闭一个文件流,使用fclose就可以把缓冲区内最后剩余的数据输出到磁盘文件中,并释放文件指针和有关的缓冲区
成功返回值为0;否则返回EOF
程序结束之前应关闭所有文件,防止因为没有关闭文件儿造成数据流失。
文件读写:
fputc ch=fputc(ch, fp);
作用:把一个字符写到磁盘文件(fp所指向的文件)中去,ch是要输出的字符,他可以是一个字符常量,也可以是一个字符变量。Fp是文件指针变量。
成功返回的就是输出的字符;失败返回EOF。
Fgetc 函数 ch=fgetc(fp);
作用:从指定的文件(fp所指向的文件)读入一个字符赋给ch,《注意文件必须读或读写的方式打开,当遇到文件结束符返回一个文件结束标志EOF》
fputS函数: fputs(字符串,文件指针); fputs(str , fp);
功能: 指定的文件写入字符串,其中字符串可以是字符串常量,也可以是字符数组名,指 针或变量。
fgets函数: fgets(字符数组名,n,文件指针); fegets(str,sizeof(str),文件指针);
功能: 从指定的文件中读出一个字符串到字符数组中。n表示所得到字符串中字符的个数(包含“\0”)。
fprintf 函数: ch=fprintf(文件类型指针,格式字符串,输出列表);
如:fprintf(fp,“%s”,i);
功能: 将整型变量i的值以“%s”(字符)的格式输出到fp指向的文件中。
fscanf 函数:fscanf(文件类型指针,格式字符串,输入列表);
如:fscanf(fp,“%d”,&i);
功能: 读入fp指向的文件中的i的值。
fread 函数: fread(buffer,size,count,fp);
fread(a,2,3,fp); 从fp指向的文件中每次读两个字节送入实数组a中,连续读3次。
功能: 从fp所指向的文件中读入count次,每次读size字节,读入的信息存在buffer地址中。
fwrite 函数: fwrite(buffer,size,count,fp);
fwrite(a,2,3,fp);将a数组的信息每次读出两个字节到fp所指向的文件中,连续读3次。
文件的定位
Fseek 函数: fseek(文件类型指针,位移量,起始点);
如:Fseek(fp,-20L,1);从当前位置后退20个字节。
作用:重定位文件内部的指针
参数:第一个为文件指针,第二个是指针的偏移量(要求long类型数据,常量表示位移加后缀L),第三个是指针偏移起始位置(0,1,2)
返回值:重定位成功返回0,否则返回非零值
需要注意的是该函数不是重定位文件指针,而是重定位文件内部的指针,让指向文件内部数据的指针移到文件中我们感兴趣的数据上,重定位主要是这个目的。
rewind 函数: int rewind(文件类型指针);
功能: 使位置指针重新返回文件开头。没有返回值。
Ftell 函数: long ftell(文件类型指针)
功能: 得到流文件中的当前位置,用相对于开头的位置来表示。当返回值为-1L时表示出错。
6.为什么数据库选择sqlite?还有哪些嵌入式数据库?特点是什么?
一.C 是最好的选择
从 2000 年 5 月 29 日开始,SQLite 就选择了 C 语言。直到今天,C 也是实现 SQLite 这样软件库的最佳语言。
C 语言是实现 SQLite 最好的语言的原因包括:
-
性能。
-
兼容性。
-
低依赖性。
-
稳定性。
性能
像 SQLite 这样被密集使用的基础库需要有很好的性能(SQLite 确实很快,可以看看 Internal Versus External BLOBs 和 35% Faster Than The Filesystem 两篇文章)。
C 语言很适合写这样有性能要求的程序。C 语言有时被称为「便携式汇编语言」,让开发者能尽可能的接近底层硬件编码,同时保证跨平台的便携性。
当然,也有其他的编程语言声称和 C 一样快或者更快,但没有一个能和 C 一样通用。
兼容性
目前几乎所有的系统都可以调用由 C 语言编写的库。
比如,用 Java 编写的 Android 应用能通过 adapter 来使用 SQLite。如果 SQLite 是用 Java 编写的,这对于 Android 肯定会更方便。但在 iPhone 上应用是 Objective-C 或者 Swift 编写的,这两种语言都没办法调用 Java 库。因此,如果 SQLite 选择用 Java 编写,那在 iPhone 上就没办法用了。
低依赖性
用 C 来编写库不会在运行时有太多的依赖。在最小的配置下,SQLite 只需要 C 标准库里的:
-
memcmp()
-
memcpy()
-
memmove()
-
memset()
-
strcmp()
-
strlen()
-
strncmp()
在更复杂的配置下,SQLite 可能还会用到 malloc(),free() 和一些操作系统接口来打开、读取、写入和关闭文件。但即使这样,依赖的数量也非常小。
稳定性
这个稳定性是指语言的稳定性。C 语言可能是老旧又无聊,但却正好很适合开发像 SQLite 这样更注重长期稳定的模块。
二.嵌入式数据库:
Progress
Progress软件公司2000年4月18号18时在京宣布,全面发售在Linux操作系统上运行的数据库及其部署产品。Progress在嵌入式数据库市场中拥有全球第一的占有率,世界上有超过200万人正在使用Progress软件公司的应用软件,目前部署Progress产品的站点数量已经超过100,000个。通过Progress软件公司第一个Linux版嵌入式数据库,独立软件开发商和最终用户可以在这一流行的操作系统上移植5,000多种商业应用。
----Progress软件公司当时推出的产品为ProgressVersion8.3,现在已经到了10.2c版本。这是一套完善的集成开发工具、应用服务器和关系型数据库产品,提供了可扩充的多层Linux支持。Progress软件公司的Linux专用产品包括:
Progress(r)AppServer(tm):这是一种可以在异构环境中部署共享应用组件的应用服务器Progress(r)EnterpriseRDBMS(tm):为需要支持大型数据库、多处理器硬件和数千个并发用户的最苛刻的应用提供了一种可扩充的存储解决方案。
----用于RedHat6.0Linux的ProgressVersion8.3部署产品现已全面上市。Progress已推出用于Linux的ProgressVersion9、Progress(r)WebSpeed(r)Version3、Progress(r)Apptivity(tm)和Progress(r)SonicMQ(tm)部署产品。
----目前全球顶尖的汽车行业ERP供应商QAD支持最新的PROGRESS版本。
SQLite
轻量级别数据库SQLite的主要特点:
1. 支持事件,不需要配置,不需要安装,也不需要管理员;
2. 支持大部分SQL92;
3. 一个完整的数据库保存在磁盘上面一个文件,同一个数据库文件可以在不同机器上面使用,最大支持数据库到2T,字符和BLOB的支持仅限制于可用内存;
4. 整个系统少于3万行代码,少于250KB的内存占用(gcc),大部分应用比目前常见的客户端/服务端的数据库快,没有其它依赖
5. 源代码开放,代码95%有较好的注释,简单易用的API。官方带有TCL的编译版本。
Empress(商业数据库)
开发阶段特点:
1. 可嵌入程序,该特性使应用程序和数据库工作于统一地址空间,增强了系统的稳定性,提高了系统的效率。
2. 确定的响应时间,Empress 可以使数据的响应时间相对一致,使用者可以设定一个超时限制,如果在规定时间内没有完成插入,修改等操作,系统会报错。
3. 快速的操作Empress 提供了内核级的CAPI,称为MR, 用MR编写的应用程序在执行时不需要解析。另外在MR中加速的机制还包括优秀的加锁控制,内存管理和基于记录数量的选择功能。
4. 灵活的开发方式,Empress 提供多种开发接口,加快开发进程而无需开发者重新学习开发语言和熟悉开发环境。
5. 友好的存储方式,Empress 数据库可以放在操作系统支持的任何存储设备中,Empress的表单甚至可以分割放在不同的存储设备中,比如在内存,硬盘和CD-ROM中。
6. 微型内核结构 Empress 高度单元化, 可根据需要选择需要的单元,从而缩小产品中Empress 数据库所占用的资源。
7. 宽广的平台支持,Empress 支持多种硬件平台和软件平台, 也可移植到客户要求的硬件平台或操作系统。
技术优势:
1. 微型内核结构,占用少量内存空间,特别适合紧凑性的设计
2.一周7天,每天24小时连续工作,无需任何额外操作免维护
3. 内核级 CAPI 接口,使运行速度最大化
4. 高度灵活的SQL接口
5. 优秀的掉电恢复能力
6. 强壮的交易和锁存机制
7. 支持SCSI,RAID,IDE,RAM,CD-RW,DVD-ROM,CF,等存储介质
8. 支持Unicode 码
9. 引擎可加载于磁盘和内存
eXtremeDB
eXtremeDB特点:
1. 内存数据库,eXtremeDB将数据以程序直接使用的格式保存在主内存之中,不仅剔除了文件I/O的开销,也剔除了文件系统数据库所需的缓冲和Cache机制。其结果是每个交易一微秒甚至更少的极限速度,相比于类磁盘数据库而言,速度成百上千倍地提高。作为内存数据库,eXtremeDB不仅性能高,而且数据存储的效率也非常高。为了提高性能并方便程序使用,数据在eXtremeDB中不做任何压缩,100M的空间可以保存高达70M以上的有效数据,这是其他数据库所不可想象的。
2. 混合数据库,eXtremeDB不仅可以建立完全运行在主内存的内存数据库,更可以建立磁盘/内存混合介质的数据库。在eXtremeDB,我们把这种建立在磁盘、内存或磁盘+内存的运行模式称为eXtremeDB Fusion融合数据库。eXtremeDB Fusion兼顾数据管理的实时性与安全性要求,是实时数据管理的台阶性进步。
3. 嵌入式数据库,eXtremeDB内核以链接库的形式包含在应用程序之中,其开销只有50KB~130KB。无论在嵌入式系统还是在实时系统之中,eXtremeDB都天然地嵌入在应用程序之中,在最终用户毫不知情的情况下工作。eXtremeDB的这种天然嵌入性对实时数据管理至关重要:各个进程都直接访问eXtremeDB数据库,避免了进程间通信,从而剔除了进程间通信的开销和不确定性。同时, eXtremeDB独特的数据格式方便程序直接使用的,剔除了数据复制及数据翻译的开销,缩短了应用程序的代码执行路径。
4. 由应用定制的API,应用程序对eXtremeDB数据库的操作接口是根据应用数据库设计而自动产生,不仅提升了性能,也剔除了通用接口所必不可少的动态内存分配,从而提高了应用系统的可靠性。定制过程简单方便,由高级语言定制eXtremeDB数据库中的表格、字段、数据类型、事件触发、访问方法等应用特征,通过eXtremeDB预编译器自动产生访问该数据库的C/C++ API接口。
5. 可预测的数据管理
eXtremeDB独特的体系结构,保证了数据管理的可预测性。eXtremeDB不仅更快、更小,而且更确定。在80双核CPU的服务器上,eXtremeDB在1TB内存里保存15B条记录;无论记录数多少,eXtremeDB可以在八十分之一微秒的时间内提取一条记录。
Firebird嵌入服务器版
Firebird嵌入服务器版(Embedded Server),从Interbase开源衍生出的Firebird,充满了勃勃生机。虽然它的体积比前辈Interbase缩小了几十倍,但功能并无阉割。为了体现Firebird短小精悍的特色,开发小组在增加了超级服务器版本之后,又增加了嵌入版本,最新版本为2.0。
Firebird的嵌入版有如下特色:
1、数据库文件与Firebird网络版本完全兼容,差别仅在于连接方式不同,可以实现零成本迁移。
2、数据库文件仅受操作系统的限制,且支持将一个数据库分割成不同文件,突破了操作系统最大文件的限制,提高了IO吞吐量。
3、完全支持SQL92标准,支持大部分SQL-99标准功能。
4、丰富的开发工具支持,绝大部分基于Interbase的组件,可以直接使用于Firebird。
6、可自己编写扩展函数(UDF)。
mSQL介绍
mSQL(mini SQL)是一个单用户数据库管理系统,个人使用免费,商业使用收费。由于它的短小精悍,使其开发的应用系统特别受到互联网用户青睐。mSQL(mini SQL)是一种小型的关系数据库,性能不是太好,对SQL语言的支持也不够完全,但在一些网络数据库应用中是足够了。由于mSQL较简单,在运行简单的SQL语句时速度比MySQL略快,而MySQL在线程和索引上下了功夫,运行复杂的SQL语句时比mSQL,PostgreSQL等都要快一些。最新版本是2005年5月8日发布的3.7.MSQL的标志是一个鹿,见下图。 图1mSQL LOGO标志
mSQL的技术特点:安全性方面,mSQL通过ACL文件设定各主机上各用户的访问权限,缺省是 全部可读/写。mSQL缺乏 ANSI SQL 的大多数特征,它仅仅实现了一个最最少的API,没有事务和参考完整性。mSQL与Lite(一种类似C的脚本语言,与分发一起发行)紧密结合,可以得到一个称为 W3-mSQL的一个网站集成包,它是JDBC、ODBC、Perl和PHP API.
8.什么时候选择多线程?什么时候选择多进程?
关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话应付考试基本上够了,但如果在工作中遇到类似的选择问题,那就没有这么简单了,选的不好,会让你深受其害。
经常在网络上看到有的XDJM问“多进程好还是多线程好?”、“Linux下用多进程还是多线程?”等等期望一劳永逸的问题,我只能说:没有最好,只有更好。根据实际情况来判断,哪个更加合适就是哪个好。
我们按照多个不同的维度,来看看多线程和多进程的对比(注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受)。
对比维度 |
多进程 |
多线程 |
总结 |
数据共享、同步 |
数据共享复杂,需要用IPC;数据是分开的,同步简单 |
因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 |
各有优势 |
内存、CPU |
占用内存多,切换复杂,CPU利用率低 |
占用内存少,切换简单,CPU利用率高 |
线程占优 |
创建销毁、切换 |
创建销毁、切换复杂,速度慢 |
创建销毁、切换简单,速度很快 |
线程占优 |
编程、调试 |
编程简单,调试简单 |
编程复杂,调试复杂 |
进程占优 |
可靠性 |
进程间不会互相影响 |
一个线程挂掉将导致整个进程挂掉 |
进程占优 |
分布式 |
适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单 |
适应于多核分布式 |
进程占优 |
1)需要频繁创建销毁的优先用线程
原因请看上面的对比。
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的
2)需要进行大量计算的优先使用线程
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
这种原则最常见的是图像处理、算法处理。
3)强相关的处理用线程,弱相关的处理用进程
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。
4)可能要扩展到多机分布的用进程,多核分布的用线程
原因请看上面对比。
5)都满足需求的情况下,用你最熟悉、最拿手的方式
至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。
需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。
消耗资源:
从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
通讯方式:
进程之间传递数据只能是通过通讯的方式,即费时又不方便。线程时间数据大部分共享(线程函数内部不共享),快捷方便。但是数据同步需要锁对于static变量尤其注意
线程自身优势:
提高应用程序响应;使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;
改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
9.进程间通信的方式有哪些?各自的优缺点。
进程间通信的方式及比较
进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户控件是互相独立的,一般而言是不能互相访问的,唯一区别的是共享内存区。但是,系统空间却是“公共场所”,所以内核显然可以提供这样的条件。除此之外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其他数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。因为那些通信手段的效率太低了,而人们对进程间通信的要求是要有一定的实时性。
一、Linux下进程间通信的几种主要手段简介:
1、管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
管道包括三种:
- 普通管道PIPE,通常有中限制,一是半双工,只能单向传输;二是只能在父子进程间使用;
- 流管道s_pipe:去除了第一种限制,可以双向传输;
- 命名管道:name_pipe,去除了第二种限制,可以在许多并不相关的进程之间进行通讯。
2、信号(Signal):信号是比较复杂的通信方式,用于通知接收进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;Linux除了支持Unix早期信号语义函数signal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能能统一对外接口,用sigaction函数重新实现了signal函数);
3、报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列systemV消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4、共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其他通信机制,如信号量结合使用,来达到进程间的同步及互斥。
5、信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
6、套接字(Socket):更为一般的进程通信机制,可用于不同机器之间进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其他类Unix系统上:Linux和System V的变种都支持套接字。
二、相关问题
FAQ1:管道与文件描述符,文件指针的关系?
答:其实管道的使用方法与文件类似,都能使用read,write,open等函数。管道描述符类似于文件描述符。事实上,管道使用的描述符,文挨指针和文件描述符最终都会转化成系统中SOCKET描述符,都收到系统内核中SOCKET描述符的限制。本质上Linux内核源码中管道是通过空文件来实现。
FAQ2:管道的使用方法?
答:主要有下面几种方法:
1)pipe,创建一个管道,返回2个管道描述符。通常用于父子进程之间通讯。
2)popen,pclose:这种方式只返回一个管道描述符,常用于通信另一方是stdin or stdout;
3)mkpipe:命名管道,在许多进程之间进行交互。
FAQ3:管道与系统IPC之间的优劣比较?
答:管道:优点是所有的Unix实现都支持,并且在最后一个访问管道的进程终止后,管道就被完全删除;缺陷是管道只允许单向传输或者用于父子进程之间。
系统IPC:优点是功能强大,能在毫不相关进程之间进行通讯;缺陷是关键字KEY_T使用了内核标识,占用了内核资源,而且只能被显式删除,而且不能使用SOCKET的一些机制,例如select,epoll等。
FAQ4:WINDOWS进程间通信与Linux进程间通信的关系?
答:事实上,WINDOWS进程通信大部分移植于Unix,WINDOWS的剪贴板,文件映射都可从Unix进程通信的共享存储中找到影子。
FAQ5:进程间通信与线程间通信之间的关系?
答:因为Windows运行的实体是线程,狭义上的进程间通信其实是指分属于不同进程的线程之间的通讯。而单个进程之间的线程之间的线程同步问题可归并为一种特殊的进程通信。它要用到内核支持的系统调用来保持线程之间同步。通常用到的一些线程同步方法包括:Event,Mutex,信号量Semaphore,临界区资源等。