Linux编程实践教程小结
来源:互联网 发布:js正则表达式匹配数字 编辑:程序博客网 时间:2024/05/25 23:29
编写Linux who
命令
Linux文件操作函数:
- 打开一个文件 int fd = open(char *name, int how)
参数 how
代表文件的打开方式:
- O_RDONLY
, O_WRONLY
和 O_RDWR
代表只读,只写,和读写
通过文件读取数据:
ssize_t numread = read(int fd, void *buf, size_t qty)
fd
文件描述符buf
用来存放数据的目的缓冲区qty
要读取的字节数
关闭文件:
int result = close(int fd)
用户登录信息存放在 utmp
结构中通过打开操作: utmpfd = open(UTMP_FILE, O_RDONLY)
获得
utmp
中 ut_time
代表时间,可以使用 ctime
函数将 long
类型的时间转换成表示时间的字符串: char *ctime(const time_t *timep);
who
命令代码:
#include <sys/types.h>#include <utmp.h>#include <stdlib.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <time.h>#include "utmplib.c"#define SHOWHOSTvoid show_time(long);void show_info(struct utmp * utbufp);int main(){ struct utmp *utbufp, *utmp_next(); // utmp结构,存储读入的信息 if (utmp_open(UTMP_FILE) == -1) { perror(UTMP_FILE); exit(1); } while((utbufp = utmp_next()) != NULLUT) show_info(utbufp); utmp_close(); return 0;}void show_info(struct utmp *utbufp){ if (utbufp->ut_type != USER_PROCESS) return; //修改 只显示当前活动用户 printf("%-8.8s", utbufp->ut_name); printf(" "); printf("%-8.8s", utbufp->ut_line); printf(" "); show_time(utbufp->ut_time); //使用ctime函数转换时间 printf(" ");#ifdef SHOWHOST if (utbufp->ut_host[0] != '\0') printf("(%s)", utbufp->ut_host);#endif printf("\n");}void show_time(long time_val){ struct tm *p; p = localtime(&time_val); printf(" %d-%d-%d %d:%d ",1900+p->tm_year, 1+p->tm_mon, p->tm_mday,p->tm_hour, p->tm_min); //年份加1900 月份加1}
编写
cp
命令: 创建新文件, 从源文件中读取数据写入新文件中- 创建一个新文件:
int fd = creat(char *filename, mode_t mode)
mode
表示新文件的访问模式 即权限向已打开文件写入数据:
ssize_t result = write(int fd, void *buf, size_t amt)
amt
要写入的字节数- 返回值: -1 遇到错误, 或者返回写入的字节数
- 创建一个新文件:
编写 ls
命令: 列出目录内容,显示文件信息
思路: 查看系统 ls
命令 ,得知需要获得当前目录下所有文件和文件夹名称,以及其的信息,包括用户,权限,文件大小,创建时间等。
解决:
- 获取当前目录信息可使用 struct dirent *readdir(DIR *dirp);
函数返回一个结构体,里面包含了:
struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* not an offset; see NOTES */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supported by all filesystem types */ char d_name[256]; /* filename */};
- 获取文件状态信息使用:
int result = stat(char *fname, struct stat *bufp)
bufp
为指向存储信息的 buffer 指针struct stat
结构体包含:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for filesystem I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */};
其中
st_mode
是一个16位的二进制数 每一位表示一个文件类型或者权限信息,使用掩码可以得到具体信息,在<sys/stat.h>
中也定义了一些宏用来表示掩码值,如:S_ISDIR(mode)
判断是否是文件夹if (mode & S_IRUSR)
用来判断用户读权限st_uid
表示用户ID 可用struct passwd *getpwuid(uid_t uid);
函数获得一个表示用户信息的结构体指针,其中包含有用户名:
struct passwd { char *pw_name; /* username */ char *pw_passwd; /* user password */ uid_t pw_uid; /* user ID */ gid_t pw_gid; /* group ID */ char *pw_gecos; /* user information */ char *pw_dir; /* home directory */ char *pw_shell; /* shell program */};
最终代码如下:
#include <string.h>#include <sys/types.h>#include <stdlib.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <dirent.h>#include <grp.h>#include <pwd.h>#include <time.h>#include <malloc.h>#include <sys/ioctl.h>//ls版本3 实现输出排序 和 参数为目录//还有许多不足 如没有检查内存分配成功与否 结构安排不合理 等等int get_win_size(struct winsize *size);void do_ls(char []);void do_ls_l(char []);void do_stat(char *); //打印文件状态void show_file_info(char*, struct stat *);void mode_to_letters(mode_t , char[]);char * uid_to_name(uid_t);char * gid_to_name(gid_t);//按照名称排序static intcompar(const void* p1, const void *p2){ return strcmp((*(struct dirent **)p1)->d_name, (*(struct dirent **)p2)->d_name);}void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));//获取窗口大小int get_win_size(struct winsize *size){ if (isatty(STDOUT_FILENO) == 0) exit(1); if(ioctl(STDOUT_FILENO, TIOCGWINSZ, size)<0) { perror("ioctl TIOCGWINSZ error"); exit(1); } return 0;}//只需列出档期内文件夹下的文件名void do_ls(char * dirname){ size_t total = 0; //记录目录下文件总数 DIR * dir_ptr; struct dirent *direntp; char ** name_array; //保存文件名的数组 size_t array_size = 64; //初始时数组大小是64 size_t max_name_len = 0; //最大文件名长度 字节为单位 struct winsize size; //窗口大小信息 get_win_size(&size); name_array = (char **)malloc(sizeof(char[MAXNAMLEN+1]) * array_size); //一次性分配64个单元 不够再分 if ((dir_ptr = opendir(dirname)) == NULL) //打开目录 { fprintf(stderr, "ls1: cannot open%s\n", dirname); } else { while((direntp = readdir(dir_ptr)) != NULL) //读取目录信息, 存储在目录的结构指针中 { //重新分配内存 if(total == array_size) { array_size = array_size * 2; name_array = (char **)realloc(name_array,sizeof(char[MAXNAMLEN+1]) * array_size); } if (strlen(direntp->d_name) > max_name_len) max_name_len = strlen(direntp->d_name); //更新最大名字长度 *(name_array + total) = direntp->d_name; //向数组里填充数据 total++; //文件数加1 } size_t col = size.ws_col / (max_name_len+1); //每行最多放置的文件数量 size_t i=0 ,j = 0; printf("total:%d\n", total); for(;i < total; i++) { if (**(name_array+i) == '.') continue; else{ printf("%-*s",max_name_len+1, *(name_array + i)); //控制输出宽度 //不能处理文件名含有汉字情况 j++; if( j % col == 0) printf("\n");//达到每行最多数量 回车 } } printf("\n"); closedir(dir_ptr); }}//列出详细信息void do_ls_l(char dirname[]){ size_t total = 0; //记录目录下文件总数 DIR * dir_ptr; struct dirent *direntp; struct dirent ** dparray; //目录数组 size_t array_size = 64; //初始时数组大小是64 dparray = (struct dirent **)malloc(sizeof(direntp) * array_size); //一次性分配64个单元 不够再分 if ((dir_ptr = opendir(dirname)) == NULL) //打开目录 { fprintf(stderr, "ls1: cannot open%s\n", dirname); } else { while((direntp = readdir(dir_ptr)) != NULL) //读取目录信息, 存储在目录的结构指针中 { //重新分配内存 if(total == array_size) { array_size = array_size*2; dparray = (struct dirent **)realloc(dparray,sizeof(direntp) * array_size); } *(dparray + total) = direntp; //向数组里填充数据 total++; //文件数加1 } qsort(dparray, total, sizeof(struct dirent *), compar); //按照名称排序 size_t i = 0; for (i = 0; i < total; i++) do_stat((*(dparray+i))->d_name); closedir(dir_ptr); }}void do_stat(char *filename){ struct stat info; //存储文件状态信息 if(stat(filename, &info) == -1) //stat 将 filename 中的状态信息存储在info中 perror(filename); else show_file_info(filename, &info);}void show_file_info(char* filename, struct stat *infop){ //打印保存在 struct stat中的文件信息 char modestr[11]; //用来存储 文件类型说明 mode_to_letters(infop->st_mode, modestr); //将文件类型转换成合适的形式 printf("%s ", modestr); //文件类型及权限信息 printf("%-4d", (int)infop->st_nlink); //文件当前链接信息 printf("%-8s", uid_to_name(infop->st_uid)); //用户名 printf("%-8s", gid_to_name(infop->st_gid)); //组名 printf("%-12ld", (long)infop->st_size); //文件大小 printf("%.12s", 4 + ctime((const time_t*)&infop->st_mtim)); //修改时间 printf(" %s\n", filename);}void mode_to_letters(mode_t mode, char str[]){ strcpy(str, "----------"); //10个字符 表示文件类型信息 //下面判断文件类型以及权限 if (S_ISDIR(mode)) str[0] = 'd'; if (S_ISCHR(mode)) str[0] = 'c'; //字符设备 if (S_ISBLK(mode)) str[0] = 'b'; //块设备 //判断用户权限权限 if (mode & S_IRUSR) str[1] = 'r'; if (mode & S_IWUSR) str[2] = 'w'; if (mode & S_IXUSR) str[3] = 'x'; //判断组权限 if (mode & S_IRGRP) str[4] = 'r'; if (mode & S_IWGRP) str[5] = 'w'; if (mode & S_IXGRP) str[6] = 'x'; //判断其他用户权限 if (mode & S_IROTH) str[7] = 'r'; if (mode & S_IWOTH) str[8] = 'w'; if (mode & S_IXOTH) str[9] = 'x';}char* uid_to_name(uid_t uid){ struct passwd* pw_p; static char numstr[10]; if ((pw_p = getpwuid(uid)) == NULL) { sprintf(numstr, "%d", uid); //如果获取用户名失败 则打印用户ID return numstr; } else return pw_p->pw_name;}char* gid_to_name(gid_t gid){ struct group * gr_p; static char numstr[10]; if ((gr_p = getgrgid(gid)) == NULL) { sprintf(numstr, "%d", gid); return numstr; } else return gr_p->gr_name;}int main(int ac, char * av[]){ if (ac == 1) do_ls("."); if (ac == 2) { if (*av[1] == '-') do_ls_l("."); else{ chdir(av[1]); do_ls("."); } } if(ac > 2) { int flag = 1; if (*av[1] == '-') flag = 2; while(ac != flag) { chdir(av[ac-1]); if (flag == 2) do_ls_l("."); if (flag == 1) do_ls("."); ac--; } } return 0;}
编写 pwd
命令
Unix 文件由内容和属性两部分组成, 文件内容存放在数据区, 文件属性放在 i-node
中 i-Node
中存放有文件存储数据块列表,目录中存放文件名和对应 i-node
位置
文件在目录中即文件的 iNode
入口和文件名存放在目录中
思路: 得到当前目录中名称为 .
的 iNode:
n
,然后切换到上一目录,得到iNode
为 n
的目录名,一直重复,直到根目录。
pwd
命令中涉及到的系统调用:
- 创建目录:
int result = mkdir(char *pathname, mode_t mode)
- 删除目录,此目录必须为空:
int result = rmdir(const char *path)
- 删除一个链接,即从一个目录文件中删除一个记录:
int result = unlink(const char* path)
- 创建一个文件的链接
int result = link(const char*orig, const char *new)
- 重命名或者删除一个链接
int result = rename(const char *from, const char* to)
- 切换当前工作目录:
int result = chdir(const char *path)
信号处理:
result = signal = (int signum, void(*action)(int))
signum
需响应的信号action
处理信号的函数
其中action
可以是信号处理函数,或者是 SIG_IGN
或 SIG_DFL
分别代表忽略信号或默认处理
时钟编程
设置发送信号的计时器:
unsigned old = alarm(unsigned seconds)
#include <stdio.h>#include <signal.h>int main(){ void wakeup(int); printf("about sleep for 4 seconds\n"); signal(SIGALRM, wakeup); //设置时钟信号处理函数 wakeup alarm(4); //设置时钟 隔四秒发送时钟信号 pause(); //挂起等待信号 printf("Morning so soon?\n");}void wakeup(int signum){ printf("Alarm received from kernal\n");}
使用间隔计时器:
在间隔计时器设置的结构体中有两个参数,it_value
和it_interval
.分别表示初始时间和间隔时间.
通过使用getitimer
和settimer
可以获得和设置间隔计时器:result = getitimer(int which, struct itimerval *val);
result = setitimer(int which, cosnt struct itimerval *newval, struct itimerval *oldval);
which
获取或设置的计时器val
指向向当前设置值的指针newval
指向要被设置值的指针oldval
指向被替换的设置值的指针
示例代码:
#include <stdio.h>#include <sys/time.h>#include <signal.h>#include <stdlib.h>void countdown(int);int set_ticker(int);int main(int argc, const char *argv[]){ signal(SIGALRM, countdown); if (set_ticker(500) == -1) perror("set_ticker"); else while(1) pause(); return 0;}void countdown(int signum){ static int num = 10; printf("%d..", num--); fflush(stdout); if (num < 0){ printf("DONE!\n"); exit(0); }}int set_ticker(int n_msecs){ struct itimerval new_timeset; long n_sec, n_usecs; //设置秒数和微秒数 n_sec = n_msecs / 1000; n_usecs = (n_msecs % 1000) * 1000L; new_timeset.it_interval.tv_sec = n_sec; new_timeset.it_interval.tv_usec = n_usecs; new_timeset.it_value.tv_sec = n_sec; new_timeset.it_value.tv_usec = n_usecs; return setitimer(ITIMER_REAL, &new_timeset, NULL);}
进程和程序:
- 在指定的路径中查找并执行一个文件
result = execvp(const char *file, const char *argv[])
- 建立一个新进程
pid_t result = fork()
在父进程中返回值是子进程的pid
,在子进程中返回的是0. - 等待进程的结束:
pid_t result = wait(int *statusptr)
示例代码:
//通过输入参数执行系统命令的程序 带提示符 能执行程序#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <signal.h>#define MAXARGS 20 //最大命令数量#define ARGLEN 100 //最大命令长度int execute(char* arglist[]); //执行命令函数char* makestring(char* buf); //将输入字符转换成字符串int main(){ char *arglist[MAXARGS+1]; int numargs; char argbuf[ARGLEN]; numargs = 0; while(numargs < MAXARGS) { printf("Arg[%d]?", numargs); //提示输入参数 if (fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n') { //添加退出条件 if (strncmp(argbuf,"exit",strlen(argbuf)-1) == 0 || *argbuf == EOF) { exit(1); } arglist[numargs++] = makestring(argbuf); } else { if (numargs > 0) { arglist[numargs] = NULL; int fork_ret = 0; fork_ret = fork(); if (fork_ret == -1) perror("fork error"); else if (fork_ret == 0) execute(arglist); else { signal(SIGINT, SIG_IGN); //父进程忽略中断信号 wait(NULL); //等待子进程退出 numargs = 0; continue; } } } } return 0;}int execute(char *arglist[]){ execvp(arglist[0], arglist); perror("execvp"); exit(1);}char * makestring(char *buf){ char *cp; buf[strlen(buf) - 1] = '\0'; cp = (char *)malloc(strlen(buf) + 1); if (cp == NULL) { fprintf(stderr, "no memory\n"); exit(1); } strcpy(cp, buf); return cp;}
输入输出重定向
int dup(int fd);
int dup2(int oldfd, int newfd);
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>//使用dup dup2重定向//dup : open -> close -> dup -> close//dup2: open -> dup2 -> close//#define CLOSE_DUPint main(int argc, const char *argv[]){ int fd; int newfd; char line[BUFSIZ]; fgets(line, 100, stdin); printf("%s\n", line); if ((fd = open("/etc/passwd", O_RDONLY)) == -1) { perror("Can't open file as fd 0\n"); exit(1); }#ifdef CLOSE_DUP close(0); //关闭原来使用的输入文件描述符 newfd = dup(fd); //将新打开的文件描述符链接到 最小可用文件描述符(即上一行关闭 的 0)#else newfd = dup2(fd, 0); //完成 close(0) 和 dup(fd) 两步#endif if (newfd != 0) { fprintf(stderr, "Can't duplicate fd to 0\n"); exit(1); } fgets(line, 100, stdin); printf("%s\n", line); return 0;}
- 管道
int pipe(int pipearray[2]);
向pipearray[1]
写 从pipearray[0]
读
#include <stdio.h> #include <unistd.h> #include <stdlib.h> //管道 int main() { int len, apipe[2]; char buf[BUFSIZ]; apipe[0] = 3;apipe[1] = 4; //管道从fd=4写入 从fd=3 读出 if (pipe(apipe) == -1) //创建管道 { perror("make pipe err:"); exit(1); } while(fgets(buf, BUFSIZ, stdin)) //从标准输入读取 { len = strlen(buf); //写入管道 if (write(apipe[1], buf, len) == -1) { perror("write pipe err:"); exit(1); } //从管道读出 len = read(apipe[0], buf, BUFSIZ); if(len == -1) { perror("reading from pipe:"); exit(1); } //向标准输出写 if (write(1, buf, len) != len) { perror("writing to stdout:"); exit(1); } } return 0; }
父进程和子进程共享打开的文件描述符和管道
同一个管道既有父进程的两个文件描述符pipe[0] pipe[1]
链接 也有子进程的pipe[0] pipe[1]
链接
管道同一时刻只能有一个进程读或写。 当管道中没有数据时,读或写被阻塞,当管道的一段被关闭,对管道另一端的读或写将返回错误码EPIPE
.
FILE* fdopen(int fd, char* mode)
能像操作文件一样操作管道:
...char mypipe[2];pipe(mypipe);FILE *fp;fp = fdopen(mypipe[0], "r");...
popen
能像操作文件一样,打开一个指向进程的带缓冲的连接:
FILE* fp;fp = popen("ls", "r");fgets(buf, len, fp); //将fp指向的流读入 bufpclose(fp);
- 管道在一个进程中被创建,通过
fork
来实现共享。所以,管道只能链接一个主机上的相关进程。socket
可以在不同主机进程之间创建连接。
socket
相关接口:
服务器端:
sockid = socket(int domain, int type, int protocol)
2. reslult = bind(int sockid, struct sockaddr *addrp, socklen_t addrlen)
其中 sockaddr
定义如下:
struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */}
bind
用来将一个地址分配给 socket
,在这之前应该先初始化 sockaddr
的成员。
result = listen(int sockid, int qsize)
listen
请求内核允许制定的 socket
接受接入呼叫
fd = accept(int sockid, struct sockaddr *callerid, socklen_t *addrlenp)
accept
阻塞当前进程,一直到制定的 socket
上的接入连接被建立起来,然后 accept
将返回文件描述符,次文件描述符链接到呼叫进程的某个文件描述符。
result = connect(int sockid, struct sockaddr *serv_addrp,
socklen_t addrlen)
connect
把 sockid
表示的socket
和由 serv_addrp
所指向的socket
地址相连接 connect
用于客户端建立链接
sendto
从socket 发送消息
nchars = sendto (int socket, const void *msg, size_t len, int flags, const struct sockaddr *dest, socklen_t dest_len)
sendto
用于UDP链接, TCP使用send
函数
nchars = recvfrom(int socket, const void *msg, size_t len,
int flags, const struct sockaddr *sender, socklen_t *sender_len)
从socket接收消息
recvfrom
用于UDP链接 TCP使用 recv
`int getpeername(int sockfd, struct sockaddr* addr, socklen_t *addrlen);`
获得远端的sockaddrint getsockname(int sockfd, struct sockaddr* addr, socklen_t *addlen);
得到自身的sockaddr
多线程相关函数
- 创建一个新的线程
int pthread_create(pthread_t *thread, pthread_attr_t *attr,
void *(*func)(void *), void *arg)
2. int pthread_join(pthread_t thread, void * *retval)
等待某线程终止
3. 互斥锁: int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
- 线程间使用条件变量通信:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_signal(pthread_cond_t *cond)
示例代码:
/* * twordcount3.c * 多线程统计文件字符数量 * Created on: 2017年4月9日 * Author: yang * 通过使用信号量实现线程和主进程的通信 */#include <stdlib.h>#include <stdio.h>#include <pthread.h>#include <ctype.h>struct arg_set{ char *fname; int count;};void *count_words(void *a);struct arg_set *mailbox;pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t flag = PTHREAD_COND_INITIALIZER;int main(int argc, char **argv) { pthread_t t1,t2; struct arg_set args1, args2; int reports_in = 0; int total_words = 0; if (argc != 3) { printf("usage: %s file1 file2", argv[0]); exit(1); } pthread_mutex_lock(&lock); //对信号箱加锁 args1.fname = argv[1]; args1.count = 0; pthread_create(&t1, NULL, count_words, (void*)& args1); args2.count = 0; args2.fname = argv[2]; pthread_create(&t2, NULL, count_words, (void *) &args2); while (reports_in < 2) { printf("MAIN: waiting for flag to go up\n" ); pthread_cond_wait(&flag, &lock); //等待线程信号 printf("MAIN: flag was raised\n"); printf("%7d: %s\n", mailbox->count, mailbox->fname); total_words += mailbox->count; if (mailbox == &args1) pthread_join(t1, NULL); //收回线程 if (mailbox == &args2); pthread_join(t2, NULL); mailbox = NULL; pthread_cond_signal(&flag); //发送消息 通知线程 mailbox 为空 可用 reports_in++; } printf("%7d: total words\n", args1.count + args2.count);}void *count_words(void *a){ struct arg_set *args = (struct arg_set *)a; FILE *fp; int c, prevc = '\0'; if ((fp = fopen(args->fname, "r")) != NULL) { while( (c = getc(fp)) != EOF) { if (!isalnum(c) && isalnum(prevc)) { args->count++; } prevc = c; } fclose(fp); } else perror(args->fname); printf("COUNT: waiting to get lock\n" ); pthread_mutex_lock(&lock); //得到 mailbox 锁的控制权 printf("COUNT: have lock, storing data\n" ); if (mailbox != NULL) pthread_cond_wait(&flag, &lock); //如果 mailbox 不为空 则等待 mailbox = args; printf("COUNT: raising flag\n"); pthread_cond_signal(&flag); //向主线程发送信号 表明数据存储完毕 printf("COUNT: unlocking box\n"); pthread_mutex_unlock(&lock); return NULL;}
- Linux编程实践教程小结
- Unix/Linux编程实践教程
- Unix/Linux编程实践教程
- Unix-linux编程实践教程
- 《unix/linux编程实践教程》之管道
- unix/linux编程实践教程--more命令
- unix/linux编程实践教程:who命令
- unix/linux编程实践教程:ls命令
- unix/linux编程实践教程:pwd命令
- unix/linux编程实践教程:学习stty
- unix/linux编程实践教程------学习笔记
- Unix/Linux编程实践教程 笔记一
- Unix/Linux编程实践教程–书评
- 《unix/linux编程实践教程》之Shell编程一
- 《unix/linux编程实践教程》之Shell编程二
- unix/linux编程实践教程:数据报编程
- Unix/linux 编程实践教程 ---------- 服务器-客户端编程
- linux开发实践小结
- VLC网页插件添加对火狐浏览器的支持
- 理解Hibernate中的query.setFirstResult(),query.setMaxResults(),取出任意n条数据
- mysql 中文乱码
- 3S技术
- windows下git修改打开bash时的默认路径
- Linux编程实践教程小结
- eclipse3.7安装flash builder 4.7插件
- 润乾报表一个页面中的echarts地图与其他区块的联动
- 百度地图显示指定位置,点击显示自定义内容
- 理解Aode Air,理解RIA开发
- MyBatis--索引号实现多条件查询
- hdu2033(惭愧)
- XML的解析
- plsql访问本机(win7 64位操作系统)数据库,报ORA-12514: TNS:listener does not currently.错误解决