并发性、共享性、虚拟性、不确定性。
1)进程是指在系统中正在运行的一个应用程序,程序一旦运行就是进程;
2)进程可以认为是程序执行的一个实例,进程是系统进行资源分配的最小单位,且每个进程拥有独立的地址空间;
3)一个进程无法直接访问另一个进程的变量和数据结构,如果希望一个进程去访问另一个进程的资源,需要使用进程间的通信,比如:管道、消息队列等
4)线程是进程的一个实体,是进程的一条执行路径;比进程更小的独立运行的基本单位,线程也被称为轻量级进程,一个程序至少有一个进程,一个进程至少有一个线程;
进程是程序的一次执行,该程序可以与其他程序并发执行;
进程有运行、阻塞、就绪三个基本状态;
进程调度算法:先来先服务调度算法、短作业优先调度算法、非抢占式优先级调度算法、抢占式优先级调度算法、高响应比优先调度算法、时间片轮转法调度算法;
1)同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间;
2)同一进程内的线程共享本进程的资源,但是进程之间的资源是独立的;
3)一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程崩溃,所以多进程比多线程健壮;
4)进程切换,消耗的资源大。所以涉及到频繁的切换,使用线程要好于进程;
5)两者均可并发执行;
6)每个独立的进程有一个程序的入口、程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
1)新状态:进程已经创建
2)就绪态:进程做好了准备,准备执行,等待分配处理机
3)执行态:该进程正在执行;
4)阻塞态:等待某事件发生才能执行,如等待I/O完成;
5)终止状态
1)fork函数创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容;
2)vfork创建的子进程与父进程共享数据段,而且由vfork创建的子进程将先于父进程运行;
3)linux上创建线程一般使用的是pthread库,实际上linux也给我们提供了创建线程的系统调用,就是clone;
1)函数原型
pid_t fork(void); //void代表没有任何形式参数
2)除了0号进程(系统创建的)之外,linux系统中都是由其他进程创建的。创建新进程的进程,即调用fork函数的进程为父进程,新建的进程为子进程。
3)fork函数不需要任何参数,对于返回值有三种情况:
①对于父进程,fork函数返回新建子进程的pid;
②对于子进程,fork函数返回 0;
③如果出错, fork 函数返回 -1。
int pid=fork();
if(pid < 0){
//失败,一般是该用户的进程数达到限制或者内存被用光了
…
}
else if(pid == 0){
//子进程执行的代码
…
}
else{
//父进程执行的代码
…
}
1)在Linux系统中实现父子进程的通信可以采用pipe()和fork()函数进行实现;
2)对于父子进程,在程序运行时首先进入的是父进程,其次是子进程,在此我个人认为,在创建父子进程的时候程序是先运行创建的程序,其次在复制父进程创建子进程。fork()函数主要是以父进程为蓝本复制一个进程,其ID号和父进程的ID号不同。对于结果fork出来的子进程的父进程ID号是执行fork()函数的进程的ID号。
3)管道:是指用于连接一个读进程和一个写进程,以实现它们之间通信的共享文件,又称pipe文件。
4)写进程在管道的尾端写入数据,读进程在管道的首端读出数据。
1)进程是程序的一次动态执行,属于动态概念;
2)一个进程可以执行一个或几个程序,同一个程序可由几个进程执行;
3)程序可以作为一种软件资源长期保留,而进程是程序的一次执行;
4)进程具有并发性,能与其他进程并发执行;
5)进程是一个独立的运行单位;
所谓死锁,是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。很显然,如果没有外力的作用,那麽死锁涉及到的各个进程都将永远处于封锁状态。当两个或两个以上的进程同时对多个互斥资源提出使用要求时,有可能导致死锁。
〈1〉互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如独木桥就是一种独占资源,两方的人不能同时过桥。
〈2〉不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。
〈3〉占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。
〈4〉循环等待条件。存在一个进程等待序列{P1,P2,…,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,…,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。
死锁的预防是保证系统不进入死锁状态的一种策略。它的基本思想是要求进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
<1>打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。
<2>打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。
<3>打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。
<4>打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁
死锁避免:银行家算法
假设的前提是,这样的问题出现的概率很低。比如,在操作系统中,为应对死锁问题,可以采用这样的一种办法。当系统发生死锁时不会对用户造成多大影响,或系统很少发生死锁的场合采用允许死锁发生的鸵鸟算法,这样一来可能开销比不允许发生死锁及检测和解除死锁的小。如果死锁很长时间才发生一次,而系统每周都会因硬件故障、编译器错误或操作系统错误而崩溃一次,那么大多数工程师不会以性能损失或者易用性损失的代价来设计较为复杂的死锁解决策略,来消除死锁。鸵鸟策略的实质:出现死锁的概率很小,并且出现之后处理死锁会花费很大的代价,还不如不做处理,OS中这种置之不理的策略称之为鸵鸟策略(也叫鸵鸟算法)。
在避免死锁的方法中,所施加的限制条件较弱,有可能获得令人满意的系统性能。在该方法中把系统的状态分为安全状态和不安全状态,只要能使系统始终都处于安全状态,便可以避免发生死锁。
银行家算法的基本思想是分配资源之前,判断系统是否是安全的;若是,才分配。它是最具有代表性的避免死锁的算法。
设进程cusneed提出请求REQUEST [i],则银行家算法按如下规则进行判断。
(1)如果REQUEST [cusneed] [i]<= NEED[cusneed][i],则转(2);否则,出错。
(2)如果REQUEST [cusneed] [i]<= AVAILABLE[i],则转(3);否则,等待。
(3)系统试探分配资源,修改相关数据:
AVAILABLE[i]-=REQUEST[cusneed][i];
ALLOCATION[cusneed][i]+=REQUEST[cusneed][i];
NEED[cusneed][i]-=REQUEST[cusneed][i];
(4)系统执行安全性检查,如安全,则分配成立;否则试探险性分配作废,系统恢复原状,进程等待。
1)管道
管道,通常指无名管道。
①半双工的,具有固定的读端和写端;
②只能用于具有亲属关系的进程之间的通信;
③可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write函数。但是它不是普通的文件,并不属于其他任何文件系统,只能用于内存中。
④Int pipe(int fd[2]);当一个管道建立时,会创建两个文件文件描述符,要关闭管道只需将这两个文件描述符关闭即可。
2)FiFO(有名管道)
①FIFO可以再无关的进程之间交换数据,与无名管道不同;
②FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中;
③Int mkfifo(const char* pathname,mode_t mode);
3)消息队列
①消息队列,是消息的连接表,存放在内核中。一个消息队列由一个标识符来标识;
②消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;
③消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除;
④消息队列可以实现消息的随机查询
4)信号量
①信号量是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据;
②信号量用于进程间同步,若要在进程间传递数据需要结合共享内存;
③信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作;
5)共享内存
①共享内存,指两个或多个进程共享一个给定的存储区;
②共享内存是最快的一种进程通信方式,因为进程是直接对内存进行存取;
③因为多个进程可以同时操作,所以需要进行同步;
④信号量+共享内存通常结合在一起使用。
1)线程同步是指多线程通过特定的设置来控制线程之间的执行顺序,也可以说在线程之间通过同步建立起执行顺序的关系;
2)主要四种方式,临界区、互斥对象、信号量、事件对象;其中临界区和互斥对象主要用于互斥控制,信号量和事件对象主要用于同步控制;
3)临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快、适合控制数据访问。在任意一个时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
4)互斥对象:互斥对象和临界区很像,采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程同时访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其他线程访问该资源。
5)信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最 大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1 ,只要当前可用资源计数是大于0 的,就可以发出信号量信号。但是当前可用计数减小 到0 时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离 开的同时通过ReleaseSemaphore ()函数将当前可用资源计数加1 。在任何时候当前可用资源计数决不可能大于最大资源计数。
6)事件对象:通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。
1)页是信息的物理单位,分页是由于系统管理的需要。段是信息的逻辑单位,分段是为了满足用户的要求。
2)页的大小固定且由系统决定,段的长度不固定,决定于用户所编写的程序,通常由编译程序在对源程序紧进行编译时,根据信息的性质来划分。
3)分页的作业的地址空间是一维的,程序员只需要利用一个记忆符,即可表示一个地址。分段的作业地址空间则是二维的,程序员在标识一个地址时,既需要给出段名,又需要给出段的地址值。
1、一般情况下,子进程是由父进程创建,而子进程和父进程的退出是无顺序的,两者之间都不知道谁先退出。正常情况下父进程先结束会调用 wait 或者 waitpid 函数等待子进程完成再退出,而一旦父进程不等待直接退出,则剩下的子进程会被init(pid=1)进程接收,成会孤儿进程。(进程树中除了init都会有父进程)。
2、如果子进程先退出了,父进程还未结束并且没有调用 wait 或者 waitpid 函数获取子进程的状态信息,则子进程残留的状态信息( task_struct 结构和少量资源信息)会变成僵尸进程。
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。
3、守护进程( daemon) 是指在后台运行,没有控制终端与之相连的进程。它独立于控制终端,通常周期性地执行某种任务 。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。
1.守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。
2.守护进程特点
1)守护进程最重要的特性是后台运行。
2)守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
3)守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(shell)执行。
3.实现
1)在父进程中执行fork并exit推出;
2)在子进程中调用setsid函数创建新的会话;
3)在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;
4)在子进程中调用umask函数,设置进程的umask为0;
5)在子进程中关闭任何不需要的文件描述符
1)一个程序至少有一个进程,一个进程至少有一个线程
2)线程的划分尺度小于进程,使得多线程程序的并发性高
3)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
4)每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
5)多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配
6)一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。
线程私有:线程栈,寄存器,程序寄存器
共享:堆,地址空间,全局变量,静态变量
进程私有:地址空间,堆,全局变量,栈,寄存器
共享:代码段,公共数据,进程目录,进程ID
1)线程在程序中是独立的,并发的执行流,但是,进程中的线程之间的隔离程度要小;
2)线程比进程更具有更高的性能,这是由于同一个进程中的线程都有共性:多个线程将共享同一个进程虚拟空间;
3)当操作系统创建一个进程时,必须为进程分配独立的内存空间,并分配大量相关资源;
1)需要频繁创建销毁的优先用线程;
2)需要进行大量计算的优先使用线程;
3)强相关的处理用线程,弱相关的处理用进程;
4)可能要扩展到多机分布的用进程,多核分布的用线程;
1)是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程;协程不是被操作系统内核管理,而完全是由程序所控制。
2)协程的开销远远小于线程;
3)协程拥有自己寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈。
4)每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据和其他资源。
5)跨平台、跨体系架构、无需线程上下文切换的开销、方便切换控制流,简化编程模型;
6)协程又称为微线程,协程的完成主要靠yeild关键字,协程执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行;
7)协程极高的执行效率,和多线程相比,线程数量越多,协程的性能优势就越明显;
8)不需要多线程的锁机制;
1)线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
2)读写锁从广义的逻辑上讲,也可以认为是一种共享版的互斥锁。如果对一个临界区大部分是读操作而只有少量的写操作,读写锁在一定程度上能够降低线程互斥产生的代价。
3)Mutex可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。
1)系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
2)异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
3)外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
①关中断,进入不可再次响应中断的状态,由硬件实现。
②保存断点,为了在中断处理结束后能正确返回到中断点。由硬件实现。
③将中断服务程序入口地址送PC,转向中断服务程序。可由硬件实现,也可由软件实现。
④保护现场、置屏蔽字、开中断,即保护CPU中某些寄存器的内容、设置中断处理次序、允许更高级的中断请求得到响应,实现中断嵌套。由软件实现。
⑤设备服务,实际上有效的中断处理工作是在此程序段中实现的。由软件程序实现
⑥退出中断。在退出时,又应进入不可中断状态,即关中断、恢复屏蔽字、恢复现场、开中断、中断返回。由软件实现。
1)内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在0级特权级上时,就可以称之为运行在内核态。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。
2)这两种状态的主要差别是: 处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的 ; 而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。
1)CPU中断是什么
①计算机处于执行期间;
②系统内发生了非寻常或非预期的急需处理事件;
③CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序;
④处理完毕后返回原来被中断处继续执行;
2)CPU中断的作用
①可以使CPU和外设同时工作,使系统可以及时地响应外部事件;
②可以允许多个外设同时工作,大大提高了CPU的利用率;
③可以使CPU及时处理各种软硬件故障。
1.执行用户程序(如:fork)
2. 根据glibc中的函数实现,取得系统调用号并执行int $0x80产生中断。
3. 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式)
4. 进行中断处理,根据系统调用表调用内核函数。
5. 执行内核函数。
6. 执行RESTORE_ALL并返回用户模式
1)系统调用
①操作系统提供给用户程序调用的一组特殊的接口。用户程序可以通过这组特殊接口来获得操作系统内核提供的服务;
②系统调用可以用来控制硬件;设置系统状态或读取内核数据;进程管理,系统调用接口用来保证系统中进程能以多任务在虚拟环境下运行;
③Linux中实现系统调用利用了0x86体系结构中的软件中断;
2)函数调用
①函数调用运行在用户空间;
②它主要是通过压栈操作来进行函数调用;
3)区别
1)虚拟内存,虚拟内存是一种内存管理技术,它会使程序自己认为自己拥有一块很大且连续的内存,然而,这个程序在内存中不是连续的,并且有些还会在磁盘上,在需要时进行数据交换;
2)优点:可以弥补物理内存大小的不足;一定程度的提高反应速度;减少对物理内存的读取从而保护内存延长内存使用寿命;
3)缺点:占用一定的物理硬盘空间;加大了对硬盘的读写;设置不得当会影响整机稳定性与速度。
4)虚拟地址空间是对于一个单一进程的概念,这个进程看到的将是地址从0000开始的整个内存空间。虚拟存储器是一个抽象概念,它为每一个进程提供了一个假象,好像每一个进程都在独占的使用主存。每个进程看到的存储器都是一致的,称为虚拟地址空间。从最低的地址看起:程序代码和数据,堆,共享库,栈,内核虚拟存储器。大多数计算机的字长都是32位,这就限制了虚拟地址空间为4GB。
1)如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
2)线程安全问题都是由全局变量及静态变量引起的。
3)若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
4)对于线程不安全的对象我们可以通过如下方法来实现线程安全:
①加锁 利用Synchronized或者ReenTrantLock来对不安全对象进行加锁,来实现线程执行的串行化,从而保证多线程同时操作对象的安全性,一个是语法层面的互斥锁,一个是API层面的互斥锁.
②非阻塞同步来实现线程安全。原理就是:通俗点讲,就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生冲突,那就再采取其他措施(最常见的措施就是不断地重试,知道成功为止)。这种方法需要硬件的支持,因为我们需要操作和冲突检测这两个步骤具备原子性。通常这种指令包括CAS SC,FAI TAS等。
③线程本地化,一种无同步的方案,就是利用Threadlocal来为每一个线程创造一个共享变量的副本来(副本之间是无关的)避免几个线程同时操作一个对象时发生线程安全问题。
1.层次分析
1)用户层,日常使用的各种程序,需要的接口主要是文件的创建、删除、读、写、关闭等;
2)VFS层,文件相关的操作都有对应的System Call函数接口,接口调用VFS对应的函数;
3)文件系统层,用户的操作通过VFS转到各种文件系统。文件系统把文件读写命令转化为对磁盘LBA的操作,起了一个翻译和磁盘管理的工作;
4)缓存层;
5)块设备层,块设备接口Block Device是用来访问磁盘LBA的层级,读写命令组合之后插入到命令队列,磁盘的驱动从队列读命令执行;
6)磁盘驱动层;
7)磁盘物理层;
2.读取文件过程
1)根据文件所在目录的inode信息,找到目录文件对应数据块;
2)根据文件名从数据块中找到对应的inode节点信息;
3)从文件inode节点信息中找到文件内容所在数据块块号;
4)读取数据块内容
1)同步
就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。就是我调用一个功能,该功能没有结束前,我死等结果。
2)异步
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
3)阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
4)非阻塞
指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。就是调用我(函数),我(函数)立即返回,通过select通知调用者。
1)阻塞I/O
应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好。 如果数据没有准备好,一直等待….数据准备好了,从内核拷贝到用户空间,IO函数返回成功指示。
2)非阻塞I/O
我们把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。
3)I/O复用
I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这三个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。
4)信号驱动I/O
首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
5)异步I/O
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作。
1)IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了。Linux中,提供了select、poll、epoll三种接口函数来实现IO复用。
2)Select
select的缺点:
①单个进程能够监视的文件描述符的数量存在最大限制,通常是1024。由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;
②内核/用户空间内存拷贝问题,select需要大量句柄数据结构,产生巨大开销;
③Select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生事件;
④Select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么每次select调用还会将这些文件描述符通知进程。
3)Poll
与select相比,poll使用链表保存文件描述符,一你才没有了监视文件数量的限制,但其他三个缺点依然存在
4)Epoll
上面所说的select缺点在epoll上不复存在,epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。Epoll是事件触发的,不是轮询查询的。没有最大的并发连接限制,内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递。
区别总结:
1)支持一个进程所能打开的最大连接数
①Select最大1024个连接,最大连接数有FD_SETSIZE宏定义,其大小是32位整数表示,可以改变宏定义进行修改,可以重新编译内核,性能可能会影响;
②Poll没有最大连接限制,原因是它是基于链表来存储的;
③连接数限数有上限,但是很大;
2)FD剧增后带来的IO效率问题
①因为每次进行线性遍历,所以随着FD的增加会造成遍历速度下降,效率降低;
②Poll同上;
③因为epool内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的现象下降的性能问题。
3)消息传递方式
①Select内核需要将消息传递到用户空间,都需要内核拷贝;
②Poll同上;
③Epoll通过内核和用户空间共享来实现的。
epoll 的 LT 和 ET 模式的理解:
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger),LT是默认模式。
区别:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
1)在固定式分区分配中, 为将一个用户作业装入内存, 内存分配程序从系统分区表中找出一个能满足作业要求的空闲分区分配给作业, 由于一个作业的大小并不一定与分区大小相等, 因此, 分区中有一部分存储空间浪费掉了. 由此可知, 固定式分区分配中存在内碎片.
2)在可变式分区分配中, 为把一个作业装入内存, 应按照一定的分配算法从系统中找出一个能满足作业需求的空闲分区分配给作业, 如果这个空闲分区的容量比作业申请的空间容量要大, 则将该分区一分为二, 一部分分配给作业, 剩下的部分仍然留作系统的空闲分区。由此可知,可变式分区分配中存在外碎片.
3)伙伴系统
4)据可移动性组织页避免内存碎片
1)基本原理
第一:每一级的函数调用都有它自己的变量。
第二:每一次函数调用都会有一次返回,并且是某一级递归返回到调用它的那一级,而不是直接返回到main()函数中的初始调用部分。
第三:递归函数中,位于递归调用前的语句和各级被调函数具有相同的执行顺序。例如在上面的程序中,打印语句#1位于递归调用语句之前,它按照递归调用的顺序被执行了4次,即依次为第一级、第二级、第三级、第四级。
第四:递归函数中,位于递归调用后的语句的执行顺序和各个被调函数的顺序相反。例如上面程序中,打印语句#2位于递归调用语句之后,其执行顺序依次是:第四级、第三级、第二级、第一级。(递归调用的这种特性在解决涉及到反向顺序的编程问题中很有用,下文会说到)
第五:虽然每一级递归都有自己的变量,但是函数代码不会复制。
第六:递归函数中必须包含终止递归的语句。通常递归函数会使用一个if条件语句或其他类似语句一边当函数参数达到某个特定值时结束递归调用,如上面程序的if(n > 4)。
2)用递归实现算法时,有两个因素是至关重要的:递归式和递归边界;
3)函数调用时通过栈(Stack)来实现的,每当调用一个函数,栈就会加一层栈帧,函数返回就减一层栈帧。而栈资源有限,当递归深度达到一定程度后,就会出现意想不到的结果,比如堆栈溢出;
4)利用循环函数或者栈加while循环来代替递归函数。
i++的操作分三步:
(1)栈中取出i
(2)i自增1
(3)将i存到栈
所以i++不是原子操作,上面的三个步骤中任何一个步骤同时操作,都可能导致i的值不正确自增
二.++i
在多核的机器上,cpu在读取内存i时也会可能发生同时读取到同一值,这就导致两次自增,实际只增加了一次。
综上,我认为i++和++i都不是原子操作。
1)一个进程对应一个页表,分页存储机制,一个进程对应很多页,执行进程时并不是所有页装入内存中,部分装入内存,当需要的那页不存在内存中,将发生缺页中断,将需要的那页从外存中调入内存中;
2)页表寻址,页分为页号(从0开始编号)与页内偏移地址,两个寄存器,页表基地址寄存器,页表长度寄存器,块表;页的大小相同,内存中的块与页大小相同,页大小相同,页在逻辑上连续在物理上不连续;
3)调页算法:先进先出,最佳页面置换算法(OPT),最近最久未使用(NRU),最近最少使用置换算法(LRU),先进先出算法(FIFO)会导致Baley问题;抖动,页面在内存与外存中的频繁调页;
4)程序局部性原理,时间局部性、空间局部性;
1)用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据的时间戳自增,并将新数据时间戳置为0插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项时间戳置为0。当数组空间已经满时,将时间戳最大的数据项淘汰;
2)利用一个链表来实现,每次新插入数据的时候将新数据插入到链表头部;每次缓存命中,则将数据移动到链表头部;那么当链表满时,就将链表尾部的数据丢弃;
3)利用链表和hashmap。当需要插入新的数据项 的时候,如果新数据命中,则把该节点放到链表头部,如果不存在,则将新数据放在链表头部。若缓存满了,则将链表尾部的节点删除。
1)固态分区,分区大小固定,但并不一定相同;
2)可变分区,分区大小动态变化,首先适配、最佳适配、最差适配、下一次适配;
1)伙伴系统是一种经典的内存管理方法。Linux伙伴系统的引入为内核提供了一种用于分配一组连续的页而建立的一种高效的分配策略,并有效的解决了外碎片问题。
2)Linux中的内存管理的“页”大小为4KB。把所有的空闲页分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页块。最大可以申请1024个连续页,对应4MB大小的连续内存。每个页块的第一个页的物理地址是该块大小的整数倍。
3)当向内核请求分配(2(i-1),2i]数目的页块时,按照2^i页块请求处理。如果对应的块链表中没有空闲页块,则在更大的页块链表中找。当分配的页块中有多余的页时,伙伴系统根据多余的页框大小插入到对应的空闲页块链表中。
当释放单页的内存时,内核将其置于CPU高速缓存中,对很可能出现在cache的页,则放到“快表”的列表中。在此过程中,内核先判断CPU高速缓存中的页数是否超过一定“阈值”,如果是,则将一批内存页还给伙伴系统,然后将该页添加到CPU高速缓存中。
当释放多页的块时,内核首先计算出该内存块的伙伴的地址。内核将满足以下条件的三个块称为伙伴:(1)两个块具有相同的大小,记作b。(2)它们的物理地址是连续的。(3)第一块的第一个页的物理地址是2*(2^b)的倍数。如果找到了该内存块的伙伴,确保该伙伴的所有页都是空闲的,以便进行合并。内存继续检查合并后页块的“伙伴”并检查是否可以合并,依次类推。
4)内核将已分配页分为以下三种不同的类型:
不可移动页:这些页在内存中有固定的位置,不能够移动。
可回收页:这些页不能移动,但可以删除。内核在回收页占据了太多的内存时或者内存短缺时进行页面回收。
可移动页:这些页可以任意移动,用户空间应用程序使用的页都属于该类别。它们是通过页表映射的。当它们移动到新的位置,页表项也会相应的更新。
1)直接I/O(轮询)
程序查询方式也称为程序轮询方式,该方式采用用户程序直接控制主机与外部设备之间输入/输出操作。CPU必须不停地循环测试I/O设备的状态端口,当发现设备处于准备好(Ready)状态时,CPU就可以与I/O设备进行数据存取操作。这种方式下的CPU与I/O设备是串行工作的,输入/输出一般以字节或字为单位进行。这个方式频繁地测试I/O设备,I/O设备的速度相对来说又很慢,极大地降低了CPU的处理效率,并且仅仅依靠测试设备状态位来进行数据传送,不能及时发现传输中的硬件错误。
2)中断
当I/O设备结束(完成、特殊或异常)时,就会向CPU发出中断请求信号,CPU收到信号就可以采取相应措施。当某个进程要启动某个设备时,CPU就向相应的设备控制器发出一条设备I/O启动指令,然后CPU又返回做原来的工作。CPU与I/O设备可以并行工作,与程序查询方式相比,大大提高了CPU的利用率。但是在中断方式下,同程序查询方式一样,也是以字节或字为单位进行。但是该方法大大降低了CPU的效率,因为当中断发生的非常频繁的时候,系统需要进行频繁的中断源识别、保护现场、中断处理、恢复现场。这种方法对于以“块”为存取单位的块设备,效率是低下的。
3)DMA
DMA方式也称为直接主存存取方式,其思想是:允许主存储器和I/O设备之间通过“DMA控制器(DMAC)”直接进行批量数据交换,除了在数据传输开始和结束时,整个过程无须CPU的干预。每传输一个“块”数据只需要占用一个主存周期。
4)通道
通道(Channel)也称为外围设备处理器、输入输出处理机,是相对于CPU而言的。是一个处理器。也能执行指令和由指令的程序,只不过通道执行的指令是与外部设备相关的指令。是一种实现主存与I/O设备进行直接数据交换的控制方式,与DMA控制方式相比,通道所需要的CPU控制更少,一个通道可以控制多个设备,并且能够一次进行多个不连续的数据块的存取交换,从而大大提高了计算机系统效率。
1)假脱机系统; 在联机的情况下实现的同时外围操作的技术称为SPOOLing技术,或称为假脱机技术。
2)组成
1.输入井和输出井:输入井和输出井的存储区域是在磁盘上开辟出来的。输入输出井中的数据一般以文件的形式组织管理,这些文件称之为井文件。一个文件仅存放某一个进程的输入或输出数据,所有进程的数据输入或输出文件链接成为一个输入输出队列。
2.输入缓冲区和输出缓冲区:输入缓冲区和输出缓冲区的存储区域是在内存中开辟出来的。主要用于缓和CPU和磁盘之间速度不匹配的矛盾。输入缓冲区用于暂存有输入设备传送的数据,之后再传送到输入井;输出缓冲区 同理。
3.输入进程和输出进程:输入进程也称为预输入进程,用于模拟脱机输入时的外围控制机,将用户要求的数据从输入设备传送到输入缓冲区,再存放到输入井。当CPU需要的时候,直接从输入井将数据读入内存。反之,输出的同理。
4.井管理程序:用于控制作业与磁盘井之间信息的交换。
3)特点
①提高了I/O的速度:,对数据执行的I/O操作,已从对低速I/O设备执行的I/O操作演变为对磁盘缓冲区中数据的存取,如同脱机输入输出一样,提高了I/O速度,缓和了CPU和低速的I/Os设备之间速度的不匹配的矛盾。
②将独占设备改造成了共享设备:因为在假脱机打印机系统中,实际上并没有为任何进程分配设备,而只是在磁盘缓冲区中为进程分配了一个空闲盘块和建立了一张I/O请求表。
③实现了虚拟设备功能:宏观上,对于每一个进程而言,它们认为是自己独占了一个设备,即使实际上是多个进程在同时使用一台独占设备。也可以说,假脱机系统,实现了将独占设备变换为若干台对应的逻辑设备的功能。
1)通道是独立于CPU,专门用来负责数据输入/输出传输工作的处理机,对外部设备实现统一管理,代替CPU对输入/输出操作进行控制,从而使输入,输出操作可与CPU并行操作。
2)引入通道的目的
为了使CPU从I/O事务中解脱出来,同时为了提高CPU与设备,设备与设备之间的并行工作能力
1)两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
2)共享内存是通过把同一块内存分别映射到不同的进程空间中实现进程间通信。而共享内存本身不带任何互斥与同步机制,但当多个进程同时对同一内存进行读写操作时会破坏该内存的内容,所以,在实际中,同步与互斥机制需要用户来完成。
3)(1)共享内存就是允许两个不想关的进程访问同一个内存
(2)共享内存是两个正在运行的进程之间共享和传递数据的最有效的方式
(3)不同进程之间共享的内存通常安排为同一段物理内存
(4)共享内存不提供任何互斥和同步机制,一般用信号量对临界资源进行保护。
(5)接口简单
1)为什么需要线程池
大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是”即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。
2)线程池原理
在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。
3)线程池的作用
需要大量的线程来完成任务,且完成任务的时间比较短;对性能要求苛刻的应用;对性能要求苛刻的应用
4)内存池的原理
在软件开发中,有些对象使用非常频繁,那么我们可以预先在堆中实例化一些对象,我们把维护这些对象的结构叫“内存池”。在需要用的时候,直接从内存池中拿,而不用从新实例化,在要销毁的时候,不是直接free/delete,而是返还给内存池。把那些常用的对象存在内存池中,就不用频繁的分配/回收内存,可以相对减少内存碎片,更重要的是实例化这样的对象更快,回收也更快。当内存池中的对象不够用的时候就扩容。
5)内存池的优缺点
内存池对象不是线程安全的,在多线程编程中,创建一个对象时必须加锁。
1)Linux操作系统引进了一个非常重要的概念inode,中文名为索引结点,引进索引接点是为了在物理内存上找到文件块,所以inode中包含文件的相关基本信息,比如文件位置、文件创建者、创建日期、文件大小等待,输入stat指令可以查看某个文件的inode信息;
2)硬盘格式化的时候,操作系统自动将硬盘分成两个区域,一个是数据区,一个是inode区,存放inode所包含的信息,查看每个硬盘分区的inode总数和已经使用的数量,可以用df命令;
3)在linux系统中,系统内部并不是采用文件名查找文件,而是使用inode编号来识别文件。查找文件分为三个过程:系统找到这个文件名对应的inode号码,通过inode号码获得inode信息,根据inode信息找到文件数据所在的block读取数据;
4)除了文件名之外的所有文件信息,都存储在inode之中。
1)软链接可以看作是Windows中的快捷方式,可以让你快速链接到目标档案或目录。硬链接则透过文件系统的inode来产生新档名,而不是产生新档案。
2)软链接(符号链接) ln -s source target
硬链接 (实体链接)ln source target
3)硬链接(hard link):A是B的硬链接(A和B都是文件名),则A的目录项中的inode节点号与B的目录项中的inode节点号相同,即一个inode节点对应两个不同的文件名,两个文件名指向同一个文件,A和B对文件系统来说是完全平等的。如果删除了其中一个,对另外一个没有影响。每增加一个文件名,inode节点上的链接数增加一,每删除一个对应的文件名,inode节点上的链接数减一,直到为0,inode节点和对应的数据块被回收。注:文件和文件名是不同的东西,rm A删除的只是A这个文件名,而A对应的数据块(文件)只有在inode节点链接数减少为0的时候才会被系统回收。
4)软链接(soft link):A是B的软链接(A和B都是文件名),A的目录项中的inode节点号与B的目录项中的inode节点号不相同,A和B指向的是两个不同的inode,继而指向两块不同的数据块。但是A的数据块中存放的只是B的路径名(可以根据这个找到B的目录项)。A和B之间是“主从”关系,如果B被删除了,A仍然存在(因为两个是不同的文件),但指向的是一个无效的链接。
5)硬链接
不能对目录创建硬链接;不能对不同的文件系统创建硬链接;不能对不存在的文件创建硬链接;
6)软连接
可以对目录创建软连接;可以跨文件系统;可以对不存在的文件创建软连接;
7)因为链接文件包含有原文件的路径信息,所以当原文件从一个目录下移到其他目录中,再访问链接文件,系统就找不到了,而硬链接就没有这个缺陷,你想怎么移就怎么移;还有它要系统分配额外的空间用于建立新的索引节点和保存原文件的路径。
1)Linux内核将这4G字节的空间分为两部分。将最高的 1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间“。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统 内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
1)管道只能在具有亲缘关系的进程间进行通信;通过文件共享,在处理效率上又差一些,而且访问文件描述符不如访问内存地址方便;
2)mmap内存共享映射,mmap本来是存储映射功能,它可以将一个文件映射到内存中,在程序里就可以直接使用内存地址对文件内容进行访问;Linux的mmap实现了一种可以在父子进程之间共享内存地址的方式;
3)XSI共享内存,XSI是X/Open组织对UNIX定义的一套接口标准(X/Open System Interface)。XSI共享内存在Linux底层的实现实际上跟mmap没有什么本质不同,只是在使用方法上有所区别。
4)POSIX共享内存,Linux提供的POSIX共享内存,实际上就是在/dev/shm下创建一个文件,并将其mmap之后映射其内存地址即可。
1)grep
grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。常用来在结果中搜索特定的内容。
2)awk
awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件(或其他方式的输入流, 如重定向输入)逐行的读入(看作一个记录集), 把每一行看作一条记录,以空格(或\t,或用户自己指定的分隔符)为默认分隔符将每行切片(类似字段),切开的部分再进行各种分析处理。
3)sed
sed更侧重对搜索文本的处理,如修改、删除、替换等等。sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
1)top
top命令可以实时动态地查看系统的整体运行情况,是一个综合了多方信息监测系统性能和运行信息的实用工具。
2)ps
ps命令就是最基本进程查看命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵尸、哪些进程占用了过多的资源等等.总之大部分信息都是可以通过执行该命令得到。ps是显示瞬间进程的状态,并不动态连续;如果想对进程进行实时监控应该用top命令。
1)预处理,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏替换,添加行号。经过预处理指令后生成一个.i文件;
2)编译,编译过程所进行的是对预处理后的文件进行语法分析、词法分析、符号汇总,然后生成汇编代码。生成.s文件;
3)汇编,将汇编文件转换成二进制文件,二进制文件就可以让机器来读取。生成.o文件;
4)链接,由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
windows是编译器决定栈的大小,记录在可执行文件中,默认是1M。linux是操作系统来决定的,在系统环境变量中设置, ulimit -s 字节数 命令查看修改,但是linux默认栈大小为10M;vs编译器设置:属性—>设置链接输出栈分配重新设置;
1 重定向符号
输出重定向到一个文件或设备 覆盖原来的文件
! 输出重定向到一个文件或设备 强制覆盖原来的文件
输出重定向到一个文件或设备 追加原来的文件
< 输入重定向到一个程序
2标准错误重定向符号
2> 将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 b-shell
2>> 将一个标准错误输出重定向到一个文件或设备 追加到原来的文件
2>&1 将一个标准错误输出重定向到标准输出 注释:1 可能就是代表 标准输出
& 将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 c-shell
|& 将一个标准错误 管道 输送 到另一个命令作为输入
3命令重导向示例
在 bash 命令执行的过程中,主要有三种输出入的状况,分别是:
1)ls命令,不仅可以查看linux文件包含的文件,而且可以查看文件权限;
2)cd命令,切换当前目录到dirName
3)pwd命令,查看当前工作目录路径;
4)mkdir命令,创建文件夹
5)rm命令,删除一个目录中的一个或多个文件或目录
6)rmdir命令,从一个目录中删除一个或多个子目录项,
7)mv命令,移动文件或修改文件名
8)cp命令,将源文件复制至目标文件,或将多个源文件复制至目标目录
9)cat命令,显示文件内容;
10)touch命令,创建一个文件
11)vim命令,
12)which命令查看可执行文件的位置,whereis查看文件的位置,find实际搜寻硬盘查询文件名称;
13)chmod命令,用于改变linux系统文件或目录的访问权限,421,ewr
14)tar命令,用来压缩和解压文件。tar本身不具有压缩功能,只具有打包功能,有关压缩及解压是调用其它的功能来完成。
15)chown命令,将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;
16)ln命令;
17)grep命令,强大的文本搜索命令,grep全局正则表达式搜素;
18)ps命令,用来查看当前运行的进程状态,一次性查看,如果需要动态连续结果使用top;
19)top命令,显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等;
20)kill命令,发送指定的信号到相应进程。不指定型号将发送SIGTERM(15)终止指定进程。
1)深度广度遍历;
2)广度优先遍历;
3)最短路径Floyed算法;
4)最短路径Dijktral算法;
5)最小生成树,Prime算法;
6)最小生成树Kuraul算法;
1)给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
2)哈夫曼树的构造
将节点权重进行升序排序;
选择权重最小的两个节点,将两个节点的和作为新节点,将新节点加入数组中;
重复以上步骤,最后数组中剩下一个值,该值就是树的带权路径长度,即哈夫曼树;
1)一个m阶的B+树具有如下特征:
①有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
②所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
③所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
④在B+树中,只有叶子节点带有数据,其余中间节点仅仅是索引,没有关联任何数据。
2)B+树的优势
①单一节点存储更多的元素,使得查询的IO次数更少;
②所有查询都要查询到叶子节点,查询性能稳定;
③所有叶子节点形成有序链表,便于范围查询;
3)B+树与B-树的区别
1)首先红黑树是不符合AVL树的平衡条件的,即每个节点的左子树和右子树的高度最多差1的二叉查找树。但是提出了为节点增加颜色,红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。所以红黑树的插入效率更高!!!红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树,avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多。
2)如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。
1)在链表头部设置两个指针,一个每次向后移动两个位置,一个每次向后移动一个位置。两个指针遍历链表过程中,如果快指针到达链表尾还没有和慢指针相遇,说明链表无环,反之有环;
2)环的长度,从两个指针的交点开始,移动指针,当指针再次指向两个指针的交点的时候,就可以求出环的长度;
3)环的入口,一个指针从头开始,一个指针指向(1)中的环中快慢指针的交点,开始遍历,直到这两个指针相遇,两个指针相遇的点就是环的入口点。
可以用DFS(O(v^2))和BFS(O(v+e))的思想都能实现,只要从一个点出发,然后判断是否能遍历完所有的点。
1)如何构造哈希函数
a)数字分析法;
b)平方取中法;
c)除留余数法;
d)伪随机数法;
2)处理冲突
e)线性探测;
f)二次探测;
g)伪随机数探测;
h)拉链探测。
3)如果负载因子是默认的0.75,HashMap(16)的时候,占16个内存空间,实际上只用到了12个,超过12个就扩容。
如果负载因子是1的话,HashMap(16)的时候,占16个内存空间,实际上会填满16个以后才会扩容。增大负载因子可以减少hash表的内存,如果负载因子是0.75,hashmap(16)最多可以存储12个元素,想存第16个就得扩容成32。如果负载因子是1,hashmap(16)最多可以存储16个元素。同样存16个元素,一个占了32个空间,一个占了16个空间的内存。
1)搜索二叉树
从树的根节点开始和两个节点进行比较,如果根节点大于两个节点值,则去根节点的左孩子去进行查找;如果根节点小于两个节点值,则去根节点的右孩子去进行查找;当根节点大于其中一个节点,小于其中一个节点,则该节点是最近的祖先节点。
方法一首先给出node1的父节点node1->_parent,然后将node1的所有父节点依次和node2->parent作比较,如果发现两个节点相等,则该节点就是最近公共祖先,直接将其返回。如果没找到相等节点,则将node2的所有父节点依次和node1->_parent->_parent作比较…直到node1->_parent==NULL。
方法二给定的两个节点都含有父节点,因此,可将这两个节点看做是两个链表的头结点,将求两个节点的最近公共祖先节点转化为求两链表的交点,这两个链表的尾节点都是根节点。
2)一般二叉树
方法一,将从根节点到node1的路径保存在数组中;将从根节点到node2的路径保存在数组中;两个数组从头开始遍历,直到找到不相同的两个值,该值前一个就是最近祖先;
方法二,从根节点开始遍历,如果node1和node2中的任一个和root匹配,那么root就是最低公共祖先。 如果都不匹配,则分别递归左、右子树,如果有一个 节点出现在左子树,并且另一个节点出现在右子树,则root就是最低公共祖先. 如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。
1)如果具有最远距离的两个节点经过根节点,那么最远的距离就是左边最深的深度加上右边最深的深度之和;如果具有最远距离的两个节点之间的路径不经过根节点,那么最远的距离就在根节点的其中一个子树上的两个叶子节点。
int _Height(root, distance)
{
if(root为空)
return 0;
left = _Height(左子树);
right = _Height(右子树);
if(left+right>distance)
distance = left+right;
return 左子树、右子树较大加1;
}
递归中序遍历;
vector<string> binaryTreePaths(TreeNode* root) {
// Write your code here
vector<string> res;
if(root==NULL) return res;
binaryTreePathsCore(root,res,to_string(root->val));
return res;
}
void binaryTreePathsCore(TreeNode* root,vector<string> &str,string strpath){
if(root->left==NULL&&root->right==NULL){
//叶子结点
str.push_back(strpath);
return;
}
if(root->left!=NULL){
binaryTreePathsCore(root->left,str,strpath+"->"+to_string(root->left->val));
}
if(root->right!=NULL){
binaryTreePathsCore(root->right,str,strpath+"->"+to_string(root->right->val));
}
}
1)利用队列来分别将每层的树添加进队列进行分析,先记录下第一个值作为最大值并弹出,然后往后边比较边弹出,如果当前值比前面的最大值还要大则替换当前最大值。在弹出每次的节点时将孩子节点加进队列。直到整棵树被遍历完。
2)伪代码
if(根节点为空)
返回
申请队列Q,将根节点入队列
While(队列不空)
{
s=队列长度
for(i=0;i < s;i++)
{
比较队首元素,并将队首元素出栈
队首左孩子入栈,右孩子入栈
}
}
1)二叉树的深度等于二叉树的高度,也就等于根节点的高度。根节点的高度为左右子树的高度较大者+1。
int deepth(root)
{
if(root为空)
return 0;
else
{
left = deepth(左孩子);
right = deepth(右孩子);
return left>right? left+1:right+1;
}
}
2)求最大深度的时候,只需要比较左右子树的深度,取较大者+1就行了;但是求最小深度的时候,需要区分双子树与单子树,双子树时,深度较小者+1,单子树时(即左右子树有一颗为空时)为深度较大者+1。
int deepth(root)
{
if(root为空)
return 0;
left = deepth(左孩子);
right = deepth(右孩子);
if(左孩子或有孩子为空)
return 不为空的深度+1;
return 左右较小者+1;
}
3)判断平衡二叉树,只需要判断平衡因子小于1即可,递归判断左右节点是否是平衡的即可;
bool isBalance(root)
{
left = 根节点左孩子高度;
right = 根节点右孩子高度;
if(right与left不满足平衡因子)
return false;
return isBalance(左孩子)&&isBalance(右孩子);
}
1)叶子节点就是左右孩子都是空的节点,在进行遍历的过程中,判断是否为叶子节点,如果是叶子节点,将计数器加1;
2)伪代码
void leafNodeNum(root,k)
{
if(树为空)
return;
if(root不为空)
{
if(叶子节点)
k++;
leafNodeNum(root->左孩子,k);
leafNodeNum(root->右孩子,k);
}
}
1)递归交换左右孩子,从跟节点开始交换,节点的左右孩子不都为空,则进行交换操作;否则返回;
void exchangeChild(root)
{
if(root->left空&&root->right空)
return;
swap(root->left,root->right);
exchangeChild(root->left);
exchangeChild(root->right);
}
1)判断两颗树的根节点是否相同,如果不相同返回false,如果相同则递归判断根节点的左右子节点;如果两颗树中有一个树没有遍历完则说明不相等;两棵树都为空则两棵树相等;两棵树一颗为空一颗不为空则不相等;
bool treesEqual(root1,root2)
{
if(root1空 && root2空)
return true;
if(root1空 || root2空)
return false;
if(root1->data == root2->data)
return treesEqual(root1->left,root2->left)&&treesEqual(root1->right,root2->right);
else
return false;
}
1)如果一个结点有右孩子而没有左孩子,那么这棵树一定不是完全二叉树。
如果一个结点有左孩子,而没有右孩子,那么按照层序遍历的结果,这个结点之后的所有结点都是叶子结点这棵树才是完全二叉树。
如果一个结点是叶子结点,那么按照层序遍历的结果,这个结点之后的所有结点都必须是叶子结点这棵树才是完全二叉树。
用一个标记变量leaf标记,当一个节点有左孩子无右孩子,leaf=true。之后所有的节点必须为叶子节点。
bool isSymmetrical(root1,root2)
{
if(root1空 && root2空)
return true;
if(root1空 || root2空)
return false;
if(root1->data != root2->data)
return false;
else
return isSymmetrical(root1->left,root2->right)&&isSymmetrical(root1->right,root2->left);
}
1)找值相同的根结点(遍历解决)
判断两结点是否包含(递归:值、左孩子、右孩子分别相同)
bool isPart(root1,root2)
{
if(root1空&&root2空)
return true;
if(root1空 || root2空)
return false;
if(root1->data != root2->data)
return false;
else
return isPart(root1->left,root2->left)&&isPart(root1->right,root2->right);
}
bool isPartTree(root1,root2)
{
if (root1不空 && root2不空)
{
if (root1->data == root2->data)
result = IsPart(root1, root2);
if (!result)
result = IsPartTree(root1->left, root2);
if (!result)
result = IsPartTree(root1->right, root2);
}
return result;
}
1.单链表反转:尾插法转头查法;设置三个指针,当前节点指针,下一个节点指针,上一个节点指针,一开始上一个节点指针置为空;最后当下一个节点指针为空时说明到达最后节点,则返回该节点指针。
2.删除指定节点:如果我们把要删除节点的下一个节点的内容复制到需要删除的节点上,然后把删除节点的下一个节点删除,就可以完成删除该节点,同时时间复杂度为O(1)。如果是尾节点,只能遍历删除,如果只有一个节点,还要删除头节点。
循环中front与rear的求值。
队首指针进1:front = (front+1)%MaxSize;
队尾指针进1:rear = (rear+1)%MaxSize;
队空:rear == front;
队满:(rear+1)%MaxSize == front
1)如果要找前K个最大的数,我们用最小堆,每次用堆顶元素和遍历的数比,如果堆顶元素小,则让堆顶元素的值等于它,然后向下调整
2)如果要找前K个最小的数,我们用最大堆,每次用堆顶元素和遍历的数比,如果堆顶元素大,则让堆顶元素的值等于它,然后向下调整
3)用快速排序将数组进行排序,然后将数组输出
4)两者的时间复杂度都是O(nlog2n),快排的空间复杂度为O(log2n),堆排序的空间复杂度为O(1)
对于二叉树,若要两个节点U,V相距最远,有两种情况:
1,从U节点到V节点之间的路径经过根节点
2,从U节点到V节点之间的路径不经过根节点,这种情况下,U,V节点必定在根节点的左子树或者右子树上,这样就转化为求以根节点的孩子节点为根节点的二叉树中最远的两个节点间的距离
核心是next()函数的书写;
1)数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况,即数组的大小一旦定义就不能改变。当数据增加时,可能超出原先 定义的元素个数;当数据减少时,造成内存浪费;链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删 除数据项时,需要移动其它数据项)。
2)(静态)数组从栈中分配空间(用NEW创建的在堆中), 对于程序员方便快速,但是自由度小;链表从堆中分配空间, 自由度大但是申请管理比较麻烦.
3)数组在内存中是连续存储的,因此,可以利用下标索引进行随机访问;链表是链式存储结构,在访问元素的时候只能通过线性的方式由前到后顺序访问,所以访问效率比数组要低。
归并排序,两个数组合并生成50个数组,生成25个数组,13个数组,6个数组,3个数组,2个数组,1个数组
1)字典树,又称单词查找树,是一种树形结构,是一种哈希树的变种;
2)根节点不包含字符,除根节点外的每一个子节点都包含一个字符;
从根节点到叶子节点,路径上经过的字符链接起来,就是该节点对应的字符串;
每个节点的所有子节点包含的字符都不同;
3)典型应用是用于统计,排序和保存大量的字符串(不仅限于字符串),经常被搜索引擎系统用于文本词频统计。
1)当我们每次划分的时候选择的基准数接近于整组数据的最大值或者最小值时,快速排序就会发生最坏的情况,但是每次选择的基准数都接近于最大数或者最小数的概率随着排序元素的增多就会越来越小,我们完全可以忽略这种情况。但是在数组有序的情况下,它也会发生最坏的情况,为了避免这种情况,我们在选择基准数的时候可以采用三数取中法来选择基准数。三数取中法:选择这组数据的第一个元素、中间的元素、最后一个元素,这三个元素里面值居中的元素作为基准数。
2)当划分的子序列很小的时候(一般认为小于13个元素左右时),我们在使用快速排序对这些小序列排序反而不如直接插入排序高效。因为快速排序对数组进行划分最后就像一颗二叉树一样,当序列小于13个元素时我们再使用快排的话就相当于增加了二叉树的最后几层的结点数目,增加了递归的次数。所以我们在当子序列小于13个元素的时候就改用直接插入排序来对这些子序列进行排序。
1)BitMap解决海量数据寻找重复、判断个别元素是否在海量数据当中等问题;
2)40亿个int占(40亿*4)/1024/1024/1024 大概为14.9G左右,很明显内存只有2G,放不下,因此不可能将这40亿数据放到内存中计算;40亿个int需要的内存空间为40亿/8/1024/1024大概为476.83MB;
1)事务(txn)是一系列在共享数据库上执行的行为,以达到更高层次更复杂逻辑的功能。事务是DBMS中最基础的单位,事务不可分割。
2)ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
3)原子性是指事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
4)一致性是指事务使得系统从一个一致的状态转换到另一个一致状态。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
5)多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。
6)持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中。
1)本地事务数据库断电的这种情况,它是怎么保证数据一致性的呢?我们使用SQL Server来举例,我们知道我们在使用 SQL Server 数据库是由两个文件组成的,一个数据库文件和一个日志文件,通常情况下,日志文件都要比数据库文件大很多。数据库进行任何写入操作的时候都是要先写日志的,同样的道理,我们在执行事务的时候数据库首先会记录下这个事务的redo操作日志,然后才开始真正操作数据库,在操作之前首先会把日志文件写入磁盘,那么当突然断电的时候,即使操作没有完成,在重新启动数据库时候,数据库会根据当前数据的情况进行undo回滚或者是redo前滚,这样就保证了数据的强一致性。
2)
1)第一范式,数据库表中的字段都是单一属性的,不可再分;每一个属性都是原子项,不可分割;如果实体中的某个属性有多个值时,必须拆分为不同的属性 通俗解释。1NF是关系模式应具备的最起码的条件,如果数据库设计不能满足第一范式,就不称为关系型数据库。也就是说,只要是关系型数据库,就一定满足第一范式。
2)第二范式,数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖,即符合第二范式;如果一个表中某一个字段A的值是由另外一个字段或一组字段B的值来确定的,就称为A函数依赖于B;当某张表中的非主键信息不是由整个主键函数来决定时,即存在依赖于该表中不是主键的部分或者依赖于主键一部分的部分时,通常会违反2NF。
3)第三范式,在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合3NF;第三范式规则查找以消除没有直接依赖于第一范式和第二范式形成的表的主键的属性。我们为没有与表的主键关联的所有信息建立了一张新表。每张新表保存了来自源表的信息和它们所依赖的主键;如果某一属性依赖于其他非主键属性,而其他非主键属性又依赖于主键,那么这个属性就是间接依赖于主键,这被称作传递依赖于主属性。 通俗理解:一张表最多只存2层同类型信息 。
1)数据库索引好比是一本书前面的目录,能加快数据库的查询速度。索引是对数据库表中一个或多个列(例如,employee 表的姓氏 (lname) 列)的值进行排序的结构。如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息。
2)优点
大大加快数据的检索速度; 创建唯一性索引,保证数据库表中每一行数据的唯一性;加速表和表之间的连接; 在使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。
3)缺点
索引需要占用数据表以外的物理存储空间;创建索引和维护索引要花费一定的时间;当对表进行更新操作时,索引需要被重建,这样降低了数据的维护速度。
4)类型
唯一索引——UNIQUE,例如:create unique index stusno on student(sno);表明此索引的每一个索引值只对应唯一的数据记录,对于单列惟一性索引,这保证单列不包含重复的值。对于多列惟一性索引,保证多个值的组合不重复。
主键索引——primary key,数据库表经常有一列或列组合,其值唯一标识表中的每一行。该列称为表的主键。 在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。该索引要求主键中的每个值都唯一。当在查询中使用主键索引时,它还允许对数据的快速访问。
聚集索引(也叫聚簇索引)——cluster,在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引,如果某索引不是聚集索引,则表中行的物理顺序与键值的逻辑顺序不匹配。与非聚集索引相比,聚集索引通常提供更快的数据访问速度。
5)实现方式
B+树、散列索引、位图索引
1)聚集索引表示表中存储的数据按照索引的顺序存储,检索效率比非聚集索引高,但对数据更新影响较大。非聚集索引表示数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置,非聚集索引检索效率比聚集索引低,但对数据更新影响较小。
2)聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个。聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续
1)Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别,关于数据库事务与其隔离级别的内容请见数据库事务与其隔离级别这篇文章。该引擎还提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于MySQL后台的完整数据库系统,MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数,当SELECT COUNT() FROM TABLE时需要扫描全表。当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用Innodb引擎会提升效率。但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表。
2)MyIASM是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。不过和Innodb不同,MyIASM中存储了表的行数,于是SELECT COUNT() FROM TABLE时只需要直接读取已经保存好的值而不需要进行全表扫描。如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyIASM也是很好的选择。
3)大尺寸的数据集趋向于选择InnoDB引擎,因为它支持事务处理和故障恢复。数据库的大小决定了故障恢复的时间长短,InnoDB可以利用事务日志进行数据恢复,这会比较快。主键查询在InnoDB引擎下也会相当快,不过需要注意的是如果主键太长也会导致性能问题,关于这个问题我会在下文中讲到。大批的INSERT语句(在每个INSERT语句中写入多行,批量插入)在MyISAM下会快一些,但是UPDATE语句在InnoDB下则会更快一些,尤其是在并发量大的时候。
1)隔离级别高的数据库的可靠性高,但并发量低,而隔离级别低的数据库可靠性低,但并发量高,系统开销小。
2)READ UNCIMMITTED(未提交读),事务中的修改,即使没有提交,其他事务也可以看得到,比如说上面的两步这种现象就叫做脏读,这种隔离级别会引起很多问题,如无必要,不要随便使用;这就是事务还没提交,而别的事务可以看到他其中修改的数据的后果,也就是脏读;
3)READ COMMITTED(提交读),大多数数据库系统的默认隔离级别是READ COMMITTED,这种隔离级别就是一个事务的开始,只能看到已经完成的事务的结果,正在执行的,是无法被其他事务看到的。这种级别会出现读取旧数据的现象
4)REPEATABLE READ(可重复读),REPEATABLE READ解决了脏读的问题,该级别保证了每行的记录的结果是一致的,也就是上面说的读了旧数据的问题,但是却无法解决另一个问题,幻行,顾名思义就是突然蹦出来的行数据。指的就是某个事务在读取某个范围的数据,但是另一个事务又向这个范围的数据去插入数据,导致多次读取的时候,数据的行数不一致。虽然读取同一条数据可以保证一致性,但是却不能保证没有插入新的数据。
5)SERIALIZABLE(可串行化),SERIALIZABLE是最高的隔离级别,它通过强制事务串行执行(注意是串行),避免了前面的幻读情况,由于他大量加上锁,导致大量的请求超时,因此性能会比较底下,再特别需要数据一致性且并发量不需要那么大的时候才可能考虑这个隔离级别。
1)在内部对象池中,维护一定数量的数据库连接,并对外暴露数据库连接的获取和返回方法,如外部使用者可通过getConnection方法获取数据库连接,使用完毕后再通过releaseConnection方法将连接返回,注意此时的连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。
2)资源重用,由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,增进了系统环境的平稳性(减少内存碎片以级数据库临时进程、线程的数量)
3)更快的系统响应速度,数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池内备用。此时连接池的初始化操作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
4)新的资源分配手段,对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接技术。
5)统一的连接管理,避免数据库连接泄露,较较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用的连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
1)锁是网络数据库中的一个非常重要的概念,当多个用户同时对数据库并发操作时,会带来数据不一致的问题,所以,锁主要用于多用户环境下保证数据库完整性和一致性。
2)数据库锁出现的目的:处理并发问题;
3)并发控制的主要采用的技术手段:乐观锁、悲观锁和时间戳。
4)从数据库系统角度分为三种:排他锁、共享锁、更新锁。从程序员角度分为两种:一种是悲观锁,一种乐观锁。
1)join 是两张表做交连后里面条件相同的部分记录产生一个记录集,union是产生的两个记录集(字段要一样的)并在一起,成为一个新的记录集 。
2)union在数据库运算中会过滤掉重复数据,并且合并之后的是根据行合并的,即:如果a表和b表中的数据各有五行,且有两行是重复数据,合并之后为8行。运用场景:适合于需要进行统计的运算
3)union all是进行全部合并运算的,即:如果a表和b表中的数据各有五行,且有两行是重复数据,合并之后为10行。
4)join是进行表关联运算的,两个表要有一定的关系。即:如果a表和b表中的数据各有五行,且有两行是重复数据,根据某一列值进行笛卡尔运算和条件过滤,假如a表有2列,b表有2列,join之后是4列。
1)在它的核心结构中包含一个被称为单例的特殊类,一个类只有一个实例,即一个类只有一个对象实例;
2)所有的单例模式都是使用静态方法进行创建的,所以单例对象在内存中静态共享区中存储;
3)单例模式分为饿汉式和懒汉式,懒汉式单例模式在类加载时不初始化,饿汉式单例模式,在类加载时就完成初始化,所以类加载较慢,但获取对象速度快。
1)工厂模式,简单工厂模式是由一个工厂对象根据收到的消息决定要创建哪一个类的对象实例。需要switch或if进行类型选择;工厂类创建的对象比较少,客户只需要传入工厂类参数,对于如何创建对象不关心;
2)工厂方法模式,定义一个创建对象的工厂接口,让子类决定实例化哪一个类,将实际创建工作推迟到子类当中。创建对象的接口,让子类决定具体实例化的对象,把简单的内部逻辑判断移动到客户端。
3)抽象工厂模式,抽象工厂是围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂。提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
1)动态地给一个对象增加一些额外的职责。在为对象增加额外职责方面,装饰模式替代了继承,它比子类继承父类更为灵活,它用无需定义子类的方式来给对象动态的增加职责,使用对象之间的关联关系来取代继承, 同时避免类型体系的快速膨胀。
2)装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。
3)装饰器的价值在于装饰,他并不影响被装饰类本身的核心功能。在一个继承的体系中,子类通常是互斥的。
1)订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有的订阅者,使它们能够自动更新自己的状态;
2)发布者向某个信道发布一条消息,订阅者绑定这个信道,当有消息发布至信道时就会接受到一个通知。
1)在对象之间定义一个一对多的依赖关系,这样一来,当一个对象改变状态,依赖他的对象会收到通知自动更新;
2)抽象被观察对象,抽象观察对象,具体被观察者对象,具体观察者对象;
3)微信公众号是一个典型的例子,有一个微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息;
4)一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。
1)MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间。
2)使用的MVC的目的:在于将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。
3)用户首先在界面中进行人机交互,然后请求发送到控制器,控制器根据请求类型和请求的指令发送到相应的模型,模型可以与数据库进行交互,进行增删改查操作,完成之后,根据业务的逻辑选择相应的视图进行显示,此时用户获得此次交互的反馈信息,用户可以进行下一步交互,如此循环。
求职者大可不必为自己的缺点遮遮掩掩,因为HR问这个问题的真实目的并不是想知道你有什么缺点,而是想借此问题考察你对自己的缺点有没有改正的态度。就好像HR问:你最大的失败经历是什么?他并不是想知道你是怎么失败的,而是想看你怎么对待失败的这件事。高考的失败,一方面原因是当时太看重高考,导致紧张,另一方面,因为过度的自信,对自己做过的题太自信,导致检查的时候检查不出错误,走出考场后发现自己在什么地方错了;后面就是吸取教训,第一,不要把得失看得太重要,第二,保持自信,但是不能过分自信,多想想,多问问。
长处:自信、乐观、责任、吃苦耐劳; 信心、恒心、耐心、细心
比较善于交际,人缘好;比较有条理,我的工位上的书,笔,杯子等物品都是摆放整洁的;
缺点:我对我认为不对的人或事,容易提出不同意见,导致经常得罪人;我办事比较急,准确性有时不够;说缺点同时表明这个缺点正在改进中,最好的方式就是说缺点的同时能带出一个优点;
自我的认知定位;对这个职位的认知;对这家公司的认知;求职态度;
这个行业是XX,职位是XX,我觉得我个人有哪几点匹配,而且过去XX原因让我有足够多的兴趣,所以未来我主要想在这个领域里深耕细作,认真成为行业专家——这个就是长远的规划和自我认知。
我在大学里做过几次iOS的相关项目,因此产生了浓厚的兴趣,我学过XX课程,XX教材,参与参加过XX项目,个人经历非常适合这个职位,因此,长远的规划,我没想太具体,主要是想深耕这个领域,争取早日成为一位iOS的编程专家。
至于短期的规划,我因为刚毕业,项目经验还不是很充足,但是如果我能获得这次宝贵的机会,那么我会努力学习,多请教前辈同事,提高自己的项目,在公司的iOS项目上做出自己的贡献,比如现在公司做的几个App,分别是xx,xx,xx,我相信我的加盟,会让开发进度大大加快,未来如果有别的iOS项目,也能保质保量的完成。
首先做好本职工作,脚踏实地,满满积攒技术经验,走技术路线。不断学习,与时俱进,有机会的话可以做其他的技术路线。长远看来,希望通过自己的不断奋斗能够给公司带来一定的价值,同时实现自己的价值。
从事学生工作,本科期间一直担任班长,研究生期间担任实验室党支部书记一职;
看书,打羽毛球;做家务;
如果是工作需要我会义不容辞加班,我现在单身,没有任何家庭负担,可以全身心的投入工作。但同时,我也会提高工作效率,减少不必要的加班。
我的导师,1、真正在搞学术;2、精神满满;3、不同流合污
同门,1、他的见识;2、他的个人能力;3、努力;
机器学习
雷锋网,只是一个科技网站。该网站上会介绍很多新奇的技术的应用。
诺贝尔文学奖获得者马尔克斯的小说《霍乱时期的爱情》,这本书讲述了一段跨越半个世纪的爱情史诗。穷尽了所有爱情的可能性,被誉为人类有史以来最伟大的爱情小说。
我认为公司需要一个xx的人,我具备xx的能力,符合公司的岗位需求,我非常期待能够加入公司,通过自己的努力与公司一起成长。希望能够在公司做到技术专家。
首先承认我没有工作经验,我会毫无保留的去学习,争取在最快的速度由学生转换成一个工作者。我有扎实的理论基础,结合自己吃苦耐劳的精神,一直保持奋斗者的状态,我相信我会做到胜任工作,而且做得非常优秀。
关于这个岗位,公司有培训吗;这个岗位的晋升空间是怎样的;这个职位所在部门在公司的地位;这个岗位具体负责的工作是哪些?