伯特说_终极笔记_备份

数组和链表的区别

  1. 从逻辑结构来看
  1. 数组必须实现定义固定长度的元素,不能适应动态地增减的情况。当数据增加时,可能超出原先设定的元素个数;当数据减少时,造成内存浪费,数组可以根据下标直接存取。
  2. 链表动态地进行存储分配,可以使用数据动态地增减的情况,且可以方便地插入、删除数据项,链表必须根据next指针找到下一元素。
  1. 从内存存储来看
  1. 数组从栈中分配空间,对于程序员方便快速,但是自由度小。
  2. 链表从堆中分配空间,自由度大但是管理比较麻烦。
  1. 由此来看,如果需要快速访问数据,很少或不插入和删除元素;相反,如果需要经常插入和删除元素,就需要用链表结构了。

 

C++内存分配方式

  1. 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区,里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。
  2. 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,new和delete,如果程序没有释放掉,那么在程序结束后,操作系统会自动回收。
  3. 自由存储区,就是那些由malloc等分配的内存块,它和堆是十分类似的,不过它是用free来结束自己的生命的。
  4. 全局/静态存储区,全局变量和静态变量被分配到同一块内存中。
  5. 常量存储区,这是一块比较特殊的存储区,它们存放的是常量,不允许修改。

 

堆和栈有什么区别

  1. 管理方式:栈是由编译器自动管理;堆则由程序员控制。
  2. 空间大小:栈的空间较小,堆的较大。
  3. 碎片问题:栈是先进后厨的队列,而对于堆来说,频繁的new/delete会造成内存空间的不连续。
  4. 生长方向:栈是由低向高,堆是由高向低。
  5. 分配方式:栈是静态分配,堆是动态分配的。
  6. 分配效率:

栈是机器系统提供的数据结构,计算机会对栈提供支持,分配专门的寄存器存放栈的地址,压栈和出栈都有专门的指令,这就决定了栈的效率比较高。

堆则是 C/C++ 函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

———————————————————————————————————————

 

红黑树

1)红黑树是许多“平衡”搜索树中的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn)。

2)它是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色(Black or Red)。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树保证没有一条路径会比其它路径长出2,因而近似于平衡的。

3)树中每个结点包含5个属性:color,key,left,right,p。如果一个结点没有子结点或父节点,则该结点相应指针属性值为NIL。

4)一棵红黑树是满足下面红黑性质的二叉搜索树

A)每个结点是红色或黑色的。

B)根结点是黑色的。

C)每个叶节点是黑色的。

D)如果一个结点是红色的,则它的两个子结点都是黑色的

E)对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点

 

旋转

  1. 搜索树操作TREE-INSERT和TREE-DELETE在含n个关键字的红黑树上,运行花费时间为O(lgn)。由于这两个操作对树做了修改,结果可能违反红黑性质。为了维护这些性质,必须要改变树中某些结点的颜色以及指针结构。
  2. 指针结构的修改是通过旋转来完成的。
  3. 左旋和右旋都在O(1)的时间内完成,在旋转过程中只有指针改变,其它所有性质都不改变。

74485293b6da99be4eaa05a765e67dab418.jpg

 

插入

首先将结点z插入T,然后将z着为红色,此时,可能有以下几种情况:

  1. 原树是空树,则会违反性质2。

直接把此结点涂为黑色。

  1. 当前结点的父节点为黑色,红黑性质没有被破坏。
  2. 当前结点的父节点是红色且叔叔结点是红色。(当前结点是父结点的左右孩子都可以)
  1. 当前结点的父结点和叔叔结点涂黑,祖父结点涂红。
  2. 当前结点指向父结点,从新的当前结点重新开始算法。
  1. 当前结点的父结点是红色且叔叔结点是黑色,当前结点是其父结点的右孩子。
  1. 当前结点的父结点作为新的当前结点。
  2. 以新的当前结点为支点左旋。
  1. 当前结点的父结点是红色且叔叔结点是黑色,当前结点是其父结点的左孩子。
  1. 父结点变为黑色,祖父结点变为红色。
  2. 以祖父结点为支点右旋。

 

删除

  1. 当前结点是红色。

直接把当前结点染成黑色,结束。

  1. 当前结点是黑色且是根结点。

结束。

  1. 当前结点是黑色,且兄弟结点为红色。
  1. 把兄弟结点染成黑色,父结点染成红色。
  2. 对父结点左旋。
  1. 当前结点是黑色,且兄弟是黑色,且兄弟结点的两个子结点全为黑色。
  1. 把兄弟结点染成红色。
  2. 父结点当前新的当前结点。
  1. 当前结点是黑色,兄弟结点是黑色,兄弟的左孩子是红色,右孩子是黑色。
  1. 把兄弟结点染成红色,兄弟左孩子染黑。
  2. 以兄弟结点为支点右旋。
  1. 当前结点是黑色,兄弟结点是黑色,兄弟的左孩子是颜色任意,右孩子是红色。
  1. 把兄弟结点染成当前父结点的颜色。
  2. 把当前父结点染成黑色,兄弟结点右孩子染成黑色。
  3. 以当前结点的父结点为支点进行左旋。

 

———————————————————————————————————————

 

Java的IO系统

 

Java流在处理上分为字符流和字节流

  1. 字符流处理的单元为2个字节Unicode字符,分别操作字符字符数组字符串
  2. 字节流处理单元为1个字节,操作字节字节数组

 

字符流和字节流的区别

  1. 读写单位不同:字节流以字节为单位,字符流以字符为单位,根据码表映射字符,一次可能读取多个字节。
  2. 处理对象不同:字节流能处理所有数据类型,而字符流只能处理字符类型数据。
  3. 字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作;而字符流在操作的时候是会用到缓冲区的,通过缓冲区来操作文件。

 

一、以字节为导向的stream (InputStream/OutputStream

62c0d96732aa9c1ac4304b80e819de659c0.jpg

ByteArrayInputStream

  1. 把内存中的一个缓冲区作为InputStream使用
  2. ByteArrayInputStream(byte[])

 

StringBufferInputString

  1. 把一个String对象作为InputStream使用
  2. 不推荐使用,此类不能将字符正确转换成字节,一个串创建一个流的最佳方法是StringReader。

 

FileInputStream

  1. 把一个文件作为InputStream,实现对文件的读取操作。
  2. 构造函数为File(File对象)、FileDescriptor(文件描述符)或String(文件名称)。
  3. 读取操作,read() - 读取一个字节,read(byte[]) - 读取b.length个字节数据到一个字节数组中,read(byte[], int, int) - 将输入流中len个字节数据读入一个字节数组中。

 

PipeInputStream

  1. 实现了pipe的概念,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个管道通过管道输入流读取数据。
  2. PipeInputStream() - 创建一个管道输入流,它还未与一个管道输出流连接。
  3. PipeInputStream(PipeOutputStream) - 创建一个管道输入流,它已连接到一个管道输出流当中。

 

SequenceInputStream

(1)把多个InputStream合并为一个InputStream,并且使他们像单个输入流一样出现,每个输入流依次被读取,直到到达该流的末尾。

7f550df4f3f958d2dbd9f0c555e5b19375f.jpg

ByteArrayOutputStream

  1. 把信息存入内存中的一个缓冲区,该类实现一个以字节数组形式写入数据的输出流,当数据写入缓冲区时,它自动扩大,用toByteArray()和toString能检索数据。
  2. ByteArrayOutputStream() - 创建一个新的字节数组输出流,

    (3)ByteArrayOutputStream(int) - 创建一个新的字节数组输出流,并带有指定大小字节的缓冲区。

(4)toString(String) - 根据指定字符编码将缓冲区内容转换为字符串,并将字节转换为字符。

(5)write(byte[], int, int) - 将指定字节数组中从偏移量off开始的len个字节写入该字节数组输出流。

(6)write(int),将指定字节写入该字节数组输出流。

 

FileOutputStream

  1. 与FileInputStream相比多了一个(String, boolean)构造方法,用与指定系统的文件名,创建一个输出文件。(true表示追加模式

 

PipedOutputStream

  1. 与PipeInputStream相对应。

 

二、以字符为导向的Stream(Reader与Writer

85fd63e5f82808a7e266c4dd8585ad81f1c.jpg

CharArrayReader

  1. 实现一个用作字符输入流的字符缓冲区。
  2. CharArrayReader(char[]),CharArrayReader(char[], int, int)。

 

StringReader

  1. 源为一个字符串的字符流。
  2. StringReader(String)。

 

FileReader

  1. 与FileInputStream对应。

 

PipedReader

  1. 与PipedInputStream对应。

00c1bd4b062c45574bf3cc96bf4b3d4adb1.jpg

 

CharArrayWriter

  1. 与ByteArrayOutputStream对应。

 

StringWriter

  1. 无与之对应的以字节为导向的Stream

 

FileWriter

  1. 与FileOutputStream对应

 

PipedWriter

  1. 与PipeOutputStream对应。

 

  • 两种流之间的转换

 

InputStringReader

  1. 从字节流到字符流的桥梁,它读入字节,并根据指定的编码方式,将之转换成为字符流。
  2. InputStringReader的read()方法之一的每次调用,可能促使从基本字节输入流中读取一个或多个字节,一般考虑用BufferedReader封装InputStringReader。
  3. InputStringReader(InputString),用缺省的字符编码方式创建。
  4. InputStringReader(InputString, String),用已命名的字符编码方式创建。

 

OutputStringWriter

  1. 将多个字符写入到一个输出流中,根据指定的编码将多个字符转换为字节。

 

  • Java IO的使用规则
  1. 按数据来源分类
  1. 文件:FileInputStream,FileOutputStream。FileReader,FileWriter。
  2. Byte[]:ByteArrayInputStream、ByteArrayOutputStream。
  3. Char[]:CharArrayReader、CharArrayWriter
  4. String:StringReader、StringWriter
  5. 网络数据流:InputStream、OuputStream
  1. 是否格式化

PrintStream、PrintWriter

  1. 是否要缓冲

BufferInputStream、BufferOutputStream、BufferReader、BufferWriter。

  1. 按数据格式
  1. 二进制格式(只要不能确定是纯文本的):InputStream、OuputStream。
  2. 纯文本格式(含纯英文与汉字或其它编码方式):Reader、Writer。
  1. 按输入输出
  2. 使用那个类以及它的构造过程的一般准则如下:
  1. 最原始的数据格式是什么
  2. 输入还是输出
  3. 是否需要转换流
  4. 数据来源
  5. 是否是要格式化输出

 

Java虚拟机由那几部分组成

  1. 程序计数器

程序计数器用于指示当前线程所执行的字节码到了第几行。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。每个程序计数器只用来记录一个线程的行号,所以它是线程私有的。

(2)虚拟机栈

一个线程的每个方法在执行的同时,都会创建一个栈帧,栈帧中存有局部变量、执行环境和操作数栈,当方法被调用时,栈帧在JVM栈中入栈,当方法完成时出栈。

每个栈框架包括以下三类信息:局部变量、执行环境和操作数栈。

局部变量用于存储一个类的方法中所用到的局部变量,vars寄存器指向该变量表中的第一个局部变量。

执行环境用于保存寄存器对Java字节码进行解释过程中所需要的信息,它们是:上次调用的方法,局部变量指针和操作数栈的栈顶和栈底指针。执行环境是执行一个方法的控制中心。例如,如果解释器要执行iadd,首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈顶弹出两个整数进行计算,最后将结果压入栈顶。

操作数栈用于存储运算所需操作数及计算的结果。

  1. 本地方法栈

虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中,会将本地方法栈与虚拟机栈放在一起使用,本地方法栈也是线程私有的。

(4)堆区

堆区由所有线程共享,在虚拟机启动时创建,堆区的存在是为了存储对象实例。

(5)方法区

方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时所需要的信息,包括版本,file,方法,接口等信息),final常量、静态变量、编译器即时代码等。

运行时常量池是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);

运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。

 

Java反射机制

  1. 反射机制指的是程序在运行时能够获取自身的信息,在java中,只要给定类的名称,那么就可以通过反射机制来获得类的所有信息;对于任意一个对象,都能够调用它的任意一个方法。
  2. 反射机制的优点与缺点
  1. 静态编译:在编译时确定类型,绑定对象,即通过。
  2. 动态编译:运行时确定类型,绑定对象,动态编译最大限度发挥了java的灵活性,有利于降低类之间的耦合性。
  3. 反射机制的优点就是可以动态创建对象和编译,体现出很大的灵活性。

(3)获取类类型(Class

bc10f341d314526da2770d863fc3acc4e79.jpg

(4)新建类的实例

4e298efc2c6f58786823e0257d972c74044.jpg

(5)获取类的构造器

964f9cdece4633677234c1a87c32fa0b4e8.jpg

 

945ed7d77aa4e5c13b7a59abedbcab25561.jpg

第一个是获得一个指定的方法,我们指定了参数是一个String类型;第二段我们获得了所有构造方法的集合,并选取其中一个创建了新的对象。

(6)获取类的成员变量

c2a05b8edfd8e074f46a5c20963149f3046.jpg

4e42afdd0dd5cb4f0ba8d43b26b3621abaa.jpg

(7)获取类的方法

c1ee2ff458b2af69684251db06412150824.jpg

490e044753ed30b2defb89e37c5084d0a32.jpg

 

Java和C/C++的区别

  1. 指针
  2. 多重继承
  3. 数据类型及类
  4. 自动内存管理
  5. 操作符重载
  6. 预处理功能
  7. 不支持缺省函数参数
  8. 字符串
  9. goto语句
  10. 类型转换

 

UML

(1)用例图

(2)类框图

类采用三格的矩形图示,顶层放置类名称、中格放置属性名称、底层放置操作名称。

  1. 可见性

+:public

-:private

#:protected

~:包可见性

  1. 关联

关联是对象之间最常见的关系,用来连接有结构关系的对象,关联的图示为实线,实线两端可以连接两个不同的类。

单箭头实线,标示导航性,意味着可以由来源端导航到箭头所在处的目标端。

  1. 多重性

多重性元素主要包含一组上下限数,用来指出可被允许生成的实例数量。

  1. 聚合和组合

关联的两端是平等的,如果想表达整体-部分关系,可以改用聚合关系组合关系

聚合与组合都具有整体和部分的特性,唯一的差别在于可否分享,聚合关系中的部件可以与其它整体分享,但是组合关系中的部件则由整体独自拥有。

聚合端为空心小菱形,组合端为实心小菱形。

(6)泛化

由特化的子类指向泛化的超类,但有大三角形箭头的实现。

  1. 依赖

某一模型元素需要另一个模型元素所提供的规格或实现时,两者的关系称为依赖。也就是说,少了供应者元素的话,依赖元素在语义上或者结构上可能会不完整,因此,一旦供应者元素变动,很可能会影响到依赖元素。

带箭头虚线,由依赖元素指向供应者元素。

  1. 接口
  2. 注释

注释可以附加在任何元素上,其内放置说明文字。

(3)时序图

  1. 协作图
  2. 状态转换图
  3. 组件图
  4. 部署图

 

类之间的关系

(1)关联

关联指的是类之间的特定对应关系,在UML中用带实线的箭头表示按照类之间的数量对比,其可以分为以下3种:

A)一对一关联:一个家庭教师只能交一个学生,一个学生只能拥有一个家庭教师。

B)一对多关联:一个足球队员只能加入一支球队,一个球队可以包含多个队员;一个客户拥有多个订单,而一个订单只能属于一个客户。

C)多对多关联:一个足球队员能加入多支球队,一个球队可以包含多个队员。

关联还可以分为单向关联双向关联

  1. 单向关联:仅建立从order到customer的多对一关联,即仅仅在order类中定义customer属性;仅仅建立从customer到order的一对多关联。
  2. 双向关联:既建立从order到customer的多对一关联,又建立从customer到order的一对多关联。

ff046c4c592c4c11be6339ab536af2ee61d.jpg

(2)依赖

依赖指的是类之间的调用关系,在UML中用带虚线的箭头表示,如果类A访问类B的属性或方法,或者类A负责实例化类B,那么可以说类A依赖于类B。

和关联关系不同,无须在类A中定义类B类型的属性   。例如Panel与Shape类之间存在依赖关系,因为Panel类会调用Shape类的draw()的方法。

f51f7a8830e6f20a4a3c0697c611fdf2285.jpg

  1. 聚集

聚集指的是整体与部分的关系,在UML中用带实线的菱形箭头表示,例如台灯和灯泡之间就是聚集关系,聚集关系还分为两类:

被聚集的子系统允许被拆卸和替换,这是普通聚集关系。例如台灯和灯泡。

被聚集的子系统不允许被拆卸和替换,这种聚集关系称为强聚集关系,也叫组成关系,例如台灯和它的电路板,强聚集关系用带实线的实心菱形箭头表示

bf02713ff29f6c9d5ddc5f8107209526708.jpg

  1. 泛化

泛化指的是类之间的继承关系,在UML中用带实线的三角形箭头表示。

7546463abce96c2edeb97d3d48b1b0ef766.jpg

  1. 实现

实现指的是类与接口之间的关系,在UML中用带虚线的三角形箭头表示。

7c0de1a807106ad491b7512e25f5e7cf261.jpg                                 

  1. 区分依赖、关联和聚集关系

当对象A和对象B之间存在依赖、关联或聚集关系时,对象A都有可能调用对象B的方法,这是3种关系之间的相同之处。

依赖关系:对于两个相对独立的系统,当一个系统负责构造另一个系统的实例,或者依赖另一个系统的服务时,这两个系统之间主要体现为依赖关系;在Bicycle类中无需定义Pump类型的变量。

关联关系:对于两个相对独立的系统,当一个系统的实例与另一个系统的一些特定实例存在固定的对应关系时,这两个系统之间为关联关系。

聚集关系:当系统A被加入到系统B中,称为系统B的组成部分时,系统B和系统A之间为聚集关系。聚集关系和关联关系的区别还表现在以下方面:

  1. 对于具有关联关系的两个对象,在大多数情况下,两者具有独立的生命周期。
  2. 对于具有聚集关系的两个对象,整体对象会制约组成对象的生命周期。部分类的对象不能单独存在,它的声明周期依赖于整体类的对象的生命周期。

 

———————————————————————————————————————

Linux的CPU调度

  1. Linux支持实时非实时两种进程,实时进程相对于普通进程有绝对优先级,对应地,实时进程采用SCHED_FIFO或者SCHED_RR调度策略,普通的进程采用SCHED_OTHER。

 

优先级

  1. Linux对普通的进程,根据动态优先级进行调整,而动态优先级是由静态优先级调整而来的,默认情况下,静态优先级不可见,用户可以通过nice值影响静态优先级,其中nice值越大使得静态优先级越大,最终进程优先级越低。

b0ea93802449731a84853991ebcef794e93.jpg

 

为什么根据睡眠和运行时间确定奖惩分数是合理的

睡眠和CPU耗时反映了进程IO密集和CPU密集两大瞬时特点,不同时期,一个进程可能是CPU密集型也可能是IO密集型。

对于表现为IO密集的进程,应该经常运行,但每次时间片不要太长。

对于表现为CPU密集的进程,CPU不应该让其经常运行,但每次运行时间要长。

交互进程为例,假如之前其大部分时间在于等待CPU,这时为了提高响应速度,就需要增加奖励分;另一方面,如果此进程总是耗尽每次分配给它的时间片,为了对其他进程公平,就要增加这个进程的奖惩分数。

 

调度的时机

  1. 进程状态转换时刻:进程终止、进程睡眠。
  2. 当前进程的时间片用完。
  3. 主动让出处理器。
  4. 从中断、系统调用或异常返回。

 

实时进程

  1. 实时进程只有静态优先级,其范围在0~MAX_RT_PRIO-1,而nice值,影响的是优先级在MAX_RT_PRIO~MAX_RT_PRIO+40范围内的进程。
  2. 不同于普通进程,系统调度时,实时优先级高的进程总是优于优先级低的进程执行,直到实时优先级高的实时进程无法执行。如果有数个优先级相同的实时进程,那么系统会按照进程出现在队列上的顺序选择进程。
  3. 对于FIFO的进程,意味着只有当前进程执行完毕才会轮到其它进程执行。
  4. 对于RR的进程,一旦时间片消耗完毕,就将该进程置于队列的末尾。

 

普通进程

  1. CFS基于一个简单的理念:所有任务都应该公平的分配处理器时间,理想情况下,n个进程的调度系统中,每个进程获得1/n的处理器时间,所有进程的vruntime相同。
  2. 每个进程一个调度周期内分配的时间跟3个因素有关:进程总数、优先级和调度周期。
  3. vruntime就是该进程的运行时间,但这个时间是经过优先级和系统负载等加权过的时间,而非物理时间。
  4. CFS选择具有最小vruntime值的进程作为下一个可执行进程,CFS用红黑树来组织调度实体,而键值就是vruntime,那么CFS只要查找最左叶子结点即可。
  5. 何时更新rbtree
  1. 上一个进程执行完ideal_time,可继续执行时,会插入红黑树。
  2. 下一个进程被选中移出rbtree。
  3. 新建进程。
  4. 进程由睡眠态被激活,变为可运行态时。
  5. 调整优先级也会更新rbtree。
  1. CFS完全公平的理解:
  1. 不再区分进程类型,所有进程公平对待。
  2. 对I/O消耗型进程,仍然会快速响应(对睡眠进程做时间补偿)。
  3. 优先级高的进程,获得CPU时间更多(vruntime增长的更慢)

 

Linux的内存管理

  1. 页面级的分配是采用Buddy算法,而内核级的内存分配是采用面向对象的Slab分配准则

 

Buddy算法

  1. 把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1/2/4/1024个连续的页框,对1024个页框的最大请求对应着4MB大小的连续RAM块。每个块的第一个页框的物理地址是该块大小的整数倍。
  2. 内核视图把大小为b的一对空闲伙伴块合并为一个大小为2b的单独块,满足以下条件的两个块称为伙伴。
  1. 两个块具有相同的大小。
  2. 它们的地址是连续的。
  3. 第一块的第一个页框的物理地址是2*b*2^12的倍数。
  1. 分配算法:当用户申请分配长度为n的存储块时,求i=hash(n),若freelist.first不为空,只要删除此链表的第一个结点,分配申请者即可;否则判断freelist[i+1]所对应的空闲页表,若不为空,则将其分为等长的两半,一半分配给管理者,另一半链入相应的链表中。
  2. 回收算法:若回收块的伙伴也为空闲块,则将它们合并为更大的空闲块,然后再判断合并后的空闲块的伙伴是否为空闲块,若是则继续合并,否则只要将释放的空间简单地插入链表中即可。

 

Slab算法

  1. 采用buddy算法,解决了外碎片问题,这种办法适合大块内存分配,不适合小内存请求。
  2. Slab分配器思想:
  1. 小对象的申请和释放通过slab分配器来管理。
  2. Slab分配器有一组高速缓存,每个高速缓存保存同一种对象类型。
  3. 内核从它们各自的缓存分配和释放对象。
  4. 每种对象的缓存区由一连串slab构成,每个slab由一个或多个连续的物理页面组成,这些页面包含了已分配的缓存对象,也包含了空闲对象。

 

Linux的磁盘调度

  1. NOOP

该算法实现了最简单的FIFO队列,所有IO请求大致按照先来后到的顺序进行操作,并在FIFO的基础上还做了相邻IO请求的合并。

  1. Anticipatory IO Scheduler

CFG和DEADLINE考虑的焦点都在满足零散IO请求上,对连续的IO请求,比如顺序读,并没有做优化。Anticipatory在DEADLINE的基础上,为每个读IO都设置了6ms的等待时间窗口。如果在这6ms内OS收到了相邻位置的读IO请求,就可以立即满足。

  1. DEADLINE

DEADLINE在CFG的基础上,解决了IO请求饿死的极端情况。除了CFG本身具有的IO排序队列之外,DEADLINE额外分别为读IO和写IO提供了FIFO队列。读FIFO队列的最大等待时间为500ms,写FIFO队列的最大等待时间为5s。FIFO队列内的IO请求优先级比CFQ中高,读FIFO队列中优先级又比写FIFO队列高。

  1. CFG

该算法的特点是按照IO请求的地址进行排序,以尽量少的磁盘旋转次数来满足尽可能多的IO请求。CFQ对每个进程维护一个IO队列,各个进程发来的IO请求会被CFQ以轮训方式处理,也就是说对每一个IO请求都是公平的。

 

Linux文件系统

  1. Linux文件系统是一个对复杂系统进行抽象化的有趣例子。通过使用一组通用的API函数,Linux可以在许多存储设备上支持许多文件系统。例如,read函数调用可以从指定的文件描述符读取一定数量的字节,但read函数不了解文件系统的类型。
  2. 文件系统是对一个存储设备上的数据和元数据进行组织的机制,由于存在多种类型,因此Linux将文件系统接口实现为分层的体系结构,从而将用户接口层、文件系统实现和操作存储设备的驱动程序分隔开。
  3. 在Linux中将一个文件系统与一个存储设备关联起来的过程称为挂装。使用mount命令将一个文件系统附着到当前系统中(根)。在执行挂装时,要提供文件系统类型文件系统一个挂装点

高层体系结构:用户空间和内核中与文件系统相关的主要组件

247d91533ba14ce003b1e18f5dbfabaed74.jpg

  1. 用户空间包含一些应用程序(例如,文件系统的使用者)和GUN C库,它们为文件系统调用(打开,读写和关闭)提供用户接口。
  2. 系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间。
  3. VFS是底层文件系统的主要接口。这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大。有2个针对文件系统对象的缓存(inode和dentry)。它们缓存最近使用过的文件系统对象。
  4. 每个文件系统实现(ext2,JFS)导出一组通用接口,供VFS使用。
  5. 缓冲区缓存会缓存文件系统和相关块设备之间的请求。例如,对底层设备驱动程序的读写请求会通过缓冲区来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度,以最近使用列表(LRU)的形式管理缓冲区缓存。

VFS

  1. 虚拟文件系统为应用程序员提供一层抽象,屏蔽底层各种文件系统的差异。
  2. VFS支持的文件系统可以归结为3类:基于磁盘的文件系统(Ext2/Ext3),网络文件系统(NFS),特殊文件系统(proc/sysfs)。
  3. Linux以一组通用对象的角度看待所有文件系统,这些对象是超级块,inode,dentry和文件。
  1. 超级块在每个文件系统的根上,超级块描述和维护文件系统的状态,包含文件系统名称,文件系统的大小和状态,块设备的引用和元数据信息(比如空闲列表等)。超级块通常存储在存储媒体上。

超级块中的一个重要元素是超级块操作的定义(super_operations),这个结构定义一组用来管理这个文件系统中的inode的函数。

  1. 文件系统中管理的每个对象(文件或目录)在Linux中表示为一个inode,它具有唯一资源标识符,inode包含管理文件系统中的对象所需的所有元数据(包括可以在对象上执行的操作)。
  2. dentry用来实现名称和inode之间映射,有一个目录原来缓存最近使用的dentry。dentry还维护目录和文件之间的关系,从而支持在文件系统中移动。
  3. VFS文件表示一个打开的文件(保存打开的文件的状态,比如写偏移量)。
  1. Linux VFS的强大扩展能力,正是因为这种通用文件模型的设计。新支持的文件系统,只需要将自己的结构转换成这种通用的模型即可插入到Linux中。

 

同步机制应遵循哪些基本准则

空闲让进、忙则等待、有限等待、让权等待。

 

———————————————————————————————————————

基本空间划分

  1. 静态划分

Application

Framework

dalvik virtual machine

libraries

linux kernel

  1. 动态划分
  1. 从操作系统的角度上看,Android就是Linux应用的集合。
  2. 从Linux角度上看,Android应用对应着Linux的一个个进程。
  1. 划分方法一

b32896a62b06f7d7bf9282ae6c1fe794d13.jpg

  1. 划分方法二

f641a8d441fbb692732c5899e856723e83a.jpg

 

IPC框架分析之Binder、Service和Service Manager

  1. Activity托管在不同的进程,Service也是托管在不同的进程,不同进程间的Activity和Service之间要交换则属于IPC。
  2. Binder就是为了Activity通讯而设计的一个轻量级的IPC框架。客户端和服务端直接通过Binder交互数据,打开Binder写入数据,通过Binder读取数据,通讯就完成了。
  3. 在Android中,要完成某个操作,所需要做的就是请求某个有能力的服务对象去完成操作,所以Android的IPC本质上属于对象请求代理框架

dab85667ffe924fbec4bcfc673531adf7b6.jpg

IPC框架包含了如下的概念:

  1. 设备上下文:

设备上下文包含客服端、环境或者请求中没有作为参数传递的上下文信息,应用程序用Context Object接口上定义的操作来创建和操作上下文。

  1. Android代理:

这个指代理对象。

  1. Binder:

Linux提供的Binder通讯机制。

 

问题一:Client端要完成云服务端的通讯,首先要知道服务在哪里

首先看Service Manager管理了哪些数据,Service Manager提供了Add Service、Check Service两个重要的方法,并且维护了一个服务列表记录登记的服务名称和句柄。

6cb1303eaefa7e58d05d24c472f355e3ae5.jpg

A)Service Manager的工作就是登记功能,Service Manager使用0来标识自己,并且在初始化的时候,通过Binder设备使用BINDER_SET_CONTEXT_MGR ioctl将自己变成了CONTEXT_MGR。ServerList中存储了服务的名字和Handler,这个Handler作为Client端发起请求时的目标地址。

B)服务通过Add Service将自己的名字和Binder标识Handler登记在ServerList中。

C) 而服务请求者,通过Check Service方法,通过服务名字在ServerList中获取到Service相关联的Binder标识的Handler,通过这个Handler作为请求包的目标地址发起请求。

 

问题二:客服端是如何建立连接的

Android设计者在Linux内核中设计了一个叫做Binder的设备文件,专门用来进行Android的数据交换。

  1. 将Java对象从JVM空间传递到C++空间,这个是靠JNI使用ENV来完成对象的映射过程。
  2. 从C++空间传入内核Binder设备,使用ProcessState来完成工作。
  3. Service从内核中Binder设备读取数据。

 

ProcessState

在ProcessState中包含了通讯细节

  1. 利用open_binder打开Linux设备dev\binder。
  2. 通过ioctrl建立的基本的通讯框架。
  3. 利用上层传递来的service Handler确定请求发送到哪个Service。

 

问题三:JVM概念空间中如何对这些概念进行包装

为了在上层使用统一的接口,在JVM层面中有2个东西。

在Android中,为了简化管理框架,引入了Service Manager这个服务。所有的服务都是从Service Manager开始的,只用通过Service Manager获取到某个特定的服务标识构建代理IBinder

在Android中,利用Service Manager是默认的Handler为0,只要设置请求包的句柄为0,就是发送给Service Manager这个Service的。在做服务请求时,Android建立一个新的Service Manager Proxy。Service Manager Proxy利用Context Object作为Binder和Service Manager进行通讯。

Android代码一般的获取Service建立本地代理的方法如下:

IXXX  mIXXX = IXXXInterface.Stub.asInterface(ServiceManager.getService(“xxx”));

这些服务代理获取过程分解如下:

  1. 通过调用getContextObject调用获取设备上下文对象(在Android JVM概念空间的Context Object只是与Service Manager通信的Binder有对应关系)。这个跟C++概念空间的getContextObject是不一样的)。使用该函数在进程空间建立了ProcessState对象,打开了Binder设备dev/binder,并且传递了参数0,这个参数0代表了与Service Manager这个服务绑定。
  2. 调用ServiceManager.asInterface(Context Object)建立一个代理Service Manager

mRemote = Context Object(Binder)

这样就建立起来Service Manager Proxy通讯框架。

  1. 客户端通过调用Service Manager的getService方法建立一个相关的代理Binder,这个就是底层C++新建的代理Binder。

 

问题四

Activity为了建立一个IPC,需要建立两个连接:访问Service Manager的连接IXXX具体XXX Service的代理对象与XXXService的连接

这个两个连接对应ProcessState中BpBinder。对IXXX的操作最后就是BpBinder的操作,由于我们在写一个Servcie时,在一个Package中写了Service Native部分Service Proxy部分,而Native和Proxy都实现相同的接口,客户端调用的方式是使用remote->transact方法向Service发出请求,而在服务端的onTransat来处理这些请求。所以在Android Client空间就看不到这个效果。

 

Service深入分析

  1. 三种服务:Native服务、Android服务。
  2. Native服务实际上就是指完全在C++空间中完成的服务,主要是指系统一开始初始化,通过init.rc脚本起来的服务,例如Service Manager Service,Zygote Service,Media Service,ril_demon Service等等。
  1. 服务进程建立了ProcessState对象,并将给对象登记在进程的上下文中。
  2. 建立一个新的AudioFlinger对象,并将对象登记Service Manager Service中。
  3. 开始接收请求,处理请求,应答这个循环闭合请求。
  1. Android服务是指在JVM空间完成的服务,虽然也要使用Native上的框架,但是服务主体存在于Android空间,Android是二阶段初始化时建立的服务。

Android的所有服务循环框架都是建立在SystemServer上,在SystemServer中看不到循环结构,只是可以看到建立了init2的实现函数,建立了一大堆服务,并Add Service到Service Manager中。

 

Service本质结构

服务的本质就是响应客户端请求,要提供服务,就必须建立接收请求、处理请求和应答客户端的框架,所以服务一定要存在一个闭合循环框架请求处理框架

f4b2fc782121e341ad847b362a24b296ac0.jpg

 

Service基本框架分析

  1. Native Service和Android Service采用了同一个闭合循环框架,这个闭合循环框架放置在Natice的C++空间中,ProcessStateIPCThreadState两个类完成了全部工作。

它们仅仅表现在对onTransact回调处理的不同。

  1. 在服务框架中,ProcessState是公用的部分,这个公用部分最主要的框架就是闭合循环框架接收到从Binder来的请求后的处理框架,将ProcessState简而言之就是:
  1. Add Service。
  2. 建立闭合循环处理框架。

 

ProcessState和IPCThreadState

  1. ProcessState及其IPCThreadState处于IPC与内核打交道包装层,有关IPC的C++空间实现都是ProcessState这个对象完成的。不管JVM的Binder做了多么复杂的操作,最终还是需要利用ProcessState这个C++空间的对象把数据传递到Binder Driver,接收数据也是通过ProcessState这个对象完成的,ProcessState是所有Binder IPC必经的通道。
  2. ProcessState放置在全局变量gProcess中,每个进程只有一个ProcessState对象,负责打开Binder设备驱动建立线程池等。
  3. IPCThreadState每个线程只有一个,IPCThreadState实例登记在Linux线程的上下文附属数据中,主要负责Binder数据读取,写入和请求处理框架。IPCThreadState在构造的时候,获取进程的ProcessState并记录在自己的成员变量mProcess中,通过mProcess可以获取到Binder的句柄。
  4. ProcessState构造的时候,使用open_binder打开driver/binder,并将句柄记录在mDriverFD,在ProcessState中并不使用这个句柄,真正使用这个Binder设备句柄的是IPCThreadState,所有关于Binder的操作放置在IPCThreadState中。

 

 

  1. 基本原理
  2. 消息系统
  3. 基本架构

 

GWES之基本原理

52b864dbeb96fe26c349e061b4caf035f92.jpg

  1. GUI的实现就是对图形、窗口和事件提到的三个基本要素的管理,根据这三个要素的特性及其涉及的范围,GUI在总体上分为三个部分:
  1. 事件管理器。收集系统消息,转换并分发系统消息和用户消息给各个窗口对象。
  2. 窗口管理器。管理窗口的创建,绘制和销毁。
  3. GDI。上下文设备管理,包括图形绘制,图像操作。

88ed90ea250226ae868a7519f61b736d0b1.jpg

 

GWES之消息系统

  1. Android要建立一个消息系统使用了Looper、MessageQueue,Handler等概念,本质的东西就是消息队列中消息的分发路径消息分发处理方式的设计。
  2. Looper只是产生一个消息循环框架,首先Looper创建了消息队列并把它挂接在Linux的线程上下文中,进入到取消息,并分发消息的循环中。
  3. Handler对象在同一个线程上下文中取得消息队列,对消息队列进行封装的操作,最主要就是sendMessage和dispatchMessage这个实际操作,外部系统要向某个Android线程发送消息,必须通过Handler这个对象进行。

Handler属于某个线程,其在构建时做了如下的默认动作:

  1. 从线程上下文取得Looper。
  2. 通过Looper获取到消息队列并记录在自己的成员mQueue中。
  1. Handler使用消息队列进行对象封装,提供如下成员函数
  1. post(Runnable):Runnable是消息处理的回调函数,通过该消息的发送,引起Runnable的回调运行,Post消息放置消息队列的前面。
  2. sendMessage:放置在所有的post消息之后。
  3. dispatchMessage:分发消息。消息带有回调函数,则执行回调函数,如果没有则使用默认处理函数,handleMessage。

6bf15e85f327a6ceb4498aee9a39e92d992.jpg

GWES之基本架构原理

  1. Android的窗口管理式C/S模式的。Android中的window是表示Top - level等顶级窗口的概念。DecorView是window的Top-level View,这个View称为主View。DecorView会缺省地Attach到Activity的主窗口中。View被加入到Window Manager中,Window Manage使用WindowState与这个View对应
  2. Activity建立一个主窗口后,在将主窗口添加到WindowManager时,首先要建立WindowManager代理对象,并打开一个会话(实现IWindowSession AIDL接口),并维持该会话。

Activity将通过该会话与WindowManager建立联系,这个WindowSession是C/S体系的基础,Client通过IWindowSession添加窗口到windowManager中,而IWindow则是windowManager分发消息给Client ViewRoot的渠道,一个完整的窗口概念横跨了View,ViewRoot,windowManager Service等概念。

f3cf200ef2aadc2af841fb2390f77cb994d.jpg

Client的Activity通过WindowSession与windowManager建立对话,而windownManager则通过IWindow接口访问Client,将消息传递到Client端,通过分发渠道,将消息传递到处理函数OnXXX。

 

客户端的组成(Window、View、ViewRoot、WindowManager Proxy

  1. 在Activity在performLaunchActivity时,会使用Activity.Attach()建立一个PhoneWindow主窗口。这个主窗口的建立并不是一个重点,handleResumActivity真正要启动一个Activity时候,将主窗口的DecorView加入到windowManager。
  2. DecorView实际上是一个ViewGroup,View的成员变量mParent用来管理View上级关系的,而ViewGroup顾名思义就是一组View的管理,于是在在ViewGroup构建了焦点管理和子View结点数组,这样通过View的mParent和ViewGroup的mChildren构建了Android的View直接的关系网。
  3. Focus Path:所谓的Focus Path就是我们的keyEvent传递的路线
  4. ViewRoot通过IWindowSession添加窗口到windowManager,而IWindow则是windowManager分发消息给Client ViewRoot的渠道。

ViewRoot实际上是一个Handler,建立主View与WindowManager通讯的桥梁。

  1. WindowManager Proxy中建立了View、Layout与ViewRoot三者的对应关系表,构造一个ViewRoot就会打开一个Session,并利用IWindowSession建立会话上下文。

 

windowManger

  1. windowManger管理的窗口是应用程序的Top - level窗口,主窗口放置在一起管理是为了计算z-order序列,根据应用程序的状态来显隐应用程序的窗口。
  2. 在服务端的窗口对象叫做windowState。在Service维护了一个mWindow数组,这个mWindow就是Window的z-order序数组,mWindowMap用于记录<Client:Binder,WindowState对象>。
  3. windowState有一个叫作mClient成员变量来记录客户端IWindow实例,通过IWindow接口实例,Service可以访问客户端的信息,所以IWindow是Service连接View的桥梁
  4. Focus window活动窗口如何计算?

基本原理就是查找前景应用(FocusActivity),并同z-order序中找出属于该FocusActivity(AppToken)的主窗口,这个窗口就是计算出来的Focus window。

  1. 为了要提出Token?

一个应用程序要管理自己的窗口,那么如何来标识该窗口是属于某个Activity,Android设计者就提出了AppToken这个概念。AppToken在本质上的描述,<Token:IBinder,allWindows>,通过Token找到属于该Token的allWindows。使用Token开始完成该应用程序的所有窗口的显示和隐藏。

  1. 系统消息收集和处理:

Service使用KeyQ作为专门的消息队列(keyEvent,touchEvent和trackballEvent),keyQ线程,通过Native函数readEvent轮训设备,将读取的结果放置在keyQ队列中。

系统dispatcher等待在keyQ消息队列上,一旦从消息队列中获取到消息,就通过分发函数通过mClient传递到Client

 

GWES之输入系统

  1. windowManager服务解决了用户信息输入收集,而focusActiviy、focusWindow和focusView这些概念的设计是为了解决用户输入应该放到哪里去这个问题
  2. Android输入系统包括:linux driver、windowManger、messageSystem,ViewFocusPath,FocusView。
  3. keyInputQ在windowMangerService中建立一个独立的线程inputDeviceReader,使用Native函数readEvent来读取linux driver的数据构建rawEvent,并放入到keyQ消息队列中。

inputDispatchThread从keyQ中读取events,找到windowManger中的focus window,通过focus window记录的mClient接口,将events传递到client端。Client再根据自己的focus path传递事件,直到事件被处理。

59a7d13cba10b0988687749a765a6d1d076.jpg

 

GWES之输入系统之输入路径

  1. 第一步:用户数据收集及其初步判断

keyInputQ在windowManagerService中建立一个独立的线程inputDeviceReader,使用Native函数readEvent来读取Linux Driver的数据构建rawEvent,放入到keyQ消息队列中。

根据powerManager获取的screenOn,screenOff状态来判定用户输入的是否wakeUpScreen。

如果按键是应用程序切换按钮,则切换应用程序。

根据windowManagerPolicy决定该用户输入是否投递。

  1. 第二步:消息分发第一层面

inputDispatchThread从keyQ中读取events,找到windowManager中的focusWindow,通过focusWindow记录的mClient接口,将events传递到client端。

在客户端建立windowManagerProxy后,添加窗口到windowManager之后,带了一个跟客户viewRoot相关的IWindow接口实例过去,记录在windowState中的mClient成员中,通过IWindow这个AIDL实例,Service可以访问客户端的消息。

  1. 第三步:应用消息队列分发

这里调用的handleMessage就是viewRoot重载的handleMessage。

  1. 第四步:向焦点进发,完成焦点路径的遍历

mView.dispatchKeyEvent:mView是与viewRoot向对应的top-level View,如果mView是一个viewGroup则分发消息到它的mFocus。

如果此时的mFocus还是一个viewGroup,这回将事件传递到下一层的焦点,直到mFocus为一个View。

  1. 第五步:缺省处理

如果事件在上述Focus View没有被处理掉,并且为方向键之类的焦点转换相关按键,则转移焦点到下一个View。

 

GDI之基本原理和整体框架

  1. GDI主要管理图形图像的输出。在实际中,我们需要在物理屏幕上输出不同的窗口,而每个窗口认为自己独占屏幕的使用,对所有窗口输出,应用程序不会关心物理屏幕是否被别的窗口占用,而只是关心自己在本窗口的输出,置于输出能否在屏幕上看见,需要GDI来管理。
  2. GDI输出抽象成了文本、画笔和位图等设备无关的操作,让应用程序只需要面对逻辑的设备上下文进行输出操作,而不要涉及到具体输出设备,以及输出边界的管理。

9eee5e0f057373618aed89c9592a599bbd0.jpg

A)Skia:是C++的2D图形引擎库,Android的2D绘制系统都是建立在该基础之上,Skia完成了:文本输出,位图,点,线,图像解码等功能。

B)应用程序通过surfaceFlinger将自己的surface投递到屏幕缓冲区。

 

GDI之显示缓冲管理

  1. FrameBuffer在系统中就是一段内存,GDI的工作就是把需要输出的内容放入到该段内存的某个位置。
  2. 我们在显示缓冲区上做的最多的操作就是区块搬运,由于在Linux不同进程之间的内存不能自由的访问,使得我们的每个Android应用对于内存区域和屏幕缓冲区的使用变得很复杂,在Android的设计中,在屏幕缓冲区显示缓冲区的管理分类有很多的层次,最上层的对象可以在进程间自由地传递,但是对于缓冲区内容则使用共享内存的机制。
  3. 所有屏幕上图形的移动都是显示缓冲区搬运的结果。
  4. Android使用了动态链接库gralloc.xxx.so来完成底层细节的封装,gralloc主要功能是完成:
  1. 打开屏幕设备“dev/fb0”,并映射硬件显示缓冲区。
  2. 提供分配共享显示缓存的接口
  3. 提供biltEngine接口(完成硬件加速器的包装)。

 

GDI之共享缓冲区机制

  1. private_handle_t是gralloc.so使用的本地缓冲区私有的数据结构,而native_handle_t是上层抽象的可以在进程间传递的数据结构。
  2. 由于在上层不要关心buffer_handle_t中data的具体内容,在进程间传递buffer_handle_t(native_handle_t)句柄是其实是将这个句柄内容传递到client端,在客户端通过Binder读取readNativeHandle新生成一个native_handle_t。

在构造客户端的native_handle时,由于对方传递过来的文件句柄的处理,由于不是同一个进程,所以需要dup一下,这样就将native_handle句柄中的,客户端感兴趣的从服务端复制过来,这样就将private_handle_t的数据复制到了客户端。

客户端利用这个新的native_buffer被mapper传唤到gralloc.xxx.so中,获取到native_handle关联的共享缓冲区映射地址,从而获取到了该缓冲区的控制权,达到了客户端和server间的内存共享,从surfaceFlinger来看就是作图区域的共享。

 

Graphic Mapper

  1. 服务端(surfaceFlinger)分配了一段内存作为surface的作图缓冲,客户端怎样在这个作图区域上工作,两个进程如何共享内存,如何获取到共享内存。
  2. GraphicBuffer:建立本地的GraphicBuffer的数据native_buffer_t,在通过接收对方传递的native_buffer_t构建GraphicBuffer。
  3. Surface:lock在Client端建立了一个新的GraphicBuffer对象,该对象通过描述的原理将sufaceFlinger的buffer_handle_t相关数据构成新的客户端buffer_handle_t数据

在客户端的surface对象就可以利用GraphicMapper对客户端buffer_handle_t进行mmap从而获取到共享缓冲区的开始地址。

  1. Android使用共享缓冲区的方式来管理与显示相关的缓存区,它设计成了两层,上层是缓冲区管理的代理机构GraphicBuffer及其相关的native_buffer_t,下层是具体的缓冲区的分配管理及其缓冲区本身。上层的对象是可以通过Binder进行传递的,而在进程间并不是传递缓冲区本身,而是使用mmap来获取指向共同物理内存的映射地址。

 

GDI之SurfaceFlinger

  1. surfaceFlinger的主要功能是:
  1. 将surface的内容刷新到屏幕上。
  2. 维持layer的z-order序列,并对layer最终输出做出裁剪计算。
  3. 响应client的要求,建立layer与客户端的surface之间的连接。
  4. 接收client的要求,修改layer属性(输出大小,alpha等设定)。
  1. surfaceFlinger管理对象为
  1. mClientsMap:管理客户端与服务端的连接
  2. ISurface、ISurfaceComposer:AIDL调用接口实例。
  3. mLayerMap:服务端的surface的管理对象。
  4. mCurrentState.layersSortedByZ:以surface的z-order序列排列的layer数组。
  5. GraphicPlane:缓冲区输出管理。
  6. OpenG:图形计算,图像合成等图形库。
  7. Gralloc.xxx.so:跟平台相关的图形

———————————————————————————————————————

根据模式的目的可分为3

  1. 创建型模式:与对象的创建有关。
  2. 结构型模式:处理类与对象的组合。
  3. 行为型模式:对类或对象怎样交互怎样分配职责进行描述。

 

面向对象设计的2个基本准则

  1. 针对接口编程,而不是针对实现编程。
  1. 接口是一系列方法的声明,也是一些方法特征的结合,一个接口只有方法的特征而没有方法的实现。因此,这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为。
  2. 针对接口编程就是要先设计一系列的接口,把设计和实现分离开,使用时只需引用接口即可,也利于系统各部分之间的解耦合。
  3. 针对接口编程时为了提高程序的可维护性、可伸缩性和可复用性。如果你在一个类中直接使用另外一个,这样就把两个类紧密地结合到了一起。
  4. 如果针对接口编程,当业务变化时,我们只需要用一个新的类实现接口即可,而客户端依旧可以使用接口引用新的类,同时也保证了客户端的不变性。
  1. 优先使用对象组合,而不是类继承。
  1. 继承关系的最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合。
  2. 组合使用灵活,不破坏封装,整体类与局部类之间松耦合,彼此相对独立。

 

面向对象设计的5个设计准则

  1. 单一职责原则
  1. 一个类的功能要单一,只做与它相关的事情。
  2. MVC的基本处理流程:用户与视图交互,视图接受并反馈用户的动作;视图把用户的请求传给相应的控制器,由控制器决定调用哪个模型,然后由模型调用相应的业务逻辑对用户的请求进行加工处理;如果需要返回数据,模型会把相应的数据返回给控制,由控制器调用相应的视图,最终由视图格式化和渲染返回的数据,以一种友好的方式展现给客户。
  1. 开放封闭原则
  1. 对扩展开放。意味着又新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  2. 对修改关闭,意味着类一旦涉及完成,就可以独立完成其工作,而不要对类进行任何修改。
  1. 里氏代换原则
  1. 一个软件实体如果是基类的话,那么也一定适用于其子类,而且它根本察觉不出使用的是子类还是基类,反过来的代换是不成立的。
  2. 实现开放封闭原则的关键是进行抽象,而继承关系又是抽象的一种具体实现,这样LSP就可以确保基类和子类关系的正确性,进而为实现开放封闭原则服务。
  1. 迪米特准则
  1. 狭义:如果两个类不必直接进行通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的方法的话,可以通过第三者转发这个调用。
  2. 广义:一个模块设计的好坏的一个重要标志就是该模块在多大程度上将自己的内部数据与实现的有关细节隐藏起来。
  3. 过度适用迪米特法则,也会造成系统的不同模块之间的通信效率降低。解决这个问题的方式是:使用依赖倒转原则,这样调用方和被调用方之间有了一个抽象层,被调用发在遵循抽象层的前提下可以自由地变化,此时抽象层成了调用方的朋友。

4294705165fbfc44db493c7bed446f8c889.jpg

  1. 合成聚合复用原则
  1. 尽量使用聚合和组合,尽量不要使用继承。
  2. 第一、继承复用破坏包装,它把父类的实现细节暴露给了子类,这违背了信息隐藏的原则。
  3. 第二、如果父类发生了变化,那么子类也要发生相应的改变,这就直接导致了类与类之间的高耦合。
  4. 如果两个类之间在符合分类学的前提下有明显的“is-a”关系,而且基类能够抽象出子类公有的属性和方法,而此时子类能通过增加父类的属性和方法来扩展基类,那么此时使用继承将是一种更好的选择。

 

23种设计模式

  1. 工厂方法模式*
  1. 定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法模式使一个类的实例化延迟到其子类

3f4d9298c69a010e00d683a58cebb5efa30.jpg

  1. 先抽象的产品类、抽象的工厂类,然后用客户端具体的工厂生产相应的具体产品,但是客户端并不知道具体的产品是怎么生产的,生产的过程封装在工厂里。
  2. 参数化工厂方法模式得到相应的对象。

为子类提供挂钩。

连接平行的类层次。

  1. 抽象工厂模式
  2. 创建者模式*
  3. 原型模式*
  4. 单例模式*
  1. 负责控制自己的实例化数量单一,替换系统中全局变量。

e1731cb017d0c6b6e8801b4ccd3dad84e2e.jpg853cabd3ea43d819c2837c47f2bb4e31ffc.jpg

B)在多线程的环境下,单例模式为了保证自己实例数量的唯一,必然会做并发控制。

C)对唯一实例的受控访问。

避免全局变量污染命名空间。

允许对操作和表示的精化。

比类操作更为灵活。

 

  1. 适配器模式*
  2. 桥模式
  3. 组合模式*
  1. ViewViewGroup类的使用,在UI中,几乎所有的Widget和布局都依靠这2个类。

87a143ceefd2feaa60643b08e01ce9a1a0a.jpg

  1. 针对View和ViewGroup的实际情况,我们选择安全式的组合模式(在组合对象中添加add、move、getChild方法)。

d78f6ab58ab6b6f3b22d7895e5ef20e386a.jpg

C)定义了包含基本对象组合对象类层次结构,这种结构能够灵活控制基本对象与组合对象的使用。

简化了客户代码,基本对象和组合对象有一致性,用户不用区分它们。

使得更容易添加新类型的组件。

是你的设计变得更加一般化。

  1. 装饰模式
  2. 外观模式
  3. 享元模式*
  1. 运用共享技术有效地支持大量细粒度的对象。

b5b822848653b7410fd6f9e0eae06fa9b07.jpg

  1. 客户端通过享元工厂获取享元对象,享元对象的创建则根据工厂的享元池来创建,如果享元池中没有这个对象,则创建这个对象并保存到享元池中,如果享元池中有这个对象,就直接使用这个对象。因为享元对象在共享的同时,说明它重用属性的不变性,不然都是变化的东西,不存在共享,这些不变的属性称之为内部状态,独立于外部场景。而另外一些属性,可以根据外部场景变化,我们称之为外部状态,我们可以通过operation改变外部状态。

3a7be9e527b863ec48ef86974bb35e32a58.jpg

C)Android中SQLiteCompliedSql的使用,其实是很多数据库系统的经典实现,这种方式大大减少了sql编译对象的创建,提高了数据库操作的性能。

D)节约存储的方法,用共享减少内部状态的消耗,用计算时间换取对外部状态的存储。

  1. 代理模式

 

  1. 解释器模式
  2. 模板方法模式*
  1. 通过父类调用子类的办法,使用继承来改变算法的一部分。

b2f5e8614e231be71119d3acead3b8720b9.jpg

  1. 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。定义了几个步骤1、2、3,在模板方法中按照一定的结构顺序执行这些步骤,父类的方法可以有缺省的实现,也可以是一个空实现,即所谓的钩子操作。

C)模板方法是一种代码复用的基本技术,它们在类库中尤为重要,它们提取了类库中的共同行为。

模板方法导致一种反向控制结构,即父类调用子类的操作。

模板调用操作的类型有具体的操作,具体的AbstracClass操作,原语操作、工厂方法、钩子操作。

Android中对这些重定义操作的命名喜欢在方法前加一个前缀on。

模板方法是用继承来改变算法的一部分,策略模式是用委托来改变整个算法。

  1. 职责链模式
  2. 命令模式*
  1. 把一个请求封装为一个对象,从而使你可用不同的请求对客户进程参数化操作,对请求排队或记录请求日志,以及支持可撤销的操作。

B)将调用对象的操作和指导如何实现该操作的对象解耦。

多个命令可以装配成一个复合命令。

增加新的命令很容易。

  1. 迭代器模式
  2. 中介者模式
  3. 备忘录模式*
  1. 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到之间保存的状态。

  1. 组织者,把原发器的State(全部或部分状态,一般是变量的值),通过CreateMemento()方法保存起来,继续运行后,等待合适的时机,再通过SetMemento方法可以恢复到之前的状态。在这个过程中,我们并没有对这些状态做任何的访问和设置,实际上这些状态都是私有的,对外是禁止访问的,我们只是通过Memento对象的两个最简单的方法就达到了这个效果。Mememto经常写成原发器的内部类。
  2. Canvas的两个方法save()和restore()方法在做图形变换的时候使用得非常多。

D)保持封装边界,把很复杂的原发器的内部信息对外部其它对象隐藏起来。

简化的原发器,把状态操作无形中转化到客户手里,简化了原发器的某些实现。

也要注意备忘录的管理代价。

  1. 观察者模式*
  1. 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都被自动更新。

B)这是一个最简单的观察者模式,目标对象能够添加和删除观察者,当自己某种状态发生改变时,可通过notify通知注册的观察者进行更新操作。

C)目标和观察者间的抽象耦合。

支持广播通信。

注意意外的更新,这也是观察者更新进行管理的原因。

  1. 状态模式
  2. 策略模式*
  3. 访问者模式

 

 

 

 

 

 

 

转载于:https://my.oschina.net/u/2288454/blog/1919672

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值