barriers / 阅读 / 详情

数据流压缩原理和数据压缩Zlib的实现

2023-08-24 16:55:07
共1条回复
牛云

压缩的本质就是去冗余,去除信息冗余,使用最短的编码保存最完整的数据信息。所以对于不同的场景,压缩采用的算法也因时制宜,比如视频和图片可以采用有损压缩,而文本数据采用无损压缩。压缩率又取决于信息的冗余度,也就是内容中重复的比例。那些均匀分布的随机字符串,压缩率会降到最低,即香农限

deflate是zip文件的默认算法。它更是一种数据流压缩算法。

LZ77压缩算法采用字典的方式进行压缩,是一种简单但是很高效的数据压缩算法。其方式就是把数据中一些可以组织成短语的字符加入字典。维护三个概念: 短语字典、滑动窗口、向前缓冲区

压缩的逆过程,通过解码标记和保持滑动窗口中的符号来更新解压数据。当解码字符被标记:将标记编码成字符拷贝到滑动窗口中,一步一步直到全部翻译完成

在流式传输中,不定长编码数据的解码想要保持唯一性,必须满足唯一可以码的条件。而异前缀码就是一种唯一可译码的候选,当然这样会增加编码的长度,却可以简化解码。

huffman编码是一种基于概率分布的贪心策略最优前缀码。huffman编码可以有效的压缩数据,压缩率取决于数据本身的信息冗余度

计算数据中各符号出现的概率,根据概率从小到大,从下往上反向构建构造码树,这样最终得到的编码的平均长度是最短的。同时也是唯一可译的

解读:在一开始,每一个字符已经按照出现概率的大小排好顺序,在后续的步骤中,每一次将概率最低的两棵树合并,然后用合并后的结果再次排序(为了找出最小的两棵树)。在gzip源码中并没有专门去排序,而是使用专门的数据结构(比如最小堆或者红黑树)。

使用优先队列实现huffman树,最后基于Huffman树最终实现文件压缩。

具体步骤:

gzip = gzip 头 + deflate 编码的实际内容 + gzip 尾

zlib = zlib 头 + deflate 编码的实际内容 + zlib 尾

压缩之前:初始化各种输入输出缓冲区;

压缩:我们可以不断往这些缓冲区中填充内容,然后由deflate函数进行压缩或者indeflate函数进行解压

总结:在调用deflate函数之前,应用程序必须保证至少一个动作被执行(avail_in或者avail_out被设置),用提供更多数据或者消耗更多的数据的方式。avail_out在函数调用之前千万不能为零。应用程序可以随时消耗被压缩的输出数据

相关推荐

红黑树原理讲解

性质1: 每个节点要么是 黑色 ,要么是 红色 。 性质2: 根节点是 黑色 。 性质3: 每个叶子节点(NIL)是黑色。 性质4: 每个 红色 节点的两个子节点一定都是 黑色 。 性质5: 任意一个节点到每个叶子节点的路径都包含 数量相同 的 黑节点 。俗称: 黑高 ! 从性质5又可以推出: 性质5.1: 如果一个节点存在黑子节点,那么该结点肯定有两个子节点。 插入操作包括两部分工作: 注意: 插入结点,必须为 红色 ,理由很简单,红色在父节点(如果存在)为黑色节点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。如果插入结点是黑色,那么插入位置所在的子树黑色结点总是1,必须做自平衡。 最简单的一种情景,直接把插入结点作为根结点就行 注意: 根据红黑树性质2: 根结点是黑色。还需要把插入结点设为黑色。 处理: 更新当前结点的值,为插入结点的值 由于插入的结点是红色的,当插入结点的父结点为黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。 再次回想红黑树的性质2: 根结点是黑色。如果插入结点的父结点为 红结点 ,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这一点很重要,因为后序的旋转操作肯定需要祖父结点的参与。 依据红黑树 性质4可知,红色结点不能相连===>祖父结点肯定为黑结点 因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为: 红黑红 处理: 1.将P和U结点改为黑色 2.将PP改为红色 3.将PP设置为当前结点,进行后序处理 注意: 单纯从插入前来看,叔叔结点非红即空(NIL结点),否则的话破坏了红黑树性质5,此路径比其它路径多一个黑色结点。 处理: 处理: 该情景对应情景4.2,只是方向反转,直接看图 处理: 处理:
2023-08-17 23:21:281

C++实习生面试,一般会问到关于STL的什么知识点

1.C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等2.标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-BlackTree)。RB树的统计性能要好于一般的 平衡二叉树3.STL map和set的使用虽不复杂,但也有一些不易理解的地方,如: map: type pair <constKey, T> ,很多不同的 const Key 对应的 T 对象的一个集合,所有的记录集中只要 const Key 不一样就可以, T 无关! set: type const Key. 只存单一的对 const Key ,没有 map 的 T 对像!可以看成 map 的一个特例(1)为何map和set的插入删除效率比用其他序列容器高?,树答:因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。map和set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点(2)为何每次insert之后,以前保存的iterator不会失效?答:iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。(3)为何map和set不能像vector一样有个reserve函数来预分配数据?答:我以前也这么问,究其原理来说时,引起它的原因在于在map和set内部存储的已经不是元素本身了,而是包含元素的节点。也就是说map内部使用的Alloc并不是map<Key, Data, Compare, Alloc>声明的时候从参数中传入的Alloc。例如:4.set, multiset set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。因为是排序的,所以set中的元素不能被修改,只能删除后再添加。 向set中添加的元素类型必须重载<操作符用来排序。排序满足以下准则: 1、非对称,若A<B为真,则B<A为假。 2、可传递,若A<B,B<C,则A<C。 3、A<A永远为假。set中判断元素是否相等: if(!(A<B || B<A)),当A<B和B<A都为假时,它们相等。 5.map,multimap map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序,map中元素的key不允许重复,multimap可以重复。map<key,value>因为是排序的,所以map中元素的key不能被修改,只能删除后再添加。key对应的value可以修改。向map中添加的元素的key类型必须重载<操作符用来排序。排序与set规则一致。6. List的功能方法 实际上有两种List: 一种是基本的ArrayList,其优点在于随机访问元素,另一种是更强大的LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。 List : 次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用。)一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。ArrayList : 由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和移除元素。因为那比LinkedList开销要大很多。 LinkedList : 对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替。)还具有下列方法:addFirst(), addLast(), getFirst(),getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用7..1 hash_map和map的区别在哪里?构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).存储结构。hash_map采用hash表存储,map一般采用 红黑树(RB Tree) 实现。因此其memory数据结构是不一样的。7.2 什么时候需要用hash_map,什么时候需要用map?总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且hash_map的构造速度较慢。现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用。8.一些使用上的建议: Level 1 - 仅仅作为Map使用:采用静态数组 Level 2 - 保存定长数据,使用时也是全部遍历:采用动态数组(长度一开始就固定的话静态数组也行) Level 3 - 保存不定长数组,需要动态增加的能力,侧重于寻找数据的速度:采用vector Level 3 - 保存不定长数组,需要动态增加的能力,侧重于增加删除数据的速度:采用list Level 4 - 对数据有复杂操作,即需要前后增删数据的能力,又要良好的数据访问速度:采用deque Level 5 - 对数据中间的增删操作比较多:采用list,建议在排序的基础上,批量进行增删可以对运行效率提供最大的保证 Level 6 - 上述中找不到适合的:组合STL容器或者自己建立特殊的数据结构来实现9.(1).vector - 会自动增长的数组vector<int>vec(10) //一个有10个int元素的容器 vector<float> vec(10, 0.5)//一个有10个float元素的容器,并且他们得值都是0.5 vector<int>::iterator iter; for(iter = vec.begin(); iter != vec.end(); iter++) { //. . . . . . . }vector由于数组的增长只能向前,所以也只提供了后端插入和后端删除, 也就是push_back和pop_back。当然在前端和中间要操作数据也是可以的, 用insert和erase,但是前端和中间对数据进行操作必然会引起数据块的移动, 这对性能影响是非常大的。最大的优势就是随机访问的能力。vector<T1>::iterator相关的方法有: begin():用来获得一个指向vector第一个元素的指针 end():用来获得一个指向vector最后一个元素之后的那个位置的指针,注意不是指向最后一个元素。 erase(vector<T1>::iterator):用来删除作为参数所传入的那个iterator所指向的那个元素。(2).list - 擅长插入删除的链表list<string>Milkshakes; list<int> Scores;push_back, pop_backpush_front. pop_frontlist是一个双向链表的实现。 为了提供双向遍历的能力,list要比一般的数据单元多出两个指向前后的指针一个使用iterator来删除元素的例子 list<string> stringList; list<string>::iterator iter; advance(iter, 5); //将iterator指向stringList的第五个元素 stringList.erase(iterator);//删除 那么删除操作进行以后,传入erase()方法的iterator指向哪里了呢?它指向了删除操作前所指向的那个元素的后一个元素。(3).deque - 拥有vector和list两者优点的双端队列(4).这三个模板的总结 比较和一般使用准则 这三个模板都属于序列类模板,可以看到他们有一些通用方法 size():得到容器大小 begin():得到指向容器内第一个元素的指针(这个指针的类型依容器的不同而不同) end():得到指向容器内最后一个元素之后一个位置的指针 erase():删除传入指针指向的那个元素 clear():清除所有的元素 ==运算符:判断两个容器是否相等 =运算符:用来给容器赋值 上面的三个模板有各自的特点 vector模板的数据在内存中连续的排列,所以随机存取元素(即通过[]运算符存取)的速度最快,这一点和数组是一致的。同样由于它的连续排列,所以它在除尾部以外的位置删除或添加元素的速度很慢,在使用vector时,要避免这种操作。 list模板的数据是链式存储,所以不能随机存取元素。它的优势在于任意位置添加 删除元素的速度。 deque模板是通过链接若干片连续的数据实现的,所以均衡了以上两个容器的特点
2023-08-17 23:21:421

java中几种Map在什么情况下使用,并简单介绍原因及原理

Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。Map接口定义了如下常用的方法:1、void clear():删除Map中所以键值对。2、boolean containsKey(Object key):查询Map中是否包含指定key,如果包含则返回true。3、boolean containsValue(Object value):查询Map中是否包含指定value,如果包含则返回true。4、Set entrySet():返回Map中所包含的键值对所组成的Set集合,每个集合元素都是Map.Entry对象(Entry是Map的内部类)。5、Object get(Object key):返回指定key所对应的value,如Map中不包含key则返回null。6、boolean isEmpty():查询Map是否为空,如果空则返回true。7、Set keySet():返回该Map中所有key所组成的set集合。8、Object put(Object key,Object value):添加一个键值对,如果已有一个相同的key值则新的键值对覆盖旧的键值对。9、void putAll(Map m):将指定Map中的键值对复制到Map中。10、Object remove(Object key):删除指定key所对应的键值对,返回可以所关联的value,如果key不存在,返回null。11、int size():返回该Map里的键值对的个数。12、Collection values():返回该Map里所有value组成的Collection。Map中包含一个内部类:Entry。该类封装了一个键值对,它包含了三个方法:1、Object getKey():返回该Entry里包含的key值。2、Object getValeu():返回该Entry里包含的value值。3、Object setValue(V value):设置该Entry里包含的value值,并返回新设置的value值。HashMap和Hashtable实现类:HashMap与HashTable的区别:1、 同步性:Hashtable是同步的,这个类中的一些方法保证了Hashtable中的对象是线程安全的。而HashMap则是异步的,因此HashMap中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率。2、 值:HashMap可以让你将空值作为一个表的条目的key或value,但是Hashtable是不能放入空值的。HashMap最多只有一个key值为null,但可以有无数多个value值为null。注意:1、用作key的对象必须实现hashCode和equals方法。2、不能保证其中的键值对的顺序3、尽量不要使用可变对象作为它们的key值。 LinkedHashMap: 它的父类是HashMap,使用双向链表来维护键值对的次序,迭代顺序与键值对的插入顺序保持一致。LinkedHashMap需要维护元素的插入顺序,so性能略低于HashMap,但在迭代访问元素时有很好的性能,因为它是以链表来维护内部顺序。TreeMap: Map接口派生了一个SortMap子接口,SortMap的实现类为TreeMap。TreeMap也是基于红黑树对所有的key进行排序,有两种排序方式:自然排序和定制排序。Treemap的key以TreeSet的形式存储,对key的要求与TreeSet对元素的要求基本一致。1、Map.Entry firstEntry():返回最小key所对应的键值对,如Map为空,则返回null。2、Object firstKey():返回最小key,如果为空,则返回null。3、Map.Entry lastEntry():返回最大key所对应的键值对,如Map为空,则返回null。4、Object lastKey():返回最大key,如果为空,则返回null。5、Map.Entry higherEntry(Object key):返回位于key后一位的键值对,如果为空,则返回null。6、Map.Entry lowerEntry(Object key):返回位于key前一位的键值对,如果为空,则返回null。7、Object lowerKey(Object key):返回位于key前一位key值,如果为空,则返回null。8、NavigableMap subMap(Object fromKey,boolean fromlnclusive,Object toKey,boolean toInciusive):返回该Map的子Map,其key范围从fromKey到toKey。9、SortMap subMap(Object fromKey,Object toKey );返回该Map的子Map,其key范围从fromkey(包括)到tokey(不包括)。10、SortMap tailMap(Object fromkey ,boolean inclusive):返回该Map的子Map,其key范围大于fromkey(是否包括取决于第二个参数)的所有key。11、 SortMap headMap(Object tokey ,boolean inclusive):返回该Map的子Map,其key范围小于tokey(是否包括取决于第二个参数)的所有key。WeakHashMap: WeakHashMap与HashMap的用法基本相同,区别在于:后者的key保留对象的强引用,即只要HashMap对象不被销毁,其对象所有key所引用的对象不会被垃圾回收,HashMap也不会自动删除这些key所对应的键值对对象。但WeakHashMap的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被回收。WeakHashMap中的每个key对象保存了实际对象的弱引用,当回收了该key所对应的实际对象后,WeakHashMap会自动删除该key所对应的键值对。 public static void main(String[] args) { // TODO Auto-generated method stub WeakHashMap w1=new WeakHashMap(); //添加三个键值对,三个key都是匿名字符串,没有其他引用 w1 .put("语文", "良好"); w1 .put("数学", "及格"); w1 .put("英语", "中等"); w1 .put("java", "good");//该key是一个系统缓存的字符串对象 System.out.println(w1 );//输出{java=good, 数学=及格, 英语=中等, 语文=良好} //通知系统进行垃圾回收 System.gc(); System.runFinalization(); System.out.println(w1 );//输出{java=good} }IdentityHashMap类:IdentityHashMap与HashMap基本相似,只是当两个key严格相等时,即key1==key2时,它才认为两个key是相等的 。IdentityHashMap也允许使用null,但不保证键值对之间的顺序。EnumMap类:1、EnumMap中所有key都必须是单个枚举类的枚举值,创建EnumMap时必须显示或隐式指定它对应的枚举类。2、EnumMap根据key的自然顺序,即枚举值在枚举类中定义的顺序,来维护键值对的次序。3、EnumMap不允许使用null作为key值,但value可以。
2023-08-17 23:21:564

数据结构面试题

1. 数据结构的定义。 2. 栈的两个应用:括号匹配和表达式的计算。是怎么应用的?表达式计算用的是哪种表达方式?有什么好处? 3. 字符串匹配算法:朴素的匹配算法、KMP算法。 4. 二叉树前序、中序、后序递归遍历算法。二叉树前序非递归遍历算法。 5. 堆,建堆算法,堆的插入和删除算法,堆排序。 6. 哈希。哈希函数的有哪些种?余数的取法? 处理冲突的方法? 闭散列方法有哪些? 7. 二叉搜索树的搜索、插入、删除。时间复杂度。 8. 二叉平衡树的插入结点的原理,有哪几种旋转方式?分别适用于哪种情况。分析二叉平衡树的时间复杂度。 9. 红黑树的定义,红黑树的性能分析和与二叉平衡树的比较。 10. 图有哪些储存表示。 11. 链表插入排序、链表归并排序。 12. 常见的有哪几种排序算法,试比较其时间复杂度,以及是否稳定,及各自使用的情形。 13. 常用分配排序有哪几种? 基数排序的定义,分类及原理。 14. 外部排序的过程。 15. B树、B+树、Trie的概念及用途,添加删除结点的原理。
2023-08-17 23:22:071

几种常见的查找算法之比较

二分法平均查找效率是O(logn),但是需要数组是排序的。如果没有排过序,就只好先用O(nlogn)的预处理为它排个序了。而且它的插入比较困难,经常需要移动整个数组,所以动态的情况下比较慢。哈希查找理想的插入和查找效率是O(1),但条件是需要找到一个良好的散列函数,使得分配较为平均。另外,哈希表需要较大的空间,至少要比O(n)大几倍,否则产生冲突的概率很高。二叉排序树查找也是O(logn)的,关键是插入值时需要做一些处理使得它较为平衡(否则容易出现轻重的不平衡,查找效率最坏会降到O(n)),而且写起来稍微麻烦一些,具体的算法你可以随便找一本介绍数据结构的书看看。当然,如果你用的是c语言,直接利用它的库类型map、multimap就可以了,它是用红黑树实现的,理论上插入、查找时间都是O(logn),很方便,不过一般会比自己实现的二叉平衡树稍微慢一些。
2023-08-17 23:22:182

24年408会考红黑树删除吗

不会。计算机考研408数据结构考试内容是计算机组成原理、操作系统等,红黑树是一种特殊形式的平衡二叉搜索树。
2023-08-17 23:22:411

一文读懂Linux任务间调度原理和整个执行过程

在前文中,我们分析了内核中进程和线程的统一结构体task_struct,并分析进程、线程的创建和派生的过程。在本文中,我们会对任务间调度进行详细剖析,了解其原理和整个执行过程。由此,进程、线程部分的大体框架就算是介绍完了。本节主要分为三个部分:Linux内核中常见的调度策略,调度的基本结构体以及调度发生的整个流程。下面将详细展开说明。 Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。为了维护 CPU 时间,Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。不同的系统可能设置不同的数值,可以通过查询 /boot/config 内核选项来查看它的配置值。 Linux的调度策略主要分为实时任务和普通任务。实时任务需求尽快返回结果,而普通任务则没有较高的要求。在前文中我们提到了task_struct中调度策略相应的变量为policy,调度优先级有prio, static_prio, normal_prio, rt_priority几个。优先级其实就是一个数值,对于实时进程来说,优先级的范围是 0 99;对于普通进程,优先级的范围是 100 139。数值越小,优先级越高。 实时调度策略主要包括以下几种 普通调度策略主要包括以下几种: 首先,我们需要一个结构体去执行调度策略,即sched_class。该类有几种实现方式 普通任务调度实体源码如下,这里面包含了 vruntime 和权重 load_weight,以及对于运行时间的统计。 在调度时,多个任务调度实体会首先区分是实时任务还是普通任务,然后通过以时间为顺序的红黑树结构组合起来,vruntime 最小的在树的左侧,vruntime最多的在树的右侧。以CFS策略为例,则会选择红黑树最左边的叶子节点作为下一个将获得 CPU 的任务。而这颗红黑树,我们称之为运行时队列(run queue),即struct rq。 其中包含结构体cfs_rq,其定义如下,主要是CFS调度相关的结构体,主要有权值相关变量、vruntime相关变量以及红黑树指针,其中结构体rb_root_cached即为红黑树的节点 对结构体dl_rq有类似的定义,运行队列由红黑树结构体构成,并按照deadline策略进行管理 对于实施队列相应的rt_rq则有所不同,并没有用红黑树实现。 下面再看看调度类sched_class,该类以函数指针的形式定义了诸多队列操作,如 调度类分为下面几种: 队列操作中函数指针指向不同策略队列的实际执行函数函数,在linux/kernel/sched/目录下,fair.c、idle.c、rt.c等文件对不同类型的策略实现了不同的函数,如fair.c中定义了 以选择下一个任务为例,CFS对应的是pick_next_task_fair,而rt_rq对应的则是pick_next_task_rt,等等。 由此,我们来总结一下: 有了上述的基本策略和基本调度结构体,我们可以形成大致的骨架,下面就是需要核心的调度流程将其拼凑成一个整体,实现调度系统。调度分为两种,主动调度和抢占式调度。 说到调用,逃不过核心函数schedule()。其中sched_submit_work()函数完成当前任务的收尾工作,以避免出现如死锁或者IO中断等情况。之后首先禁止抢占式调度的发生,然后调用__schedule()函数完成调度,之后重新打开抢占式调度,如果需要重新调度则会一直重复该过程,否则结束函数。 而__schedule()函数则是实际的核心调度函数,该函数主要操作包括选取下一进程和进行上下文切换,而上下文切换又包括用户态空间切换和内核态的切换。具体的解释可以参照英文源码注释以及中文对各个步骤的注释。 其中核心函数是获取下一个任务的pick_next_task()以及上下文切换的context_switch(),下面详细展开剖析。首先看看pick_next_task(),该函数会根据调度策略分类,调用该类对应的调度函数选择下一个任务实体。根据前文分析我们知道,最终是在不同的红黑树上选择最左节点作为下一个任务实体并返回。 下面来看看上下文切换。上下文切换主要干两件事情,一是切换任务空间,也即虚拟内存;二是切换寄存器和 CPU 上下文。关于任务空间的切换放在内存部分的文章中详细介绍,这里先按下不表,通过任务空间切换实际完成了用户态的上下文切换工作。下面我们重点看一下内核态切换,即寄存器和CPU上下文的切换。 switch_to()就是寄存器和栈的切换,它调用到了 __switch_to_asm。这是一段汇编代码,主要用于栈的切换, 其中32位使用esp作为栈顶指针,64位使用rsp,其他部分代码一致。通过该段汇编代码我们完成了栈顶指针的切换,并调用__switch_to完成最终TSS的切换。注意switch_to中其实是有三个变量,分别是prev, next, last,而实际在使用时,我们会对last也赋值为prev。这里的设计意图需要结合一个例子来说明。假设有ABC三个任务,从A调度到B,B到C,最后C回到A,我们假设仅保存prev和next,则流程如下 最终调用__switch_to()函数。该函数中涉及到一个结构体TSS(Task State Segment),该结构体存放了所有的寄存器。另外还有一个特殊的寄存器TR(Task Register)会指向TSS,我们通过更改TR的值,会触发硬件保存CPU所有寄存器在当前TSS,并从新的TSS读取寄存器的值加载入CPU,从而完成一次硬中断带来的上下文切换工作。系统初始化的时候,会调用 cpu_init()给每一个 CPU 关联一个 TSS,然后将 TR 指向这个 TSS,然后在操作系统的运行过程中,TR 就不切换了,永远指向这个 TSS。当修改TR的值得时候,则为任务调度。 更多Linux内核视频教程文本资料免费领取后台私信【 内核大礼包 】自行获取。 在完成了switch_to()的内核态切换后,还有一个重要的函数finish_task_switch()负责善后清理工作。在前面介绍switch_to三个参数的时候我们已经说明了使用last的重要性。而这里为何让prev和last均赋值为prev,是因为prev在后面没有需要用到,所以节省了一个指针空间来存储last。 至此,我们完成了内核态的切换工作,也完成了整个主动调度的过程。 抢占式调度通常发生在两种情况下。一种是某任务执行时间过长,另一种是当某任务被唤醒的时候。首先看看任务执行时间过长的情况。 该情况需要衡量一个任务的执行时间长短,执行时间过长则发起抢占。在计算机里面有一个时钟,会过一段时间触发一次时钟中断,通知操作系统时间又过去一个时钟周期,通过这种方式可以查看是否是需要抢占的时间点。 时钟中断处理函数会调用scheduler_tick()。该函数首先取出当前CPU,并由此获取对应的运行队列rq和当前任务curr。接着调用该任务的调度类sched_class对应的task_tick()函数进行时间事件处理。 以普通任务队列为例,对应的调度类为fair_sched_class,对应的时钟处理函数为task_tick_fair(),该函数会获取当前的调度实体和运行队列,并调用entity_tick()函数更新时间。 在entity_tick()中,首先会调用update_curr()更新当前任务的vruntime,然后调用check_preempt_tick()检测现在是否可以发起抢占。 check_preempt_tick() 先是调用 sched_slice() 函数计算出一个调度周期中该任务运行的实际时间 ideal_runtime。sum_exec_runtime 指任务总共执行的实际时间,prev_sum_exec_runtime 指上次该进程被调度时已经占用的实际时间,所以 sum_exec_runtime - prev_sum_exec_runtime 就是这次调度占用实际时间。如果这个时间大于 ideal_runtime,则应该被抢占了。除了这个条件之外,还会通过 __pick_first_entity 取出红黑树中最小的进程。如果当前进程的 vruntime 大于红黑树中最小的进程的 vruntime,且差值大于 ideal_runtime,也应该被抢占了。 如果确认需要被抢占,则会调用resched_curr()函数,该函数会调用set_tsk_need_resched()标记该任务为_TIF_NEED_RESCHED,即该任务应该被抢占。 某些任务会因为中断而唤醒,如当 I/O 到来的时候,I/O进程往往会被唤醒。在这种时候,如果被唤醒的任务优先级高于 CPU 上的当前任务,就会触发抢占。try_to_wake_up() 调用 ttwu_queue() 将这个唤醒的任务添加到队列当中。ttwu_queue() 再调用 ttwu_do_activate() 激活这个任务。ttwu_do_activate() 调用 ttwu_do_wakeup()。这里面调用了 check_preempt_curr() 检查是否应该发生抢占。如果应该发生抢占,也不是直接踢走当前进程,而是将当前进程标记为应该被抢占。 由前面的分析,我们知道了不论是是当前任务执行时间过长还是新任务唤醒,我们均会对现在的任务标记位_TIF_NEED_RESCUED,下面分析实际抢占的发生。真正的抢占还需要一个特定的时机让正在运行中的进程有机会调用一下 __schedule()函数,发起真正的调度。 实际上会调用__schedule()函数共有以下几个时机 从系统调用返回用户态:以64位为例,系统调用的链路为do_syscall_64->syscall_return_slowpath->prepare_exit_to_usermode->exit_to_usermode_loop。在exit_to_usermode_loop中,会检测是否为_TIF_NEED_RESCHED,如果是则调用__schedule() 内核态启动:内核态的执行中,被抢占的时机一般发生在 preempt_enable() 中。在内核态的执行中,有的操作是不能被中断的,所以在进行这些操作之前,总是先调用 preempt_disable() 关闭抢占,当再次打开的时候,就是一次内核态代码被抢占的机会。preempt_enable() 会调用 preempt_count_dec_and_test(),判断 preempt_count 和 TIF_NEED_RESCHED 是否可以被抢占。如果可以,就调用 preempt_schedule->preempt_schedule_common->__schedule 进行调度。 u2003u2003 本文分析了任务调度的策略、结构体以及整个调度流程,其中关于内存上下文切换的部分尚未详细叙述,留待内存部分展开剖析。 1、调度相关结构体及函数实现 2、schedule核心函数
2023-08-17 23:22:511

mysql索引的数据结构,为什么用b+树

1、MySQL支持的索引结构有四种:B+树,R树,HASH,FULLTEXT。B树是一种多叉的AVL树。B-Tree减少了AVL数的高度,增加了每个节点的KEY数量。2、其余节点用来索引,而B-树是每个索引节点都会有Data域。这就决定了B+树更适合用来存储外部数据,也就是所谓的磁盘数据。3、mysql的数据结构用的是b+而不是b红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用B-/+Tree作为索引结构,这一节将结合计算机组成原理相关知识讨论B-/+Tree作为索引的理论基础。
2023-08-17 23:23:451

什么是《平衡二叉树》

平衡二叉树(AVL)那对图 1 进行下改造,把数据重新节点重新连接下,图 2 如下:图 2 可以看到以下特性:1. 所有左子树的节点都小于其对应的父节点(4,5,6)<(7);(4)<(5);(8)< (9);2. 所有右子树上的节点都大于其对应的父节点(8,9,10)>(7);(6)>(5);(10)>(9);3. 每个节点的平衡因子差值绝对值 <=1;4. 每个节点都符合以上三个特征。满足这样条件的树叫平衡二叉树(AVL)树。问:那再次查找节点 5,需要遍历多少次呢?由于数据是按照顺序组织的,那查找起来非常快,从上往下找:7-5,只需要在左子树上查找,也就是遍历 2 次就找到了 5。假设要找到叶子节点 10,只需要在右子树上查找,那也最多需要 3 次,7-9-10。也就说 AVL 树在查找方面性能很好,最坏的情况是找到一个节点需要消耗的次数也就是树的层数, 复杂度为 O(logN)如果节点非常多呢?假设现在有 31 个节点,用 AVL 树表示如图 3:图 3 是一棵高度为 4 的 AVL 树,有 5 层共 31 个节点,橙色是 ROOT 节点,蓝色是叶子节点。对 AVL 树的查找来看起来已经很完美了,能不能再优化下?比如,能否把这个节点里存放的 KEY 增加?能否减少树的总层数?那减少纵深只能从横向来想办法,这时候可以考虑用多叉树。
2023-08-17 23:23:545

跟着培训班学java感觉很痛苦,还要不要继续学习

培训机构就是速成的所以怎么会将原理,我现在在看某马的视频,引入一个新的类/方法或者概念的时候,我都会听他讲一遍我再去网上论坛理解一遍。最主要的还是需要靠自己,多敲多练,听课的时候老师进度会很快所以你先标注下不懂的,下午晚上休息的时候再去网上解答,把一天的内容巩固一遍
2023-08-17 23:24:2314

请问在noip和noi这种信息学竞赛中,程序的时间复杂度在10的几次方内不会超时(1s)?

9 一般的评测机器是一亿次
2023-08-17 23:24:514

如何提高Linux下块设备IO的整体性能

前言:本文主要讲解Linux IO调度层的三种模式:cfp、deadline和noop,并给出各自的优化和适用场景建议。IO调度发生在Linux内核的IO调度层。这个层次是针对Linux的整体IO层次体系来说的。从read()或者write()系统调用的角度来说,Linux整体IO体系可以分为七层,它们分别是:VFS层: 虚拟文件系统层。由于内核要跟多种文件系统打交道,而每一种文件系统所实现的数据结构和相关方法都可能不尽相同,所以,内核抽象了这一层,专门用来适配各种文件系统,并对外提供统一操作接口。文件系统层: 不同的文件系统实现自己的操作过程,提供自己特有的特征,具体不多说了,大家愿意的话自己去看代码即可。页缓存层: 负责真对page的缓存。通用块层: 由于绝大多数情况的io操作是跟块设备打交道,所以Linux在此提供了一个类似vfs层的块设备操作抽象层。下层对接各种不同属性的块设备,对上提供统一的Block IO请求标准。IO调度层 :因为绝大多数的块设备都是类似磁盘这样的设备,所以有必要根据这类设备的特点以及应用的不同特点来设置一些不同的调度算法和队列。以便在不同的应用环境下有针对性的提高磁盘的读写效率,这里就是大名鼎鼎的Linux电梯所起作用的地方。针对机械硬盘的各种调度方法就是在这实现的。块设备驱动层: 驱动层对外提供相对比较高级的设备操作接口,往往是C语言的,而下层对接设备本身的操作方法和规范。块设备层: 这层就是具体的物理设备了,定义了各种真对设备操作方法和规范。有一个已经整理好的[Linux IO结构图],非常经典,一图胜千言:我们今天要研究的内容主要在IO调度这一层。它要解决的核心问题是,如何提高块设备IO的整体性能?这一层也主要是针对机械硬盘结构而设计的。众所周知,机械硬盘的存储介质是磁盘,磁头在盘片上移动进行磁道寻址,行为类似播放一张唱片。这种结构的特点是,顺序访问时吞吐量较高,但是如果一旦对盘片有随机访问,那么大量的时间都会浪费在磁头的移动上,这时候就会导致每次IO的响应时间变长,极大的降低IO的响应速度。磁头在盘片上寻道的操作,类似电梯调度,实际上在最开始的时期,Linux把这个算法命名为Linux电梯算法,即:如果在寻道的过程中,能把顺序路过的相关磁道的数据请求都“顺便”处理掉,那么就可以在比较小影响响应速度的前提下,提高整体IO的吞吐量。这就是我们为什么要设计IO调度算法的原因。目前在内核中默认开启了三种算法/模式:noop,cfq和deadline。严格算应该是两种:因为第一种叫做noop,就是空操作调度算法,也就是没有任何调度操作,并不对io请求进行排序,仅仅做适当的io合并的一个fifo队列。目前内核中默认的调度算法应该是cfq,叫做完全公平队列调度。这个调度算法人如其名,它试图给所有进程提供一个完全公平的IO操作环境。注:请大家一定记住这个词语,cfq,完全公平队列调度,不然下文就没法看了。cfq为每个进程创建一个同步IO调度队列,并默认以时间片和请求数限定的方式分配IO资源,以此保证每个进程的IO资源占用是公平的,cfq还实现了针对进程级别的优先级调度,这个我们后面会详细解释。查看和修改IO调度算法的方法是:cfq是通用服务器比较好的IO调度算法选择,对桌面用户也是比较好的选择。但是对于很多IO压力较大的场景就并不是很适应,尤其是IO压力集中在某些进程上的场景。因为这种场景我们需要更多的满足某个或者某几个进程的IO响应速度,而不是让所有的进程公平的使用IO,比如数据库应用。deadline调度(最终期限调度)就是更适合上述场景的解决方案。deadline实现了四个队列:其中两个分别处理正常read和write,按扇区号排序,进行正常io的合并处理以提高吞吐量。因为IO请求可能会集中在某些磁盘位置,这样会导致新来的请求一直被合并,可能会有其他磁盘位置的io请求被饿死。另外两个处理超时read和write的队列,按请求创建时间排序,如果有超时的请求出现,就放进这两个队列,调度算法保证超时(达到最终期限时间)的队列中的请求会优先被处理,防止请求被饿死。不久前,内核还是默认标配四种算法,还有一种叫做as的算法(Anticipatory scheduler),预测调度算法。一个高大上的名字,搞得我一度认为Linux内核都会算命了。结果发现,无非是在基于deadline算法做io调度的之前等一小会时间,如果这段时间内有可以合并的io请求到来,就可以合并处理,提高deadline调度的在顺序读写情况下的数据吞吐量。其实这根本不是啥预测,我觉得不如叫撞大运调度算法,当然这种策略在某些特定场景差效果不错。但是在大多数场景下,这个调度不仅没有提高吞吐量,还降低了响应速度,所以内核干脆把它从默认配置里删除了。毕竟Linux的宗旨是实用,而我们也就不再这个调度算法上多费口舌了。1、cfq:完全公平队列调度cfq是内核默认选择的IO调度队列,它在桌面应用场景以及大多数常见应用场景下都是很好的选择。如何实现一个所谓的完全公平队列(Completely Fair Queueing)?首先我们要理解所谓的公平是对谁的公平?从操作系统的角度来说,产生操作行为的主体都是进程,所以这里的公平是针对每个进程而言的,我们要试图让进程可以公平的占用IO资源。那么如何让进程公平的占用IO资源?我们需要先理解什么是IO资源。当我们衡量一个IO资源的时候,一般喜欢用的是两个单位,一个是数据读写的带宽,另一个是数据读写的IOPS。带宽就是以时间为单位的读写数据量,比如,100Mbyte/s。而IOPS是以时间为单位的读写次数。在不同的读写情境下,这两个单位的表现可能不一样,但是可以确定的是,两个单位的任何一个达到了性能上限,都会成为IO的瓶颈。从机械硬盘的结构考虑,如果读写是顺序读写,那么IO的表现是可以通过比较少的IOPS达到较大的带宽,因为可以合并很多IO,也可以通过预读等方式加速数据读取效率。当IO的表现是偏向于随机读写的时候,那么IOPS就会变得更大,IO的请求的合并可能性下降,当每次io请求数据越少的时候,带宽表现就会越低。从这里我们可以理解,针对进程的IO资源的主要表现形式有两个: 进程在单位时间内提交的IO请求个数和进程占用IO的带宽。其实无论哪个,都是跟进程分配的IO处理时间长度紧密相关的。有时业务可以在较少IOPS的情况下占用较大带宽,另外一些则可能在较大IOPS的情况下占用较少带宽,所以对进程占用IO的时间进行调度才是相对最公平的。即,我不管你是IOPS高还是带宽占用高,到了时间咱就换下一个进程处理,你爱咋样咋样。所以,cfq就是试图给所有进程分配等同的块设备使用的时间片,进程在时间片内,可以将产生的IO请求提交给块设备进行处理,时间片结束,进程的请求将排进它自己的队列,等待下次调度的时候进行处理。这就是cfq的基本原理。当然,现实生活中不可能有真正的“公平”,常见的应用场景下,我们很肯能需要人为的对进程的IO占用进行人为指定优先级,这就像对进程的CPU占用设置优先级的概念一样。所以,除了针对时间片进行公平队列调度外,cfq还提供了优先级支持。每个进程都可以设置一个IO优先级,cfq会根据这个优先级的设置情况作为调度时的重要参考因素。优先级首先分成三大类:RT、BE、IDLE,它们分别是实时(Real Time)、最佳效果(Best Try)和闲置(Idle)三个类别,对每个类别的IO,cfq都使用不同的策略进行处理。另外,RT和BE类别中,分别又再划分了8个子优先级实现更细节的QOS需求,而IDLE只有一个子优先级。另外,我们都知道内核默认对存储的读写都是经过缓存(buffer/cache)的,在这种情况下,cfq是无法区分当前处理的请求是来自哪一个进程的。只有在进程使用同步方式(sync read或者sync wirte)或者直接IO(Direct IO)方式进行读写的时候,cfq才能区分出IO请求来自哪个进程。所以,除了针对每个进程实现的IO队列以外,还实现了一个公共的队列用来处理异步请求。当前内核已经实现了针对IO资源的cgroup资源隔离,所以在以上体系的基础上,cfq也实现了针对cgroup的调度支持。总的来说,cfq用了一系列的数据结构实现了以上所有复杂功能的支持,大家可以通过源代码看到其相关实现,文件在源代码目录下的block/cfq-iosched.c。1.1 cfq设计原理在此,我们对整体数据结构做一个简要描述:首先,cfq通过一个叫做cfq_data的数据结构维护了整个调度器流程。在一个支持了cgroup功能的cfq中,全部进程被分成了若干个contral group进行管理。每个cgroup在cfq中都有一个cfq_group的结构进行描述,所有的cgroup都被作为一个调度对象放进一个红黑树中,并以vdisktime为key进行排序。vdisktime这个时间纪录的是当前cgroup所占用的io时间,每次对cgroup进行调度时,总是通过红黑树选择当前vdisktime时间最少的cgroup进行处理,以保证所有cgroups之间的IO资源占用“公平”。当然我们知道,cgroup是可以对blkio进行资源比例分配的,其作用原理就是,分配比例大的cgroup占用vdisktime时间增长较慢,分配比例小的vdisktime时间增长较快,快慢与分配比例成正比。这样就做到了不同的cgroup分配的IO比例不一样,并且在cfq的角度看来依然是“公平“的。选择好了需要处理的cgroup(cfq_group)之后,调度器需要决策选择下一步的service_tree。service_tree这个数据结构对应的都是一系列的红黑树,主要目的是用来实现请求优先级分类的,就是RT、BE、IDLE的分类。每一个cfq_group都维护了7个service_trees,其定义如下:其中service_tree_idle就是用来给IDLE类型的请求进行排队用的红黑树。而上面二维数组,首先第一个维度针对RT和BE分别各实现了一个数组,每一个数组中都维护了三个红黑树,分别对应三种不同子类型的请求,分别是:SYNC、SYNC_NOIDLE以及ASYNC。我们可以认为SYNC相当于SYNC_IDLE并与SYNC_NOIDLE对应。idling是cfq在设计上为了尽量合并连续的IO请求以达到提高吞吐量的目的而加入的机制,我们可以理解为是一种“空转”等待机制。空转是指,当一个队列处理一个请求结束后,会在发生调度之前空等一小会时间,如果下一个请求到来,则可以减少磁头寻址,继续处理顺序的IO请求。为了实现这个功能,cfq在service_tree这层数据结构这实现了SYNC队列,如果请求是同步顺序请求,就入队这个service tree,如果请求是同步随机请求,则入队SYNC_NOIDLE队列,以判断下一个请求是否是顺序请求。所有的异步写操作请求将入队ASYNC的service tree,并且针对这个队列没有空转等待机制。此外,cfq还对SSD这样的硬盘有特殊调整,当cfq发现存储设备是一个ssd硬盘这样的队列深度更大的设备时,所有针对单独队列的空转都将不生效,所有的IO请求都将入队SYNC_NOIDLE这个service tree。每一个service tree都对应了若干个cfq_queue队列,每个cfq_queue队列对应一个进程,这个我们后续再详细说明。cfq_group还维护了一个在cgroup内部所有进程公用的异步IO请求队列,其结构如下:异步请求也分成了RT、BE、IDLE这三类进行处理,每一类对应一个cfq_queue进行排队。BE和RT也实现了优先级的支持,每一个类型有IOPRIO_BE_NR这么多个优先级,这个值定义为8,数组下标为0-7。我们目前分析的内核代码版本为Linux 4.4,可以看出,从cfq的角度来说,已经可以实现异步IO的cgroup支持了,我们需要定义一下这里所谓异步IO的含义,它仅仅表示从内存的buffer/cache中的数据同步到硬盘的IO请求,而不是aio(man 7 aio)或者linux的native异步io以及libaio机制,实际上这些所谓的“异步”IO机制,在内核中都是同步实现的(本质上冯诺伊曼计算机没有真正的“异步”机制)。我们在上面已经说明过,由于进程正常情况下都是将数据先写入buffer/cache,所以这种异步IO都是统一由cfq_group中的async请求队列处理的。那么为什么在上面的service_tree中还要实现和一个ASYNC的类型呢?这当然是为了支持区分进程的异步IO并使之可以“完全公平”做准备喽。实际上在最新的cgroup v2的blkio体系中,内核已经支持了针对buffer IO的cgroup限速支持,而以上这些可能容易混淆的一堆类型,都是在新的体系下需要用到的类型标记。新体系的复杂度更高了,功能也更加强大,但是大家先不要着急,正式的cgroup v2体系,在Linux 4.5发布的时候会正式跟大家见面。我们继续选择service_tree的过程,三种优先级类型的service_tree的选择就是根据类型的优先级来做选择的,RT优先级最高,BE其次,IDLE最低。就是说,RT里有,就会一直处理RT,RT没了再处理BE。每个service_tree对应一个元素为cfq_queue排队的红黑树,而每个cfq_queue就是内核为进程(线程)创建的请求队列。每一个cfq_queue都会维护一个rb_key的变量,这个变量实际上就是这个队列的IO服务时间(service time)。这里还是通过红黑树找到service time时间最短的那个cfq_queue进行服务,以保证“完全公平”。选择好了cfq_queue之后,就要开始处理这个队列里的IO请求了。这里的调度方式基本跟deadline类似。cfq_queue会对进入队列的每一个请求进行两次入队,一个放进fifo中,另一个放进按访问扇区顺序作为key的红黑树中。默认从红黑树中取请求进行处理,当请求的延时时间达到deadline时,就从红黑树中取等待时间最长的进行处理,以保证请求不被饿死。这就是整个cfq的调度流程,当然其中还有很多细枝末节没有交代,比如合并处理以及顺序处理等等。1.2 cfq的参数调整理解整个调度流程有助于我们决策如何调整cfq的相关参数。所有cfq的可调参数都可以在/sys/class/block/sda/queue/iosched/目录下找到,当然,在你的系统上,请将sda替换为相应的磁盘名称。我们来看一下都有什么:这些参数部分是跟机械硬盘磁头寻道方式有关的,如果其说明你看不懂,请先补充相关知识:back_seek_max:磁头可以向后寻址的最大范围,默认值为16M。back_seek_penalty:向后寻址的惩罚系数。这个值是跟向前寻址进行比较的。以上两个是为了防止磁头寻道发生抖动而导致寻址过慢而设置的。基本思路是这样,一个io请求到来的时候,cfq会根据其寻址位置预估一下其磁头寻道成本。设置一个最大值back_seek_max,对于请求所访问的扇区号在磁头后方的请求,只要寻址范围没有超过这个值,cfq会像向前寻址的请求一样处理它。再设置一个评估成本的系数back_seek_penalty,相对于磁头向前寻址,向后寻址的距离为1/2(1/back_seek_penalty)时,cfq认为这两个请求寻址的代价是相同。这两个参数实际上是cfq判断请求合并处理的条件限制,凡事复合这个条件的请求,都会尽量在本次请求处理的时候一起合并处理。fifo_expire_async:设置异步请求的超时时间。同步请求和异步请求是区分不同队列处理的,cfq在调度的时候一般情况都会优先处理同步请求,之后再处理异步请求,除非异步请求符合上述合并处理的条件限制范围内。当本进程的队列被调度时,cfq会优先检查是否有异步请求超时,就是超过fifo_expire_async参数的限制。如果有,则优先发送一个超时的请求,其余请求仍然按照优先级以及扇区编号大小来处理。fifo_expire_sync:这个参数跟上面的类似,区别是用来设置同步请求的超时时间。slice_idle:参数设置了一个等待时间。这让cfq在切换cfq_queue或service tree的时候等待一段时间,目的是提高机械硬盘的吞吐量。一般情况下,来自同一个cfq_queue或者service tree的IO请求的寻址局部性更好,所以这样可以减少磁盘的寻址次数。这个值在机械硬盘上默认为非零。当然在固态硬盘或者硬RAID设备上设置这个值为非零会降低存储的效率,因为固态硬盘没有磁头寻址这个概念,所以在这样的设备上应该设置为0,关闭此功能。group_idle:这个参数也跟上一个参数类似,区别是当cfq要切换cfq_group的时候会等待一段时间。在cgroup的场景下,如果我们沿用slice_idle的方式,那么空转等待可能会在cgroup组内每个进程的cfq_queue切换时发生。这样会如果这个进程一直有请求要处理的话,那么直到这个cgroup的配额被耗尽,同组中的其它进程也可能无法被调度到。这样会导致同组中的其它进程饿死而产生IO性能瓶颈。在这种情况下,我们可以将slice_idle = 0而group_idle = 8。这样空转等待就是以cgroup为单位进行的,而不是以cfq_queue的进程为单位进行,以防止上述问题产生。low_latency:这个是用来开启或关闭cfq的低延时(low latency)模式的开关。当这个开关打开时,cfq将会根据target_latency的参数设置来对每一个进程的分片时间(slice time)进行重新计算。这将有利于对吞吐量的公平(默认是对时间片分配的公平)。关闭这个参数(设置为0)将忽略target_latency的值。这将使系统中的进程完全按照时间片方式进行IO资源分配。这个开关默认是打开的。我们已经知道cfq设计上有“空转”(idling)这个概念,目的是为了可以让连续的读写操作尽可能多的合并处理,减少磁头的寻址操作以便增大吞吐量。如果有进程总是很快的进行顺序读写,那么它将因为cfq的空转等待命中率很高而导致其它需要处理IO的进程响应速度下降,如果另一个需要调度的进程不会发出大量顺序IO行为的话,系统中不同进程IO吞吐量的表现就会很不均衡。就比如,系统内存的cache中有很多脏页要写回时,桌面又要打开一个浏览器进行操作,这时脏页写回的后台行为就很可能会大量命中空转时间,而导致浏览器的小量IO一直等待,让用户感觉浏览器运行响应速度变慢。这个low_latency主要是对这种情况进行优化的选项,当其打开时,系统会根据target_latency的配置对因为命中空转而大量占用IO吞吐量的进程进行限制,以达到不同进程IO占用的吞吐量的相对均衡。这个开关比较合适在类似桌面应用的场景下打开。target_latency:当low_latency的值为开启状态时,cfq将根据这个值重新计算每个进程分配的IO时间片长度。quantum:这个参数用来设置每次从cfq_queue中处理多少个IO请求。在一个队列处理事件周期中,超过这个数字的IO请求将不会被处理。这个参数只对同步的请求有效。slice_sync:当一个cfq_queue队列被调度处理时,它可以被分配的处理总时间是通过这个值来作为一个计算参数指定的。公式为:time_slice = slice_sync + (slice_sync/5 * (4 - prio))。这个参数对同步请求有效。slice_async:这个值跟上一个类似,区别是对异步请求有效。slice_async_rq:这个参数用来限制在一个slice的时间范围内,一个队列最多可以处理的异步请求个数。请求被处理的最大个数还跟相关进程被设置的io优先级有关。1.3 cfq的IOPS模式我们已经知道,默认情况下cfq是以时间片方式支持的带优先级的调度来保证IO资源占用的公平。高优先级的进程将得到更多的时间片长度,而低优先级的进程时间片相对较小。当我们的存储是一个高速并且支持NCQ(原生指令队列)的设备的时候,我们最好可以让其可以从多个cfq队列中处理多路的请求,以便提升NCQ的利用率。此时使用时间片的分配方式分配资源就显得不合时宜了,因为基于时间片的分配,同一时刻最多能处理的请求队列只有一个。这时,我们需要切换cfq的模式为IOPS模式。切换方式很简单,就是将slice_idle=0即可。内核会自动检测你的存储设备是否支持NCQ,如果支持的话cfq会自动切换为IOPS模式。另外,在默认的基于优先级的时间片方式下,我们可以使用ionice命令来调整进程的IO优先级。进程默认分配的IO优先级是根据进程的nice值计算而来的,计算方法可以在man ionice中看到,这里不再废话。2、deadline:最终期限调度deadline调度算法相对cfq要简单很多。其设计目标是:在保证请求按照设备扇区的顺序进行访问的同时,兼顾其它请求不被饿死,要在一个最终期限前被调度到。我们知道磁头对磁盘的寻道是可以进行顺序访问和随机访问的,因为寻道延时时间的关系,顺序访问时IO的吞吐量更大,随机访问的吞吐量小。如果我们想为一个机械硬盘进行吞吐量优化的话,那么就可以让调度器按照尽量复合顺序访问的IO请求进行排序,之后请求以这样的顺序发送给硬盘,就可以使IO的吞吐量更大。但是这样做也有另一个问题,就是如果此时出现了一个请求,它要访问的磁道离目前磁头所在磁道很远,应用的请求又大量集中在目前磁道附近。导致大量请求一直会被合并和插队处理,而那个要访问比较远磁道的请求将因为一直不能被调度而饿死。deadline就是这样一种调度器,能在保证IO最大吞吐量的情况下,尽量使远端请求在一个期限内被调度而不被饿死的调度器。
2023-08-17 23:25:001

用比喻的方法讲解一下 java 中 hashmap 的底层原理?

想象你有一个魔法箱子(HashMap),你可以把东西放进去,并且给每个东西贴上标签(键)以便稍后找到它们。箱子的内部是由一系列抽屉组成,每个抽屉都有一个唯一的标签(哈希码)。当你要放入一样东西时,首先你会生成一个标签(哈希码),这个标签会告诉你应该将东西放入哪个抽屉。假设你想放入一本书,并给它贴上标签 "科学书",然后根据这个标签(哈希码),你找到对应的抽屉并把书放进去。现在,如果你想找回这本书,你只需要记得它的标签 "科学书",然后根据这个标签(哈希码)再次找到对应的抽屉,从抽屉里拿出书来。这样你就能快速找到你之前放入箱子的东西。然而,有时候不同的东西可能会有相同的标签(哈希码),这就是所谓的哈希碰撞。在箱子内部,为了解决这个问题,每个抽屉都是一个链表,可以存放多个东西。当发生哈希碰撞时,新的东西会被添加到链表中,而不是新建一个抽屉。当箱子的负载(放入的东西数量)逐渐增加时,为了保持箱子的高效性能,箱子会自动增大,并重新调整抽屉的布局,确保箱子里的抽屉数量适合放入的东西的数量。这就是 Java 中 HashMap 的底层原理。它利用哈希函数将键映射到特定的索引位置,然后在该位置存储值。如果存在哈希碰撞,它会使用链表来处理冲突。当箱子内的东西越来越多时,箱子会自动调整大小,以保持高效性能。这种数据结构使得 HashMap 在大多数情况下能够快速查找和插入数据。
2023-08-17 23:25:072

Hashmap 桶内元素由链表转化为红黑树的条件

// 1. 最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树) // 否则,若桶内元素太多时,则直接扩容,而不是树形化 // 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD static final int MIN_TREEIFY_CAPACITY = 64; // 2. 桶的树化阈值:即 链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,则将链表转换成红黑树 static final int TREEIFY_THRESHOLD = 8; // 3. 桶的链表还原阈值:即 红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 < 6时,则将 红黑树转换成链表 static final int UNTREEIFY_THRESHOLD = 6;
2023-08-17 23:25:261

epoll为什么这么快,epoll的实现原理

以一个生活中的例子来解释. 假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,但是不知道你具体住在哪里,于是你们约好了在A号楼门口见面. 如果你使用的阻塞IO模型来处理这个问题,那么你就只能一直守候在A号楼门口等待朋友的到来,在这段时间里你不能做别的事情,不难知道,这种方式的效率是低下的. 进一步解释select和epoll模型的差异. select版大妈做的是如下的事情:比如同学甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同学甲,你等的朋友来了,于是在实际的代码中,select版大妈做的是以下的事情: int n = select(&readset,NULL,NULL,100); for (int i = 0; n > 0; ++i) { if (FD_ISSET(fdarray[i], &readset)) { do_something(fdarray[i]); --n; } }epoll版大妈就比较先进了,她记下了同学甲的信息,比如说他的房间号,那么等同学甲的朋友到来时,只需要告诉该朋友同学甲在哪个房间即可,不用自己亲自带着人满大楼的找人了.于是epoll版大妈做的事情可以用如下的代码表示: n = epoll_wait(epfd,events,20,500); for(i=0;i<n;++i) { do_something(events[n]); } 在epoll中,关键的数据结构epoll_event定义如下: typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; 可以看到,epoll_data是一个union结构体,它就是epoll版大妈用于保存同学信息的结构体,它可以保存很多类型的信息:fd,指针,等等.有了这个结构体,epoll大妈可以不用吹灰之力就可以定位到同学甲. 别小看了这些效率的提高,在一个大规模并发的服务器中,轮询IO是最耗时间的操作之一.再回到那个例子中,如果每到来一个朋友楼管大妈都要全楼的查询同学,那么处理的效率必然就低下了,过不久楼底就有不少的人了. 对比最早给出的阻塞IO的处理模型, 可以看到采用了多路复用IO之后, 程序可以自由的进行自己除了IO操作之外的工作, 只有到IO状态发生变化的时候由多路复用IO进行通知, 然后再采取相应的操作, 而不用一直阻塞等待IO状态发生变化了. 从上面的分析也可以看出,epoll比select的提高实际上是一个用空间换时间思想的具体应用. 二、深入理解epoll的实现原理:开发高性能网络程序时,windows开发者们言必称iocp,linux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。我们用起epoll来都感觉挺爽,确实快,那么,它到底为什么可以高速处理这么多并发连接呢? 先简单回顾下如何使用C库封装的3个epoll系统调用吧。int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 使用起来很清晰,首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。 epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。 epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。 从上面的调用方式就可以看到epoll比select/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socket给select/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。 所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。 在内核里,一切皆文件。所以,epoll向内核注册了一个文件系统,用于存储上述的被监控socket。当你调用epoll_create时,就会在这个虚拟的epoll文件系统里创建一个file结点。当然这个file不是普通文件,它只服务于epoll。epoll在被内核初始化时(操作系统启动),同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配好的对象。static int __init eventpoll_init(void) { ... ... /* Allocates slab cache used to allocate "struct epitem" items */ epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem), 0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL); /* Allocates slab cache used to allocate "struct eppoll_entry" */ pwq_cache = kmem_cache_create("eventpoll_pwq", sizeof(struct eppoll_entry), 0, EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL); ... ... epoll的高效就在于,当我们调用epoll_ctl往里塞入百万个句柄时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的句柄给我们用户。这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。 那么,这个准备就绪list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。 如此,一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。 最后看看epoll独有的两种模式LT和ET。无论是LT和ET模式,都适用于以上所说的流程。区别是,LT模式下,只要一个句柄上的事件一次没有处理完,会在以后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。 这件事怎么做到的呢?当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。三、扩展阅读(epoll与之前其他相关技术的比较): Linux提供了select、poll、epoll接口来实现IO复用,三者的原型如下所示,本文从参数、实现、性能等方面对三者进行对比。 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int poll(struct pollfd *fds, nfds_t nfds, int timeout); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); select、poll、epoll_wait参数及实现对比 1. select的第一个参数nfds为fdset集合中最大描述符值加1,fdset是一个位数组,其大小限制为__FD_SETSIZE(1024),位数组的每一位代表其对应的描述符是否需要被检查。 select的第二三四个参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件。所以每次调用select前都需要重新初始化fdset。 timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。 select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。 select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。 2. poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。 poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。 poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。 3. epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。 epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
2023-08-17 23:25:371

怎么确定红黑树节点的颜色

关于节点默认是红色还是黑色,可以通过给树中插入红色节点或者黑色节点对树造成的影响大小,而判断应该将节点的默认颜色设置为红色还是黑色。 根据红黑树的性质: 插入红色节点树的性质可能不会改变,而插入黑色节点每次都会违反性质4. 通过性质发现: 将节点设置为红色在插入时对红黑树造成的影响是小的,而黑色是最大的 总结:将红黑树的节点默认颜色设置为红色,是为尽可能减少在插入新节点对红黑树造成的影响。
2023-08-17 23:25:451

作为java程序员,怎么看待原理性知识?

原理性知识之所以重要,是相对于以后的编程,学习框架来说的。懂了原理的东西,学习框架就会轻松,还可以阅读懂源代码。
2023-08-17 23:25:581

红黑树的原理

红黑树的原理是通过进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而实现关联数组,存储有序的数据。它是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,其典型的用途就是实现关联数组。红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。若一棵二叉查找树是红黑树,则它的任一子树必为红黑树。而由于每一颗红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。行为特征红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:性质1、节点是红色或黑色。性质2、根节点是黑色。性质3、所有叶子都是黑色。(叶子是NUIL节点)性质4、每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)性质5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
2023-08-17 23:26:181

哪种树结构是一种自平衡二叉搜索树

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。红黑树的原理是通过进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而实现关联数组,存储有序的数据。它是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,其典型的用途就是实现关联数组。红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。若一棵二叉查找树是红黑树,则它的任一子树必为红黑树。而由于每一颗红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。行为特征:红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:性质1、节点是红色或黑色。性质2、根节点是黑色。性质3、所有叶子都是黑色。(叶子是NUIL节点)。性质4、每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)。性质5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
2023-08-17 23:26:361

红黑树原理讲解

性质1: 每个节点要么是 黑色 ,要么是 红色 。 性质2: 根节点是 黑色 。 性质3: 每个叶子节点(NIL)是黑色。 性质4: 每个 红色 节点的两个子节点一定都是 黑色 。 性质5: 任意一个节点到每个叶子节点的路径都包含 数量相同 的 黑节点 。俗称: 黑高 ! 从性质5又可以推出: 性质5.1: 如果一个节点存在黑子节点,那么该结点肯定有两个子节点。 插入操作包括两部分工作: 注意: 插入结点,必须为 红色 ,理由很简单,红色在父节点(如果存在)为黑色节点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。如果插入结点是黑色,那么插入位置所在的子树黑色结点总是1,必须做自平衡。 最简单的一种情景,直接把插入结点作为根结点就行 注意: 根据红黑树性质2: 根结点是黑色。还需要把插入结点设为黑色。 处理: 更新当前结点的值,为插入结点的值 由于插入的结点是红色的,当插入结点的父结点为黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。 再次回想红黑树的性质2: 根结点是黑色。如果插入结点的父结点为 红结点 ,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这一点很重要,因为后序的旋转操作肯定需要祖父结点的参与。 依据红黑树 性质4可知,红色结点不能相连===>祖父结点肯定为黑结点 因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为: 红黑红 处理: 1.将P和U结点改为黑色 2.将PP改为红色 3.将PP设置为当前结点,进行后序处理 注意: 单纯从插入前来看,叔叔结点非红即空(NIL结点),否则的话破坏了红黑树性质5,此路径比其它路径多一个黑色结点。 处理: 处理: 该情景对应情景4.2,只是方向反转,直接看图 处理: 处理:
2023-08-17 23:26:551

什么是容器

首先,我们必须理解一下,在C++ 中容器被定义为:在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。很简单,容器就是保存其它对象的对象,当然这是一个朴素的理解,这种“对象”还包含了一系列处理“其它对象”的方法,因为这些方法在程序的设计上会经常被用到,所以容器也体现了一个好处,就是“容器类是一种对特定代码重用问题的良好的解决方案”。 容器还有另一个特点是容器可以自行扩展。在解决问题时我们常常不知道我们需要存储多少个对象,也就是说我们不知道应该创建多大的内存空间来保存我们的对象。显然,数组在这一方面也力不从心。容器的优势就在这里,它不需要你预先告诉它你要存储多少对象,只要你创建一个容器对象,并合理的调用它所提供的方法,所有的处理细节将由容器来自身完成。它可以为你申请内存或释放内存,并且用最优的算法来执行您的命令。 容器是随着面向对象语言的诞生而提出的,容器类在面向对象语言中特别重要,甚至它被认为是早期面向对象语言的基础。在现在几乎所有的面向对象的语言中也都伴随着一个容器集,在C++ 中,就是标准模板库(STL)。 和其它语言不一样,C++ 中处理容器是采用基于模板的方式。标准C++ 库中的容器提供了多种数据结构,这些数据结构可以与标准算法一起很好的工作,这为我们的软件开发提供了良好的支持! 通用容器的分类 STL对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器。 顺序性容器 是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。这个位置和元素本身无关,而和操作的时间和地点有关,顺序性容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。比如我们一次性对一个顺序性容器追加三个元素,这三个元素在容器中的相对位置和追加时的逻辑次序是一致的。 关联式容器 和顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。 关联式容器另一个显著的特点是它是以键值的方式来保存数据,就是说它能把关键字和值关联起来保存,而顺序性容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)。这在下面具体的容器类中可以说明这一点。 容器适配器是一个比较抽象的概念, C++的解释是:适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器是让一种已存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机制。其实仅是发生了接口转换。那么你可以把它理解为容器的容器,它实质还是一个容器,只是他不依赖于具体的标准容器类型,可以理解是容器的模版。或者把它理解为容器的接口,而适配器具体采用哪种容器类型去实现,在定义适配器的时候可以由你决定。 下表列出STL 定义的三类容器所包含的具体容器类: 标准容器类 特点顺序性容器 vector从后面快速的插入与删除,直接访问任何元素 deque从前面或后面快速的插入与删除,直接访问任何元素 list双链表,从任何地方快速插入与删除 关联容器 set快速查找,不允许重复值 multiset快速查找,允许重复值 map一对多映射,基于关键字快速查找,不允许重复值 multimap一对多映射,基于关键字快速查找,允许重复值 容器适配器 stack后进先出 queue先进先出 priority_queue最高优先级元素总是第一个出列 vector ,deque 和 list顺序性容器: 向量vector: 是一个线性顺序结构。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。在创建一个vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定,这个大小即capacity()函数的返回值。当存储的数据超过分配的空间时vector 会重新分配一块内存块,但这样的分配是很耗时的,在重新分配空间时它会做这样的动作: 首先,vector 会申请一块更大的内存块; 然后,将原来的数据拷贝到新的内存块中; 其次,销毁掉原内存块中的对象(调用对象的析构函数); 最后,将原来的内存空间释放掉。 如果vector 保存的数据量很大时,这样的操作一定会导致糟糕的性能(这也是vector 被设计成比较容易拷贝的值类型的原因)。所以说vector 不是在什么情况下性能都好,只有在预先知道它大小的情况下vector 的性能才是最优的。 vector的特点:(1) 指定一块如同数组一样的连续存储,但空间可以动态扩展。即它可以像数组一样操作,并且可以进行动态操作。通常体现在push_back() pop_back()。(2) 随机访问方便,它像数组一样被访问,即支持[ ] 操作符和vector.at() (3) 节省空间,因为它是连续存储,在存储数据的区域都是没有被浪费的,但是要明确一点vector 大多情况下并不是满存的,在未存储的区域实际是浪费的。 (4) 在内部进行插入、删除操作效率非常低,这样的操作基本上是被禁止的。Vector 被设计成只能在后端进行追加和删除操作,其原因是vector 内部的实现是按照顺序表的原理。(5) 只能在vector 的最后进行push 和pop ,不能在vector 的头进行push 和pop。(6) 当动态添加的数据超过vector 默认分配的大小时要进行内存的重新分配、拷贝与释放,这个操作非常消耗性能。所以要vector 达到最优的性能,最好在创建vector 时就指定其空间大小。 双向链表list是一个线性链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。 由于其结构的原因,list 随机检索的性能非常的不好,因为它不像vector 那样直接找到元素的地址,而是要从头一个一个的顺序查找,这样目标元素越靠后,它的检索时间就越长。检索时间与目标元素的位置成正比。 虽然随机检索的速度不够快,但是它可以迅速地在任何节点进行插入和删除操作。因为list 的每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响,不像vector 会对操作点之后的所有元素的存储地址都有所影响,这一点是vector 不可比拟的。 list的特点:(1) 不使用连续的内存空间这样可以随意地进行动态操作;(2) 可以在内部任何位置快速地插入或删除,当然也可以在两端进行push 和pop。(3) 不能进行内部的随机访问,即不支持[ ] 操作符和vector.at();(4) 相对于verctor 占用更多的内存。 双端队列deque是一种优化了的、对序列两端元素进行添加和删除操作的基本序列容器。它允许较为快速地随机访问,但它不像vector 把所有的对象保存在一块连续的内存块,而是采用多个连续的存储块,并且在一个映射结构中保存对这些块及其顺序的跟踪。向deque 两端添加或删除元素的开销很小。它不需要重新分配空间,所以向末端增加元素比vector 更有效。 实际上,deque 是对vector 和list 优缺点的结合,它是处于两者之间的一种容器。 deque的特点:(1) 随机访问方便,即支持[ ] 操作符和vector.at() ,但性能没有vector 好;(2) 可以在内部进行插入和删除操作,但性能不及list;(3) 可以在两端进行push、pop;三者的比较 下图描述了vector、list、deque 在内存结构上的特点: vector是一段连续的内存块,而deque 是多个连续的内存块, list 是所有数据元素分开保存,可以是任何两个元素没有连续。 vector的查询性能最好,并且在末端增加数据也很好,除非它重新申请内存段;适合高效地随机存储。 list是一个链表,任何一个元素都可以是不连续的,但它都有两个指向上一元素和下一元素的指针。所以它对插入、删除元素性能是最好的,而查询性能非常差;适合大量地插入和删除操作而不关心随机存取的需求。deque是介于两者之间,它兼顾了数组和链表的优点,它是分块的链表和多个数组的联合。所以它有被list 好的查询性能,有被vector 好的插入、删除性能。如果你需要随即存取又关心两端数据的插入和删除,那么deque 是最佳之选。关联容器 set, multiset, map, multimap是一种非线性的树结构,具体的说采用的是一种比较高效的特殊的平衡检索二叉树—— 红黑树结构。(至于什么是红黑树,我也不太理解,只能理解到它是一种二叉树结构)因为关联容器的这四种容器类都使用同一原理,所以他们核心的算法是一致的,但是它们在应用上又有一些差别,先描述一下它们之间的差别。 set,又称集合,实际上就是一组元素的集合,但其中所包含的元素的值是唯一的,且是按一定顺序排列的,集合中的每个元素被称作集合中的实例。因为其内部是通过链表的方式来组织,所以在插入的时候比vector 快,但在查找和末尾添加上被vector 慢。 multiset,是多重集合,其实现方式和set 是相似的,只是它不要求集合中的元素是唯一的,也就是说集合中的同一个元素可以出现多次。map,提供一种“键- 值”关系的一对一的数据存储能力。其“键”在容器中不可重复,且按一定顺序排列(其实我们可以将set 也看成是一种键- 值关系的存储,只是它只有键没有值。它是map 的一种特殊形式)。由于其是按链表的方式存储,它也继承了链表的优缺点。 multimap,和map 的原理基本相似,它允许“键”在容器中可以不唯一。关联容器的特点是明显的,相对于顺序容器,有以下几个主要特点: 1,其内部实现是采用非线性的二叉树结构,具体的说是红黑树的结构原理实现的; 2,set和map 保证了元素的唯一性,mulset 和mulmap 扩展了这一属性,可以允许元素不唯一; 3,元素是有序的集合,默认在插入的时候按升序排列。 基于以上特点, 1,关联容器对元素的插入和删除操作比vector 要快,因为vector 是顺序存储,而关联容器是链式存储;比list 要慢,是因为即使它们同是链式结构,但list 是线性的,而关联容器是二叉树结构,其改变一个元素涉及到其它元素的变动比list 要多,并且它是排序的,每次插入和删除都需要对元素重新排序; 2,关联容器对元素的检索操作比vector 慢,但是比list 要快很多。vector 是顺序的连续存储,当然是比不上的,但相对链式的list 要快很多是因为list 是逐个搜索,它搜索的时间是跟容器的大小成正比,而关联容器查找的复杂度基本是Log(N) ,比如如果有1000 个记录,最多查找10 次,1,000,000 个记录,最多查找20 次。容器越大,关联容器相对list 的优越性就越能体现;3,在使用上set 区别于vector,deque,list 的最大特点就是set 是内部排序的,这在查询上虽然逊色于vector ,但是却大大的强于list。 4,在使用上map 的功能是不可取代的,它保存了“键- 值”关系的数据,而这种键值关系采用了类数组的方式。数组是用数字类型的下标来索引元素的位置,而map 是用字符型关键字来索引元素的位置。在使用上map 也提供了一种类数组操作的方式,即它可以通过下标来检索数据,这是其他容器做不到的,当然也包括set。(STL 中只有vector 和map 可以通过类数组的方式操作元素,即如同ele[1] 方式)容器适配器 STL中包含三种适配器:栈stack 、队列queue 和优先级priority_queue。 适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现,即可以把适配器看作“它保存一个容器,这个容器再保存所有元素”。 STL中提供的三种适配器可以由某一种顺序容器去实现。默认下stack 和queue 基于deque 容器实现,priority_queue 则基于vector 容器实现。当然在创建一个适配器时也可以指定具体的实现容器,创建适配器时在第二个参数上指定具体的顺序容器可以覆盖适配器的默认实现。 由于适配器的特点,一个适配器不是可以由任一个顺序容器都可以实现的。
2023-08-17 23:27:041

java中几种Map在什么情况下使用,并简单介绍原因及原理

HashMap 散列 线程不安全HashTable 由hashmap实现 有linklist 且线程安全ConcurrentHashMap 分段式锁机制的线程安全的Map单独业务 没有共享竞争 HashMap单独业务 并发不大 并对数据先后顺序有要求 可采用 HashTable高并发ConcurrentHashMap
2023-08-17 23:27:152

java的monitor机制中,为什么阻塞队列用list等待队列用set

是否是使用了第三方的锁屏程序导致,建议您可以使用自带的锁屏后对比;
2023-08-17 23:27:231

epoll为什么这么快,epoll的实现原理

以一个生活中的例子来解释.假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,但是不知道你具体住在哪里,于是你们约好了在A号楼门口见面.如果你使用的阻塞IO模型来处理这个问题,那么你就只能一直守候在A号楼门口等待朋友的到来,在这段时间里你不能做别的事情,不难知道,这种方式的效率是低下的.进一步解释select和epoll模型的差异.select版大妈做的是如下的事情:比如同学甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同学甲,你等的朋友来了,于是在实际的代码中,select版大妈做的是以下的事情:int n = select(&readset,NULL,NULL,100); for (int i = 0; n > 0; ++i) { if (FD_ISSET(fdarray[i], &readset)) { do_something(fdarray[i]); --n; } }epoll版大妈就比较先进了,她记下了同学甲的信息,比如说他的房间号,那么等同学甲的朋友到来时,只需要告诉该朋友同学甲在哪个房间即可,不用自己亲自带着人满大楼的找人了.于是epoll版大妈做的事情可以用如下的代码表示:n = epoll_wait(epfd,events,20,500); for(i=0;i<n;++i) { do_something(events[n]); } 在epoll中,关键的数据结构epoll_event定义如下:typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; 可以看到,epoll_data是一个union结构体,它就是epoll版大妈用于保存同学信息的结构体,它可以保存很多类型的信息:fd,指针,等等.有了这个结构体,epoll大妈可以不用吹灰之力就可以定位到同学甲.别小看了这些效率的提高,在一个大规模并发的服务器中,轮询IO是最耗时间的操作之一.再回到那个例子中,如果每到来一个朋友楼管大妈都要全楼的查询同学,那么处理的效率必然就低下了,过不久楼底就有不少的人了.对比最早给出的阻塞IO的处理模型, 可以看到采用了多路复用IO之后, 程序可以自由的进行自己除了IO操作之外的工作, 只有到IO状态发生变化的时候由多路复用IO进行通知, 然后再采取相应的操作, 而不用一直阻塞等待IO状态发生变化了.从上面的分析也可以看出,epoll比select的提高实际上是一个用空间换时间思想的具体应用.二、深入理解epoll的实现原理:开发高性能网络程序时,windows开发者们言必称iocp,linux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。我们用起epoll来都感觉挺爽,确实快,那么,它到底为什么可以高速处理这么多并发连接呢? 先简单回顾下如何使用C库封装的3个epoll系统调用吧。int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 使用起来很清晰,首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。从上面的调用方式就可以看到epoll比select/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socket给select/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。在内核里,一切皆文件。所以,epoll向内核注册了一个文件系统,用于存储上述的被监控socket。当你调用epoll_create时,就会在这个虚拟的epoll文件系统里创建一个file结点。当然这个file不是普通文件,它只服务于epoll。epoll在被内核初始化时(操作系统启动),同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配好的对象。static int __init eventpoll_init(void) { ... ... /* Allocates slab cache used to allocate "struct epitem" items */ epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem), 0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL); /* Allocates slab cache used to allocate "struct eppoll_entry" */ pwq_cache = kmem_cache_create("eventpoll_pwq", sizeof(struct eppoll_entry), 0, EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL); ... ... epoll的高效就在于,当我们调用epoll_ctl往里塞入百万个句柄时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的句柄给我们用户。这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。那么,这个准备就绪list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。如此,一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。最后看看epoll独有的两种模式LT和ET。无论是LT和ET模式,都适用于以上所说的流程。区别是,LT模式下,只要一个句柄上的事件一次没有处理完,会在以后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。这件事怎么做到的呢?当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。三、扩展阅读(epoll与之前其他相关技术的比较): Linux提供了select、poll、epoll接口来实现IO复用,三者的原型如下所示,本文从参数、实现、性能等方面对三者进行对比。 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); int poll(struct pollfd *fds, nfds_t nfds, int timeout); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); select、poll、epoll_wait参数及实现对比 1. select的第一个参数nfds为fdset集合中最大描述符值加1,fdset是一个位数组,其大小限制为__FD_SETSIZE(1024),位数组的每一位代表其对应的描述符是否需要被检查。 select的第二三四个参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件。所以每次调用select前都需要重新初始化fdset。 timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。 select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。 select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。 2. poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。 poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。 poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。 3. epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。 epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。另外epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
2023-08-17 23:28:011

计算机考研408数据结构不考红黑树么

不靠。计算机考研408数据结构考试内容是计算机组成原理、操作系统等,红黑树是一种特殊形式的平衡二叉搜索树,根据查询相关资料,2022年08考纲的修订,平衡树、红黑树、外部排序等都不被列入考核中,所以不会考红黑树。
2023-08-17 23:28:111

C++数据结构原理与经典问题求解的图书目录

第1章绪论11.1数据与数据结构21.1.1数据及其类型21.1.2数据结构简介41.2算法61.2.1算法的概念61.2.2算法的分析81.2.3算法的设计121.3C++语言简介181.3.1C++的产生与发展181.3.2C++与面向对象思想201.3.3C++中的类和对象231.4本章小结28第2章C++编程基础292.1开始C++编程302.1.1输入输出302.1.2预处理382.1.3名字空间442.2深入的类编程502.2.1访问控制502.2.2初始化与清除532.2.3动态创建对象572.2.4友元函数602.2.5拷贝构造函数612.3丰富的C++特性652.3.1常量652.3.2函数重载682.3.3运算符重载712.3.4异常处理772.4代码重用机制792.4.1继承802.4.2多态872.4.3模板902.5标准模板库932.5.1STL简介942.5.2STL构成952.5.3STL的不同版本972.6本章小结98第3章指针、数组与字符串993.1指针1003.1.1指针的概念1003.1.2指针的语法1023.1.3函数与参数传递1033.2数组1083.2.1数组定义与初始化1093.2.2数组与指针1133.2.3数组的抽象数据类型1163.2.4大整数乘法问题1203.2.5荷兰国旗问题1213.3字符串1243.3.1C++中的字符串1243.3.2字符串抽象数据类型1263.3.3字符串的匹配算法1283.3.4字符串指数问题1413.4动态内存管理1423.4.1关键词new和delete1433.4.2避免内存错误1463.5本章小结152第4章链表1534.1单向链表1544.1.1单向链表的结构1544.1.2单向链表类的实现1554.1.3有序链表的合并1624.1.4多项式加法问题1634.2单向循环链表1644.2.1单向循环链表的结构1644.2.2单向循环链表类的实现1664.2.3约瑟夫问题1694.2.4魔术师发牌问题1704.2.5拉丁方阵问题1724.3双向循环链表1734.3.1双向循环链表的结构1734.3.2双向循环链表类的实现1744.3.3Vigenere加密问题1824.3.4选美比赛问题1844.4游标类的设计与实现1864.4.1游标类的结构1864.4.2游标类的实现1874.5STL与链表1914.5.1STL中链表类的接口1914.5.2遍历1944.5.3元素的插入与删除1964.6本章小结196第5章栈与队列1975.1栈1985.1.1栈的结构1985.1.2栈的实现1995.1.3括号匹配问题2035.1.4停车场模拟问题2045.2队列2085.2.1队列的结构2085.2.2队列的实现2105.2.3舞伴问题2145.2.4杨辉三角形问题2155.2.5游程编码问题2165.3优先级队列2185.3.1优先级队列的结构2185.3.2优先级队列的实现2205.4STL中的栈与队列2225.4.1STL中的stack2225.4.2STL中的queue2245.4.3STL中的priority_queue2265.5本章小结229第6章递归2316.1递归的概念2326.1.1递归的定义2326.1.2应用递归的原则2356.1.3递归和非递归的转化2406.2分治法2436.2.1分治法简述2436.2.2汉诺塔问题2446.2.3传染病问题2466.3回溯法2506.3.1回溯法简述2516.3.2迷宫问题2516.3.3八皇后问题2556.3.4骑士周游问题2586.4本章小结265第7章树2677.1树的概念2687.1.1树的定义2687.1.2树的术语2717.1.3树的抽象数据类型2727.2二叉树2737.2.1二叉树的定义2737.2.2二叉树的性质2757.2.3二叉树的实现2767.2.4二叉树的遍历2857.2.5二叉树的线索化2897.3树与森林2917.3.1树的存储表示2917.3.2树的实现2947.3.3树与森林的遍历2987.3.4森林与二叉树的转换3007.4霍夫曼树3047.4.1霍夫曼树的概念3047.4.2霍夫曼树的构造方法3057.4.3霍夫曼编码及其实现3077.5堆3137.5.1堆的概念3147.5.2堆的建立3147.5.3堆的操作3167.6基于STL实现树结构3177.6.1STL中的vector3177.6.2STL中的map3217.7医院建模问题3237.8本章小结328第8章图3298.1图的基本概念3308.1.1图的定义3308.1.2图的术语3318.1.3图的运算3348.1.4图的抽象数据类型3368.2图的存储与表示3378.2.1图的邻接矩阵表示3378.2.2图的邻接表表示3398.2.3两种表示法的比较3428.3图的遍历3428.3.1欧拉路径与欧拉回路3438.3.2哈密尔顿路径与哈密尔顿回路3458.3.3广度优先遍历3468.3.4深度优先遍历3498.4最短路径问题3538.4.1固定起点最短路问题3538.4.2非固定起点最短路问题3558.4.3最短路径的动态规划解法3588.4.4旅游交通路线问题3648.5最小生成树3728.5.1最小生成树的定义3728.5.2克鲁斯卡尔算法3738.5.3普里姆算法3758.6经典问题举例3798.6.1文字游戏问题3808.6.2道路修建问题3828.6.3回家路线问题3858.6.4水塘计算问题3878.6.5棍子还原问题3898.7本章小结392第9章树形搜索结构3939.1二叉搜索树3949.1.1二叉搜索树的概念3949.1.2二叉搜索树的操作3959.1.3二叉搜索树的实现3979.1.4二叉搜索树的分析4009.2AVL树4039.2.1AVL树的概念4049.2.2AVL树的旋转4059.2.3AVL树的实现4109.3红黑树4189.3.1红黑树的概念4189.3.2红黑树的操作4219.3.3红黑树的实现4289.4Trie树4339.4.1Trie树的概念4339.4.2Trie树的表示4349.4.3Trie树的实现4359.5本章小结439第10章集合与字典44110.1集合论基础44210.1.1集合的概念44210.1.2集合的运算44410.2集合的实现44510.2.1位向量集合44510.2.2链表集合45110.3字典46010.3.1字典的概念46110.3.2搜索运算46310.4散列46710.4.1散列的概念46710.4.2散列函数46910.4.3处理散列冲突47110.4.4散列的应用47510.5经典问题举例47610.5.1拼写检查问题47610.5.2无线网络问题48510.5.3第K个数问题48810.6STL中的set49010.7本章小结493第11章排序49511.1排序问题概述49611.1.1基本概念和定义49611.1.2排序算法的分类49711.1.3排序算法分析与选择49711.2插入排序49811.2.1直接插入排序49811.2.2二分法插入排序50111.2.3希尔排序50311.3选择排序50611.3.1直接选择排序50611.3.2堆排序50811.4交换排序51211.4.1冒泡法排序51211.4.2Shaker排序51411.4.3快速排序51711.5归并排序52211.6计数排序52611.7本章小结531参考文献533……
2023-08-17 23:28:301

高度为h的红黑树上的最少结点个数是多少

二叉树没有度为1的点,至少情况应该如下(除根节点外每一层都是两个结点)o/oo/oo根据上述二叉树情况,其结点数公式为2h-1所以本题至少有2h-1个结点
2023-08-17 23:29:051

如何提高Linux下块设备IO的整体性能

前言:本文主要讲解Linux IO调度层的三种模式:cfp、deadline和noop,并给出各自的优化和适用场景建议。IO调度发生在Linux内核的IO调度层。这个层次是针对Linux的整体IO层次体系来说的。从read()或者write()系统调用的角度来说,Linux整体IO体系可以分为七层,它们分别是:VFS层: 虚拟文件系统层。由于内核要跟多种文件系统打交道,而每一种文件系统所实现的数据结构和相关方法都可能不尽相同,所以,内核抽象了这一层,专门用来适配各种文件系统,并对外提供统一操作接口。文件系统层: 不同的文件系统实现自己的操作过程,提供自己特有的特征,具体不多说了,大家愿意的话自己去看代码即可。页缓存层: 负责真对page的缓存。通用块层: 由于绝大多数情况的io操作是跟块设备打交道,所以Linux在此提供了一个类似vfs层的块设备操作抽象层。下层对接各种不同属性的块设备,对上提供统一的Block IO请求标准。IO调度层 :因为绝大多数的块设备都是类似磁盘这样的设备,所以有必要根据这类设备的特点以及应用的不同特点来设置一些不同的调度算法和队列。以便在不同的应用环境下有针对性的提高磁盘的读写效率,这里就是大名鼎鼎的Linux电梯所起作用的地方。针对机械硬盘的各种调度方法就是在这实现的。块设备驱动层: 驱动层对外提供相对比较高级的设备操作接口,往往是C语言的,而下层对接设备本身的操作方法和规范。块设备层: 这层就是具体的物理设备了,定义了各种真对设备操作方法和规范。有一个已经整理好的[Linux IO结构图],非常经典,一图胜千言:我们今天要研究的内容主要在IO调度这一层。它要解决的核心问题是,如何提高块设备IO的整体性能?这一层也主要是针对机械硬盘结构而设计的。众所周知,机械硬盘的存储介质是磁盘,磁头在盘片上移动进行磁道寻址,行为类似播放一张唱片。这种结构的特点是,顺序访问时吞吐量较高,但是如果一旦对盘片有随机访问,那么大量的时间都会浪费在磁头的移动上,这时候就会导致每次IO的响应时间变长,极大的降低IO的响应速度。磁头在盘片上寻道的操作,类似电梯调度,实际上在最开始的时期,Linux把这个算法命名为Linux电梯算法,即:如果在寻道的过程中,能把顺序路过的相关磁道的数据请求都“顺便”处理掉,那么就可以在比较小影响响应速度的前提下,提高整体IO的吞吐量。这就是我们为什么要设计IO调度算法的原因。目前在内核中默认开启了三种算法/模式:noop,cfq和deadline。严格算应该是两种:因为第一种叫做noop,就是空操作调度算法,也就是没有任何调度操作,并不对io请求进行排序,仅仅做适当的io合并的一个fifo队列。目前内核中默认的调度算法应该是cfq,叫做完全公平队列调度。这个调度算法人如其名,它试图给所有进程提供一个完全公平的IO操作环境。注:请大家一定记住这个词语,cfq,完全公平队列调度,不然下文就没法看了。cfq为每个进程创建一个同步IO调度队列,并默认以时间片和请求数限定的方式分配IO资源,以此保证每个进程的IO资源占用是公平的,cfq还实现了针对进程级别的优先级调度,这个我们后面会详细解释。查看和修改IO调度算法的方法是:cfq是通用服务器比较好的IO调度算法选择,对桌面用户也是比较好的选择。但是对于很多IO压力较大的场景就并不是很适应,尤其是IO压力集中在某些进程上的场景。因为这种场景我们需要更多的满足某个或者某几个进程的IO响应速度,而不是让所有的进程公平的使用IO,比如数据库应用。deadline调度(最终期限调度)就是更适合上述场景的解决方案。deadline实现了四个队列:其中两个分别处理正常read和write,按扇区号排序,进行正常io的合并处理以提高吞吐量。因为IO请求可能会集中在某些磁盘位置,这样会导致新来的请求一直被合并,可能会有其他磁盘位置的io请求被饿死。另外两个处理超时read和write的队列,按请求创建时间排序,如果有超时的请求出现,就放进这两个队列,调度算法保证超时(达到最终期限时间)的队列中的请求会优先被处理,防止请求被饿死。不久前,内核还是默认标配四种算法,还有一种叫做as的算法(Anticipatory scheduler),预测调度算法。一个高大上的名字,搞得我一度认为Linux内核都会算命了。结果发现,无非是在基于deadline算法做io调度的之前等一小会时间,如果这段时间内有可以合并的io请求到来,就可以合并处理,提高deadline调度的在顺序读写情况下的数据吞吐量。其实这根本不是啥预测,我觉得不如叫撞大运调度算法,当然这种策略在某些特定场景差效果不错。但是在大多数场景下,这个调度不仅没有提高吞吐量,还降低了响应速度,所以内核干脆把它从默认配置里删除了。毕竟Linux的宗旨是实用,而我们也就不再这个调度算法上多费口舌了。1、cfq:完全公平队列调度cfq是内核默认选择的IO调度队列,它在桌面应用场景以及大多数常见应用场景下都是很好的选择。如何实现一个所谓的完全公平队列(Completely Fair Queueing)?首先我们要理解所谓的公平是对谁的公平?从操作系统的角度来说,产生操作行为的主体都是进程,所以这里的公平是针对每个进程而言的,我们要试图让进程可以公平的占用IO资源。那么如何让进程公平的占用IO资源?我们需要先理解什么是IO资源。当我们衡量一个IO资源的时候,一般喜欢用的是两个单位,一个是数据读写的带宽,另一个是数据读写的IOPS。带宽就是以时间为单位的读写数据量,比如,100Mbyte/s。而IOPS是以时间为单位的读写次数。在不同的读写情境下,这两个单位的表现可能不一样,但是可以确定的是,两个单位的任何一个达到了性能上限,都会成为IO的瓶颈。从机械硬盘的结构考虑,如果读写是顺序读写,那么IO的表现是可以通过比较少的IOPS达到较大的带宽,因为可以合并很多IO,也可以通过预读等方式加速数据读取效率。当IO的表现是偏向于随机读写的时候,那么IOPS就会变得更大,IO的请求的合并可能性下降,当每次io请求数据越少的时候,带宽表现就会越低。从这里我们可以理解,针对进程的IO资源的主要表现形式有两个: 进程在单位时间内提交的IO请求个数和进程占用IO的带宽。其实无论哪个,都是跟进程分配的IO处理时间长度紧密相关的。有时业务可以在较少IOPS的情况下占用较大带宽,另外一些则可能在较大IOPS的情况下占用较少带宽,所以对进程占用IO的时间进行调度才是相对最公平的。即,我不管你是IOPS高还是带宽占用高,到了时间咱就换下一个进程处理,你爱咋样咋样。所以,cfq就是试图给所有进程分配等同的块设备使用的时间片,进程在时间片内,可以将产生的IO请求提交给块设备进行处理,时间片结束,进程的请求将排进它自己的队列,等待下次调度的时候进行处理。这就是cfq的基本原理。当然,现实生活中不可能有真正的“公平”,常见的应用场景下,我们很肯能需要人为的对进程的IO占用进行人为指定优先级,这就像对进程的CPU占用设置优先级的概念一样。所以,除了针对时间片进行公平队列调度外,cfq还提供了优先级支持。每个进程都可以设置一个IO优先级,cfq会根据这个优先级的设置情况作为调度时的重要参考因素。优先级首先分成三大类:RT、BE、IDLE,它们分别是实时(Real Time)、最佳效果(Best Try)和闲置(Idle)三个类别,对每个类别的IO,cfq都使用不同的策略进行处理。另外,RT和BE类别中,分别又再划分了8个子优先级实现更细节的QOS需求,而IDLE只有一个子优先级。另外,我们都知道内核默认对存储的读写都是经过缓存(buffer/cache)的,在这种情况下,cfq是无法区分当前处理的请求是来自哪一个进程的。只有在进程使用同步方式(sync read或者sync wirte)或者直接IO(Direct IO)方式进行读写的时候,cfq才能区分出IO请求来自哪个进程。所以,除了针对每个进程实现的IO队列以外,还实现了一个公共的队列用来处理异步请求。当前内核已经实现了针对IO资源的cgroup资源隔离,所以在以上体系的基础上,cfq也实现了针对cgroup的调度支持。总的来说,cfq用了一系列的数据结构实现了以上所有复杂功能的支持,大家可以通过源代码看到其相关实现,文件在源代码目录下的block/cfq-iosched.c。1.1 cfq设计原理在此,我们对整体数据结构做一个简要描述:首先,cfq通过一个叫做cfq_data的数据结构维护了整个调度器流程。在一个支持了cgroup功能的cfq中,全部进程被分成了若干个contral group进行管理。每个cgroup在cfq中都有一个cfq_group的结构进行描述,所有的cgroup都被作为一个调度对象放进一个红黑树中,并以vdisktime为key进行排序。vdisktime这个时间纪录的是当前cgroup所占用的io时间,每次对cgroup进行调度时,总是通过红黑树选择当前vdisktime时间最少的cgroup进行处理,以保证所有cgroups之间的IO资源占用“公平”。当然我们知道,cgroup是可以对blkio进行资源比例分配的,其作用原理就是,分配比例大的cgroup占用vdisktime时间增长较慢,分配比例小的vdisktime时间增长较快,快慢与分配比例成正比。这样就做到了不同的cgroup分配的IO比例不一样,并且在cfq的角度看来依然是“公平“的。选择好了需要处理的cgroup(cfq_group)之后,调度器需要决策选择下一步的service_tree。service_tree这个数据结构对应的都是一系列的红黑树,主要目的是用来实现请求优先级分类的,就是RT、BE、IDLE的分类。每一个cfq_group都维护了7个service_trees,其定义如下:其中service_tree_idle就是用来给IDLE类型的请求进行排队用的红黑树。而上面二维数组,首先第一个维度针对RT和BE分别各实现了一个数组,每一个数组中都维护了三个红黑树,分别对应三种不同子类型的请求,分别是:SYNC、SYNC_NOIDLE以及ASYNC。我们可以认为SYNC相当于SYNC_IDLE并与SYNC_NOIDLE对应。idling是cfq在设计上为了尽量合并连续的IO请求以达到提高吞吐量的目的而加入的机制,我们可以理解为是一种“空转”等待机制。空转是指,当一个队列处理一个请求结束后,会在发生调度之前空等一小会时间,如果下一个请求到来,则可以减少磁头寻址,继续处理顺序的IO请求。为了实现这个功能,cfq在service_tree这层数据结构这实现了SYNC队列,如果请求是同步顺序请求,就入队这个service tree,如果请求是同步随机请求,则入队SYNC_NOIDLE队列,以判断下一个请求是否是顺序请求。所有的异步写操作请求将入队ASYNC的service tree,并且针对这个队列没有空转等待机制。此外,cfq还对SSD这样的硬盘有特殊调整,当cfq发现存储设备是一个ssd硬盘这样的队列深度更大的设备时,所有针对单独队列的空转都将不生效,所有的IO请求都将入队SYNC_NOIDLE这个service tree。每一个service tree都对应了若干个cfq_queue队列,每个cfq_queue队列对应一个进程,这个我们后续再详细说明。cfq_group还维护了一个在cgroup内部所有进程公用的异步IO请求队列,其结构如下:异步请求也分成了RT、BE、IDLE这三类进行处理,每一类对应一个cfq_queue进行排队。BE和RT也实现了优先级的支持,每一个类型有IOPRIO_BE_NR这么多个优先级,这个值定义为8,数组下标为0-7。我们目前分析的内核代码版本为Linux 4.4,可以看出,从cfq的角度来说,已经可以实现异步IO的cgroup支持了,我们需要定义一下这里所谓异步IO的含义,它仅仅表示从内存的buffer/cache中的数据同步到硬盘的IO请求,而不是aio(man 7 aio)或者linux的native异步io以及libaio机制,实际上这些所谓的“异步”IO机制,在内核中都是同步实现的(本质上冯诺伊曼计算机没有真正的“异步”机制)。我们在上面已经说明过,由于进程正常情况下都是将数据先写入buffer/cache,所以这种异步IO都是统一由cfq_group中的async请求队列处理的。那么为什么在上面的service_tree中还要实现和一个ASYNC的类型呢?这当然是为了支持区分进程的异步IO并使之可以“完全公平”做准备喽。实际上在最新的cgroup v2的blkio体系中,内核已经支持了针对buffer IO的cgroup限速支持,而以上这些可能容易混淆的一堆类型,都是在新的体系下需要用到的类型标记。新体系的复杂度更高了,功能也更加强大,但是大家先不要着急,正式的cgroup v2体系,在Linux 4.5发布的时候会正式跟大家见面。我们继续选择service_tree的过程,三种优先级类型的service_tree的选择就是根据类型的优先级来做选择的,RT优先级最高,BE其次,IDLE最低。就是说,RT里有,就会一直处理RT,RT没了再处理BE。每个service_tree对应一个元素为cfq_queue排队的红黑树,而每个cfq_queue就是内核为进程(线程)创建的请求队列。每一个cfq_queue都会维护一个rb_key的变量,这个变量实际上就是这个队列的IO服务时间(service time)。这里还是通过红黑树找到service time时间最短的那个cfq_queue进行服务,以保证“完全公平”。选择好了cfq_queue之后,就要开始处理这个队列里的IO请求了。这里的调度方式基本跟deadline类似。cfq_queue会对进入队列的每一个请求进行两次入队,一个放进fifo中,另一个放进按访问扇区顺序作为key的红黑树中。默认从红黑树中取请求进行处理,当请求的延时时间达到deadline时,就从红黑树中取等待时间最长的进行处理,以保证请求不被饿死。这就是整个cfq的调度流程,当然其中还有很多细枝末节没有交代,比如合并处理以及顺序处理等等。1.2 cfq的参数调整理解整个调度流程有助于我们决策如何调整cfq的相关参数。所有cfq的可调参数都可以在/sys/class/block/sda/queue/iosched/目录下找到,当然,在你的系统上,请将sda替换为相应的磁盘名称。我们来看一下都有什么:这些参数部分是跟机械硬盘磁头寻道方式有关的,如果其说明你看不懂,请先补充相关知识:back_seek_max:磁头可以向后寻址的最大范围,默认值为16M。back_seek_penalty:向后寻址的惩罚系数。这个值是跟向前寻址进行比较的。以上两个是为了防止磁头寻道发生抖动而导致寻址过慢而设置的。基本思路是这样,一个io请求到来的时候,cfq会根据其寻址位置预估一下其磁头寻道成本。设置一个最大值back_seek_max,对于请求所访问的扇区号在磁头后方的请求,只要寻址范围没有超过这个值,cfq会像向前寻址的请求一样处理它。再设置一个评估成本的系数back_seek_penalty,相对于磁头向前寻址,向后寻址的距离为1/2(1/back_seek_penalty)时,cfq认为这两个请求寻址的代价是相同。这两个参数实际上是cfq判断请求合并处理的条件限制,凡事复合这个条件的请求,都会尽量在本次请求处理的时候一起合并处理。fifo_expire_async:设置异步请求的超时时间。同步请求和异步请求是区分不同队列处理的,cfq在调度的时候一般情况都会优先处理同步请求,之后再处理异步请求,除非异步请求符合上述合并处理的条件限制范围内。当本进程的队列被调度时,cfq会优先检查是否有异步请求超时,就是超过fifo_expire_async参数的限制。如果有,则优先发送一个超时的请求,其余请求仍然按照优先级以及扇区编号大小来处理。fifo_expire_sync:这个参数跟上面的类似,区别是用来设置同步请求的超时时间。slice_idle:参数设置了一个等待时间。这让cfq在切换cfq_queue或service tree的时候等待一段时间,目的是提高机械硬盘的吞吐量。一般情况下,来自同一个cfq_queue或者service tree的IO请求的寻址局部性更好,所以这样可以减少磁盘的寻址次数。这个值在机械硬盘上默认为非零。当然在固态硬盘或者硬RAID设备上设置这个值为非零会降低存储的效率,因为固态硬盘没有磁头寻址这个概念,所以在这样的设备上应该设置为0,关闭此功能。group_idle:这个参数也跟上一个参数类似,区别是当cfq要切换cfq_group的时候会等待一段时间。在cgroup的场景下,如果我们沿用slice_idle的方式,那么空转等待可能会在cgroup组内每个进程的cfq_queue切换时发生。这样会如果这个进程一直有请求要处理的话,那么直到这个cgroup的配额被耗尽,同组中的其它进程也可能无法被调度到。这样会导致同组中的其它进程饿死而产生IO性能瓶颈。在这种情况下,我们可以将slice_idle = 0而group_idle = 8。这样空转等待就是以cgroup为单位进行的,而不是以cfq_queue的进程为单位进行,以防止上述问题产生。low_latency:这个是用来开启或关闭cfq的低延时(low latency)模式的开关。当这个开关打开时,cfq将会根据target_latency的参数设置来对每一个进程的分片时间(slice time)进行重新计算。这将有利于对吞吐量的公平(默认是对时间片分配的公平)。关闭这个参数(设置为0)将忽略target_latency的值。这将使系统中的进程完全按照时间片方式进行IO资源分配。这个开关默认是打开的。我们已经知道cfq设计上有“空转”(idling)这个概念,目的是为了可以让连续的读写操作尽可能多的合并处理,减少磁头的寻址操作以便增大吞吐量。如果有进程总是很快的进行顺序读写,那么它将因为cfq的空转等待命中率很高而导致其它需要处理IO的进程响应速度下降,如果另一个需要调度的进程不会发出大量顺序IO行为的话,系统中不同进程IO吞吐量的表现就会很不均衡。就比如,系统内存的cache中有很多脏页要写回时,桌面又要打开一个浏览器进行操作,这时脏页写回的后台行为就很可能会大量命中空转时间,而导致浏览器的小量IO一直等待,让用户感觉浏览器运行响应速度变慢。这个low_latency主要是对这种情况进行优化的选项,当其打开时,系统会根据target_latency的配置对因为命中空转而大量占用IO吞吐量的进程进行限制,以达到不同进程IO占用的吞吐量的相对均衡。这个开关比较合适在类似桌面应用的场景下打开。target_latency:当low_latency的值为开启状态时,cfq将根据这个值重新计算每个进程分配的IO时间片长度。quantum:这个参数用来设置每次从cfq_queue中处理多少个IO请求。在一个队列处理事件周期中,超过这个数字的IO请求将不会被处理。这个参数只对同步的请求有效。slice_sync:当一个cfq_queue队列被调度处理时,它可以被分配的处理总时间是通过这个值来作为一个计算参数指定的。公式为:time_slice = slice_sync + (slice_sync/5 * (4 - prio))。这个参数对同步请求有效。slice_async:这个值跟上一个类似,区别是对异步请求有效。slice_async_rq:这个参数用来限制在一个slice的时间范围内,一个队列最多可以处理的异步请求个数。请求被处理的最大个数还跟相关进程被设置的io优先级有关。1.3 cfq的IOPS模式我们已经知道,默认情况下cfq是以时间片方式支持的带优先级的调度来保证IO资源占用的公平。高优先级的进程将得到更多的时间片长度,而低优先级的进程时间片相对较小。当我们的存储是一个高速并且支持NCQ(原生指令队列)的设备的时候,我们最好可以让其可以从多个cfq队列中处理多路的请求,以便提升NCQ的利用率。此时使用时间片的分配方式分配资源就显得不合时宜了,因为基于时间片的分配,同一时刻最多能处理的请求队列只有一个。这时,我们需要切换cfq的模式为IOPS模式。切换方式很简单,就是将slice_idle=0即可。内核会自动检测你的存储设备是否支持NCQ,如果支持的话cfq会自动切换为IOPS模式。另外,在默认的基于优先级的时间片方式下,我们可以使用ionice命令来调整进程的IO优先级。进程默认分配的IO优先级是根据进程的nice值计算而来的,计算方法可以在man ionice中看到,这里不再废话。2、deadline:最终期限调度deadline调度算法相对cfq要简单很多。其设计目标是:在保证请求按照设备扇区的顺序进行访问的同时,兼顾其它请求不被饿死,要在一个最终期限前被调度到。我们知道磁头对磁盘的寻道是可以进行顺序访问和随机访问的,因为寻道延时时间的关系,顺序访问时IO的吞吐量更大,随机访问的吞吐量小。如果我们想为一个机械硬盘进行吞吐量优化的话,那么就可以让调度器按照尽量复合顺序访问的IO请求进行排序,之后请求以这样的顺序发送给硬盘,就可以使IO的吞吐量更大。但是这样做也有另一个问题,就是如果此时出现了一个请求,它要访问的磁道离目前磁头所在磁道很远,应用的请求又大量集中在目前磁道附近。导致大量请求一直会被合并和插队处理,而那个要访问比较远磁道的请求将因为一直不能被调度而饿死。deadline就是这样一种调度器,能在保证IO最大吞吐量的情况下,尽量使远端请求在一个期限内被调度而不被饿死的调度器。
2023-08-17 23:29:241

如何提高Linux下块设备IO的整体性能

前言:本文主要讲解Linux IO调度层的三种模式:cfp、deadline和noop,并给出各自的优化和适用场景建议。IO调度发生在Linux内核的IO调度层。这个层次是针对Linux的整体IO层次体系来说的。从read()或者write()系统调用的角度来说,Linux整体IO体系可以分为七层,它们分别是:VFS层: 虚拟文件系统层。由于内核要跟多种文件系统打交道,而每一种文件系统所实现的数据结构和相关方法都可能不尽相同,所以,内核抽象了这一层,专门用来适配各种文件系统,并对外提供统一操作接口。文件系统层: 不同的文件系统实现自己的操作过程,提供自己特有的特征,具体不多说了,大家愿意的话自己去看代码即可。页缓存层: 负责真对page的缓存。通用块层: 由于绝大多数情况的io操作是跟块设备打交道,所以Linux在此提供了一个类似vfs层的块设备操作抽象层。下层对接各种不同属性的块设备,对上提供统一的Block IO请求标准。IO调度层 :因为绝大多数的块设备都是类似磁盘这样的设备,所以有必要根据这类设备的特点以及应用的不同特点来设置一些不同的调度算法和队列。以便在不同的应用环境下有针对性的提高磁盘的读写效率,这里就是大名鼎鼎的Linux电梯所起作用的地方。针对机械硬盘的各种调度方法就是在这实现的。块设备驱动层: 驱动层对外提供相对比较高级的设备操作接口,往往是C语言的,而下层对接设备本身的操作方法和规范。块设备层: 这层就是具体的物理设备了,定义了各种真对设备操作方法和规范。有一个已经整理好的[Linux IO结构图],非常经典,一图胜千言:我们今天要研究的内容主要在IO调度这一层。它要解决的核心问题是,如何提高块设备IO的整体性能?这一层也主要是针对机械硬盘结构而设计的。众所周知,机械硬盘的存储介质是磁盘,磁头在盘片上移动进行磁道寻址,行为类似播放一张唱片。这种结构的特点是,顺序访问时吞吐量较高,但是如果一旦对盘片有随机访问,那么大量的时间都会浪费在磁头的移动上,这时候就会导致每次IO的响应时间变长,极大的降低IO的响应速度。磁头在盘片上寻道的操作,类似电梯调度,实际上在最开始的时期,Linux把这个算法命名为Linux电梯算法,即:如果在寻道的过程中,能把顺序路过的相关磁道的数据请求都“顺便”处理掉,那么就可以在比较小影响响应速度的前提下,提高整体IO的吞吐量。这就是我们为什么要设计IO调度算法的原因。目前在内核中默认开启了三种算法/模式:noop,cfq和deadline。严格算应该是两种:因为第一种叫做noop,就是空操作调度算法,也就是没有任何调度操作,并不对io请求进行排序,仅仅做适当的io合并的一个fifo队列。目前内核中默认的调度算法应该是cfq,叫做完全公平队列调度。这个调度算法人如其名,它试图给所有进程提供一个完全公平的IO操作环境。注:请大家一定记住这个词语,cfq,完全公平队列调度,不然下文就没法看了。cfq为每个进程创建一个同步IO调度队列,并默认以时间片和请求数限定的方式分配IO资源,以此保证每个进程的IO资源占用是公平的,cfq还实现了针对进程级别的优先级调度,这个我们后面会详细解释。查看和修改IO调度算法的方法是:cfq是通用服务器比较好的IO调度算法选择,对桌面用户也是比较好的选择。但是对于很多IO压力较大的场景就并不是很适应,尤其是IO压力集中在某些进程上的场景。因为这种场景我们需要更多的满足某个或者某几个进程的IO响应速度,而不是让所有的进程公平的使用IO,比如数据库应用。deadline调度(最终期限调度)就是更适合上述场景的解决方案。deadline实现了四个队列:其中两个分别处理正常read和write,按扇区号排序,进行正常io的合并处理以提高吞吐量。因为IO请求可能会集中在某些磁盘位置,这样会导致新来的请求一直被合并,可能会有其他磁盘位置的io请求被饿死。另外两个处理超时read和write的队列,按请求创建时间排序,如果有超时的请求出现,就放进这两个队列,调度算法保证超时(达到最终期限时间)的队列中的请求会优先被处理,防止请求被饿死。不久前,内核还是默认标配四种算法,还有一种叫做as的算法(Anticipatory scheduler),预测调度算法。一个高大上的名字,搞得我一度认为Linux内核都会算命了。结果发现,无非是在基于deadline算法做io调度的之前等一小会时间,如果这段时间内有可以合并的io请求到来,就可以合并处理,提高deadline调度的在顺序读写情况下的数据吞吐量。其实这根本不是啥预测,我觉得不如叫撞大运调度算法,当然这种策略在某些特定场景差效果不错。但是在大多数场景下,这个调度不仅没有提高吞吐量,还降低了响应速度,所以内核干脆把它从默认配置里删除了。毕竟Linux的宗旨是实用,而我们也就不再这个调度算法上多费口舌了。1、cfq:完全公平队列调度cfq是内核默认选择的IO调度队列,它在桌面应用场景以及大多数常见应用场景下都是很好的选择。如何实现一个所谓的完全公平队列(Completely Fair Queueing)?首先我们要理解所谓的公平是对谁的公平?从操作系统的角度来说,产生操作行为的主体都是进程,所以这里的公平是针对每个进程而言的,我们要试图让进程可以公平的占用IO资源。那么如何让进程公平的占用IO资源?我们需要先理解什么是IO资源。当我们衡量一个IO资源的时候,一般喜欢用的是两个单位,一个是数据读写的带宽,另一个是数据读写的IOPS。带宽就是以时间为单位的读写数据量,比如,100Mbyte/s。而IOPS是以时间为单位的读写次数。在不同的读写情境下,这两个单位的表现可能不一样,但是可以确定的是,两个单位的任何一个达到了性能上限,都会成为IO的瓶颈。从机械硬盘的结构考虑,如果读写是顺序读写,那么IO的表现是可以通过比较少的IOPS达到较大的带宽,因为可以合并很多IO,也可以通过预读等方式加速数据读取效率。当IO的表现是偏向于随机读写的时候,那么IOPS就会变得更大,IO的请求的合并可能性下降,当每次io请求数据越少的时候,带宽表现就会越低。从这里我们可以理解,针对进程的IO资源的主要表现形式有两个: 进程在单位时间内提交的IO请求个数和进程占用IO的带宽。其实无论哪个,都是跟进程分配的IO处理时间长度紧密相关的。有时业务可以在较少IOPS的情况下占用较大带宽,另外一些则可能在较大IOPS的情况下占用较少带宽,所以对进程占用IO的时间进行调度才是相对最公平的。即,我不管你是IOPS高还是带宽占用高,到了时间咱就换下一个进程处理,你爱咋样咋样。所以,cfq就是试图给所有进程分配等同的块设备使用的时间片,进程在时间片内,可以将产生的IO请求提交给块设备进行处理,时间片结束,进程的请求将排进它自己的队列,等待下次调度的时候进行处理。这就是cfq的基本原理。当然,现实生活中不可能有真正的“公平”,常见的应用场景下,我们很肯能需要人为的对进程的IO占用进行人为指定优先级,这就像对进程的CPU占用设置优先级的概念一样。所以,除了针对时间片进行公平队列调度外,cfq还提供了优先级支持。每个进程都可以设置一个IO优先级,cfq会根据这个优先级的设置情况作为调度时的重要参考因素。优先级首先分成三大类:RT、BE、IDLE,它们分别是实时(Real Time)、最佳效果(Best Try)和闲置(Idle)三个类别,对每个类别的IO,cfq都使用不同的策略进行处理。另外,RT和BE类别中,分别又再划分了8个子优先级实现更细节的QOS需求,而IDLE只有一个子优先级。另外,我们都知道内核默认对存储的读写都是经过缓存(buffer/cache)的,在这种情况下,cfq是无法区分当前处理的请求是来自哪一个进程的。只有在进程使用同步方式(sync read或者sync wirte)或者直接IO(Direct IO)方式进行读写的时候,cfq才能区分出IO请求来自哪个进程。所以,除了针对每个进程实现的IO队列以外,还实现了一个公共的队列用来处理异步请求。当前内核已经实现了针对IO资源的cgroup资源隔离,所以在以上体系的基础上,cfq也实现了针对cgroup的调度支持。总的来说,cfq用了一系列的数据结构实现了以上所有复杂功能的支持,大家可以通过源代码看到其相关实现,文件在源代码目录下的block/cfq-iosched.c。1.1 cfq设计原理在此,我们对整体数据结构做一个简要描述:首先,cfq通过一个叫做cfq_data的数据结构维护了整个调度器流程。在一个支持了cgroup功能的cfq中,全部进程被分成了若干个contral group进行管理。每个cgroup在cfq中都有一个cfq_group的结构进行描述,所有的cgroup都被作为一个调度对象放进一个红黑树中,并以vdisktime为key进行排序。vdisktime这个时间纪录的是当前cgroup所占用的io时间,每次对cgroup进行调度时,总是通过红黑树选择当前vdisktime时间最少的cgroup进行处理,以保证所有cgroups之间的IO资源占用“公平”。当然我们知道,cgroup是可以对blkio进行资源比例分配的,其作用原理就是,分配比例大的cgroup占用vdisktime时间增长较慢,分配比例小的vdisktime时间增长较快,快慢与分配比例成正比。这样就做到了不同的cgroup分配的IO比例不一样,并且在cfq的角度看来依然是“公平“的。选择好了需要处理的cgroup(cfq_group)之后,调度器需要决策选择下一步的service_tree。service_tree这个数据结构对应的都是一系列的红黑树,主要目的是用来实现请求优先级分类的,就是RT、BE、IDLE的分类。每一个cfq_group都维护了7个service_trees,其定义如下:其中service_tree_idle就是用来给IDLE类型的请求进行排队用的红黑树。而上面二维数组,首先第一个维度针对RT和BE分别各实现了一个数组,每一个数组中都维护了三个红黑树,分别对应三种不同子类型的请求,分别是:SYNC、SYNC_NOIDLE以及ASYNC。我们可以认为SYNC相当于SYNC_IDLE并与SYNC_NOIDLE对应。idling是cfq在设计上为了尽量合并连续的IO请求以达到提高吞吐量的目的而加入的机制,我们可以理解为是一种“空转”等待机制。空转是指,当一个队列处理一个请求结束后,会在发生调度之前空等一小会时间,如果下一个请求到来,则可以减少磁头寻址,继续处理顺序的IO请求。为了实现这个功能,cfq在service_tree这层数据结构这实现了SYNC队列,如果请求是同步顺序请求,就入队这个service tree,如果请求是同步随机请求,则入队SYNC_NOIDLE队列,以判断下一个请求是否是顺序请求。所有的异步写操作请求将入队ASYNC的service tree,并且针对这个队列没有空转等待机制。此外,cfq还对SSD这样的硬盘有特殊调整,当cfq发现存储设备是一个ssd硬盘这样的队列深度更大的设备时,所有针对单独队列的空转都将不生效,所有的IO请求都将入队SYNC_NOIDLE这个service tree。每一个service tree都对应了若干个cfq_queue队列,每个cfq_queue队列对应一个进程,这个我们后续再详细说明。cfq_group还维护了一个在cgroup内部所有进程公用的异步IO请求队列,其结构如下:异步请求也分成了RT、BE、IDLE这三类进行处理,每一类对应一个cfq_queue进行排队。BE和RT也实现了优先级的支持,每一个类型有IOPRIO_BE_NR这么多个优先级,这个值定义为8,数组下标为0-7。我们目前分析的内核代码版本为Linux 4.4,可以看出,从cfq的角度来说,已经可以实现异步IO的cgroup支持了,我们需要定义一下这里所谓异步IO的含义,它仅仅表示从内存的buffer/cache中的数据同步到硬盘的IO请求,而不是aio(man 7 aio)或者linux的native异步io以及libaio机制,实际上这些所谓的“异步”IO机制,在内核中都是同步实现的(本质上冯诺伊曼计算机没有真正的“异步”机制)。我们在上面已经说明过,由于进程正常情况下都是将数据先写入buffer/cache,所以这种异步IO都是统一由cfq_group中的async请求队列处理的。那么为什么在上面的service_tree中还要实现和一个ASYNC的类型呢?这当然是为了支持区分进程的异步IO并使之可以“完全公平”做准备喽。实际上在最新的cgroup v2的blkio体系中,内核已经支持了针对buffer IO的cgroup限速支持,而以上这些可能容易混淆的一堆类型,都是在新的体系下需要用到的类型标记。新体系的复杂度更高了,功能也更加强大,但是大家先不要着急,正式的cgroup v2体系,在Linux 4.5发布的时候会正式跟大家见面。我们继续选择service_tree的过程,三种优先级类型的service_tree的选择就是根据类型的优先级来做选择的,RT优先级最高,BE其次,IDLE最低。就是说,RT里有,就会一直处理RT,RT没了再处理BE。每个service_tree对应一个元素为cfq_queue排队的红黑树,而每个cfq_queue就是内核为进程(线程)创建的请求队列。每一个cfq_queue都会维护一个rb_key的变量,这个变量实际上就是这个队列的IO服务时间(service time)。这里还是通过红黑树找到service time时间最短的那个cfq_queue进行服务,以保证“完全公平”。选择好了cfq_queue之后,就要开始处理这个队列里的IO请求了。这里的调度方式基本跟deadline类似。cfq_queue会对进入队列的每一个请求进行两次入队,一个放进fifo中,另一个放进按访问扇区顺序作为key的红黑树中。默认从红黑树中取请求进行处理,当请求的延时时间达到deadline时,就从红黑树中取等待时间最长的进行处理,以保证请求不被饿死。这就是整个cfq的调度流程,当然其中还有很多细枝末节没有交代,比如合并处理以及顺序处理等等。1.2 cfq的参数调整理解整个调度流程有助于我们决策如何调整cfq的相关参数。所有cfq的可调参数都可以在/sys/class/block/sda/queue/iosched/目录下找到,当然,在你的系统上,请将sda替换为相应的磁盘名称。我们来看一下都有什么:这些参数部分是跟机械硬盘磁头寻道方式有关的,如果其说明你看不懂,请先补充相关知识:back_seek_max:磁头可以向后寻址的最大范围,默认值为16M。back_seek_penalty:向后寻址的惩罚系数。这个值是跟向前寻址进行比较的。以上两个是为了防止磁头寻道发生抖动而导致寻址过慢而设置的。基本思路是这样,一个io请求到来的时候,cfq会根据其寻址位置预估一下其磁头寻道成本。设置一个最大值back_seek_max,对于请求所访问的扇区号在磁头后方的请求,只要寻址范围没有超过这个值,cfq会像向前寻址的请求一样处理它。再设置一个评估成本的系数back_seek_penalty,相对于磁头向前寻址,向后寻址的距离为1/2(1/back_seek_penalty)时,cfq认为这两个请求寻址的代价是相同。这两个参数实际上是cfq判断请求合并处理的条件限制,凡事复合这个条件的请求,都会尽量在本次请求处理的时候一起合并处理。fifo_expire_async:设置异步请求的超时时间。同步请求和异步请求是区分不同队列处理的,cfq在调度的时候一般情况都会优先处理同步请求,之后再处理异步请求,除非异步请求符合上述合并处理的条件限制范围内。当本进程的队列被调度时,cfq会优先检查是否有异步请求超时,就是超过fifo_expire_async参数的限制。如果有,则优先发送一个超时的请求,其余请求仍然按照优先级以及扇区编号大小来处理。fifo_expire_sync:这个参数跟上面的类似,区别是用来设置同步请求的超时时间。slice_idle:参数设置了一个等待时间。这让cfq在切换cfq_queue或service tree的时候等待一段时间,目的是提高机械硬盘的吞吐量。一般情况下,来自同一个cfq_queue或者service tree的IO请求的寻址局部性更好,所以这样可以减少磁盘的寻址次数。这个值在机械硬盘上默认为非零。当然在固态硬盘或者硬RAID设备上设置这个值为非零会降低存储的效率,因为固态硬盘没有磁头寻址这个概念,所以在这样的设备上应该设置为0,关闭此功能。group_idle:这个参数也跟上一个参数类似,区别是当cfq要切换cfq_group的时候会等待一段时间。在cgroup的场景下,如果我们沿用slice_idle的方式,那么空转等待可能会在cgroup组内每个进程的cfq_queue切换时发生。这样会如果这个进程一直有请求要处理的话,那么直到这个cgroup的配额被耗尽,同组中的其它进程也可能无法被调度到。这样会导致同组中的其它进程饿死而产生IO性能瓶颈。在这种情况下,我们可以将slice_idle = 0而group_idle = 8。这样空转等待就是以cgroup为单位进行的,而不是以cfq_queue的进程为单位进行,以防止上述问题产生。low_latency:这个是用来开启或关闭cfq的低延时(low latency)模式的开关。当这个开关打开时,cfq将会根据target_latency的参数设置来对每一个进程的分片时间(slice time)进行重新计算。这将有利于对吞吐量的公平(默认是对时间片分配的公平)。关闭这个参数(设置为0)将忽略target_latency的值。这将使系统中的进程完全按照时间片方式进行IO资源分配。这个开关默认是打开的。我们已经知道cfq设计上有“空转”(idling)这个概念,目的是为了可以让连续的读写操作尽可能多的合并处理,减少磁头的寻址操作以便增大吞吐量。如果有进程总是很快的进行顺序读写,那么它将因为cfq的空转等待命中率很高而导致其它需要处理IO的进程响应速度下降,如果另一个需要调度的进程不会发出大量顺序IO行为的话,系统中不同进程IO吞吐量的表现就会很不均衡。就比如,系统内存的cache中有很多脏页要写回时,桌面又要打开一个浏览器进行操作,这时脏页写回的后台行为就很可能会大量命中空转时间,而导致浏览器的小量IO一直等待,让用户感觉浏览器运行响应速度变慢。这个low_latency主要是对这种情况进行优化的选项,当其打开时,系统会根据target_latency的配置对因为命中空转而大量占用IO吞吐量的进程进行限制,以达到不同进程IO占用的吞吐量的相对均衡。这个开关比较合适在类似桌面应用的场景下打开。target_latency:当low_latency的值为开启状态时,cfq将根据这个值重新计算每个进程分配的IO时间片长度。quantum:这个参数用来设置每次从cfq_queue中处理多少个IO请求。在一个队列处理事件周期中,超过这个数字的IO请求将不会被处理。这个参数只对同步的请求有效。slice_sync:当一个cfq_queue队列被调度处理时,它可以被分配的处理总时间是通过这个值来作为一个计算参数指定的。公式为:time_slice = slice_sync + (slice_sync/5 * (4 - prio))。这个参数对同步请求有效。slice_async:这个值跟上一个类似,区别是对异步请求有效。slice_async_rq:这个参数用来限制在一个slice的时间范围内,一个队列最多可以处理的异步请求个数。请求被处理的最大个数还跟相关进程被设置的io优先级有关。1.3 cfq的IOPS模式我们已经知道,默认情况下cfq是以时间片方式支持的带优先级的调度来保证IO资源占用的公平。高优先级的进程将得到更多的时间片长度,而低优先级的进程时间片相对较小。当我们的存储是一个高速并且支持NCQ(原生指令队列)的设备的时候,我们最好可以让其可以从多个cfq队列中处理多路的请求,以便提升NCQ的利用率。此时使用时间片的分配方式分配资源就显得不合时宜了,因为基于时间片的分配,同一时刻最多能处理的请求队列只有一个。这时,我们需要切换cfq的模式为IOPS模式。切换方式很简单,就是将slice_idle=0即可。内核会自动检测你的存储设备是否支持NCQ,如果支持的话cfq会自动切换为IOPS模式。另外,在默认的基于优先级的时间片方式下,我们可以使用ionice命令来调整进程的IO优先级。进程默认分配的IO优先级是根据进程的nice值计算而来的,计算方法可以在man ionice中看到,这里不再废话。2、deadline:最终期限调度deadline调度算法相对cfq要简单很多。其设计目标是:在保证请求按照设备扇区的顺序进行访问的同时,兼顾其它请求不被饿死,要在一个最终期限前被调度到。我们知道磁头对磁盘的寻道是可以进行顺序访问和随机访问的,因为寻道延时时间的关系,顺序访问时IO的吞吐量更大,随机访问的吞吐量小。如果我们想为一个机械硬盘进行吞吐量优化的话,那么就可以让调度器按照尽量复合顺序访问的IO请求进行排序,之后请求以这样的顺序发送给硬盘,就可以使IO的吞吐量更大。但是这样做也有另一个问题,就是如果此时出现了一个请求,它要访问的磁道离目前磁头所在磁道很远,应用的请求又大量集中在目前磁道附近。导致大量请求一直会被合并和插队处理,而那个要访问比较远磁道的请求将因为一直不能被调度而饿死。deadline就是这样一种调度器,能在保证IO最大吞吐量的情况下,尽量使远端请求在一个期限内被调度而不被饿死的调度器。
2023-08-17 23:29:331

如何提高Linux下块设备IO的整体性能

前言:本文主要讲解Linux IO调度层的三种模式:cfp、deadline和noop,并给出各自的优化和适用场景建议。IO调度发生在Linux内核的IO调度层。这个层次是针对Linux的整体IO层次体系来说的。从read()或者write()系统调用的角度来说,Linux整体IO体系可以分为七层,它们分别是:VFS层: 虚拟文件系统层。由于内核要跟多种文件系统打交道,而每一种文件系统所实现的数据结构和相关方法都可能不尽相同,所以,内核抽象了这一层,专门用来适配各种文件系统,并对外提供统一操作接口。文件系统层: 不同的文件系统实现自己的操作过程,提供自己特有的特征,具体不多说了,大家愿意的话自己去看代码即可。页缓存层: 负责真对page的缓存。通用块层: 由于绝大多数情况的io操作是跟块设备打交道,所以Linux在此提供了一个类似vfs层的块设备操作抽象层。下层对接各种不同属性的块设备,对上提供统一的Block IO请求标准。IO调度层 :因为绝大多数的块设备都是类似磁盘这样的设备,所以有必要根据这类设备的特点以及应用的不同特点来设置一些不同的调度算法和队列。以便在不同的应用环境下有针对性的提高磁盘的读写效率,这里就是大名鼎鼎的Linux电梯所起作用的地方。针对机械硬盘的各种调度方法就是在这实现的。块设备驱动层: 驱动层对外提供相对比较高级的设备操作接口,往往是C语言的,而下层对接设备本身的操作方法和规范。块设备层: 这层就是具体的物理设备了,定义了各种真对设备操作方法和规范。有一个已经整理好的[Linux IO结构图],非常经典,一图胜千言:我们今天要研究的内容主要在IO调度这一层。它要解决的核心问题是,如何提高块设备IO的整体性能?这一层也主要是针对机械硬盘结构而设计的。众所周知,机械硬盘的存储介质是磁盘,磁头在盘片上移动进行磁道寻址,行为类似播放一张唱片。这种结构的特点是,顺序访问时吞吐量较高,但是如果一旦对盘片有随机访问,那么大量的时间都会浪费在磁头的移动上,这时候就会导致每次IO的响应时间变长,极大的降低IO的响应速度。磁头在盘片上寻道的操作,类似电梯调度,实际上在最开始的时期,Linux把这个算法命名为Linux电梯算法,即:如果在寻道的过程中,能把顺序路过的相关磁道的数据请求都“顺便”处理掉,那么就可以在比较小影响响应速度的前提下,提高整体IO的吞吐量。这就是我们为什么要设计IO调度算法的原因。目前在内核中默认开启了三种算法/模式:noop,cfq和deadline。严格算应该是两种:因为第一种叫做noop,就是空操作调度算法,也就是没有任何调度操作,并不对io请求进行排序,仅仅做适当的io合并的一个fifo队列。目前内核中默认的调度算法应该是cfq,叫做完全公平队列调度。这个调度算法人如其名,它试图给所有进程提供一个完全公平的IO操作环境。注:请大家一定记住这个词语,cfq,完全公平队列调度,不然下文就没法看了。cfq为每个进程创建一个同步IO调度队列,并默认以时间片和请求数限定的方式分配IO资源,以此保证每个进程的IO资源占用是公平的,cfq还实现了针对进程级别的优先级调度,这个我们后面会详细解释。查看和修改IO调度算法的方法是:cfq是通用服务器比较好的IO调度算法选择,对桌面用户也是比较好的选择。但是对于很多IO压力较大的场景就并不是很适应,尤其是IO压力集中在某些进程上的场景。因为这种场景我们需要更多的满足某个或者某几个进程的IO响应速度,而不是让所有的进程公平的使用IO,比如数据库应用。deadline调度(最终期限调度)就是更适合上述场景的解决方案。deadline实现了四个队列:其中两个分别处理正常read和write,按扇区号排序,进行正常io的合并处理以提高吞吐量。因为IO请求可能会集中在某些磁盘位置,这样会导致新来的请求一直被合并,可能会有其他磁盘位置的io请求被饿死。另外两个处理超时read和write的队列,按请求创建时间排序,如果有超时的请求出现,就放进这两个队列,调度算法保证超时(达到最终期限时间)的队列中的请求会优先被处理,防止请求被饿死。不久前,内核还是默认标配四种算法,还有一种叫做as的算法(Anticipatory scheduler),预测调度算法。一个高大上的名字,搞得我一度认为Linux内核都会算命了。结果发现,无非是在基于deadline算法做io调度的之前等一小会时间,如果这段时间内有可以合并的io请求到来,就可以合并处理,提高deadline调度的在顺序读写情况下的数据吞吐量。其实这根本不是啥预测,我觉得不如叫撞大运调度算法,当然这种策略在某些特定场景差效果不错。但是在大多数场景下,这个调度不仅没有提高吞吐量,还降低了响应速度,所以内核干脆把它从默认配置里删除了。毕竟Linux的宗旨是实用,而我们也就不再这个调度算法上多费口舌了。1、cfq:完全公平队列调度cfq是内核默认选择的IO调度队列,它在桌面应用场景以及大多数常见应用场景下都是很好的选择。如何实现一个所谓的完全公平队列(Completely Fair Queueing)?首先我们要理解所谓的公平是对谁的公平?从操作系统的角度来说,产生操作行为的主体都是进程,所以这里的公平是针对每个进程而言的,我们要试图让进程可以公平的占用IO资源。那么如何让进程公平的占用IO资源?我们需要先理解什么是IO资源。当我们衡量一个IO资源的时候,一般喜欢用的是两个单位,一个是数据读写的带宽,另一个是数据读写的IOPS。带宽就是以时间为单位的读写数据量,比如,100Mbyte/s。而IOPS是以时间为单位的读写次数。在不同的读写情境下,这两个单位的表现可能不一样,但是可以确定的是,两个单位的任何一个达到了性能上限,都会成为IO的瓶颈。从机械硬盘的结构考虑,如果读写是顺序读写,那么IO的表现是可以通过比较少的IOPS达到较大的带宽,因为可以合并很多IO,也可以通过预读等方式加速数据读取效率。当IO的表现是偏向于随机读写的时候,那么IOPS就会变得更大,IO的请求的合并可能性下降,当每次io请求数据越少的时候,带宽表现就会越低。从这里我们可以理解,针对进程的IO资源的主要表现形式有两个: 进程在单位时间内提交的IO请求个数和进程占用IO的带宽。其实无论哪个,都是跟进程分配的IO处理时间长度紧密相关的。有时业务可以在较少IOPS的情况下占用较大带宽,另外一些则可能在较大IOPS的情况下占用较少带宽,所以对进程占用IO的时间进行调度才是相对最公平的。即,我不管你是IOPS高还是带宽占用高,到了时间咱就换下一个进程处理,你爱咋样咋样。所以,cfq就是试图给所有进程分配等同的块设备使用的时间片,进程在时间片内,可以将产生的IO请求提交给块设备进行处理,时间片结束,进程的请求将排进它自己的队列,等待下次调度的时候进行处理。这就是cfq的基本原理。当然,现实生活中不可能有真正的“公平”,常见的应用场景下,我们很肯能需要人为的对进程的IO占用进行人为指定优先级,这就像对进程的CPU占用设置优先级的概念一样。所以,除了针对时间片进行公平队列调度外,cfq还提供了优先级支持。每个进程都可以设置一个IO优先级,cfq会根据这个优先级的设置情况作为调度时的重要参考因素。优先级首先分成三大类:RT、BE、IDLE,它们分别是实时(Real Time)、最佳效果(Best Try)和闲置(Idle)三个类别,对每个类别的IO,cfq都使用不同的策略进行处理。另外,RT和BE类别中,分别又再划分了8个子优先级实现更细节的QOS需求,而IDLE只有一个子优先级。另外,我们都知道内核默认对存储的读写都是经过缓存(buffer/cache)的,在这种情况下,cfq是无法区分当前处理的请求是来自哪一个进程的。只有在进程使用同步方式(sync read或者sync wirte)或者直接IO(Direct IO)方式进行读写的时候,cfq才能区分出IO请求来自哪个进程。所以,除了针对每个进程实现的IO队列以外,还实现了一个公共的队列用来处理异步请求。当前内核已经实现了针对IO资源的cgroup资源隔离,所以在以上体系的基础上,cfq也实现了针对cgroup的调度支持。总的来说,cfq用了一系列的数据结构实现了以上所有复杂功能的支持,大家可以通过源代码看到其相关实现,文件在源代码目录下的block/cfq-iosched.c。1.1 cfq设计原理在此,我们对整体数据结构做一个简要描述:首先,cfq通过一个叫做cfq_data的数据结构维护了整个调度器流程。在一个支持了cgroup功能的cfq中,全部进程被分成了若干个contral group进行管理。每个cgroup在cfq中都有一个cfq_group的结构进行描述,所有的cgroup都被作为一个调度对象放进一个红黑树中,并以vdisktime为key进行排序。vdisktime这个时间纪录的是当前cgroup所占用的io时间,每次对cgroup进行调度时,总是通过红黑树选择当前vdisktime时间最少的cgroup进行处理,以保证所有cgroups之间的IO资源占用“公平”。当然我们知道,cgroup是可以对blkio进行资源比例分配的,其作用原理就是,分配比例大的cgroup占用vdisktime时间增长较慢,分配比例小的vdisktime时间增长较快,快慢与分配比例成正比。这样就做到了不同的cgroup分配的IO比例不一样,并且在cfq的角度看来依然是“公平“的。选择好了需要处理的cgroup(cfq_group)之后,调度器需要决策选择下一步的service_tree。service_tree这个数据结构对应的都是一系列的红黑树,主要目的是用来实现请求优先级分类的,就是RT、BE、IDLE的分类。每一个cfq_group都维护了7个service_trees,其定义如下:其中service_tree_idle就是用来给IDLE类型的请求进行排队用的红黑树。而上面二维数组,首先第一个维度针对RT和BE分别各实现了一个数组,每一个数组中都维护了三个红黑树,分别对应三种不同子类型的请求,分别是:SYNC、SYNC_NOIDLE以及ASYNC。我们可以认为SYNC相当于SYNC_IDLE并与SYNC_NOIDLE对应。idling是cfq在设计上为了尽量合并连续的IO请求以达到提高吞吐量的目的而加入的机制,我们可以理解为是一种“空转”等待机制。空转是指,当一个队列处理一个请求结束后,会在发生调度之前空等一小会时间,如果下一个请求到来,则可以减少磁头寻址,继续处理顺序的IO请求。为了实现这个功能,cfq在service_tree这层数据结构这实现了SYNC队列,如果请求是同步顺序请求,就入队这个service tree,如果请求是同步随机请求,则入队SYNC_NOIDLE队列,以判断下一个请求是否是顺序请求。所有的异步写操作请求将入队ASYNC的service tree,并且针对这个队列没有空转等待机制。此外,cfq还对SSD这样的硬盘有特殊调整,当cfq发现存储设备是一个ssd硬盘这样的队列深度更大的设备时,所有针对单独队列的空转都将不生效,所有的IO请求都将入队SYNC_NOIDLE这个service tree。每一个service tree都对应了若干个cfq_queue队列,每个cfq_queue队列对应一个进程,这个我们后续再详细说明。cfq_group还维护了一个在cgroup内部所有进程公用的异步IO请求队列,其结构如下:异步请求也分成了RT、BE、IDLE这三类进行处理,每一类对应一个cfq_queue进行排队。BE和RT也实现了优先级的支持,每一个类型有IOPRIO_BE_NR这么多个优先级,这个值定义为8,数组下标为0-7。我们目前分析的内核代码版本为Linux 4.4,可以看出,从cfq的角度来说,已经可以实现异步IO的cgroup支持了,我们需要定义一下这里所谓异步IO的含义,它仅仅表示从内存的buffer/cache中的数据同步到硬盘的IO请求,而不是aio(man 7 aio)或者linux的native异步io以及libaio机制,实际上这些所谓的“异步”IO机制,在内核中都是同步实现的(本质上冯诺伊曼计算机没有真正的“异步”机制)。我们在上面已经说明过,由于进程正常情况下都是将数据先写入buffer/cache,所以这种异步IO都是统一由cfq_group中的async请求队列处理的。那么为什么在上面的service_tree中还要实现和一个ASYNC的类型呢?这当然是为了支持区分进程的异步IO并使之可以“完全公平”做准备喽。实际上在最新的cgroup v2的blkio体系中,内核已经支持了针对buffer IO的cgroup限速支持,而以上这些可能容易混淆的一堆类型,都是在新的体系下需要用到的类型标记。新体系的复杂度更高了,功能也更加强大,但是大家先不要着急,正式的cgroup v2体系,在Linux 4.5发布的时候会正式跟大家见面。我们继续选择service_tree的过程,三种优先级类型的service_tree的选择就是根据类型的优先级来做选择的,RT优先级最高,BE其次,IDLE最低。就是说,RT里有,就会一直处理RT,RT没了再处理BE。每个service_tree对应一个元素为cfq_queue排队的红黑树,而每个cfq_queue就是内核为进程(线程)创建的请求队列。每一个cfq_queue都会维护一个rb_key的变量,这个变量实际上就是这个队列的IO服务时间(service time)。这里还是通过红黑树找到service time时间最短的那个cfq_queue进行服务,以保证“完全公平”。选择好了cfq_queue之后,就要开始处理这个队列里的IO请求了。这里的调度方式基本跟deadline类似。cfq_queue会对进入队列的每一个请求进行两次入队,一个放进fifo中,另一个放进按访问扇区顺序作为key的红黑树中。默认从红黑树中取请求进行处理,当请求的延时时间达到deadline时,就从红黑树中取等待时间最长的进行处理,以保证请求不被饿死。这就是整个cfq的调度流程,当然其中还有很多细枝末节没有交代,比如合并处理以及顺序处理等等。1.2 cfq的参数调整理解整个调度流程有助于我们决策如何调整cfq的相关参数。所有cfq的可调参数都可以在/sys/class/block/sda/queue/iosched/目录下找到,当然,在你的系统上,请将sda替换为相应的磁盘名称。我们来看一下都有什么:这些参数部分是跟机械硬盘磁头寻道方式有关的,如果其说明你看不懂,请先补充相关知识:back_seek_max:磁头可以向后寻址的最大范围,默认值为16M。back_seek_penalty:向后寻址的惩罚系数。这个值是跟向前寻址进行比较的。以上两个是为了防止磁头寻道发生抖动而导致寻址过慢而设置的。基本思路是这样,一个io请求到来的时候,cfq会根据其寻址位置预估一下其磁头寻道成本。设置一个最大值back_seek_max,对于请求所访问的扇区号在磁头后方的请求,只要寻址范围没有超过这个值,cfq会像向前寻址的请求一样处理它。再设置一个评估成本的系数back_seek_penalty,相对于磁头向前寻址,向后寻址的距离为1/2(1/back_seek_penalty)时,cfq认为这两个请求寻址的代价是相同。这两个参数实际上是cfq判断请求合并处理的条件限制,凡事复合这个条件的请求,都会尽量在本次请求处理的时候一起合并处理。fifo_expire_async:设置异步请求的超时时间。同步请求和异步请求是区分不同队列处理的,cfq在调度的时候一般情况都会优先处理同步请求,之后再处理异步请求,除非异步请求符合上述合并处理的条件限制范围内。当本进程的队列被调度时,cfq会优先检查是否有异步请求超时,就是超过fifo_expire_async参数的限制。如果有,则优先发送一个超时的请求,其余请求仍然按照优先级以及扇区编号大小来处理。fifo_expire_sync:这个参数跟上面的类似,区别是用来设置同步请求的超时时间。slice_idle:参数设置了一个等待时间。这让cfq在切换cfq_queue或service tree的时候等待一段时间,目的是提高机械硬盘的吞吐量。一般情况下,来自同一个cfq_queue或者service tree的IO请求的寻址局部性更好,所以这样可以减少磁盘的寻址次数。这个值在机械硬盘上默认为非零。当然在固态硬盘或者硬RAID设备上设置这个值为非零会降低存储的效率,因为固态硬盘没有磁头寻址这个概念,所以在这样的设备上应该设置为0,关闭此功能。group_idle:这个参数也跟上一个参数类似,区别是当cfq要切换cfq_group的时候会等待一段时间。在cgroup的场景下,如果我们沿用slice_idle的方式,那么空转等待可能会在cgroup组内每个进程的cfq_queue切换时发生。这样会如果这个进程一直有请求要处理的话,那么直到这个cgroup的配额被耗尽,同组中的其它进程也可能无法被调度到。这样会导致同组中的其它进程饿死而产生IO性能瓶颈。在这种情况下,我们可以将slice_idle = 0而group_idle = 8。这样空转等待就是以cgroup为单位进行的,而不是以cfq_queue的进程为单位进行,以防止上述问题产生。low_latency:这个是用来开启或关闭cfq的低延时(low latency)模式的开关。当这个开关打开时,cfq将会根据target_latency的参数设置来对每一个进程的分片时间(slice time)进行重新计算。这将有利于对吞吐量的公平(默认是对时间片分配的公平)。关闭这个参数(设置为0)将忽略target_latency的值。这将使系统中的进程完全按照时间片方式进行IO资源分配。这个开关默认是打开的。我们已经知道cfq设计上有“空转”(idling)这个概念,目的是为了可以让连续的读写操作尽可能多的合并处理,减少磁头的寻址操作以便增大吞吐量。如果有进程总是很快的进行顺序读写,那么它将因为cfq的空转等待命中率很高而导致其它需要处理IO的进程响应速度下降,如果另一个需要调度的进程不会发出大量顺序IO行为的话,系统中不同进程IO吞吐量的表现就会很不均衡。就比如,系统内存的cache中有很多脏页要写回时,桌面又要打开一个浏览器进行操作,这时脏页写回的后台行为就很可能会大量命中空转时间,而导致浏览器的小量IO一直等待,让用户感觉浏览器运行响应速度变慢。这个low_latency主要是对这种情况进行优化的选项,当其打开时,系统会根据target_latency的配置对因为命中空转而大量占用IO吞吐量的进程进行限制,以达到不同进程IO占用的吞吐量的相对均衡。这个开关比较合适在类似桌面应用的场景下打开。target_latency:当low_latency的值为开启状态时,cfq将根据这个值重新计算每个进程分配的IO时间片长度。quantum:这个参数用来设置每次从cfq_queue中处理多少个IO请求。在一个队列处理事件周期中,超过这个数字的IO请求将不会被处理。这个参数只对同步的请求有效。slice_sync:当一个cfq_queue队列被调度处理时,它可以被分配的处理总时间是通过这个值来作为一个计算参数指定的。公式为:time_slice = slice_sync + (slice_sync/5 * (4 - prio))。这个参数对同步请求有效。slice_async:这个值跟上一个类似,区别是对异步请求有效。slice_async_rq:这个参数用来限制在一个slice的时间范围内,一个队列最多可以处理的异步请求个数。请求被处理的最大个数还跟相关进程被设置的io优先级有关。1.3 cfq的IOPS模式我们已经知道,默认情况下cfq是以时间片方式支持的带优先级的调度来保证IO资源占用的公平。高优先级的进程将得到更多的时间片长度,而低优先级的进程时间片相对较小。当我们的存储是一个高速并且支持NCQ(原生指令队列)的设备的时候,我们最好可以让其可以从多个cfq队列中处理多路的请求,以便提升NCQ的利用率。此时使用时间片的分配方式分配资源就显得不合时宜了,因为基于时间片的分配,同一时刻最多能处理的请求队列只有一个。这时,我们需要切换cfq的模式为IOPS模式。切换方式很简单,就是将slice_idle=0即可。内核会自动检测你的存储设备是否支持NCQ,如果支持的话cfq会自动切换为IOPS模式。另外,在默认的基于优先级的时间片方式下,我们可以使用ionice命令来调整进程的IO优先级。进程默认分配的IO优先级是根据进程的nice值计算而来的,计算方法可以在man ionice中看到,这里不再废话。2、deadline:最终期限调度deadline调度算法相对cfq要简单很多。其设计目标是:在保证请求按照设备扇区的顺序进行访问的同时,兼顾其它请求不被饿死,要在一个最终期限前被调度到。我们知道磁头对磁盘的寻道是可以进行顺序访问和随机访问的,因为寻道延时时间的关系,顺序访问时IO的吞吐量更大,随机访问的吞吐量小。如果我们想为一个机械硬盘进行吞吐量优化的话,那么就可以让调度器按照尽量复合顺序访问的IO请求进行排序,之后请求以这样的顺序发送给硬盘,就可以使IO的吞吐量更大。但是这样做也有另一个问题,就是如果此时出现了一个请求,它要访问的磁道离目前磁头所在磁道很远,应用的请求又大量集中在目前磁道附近。导致大量请求一直会被合并和插队处理,而那个要访问比较远磁道的请求将因为一直不能被调度而饿死。deadline就是这样一种调度器,能在保证IO最大吞吐量的情况下,尽量使远端请求在一个期限内被调度而不被饿死的调度器。
2023-08-17 23:29:411

汽车怎么开可以漂移起来啊?

汽车产生漂移的原因归咎到底就是一种:后轮失去大部分(或者全部)抓地力,同时前轮要能保持抓地力(最多只能失去小部分,最好当然是获得额外的抓地力了),这时只要前轮有一定的横向力,车就甩尾,便会产生漂移。 令后轮失去抓地力的方法: 1.行驶中使后轮与地面间有负速度差(后轮速度相对低) 2.任何情况下使后轮与地面间有正速度差(后轮速度相对高) 3.行驶中减小后轮与地面之间的正压力。 这三项里面只要满足一项就够 实际上1,2都是减小摩擦系数的方法,将它们分开,是因为应用方法不同。 保持前轮抓地力的方法: 1.行驶中不使前轮与地面间有很大的速度差 2.行驶中不使前轮与地面间正压力减少太多,最好就是可以增大正压力。这两项要同时满足才行。 实际操作里面,拉手刹就一定同时满足行驶中使后轮与地面间有负速度差(后轮速度相对低) 行驶中不使前轮与地面间有很大的速度差; 见图:注意前轮的角度 漂移初状态的简单操作: 产生漂移的方法有: 1.直路行驶中拉起手刹之后打方向 2. 转弯中拉手刹 3. 直路行驶中猛踩刹车后打方向 4. 转弯中猛踩刹车 5.功率足够大的后驱车(或前后轮驱动力分配比例趋向于后驱车的四驱车)在速度不很高时猛踩油门并且打方向 其中3,4是利用重量转移(后轮重量转移到前轮上),是最少伤车的方法。 1,2只用于前驱车和拉力比赛用的四驱车,而且可免则免,除非你不怕弄坏车。 注意1和2,3和4分开, 是因为车的运动路线会有很大的不同。重要说明:漂移过弯和普通过弯一样,都有速度极限,而且漂移过弯的速度极限最多只可能比普通过弯高一点,在硬地上漂移过弯的速度极限比普通过弯还低! 至于最终能不能甩尾,跟轮胎与路面间的摩擦系数、车的速度、刹车力度、油门大小、前轮角度大小、车重分配、轮距轴距、悬挂软硬等多个因素有关。例如雨天、雪地上行车想甩尾很容易,想不甩尾反而难些;行车速度越高越容易甩尾(所以安全驾驶第一条就是不要开快车哦);打方向快,也容易甩尾(教我驾驶的师傅就叫我打方向盘不要太快哦);轮距轴距越小、车身越高,重量转移越厉害,越容易甩尾(也容易翻车!);前悬挂系统的防倾作用越弱,越容易甩尾。 有人提到多种漂移方式,实际上都在上面五种之内。 甩尾中的控制: 如果是用手刹产生漂移的,那么当车旋转到你所希望的角度后,就应该放开手刹了。 漂移的中途的任务就是要调整车身姿势。因为路面凹凸、路线弯曲程度、汽车的过弯特性等因素是会经常变化的。所以车手经常要控制方向盘、油门、刹车、甚至离合器(不推荐),以让汽车按照车手所希望的路线行驶。 先说明一点原理:要让车轮滑动距离长,就应尽量减小车轮与地面间的摩擦力;要让车轮少滑动,就应尽量增大摩擦力。减小摩擦力的方法前面说过,一个是让车轮太快或太慢地转动,一个是减小车轮与地面间正压力;增大摩擦力的方法就是相反了。 其中,让车轮太慢转动的方法即是踩脚刹或者拉手刹了(再强调一次:脚刹是作用于四个车轮,手刹是作用于后轮的。不管是否有手刹作用于其他车轮的车,我所知道的有手刹的赛车全都是我所说的情况) 踩脚刹:四个车轮都会减速,最终是前轮失去较多摩擦力还是后轮失去较多摩擦力不能一概而论。 拉手刹:前轮不会失去摩擦力而后轮就失去大量摩擦力,所以就容易产生转向过度了。因为无论脚刹、手刹都有减速的作用,所以车很快就会停止侧滑。 真正的漂移: 而如果想车轮长距离侧滑,唯一的方法就是让驱动轮高速空转,必须要装有LSD的、功率足够大的车才可以这样做。为什么要有LSD呢?因为车漂移时车身会倾斜,外侧车轮对地面的压力大,内侧的车轮压力小。没有LSD的车会出现内侧驱动轮空转,外侧驱动轮转得很慢的情况。这个转得慢的车轮与地面间摩擦力大,车的侧滑就会很快停止。 车分为前驱、后驱、四驱,没有驱动力的车轮是不可能高速空转的。那么前驱车的后轮就不能做长距离的侧滑,如果驱动轮(即是前轮)高速空转,侧滑比后轮多,漂移角度就减小,所以前驱车是不能做长距离漂移的。四驱的车很显然是可以的。后驱车呢?后驱车前轮没有驱动力,但前轮可以向车身滑动的方向摆一个角度,所以后驱车也可以作长距离漂移。 侧滑距离与侧滑开始前的速度有关,通常会越滑越慢,最后还是停下来,但如果场地允许、控制得好,理论上可以做无限长的侧滑。因为打滑的车轮仍有一定的加速所用,而侧滑的轮胎也受到地面的阻力,当这两个作用平衡时,车的速度就不会降低了。例如 Doughnut(原地转圈)就是无限长漂移中的一种,当然也可以做出转弯半径较大的无限长漂移。 上面说的都是控制驱动轮侧滑长度的方法。知道这些原理之后,再说-- 调整车身姿势用到的方法: 1.控制前轮的角度,不能太大或太小,特别是对于后驱车 2.调节油门、刹车,令车有加速或减速的趋势,就产生重量转移,通过重量转移控制车头向外滑更多还是车尾向外滑更多 3.利用手刹再次产生转向过度。 注意:2中,后驱车(或动力分配比趋向于后驱的四驱车)加油所产生的效果不一定是加速,如果加油太猛,就有可能因为后轮转速太高而减小摩擦力,车尾向外滑得更多。 重要讲解: 最大漂移角度 : 最大漂移角度--在漂移中途,车头指向与车身运动方向之间夹角如果大于这个角度,就必须要停车(不停的话就撞出去)。注意不包括漂移产生时。 后轮驱动车来说,因为前轮没有驱动力,不能产生高速空转向外滑,只是靠地面对前轮的侧向力控制车头运动。所以车头指向与车身运动方向之间的夹角最多只能和前轮最大摆角相等(不同的车前轮摆角不同,一般轿车的前轮摆角可以有30度左右),再大一点的话,除了停车再起步之外就没有任何方法恢复正确行驶。注意平常人提到的“大角度漂移”不是指车头指向与车身运动方向之间的夹角,而是附图红色标志出的角度,弯越急,显得角度越大。 后驱车也有前轮抓地力不够、转向不足的情况。在这样的情况下,车头指向与车身运动方向之间的夹角同样不能超越最大漂移角度,否则也必须停车才能恢复正常行驶。 前驱车因为可以保持后轮的抓地力而加大油门让前轮向外滑,所以前驱车的最大漂移角度很大,可以接近90度。 四驱车因为前后轮都可以高速空转,加油时有前轮向外滑得更多的可能性(为什么?因为加油时重量转移到后轮,前轮与地面间摩擦力小)再加上前轮可以向外摆,那么四驱车的最大漂移角度就比后驱车大。( DRIIFT : 反对意见出现,后驱车在完整的车架SET UP 下漂移角度比4WD大.) 比较三种驱动形式的车,前驱车是最容易驾驶、最安全的。(DRIIFT: 反对意见出现 ,呵呵我觉得FR最好开,停车的时候真是"感觉好极了") 漂移的出弯: 出弯的时候就应该结束漂移了,结束方法与漂移过程中减小漂移角度的方法一样。 对于前驱车 1.加油使车头向外滑动(因为除了漂移产生的时候,前驱车基本上是转向不足的) 2.通过前轮向外摆修正车头角度 3.也可以前轮向外摆之后放一点油门。 对于四驱车,2通常是必要的,3也很有效,1则不一定奏效。 对于后驱车,最主要靠2。视具体情况而定,车的重量分配、驱动力分配、之前漂移角度、路面状况等多种因素都有影响。
2023-08-17 23:23:191

举例说明酶的竞争性抑制剂在临床上的应用并简述起作用原理

酶的竞争性抑制剂作用原理:与被抑制的酶的底物通常有结构上的相似性,能与底物竞相争夺酶分子上的结合位点,从而产生酶活性的可逆的抑制作用。例如磺胺药与对氨基苯甲酸具有类似的结构,而对氨基苯甲酸、二氢喋呤及谷氨酸是某些细菌合成二氢叶酸的原料,后者能转变为四氢叶酸,它是细菌合成核酸不可缺少的辅酶。由于磺胺药是二氢叶酸合成酶的竞争性抑制剂,进而减少菌体内四氢叶酸的合成,使核酸合成障碍,导致细菌死亡。
2023-08-17 23:23:201

七情:喜,怒,忧,思,悲,恐,惊,每个字用一个英文单词表达。

名词还是动词,还是形容词? 说清楚了才好回答.不然就跟楼上几位一样混乱在一起了.
2023-08-17 23:23:226

水龙头节水器怎么选

摘要:节水器是在现有普通水龙头的基础上通过技术革新达到节水目的一种节水装置。节水一种是分流节水控制用水量。节水器与普通水龙头功能一样,必须兼具省水、维持水流通的功能,下面我们就一起来看看怎么选购节水器与一些节水器的日常维护说明。水龙头节水器怎么选节水器的日常维护说明1怎样选购节水器水龙头节水器在用水终端,节水器是一种在不影响使用效果的同时,能减少用水量的节水装置。节水器主流可分为机械式节水器(如恒流节水器、水龙头节水器、淋浴节水器、延时自闭水龙头等)和感应式节水器(如感应水龙头、槽式节水器,IC卡节水器等)两大类。水龙头节水器基本工作用原理是由阀芯内的止水塞和浮动式活塞及弹簧等组成自动检测装置,当供水压力增加时,压缩内弹簧,活塞带动止水塞向上运动,使过水通道有效供水面积减少,输出水流减少;当供水压力减少时,内弹簧复位使活塞带动止水塞向反方向运动,使过水通道有效供水面积增加,输出水流增大,这样往复的自动调节,使止水塞可以随进水压力的大小变化而上下浮动来保持出水流量的相对稳定,达到恒流的目的。水龙头节水器最适合单把冷热水龙头的节水改造,由于冷热水龙头分冷水和热水双路供水,双路水压相差较大,调节时既调温双要调水量,两者很难配合,使调温时间过长,造成严重浪费。在单把水龙头安装恒流节水器后,由于恒流的原因,不用节调流量,只调温度就可以了,大大缩短了调温时间,减少调温时的流量损失,节约用水。节水器包含沟槽中的大小便器,专用于带沟槽式的卫生间中(大便槽带自动水箱,小便槽无需带水箱),通过红外线感应来达到节水目的的产品,在水资源日益缺乏的地球,更加应该被普遍使用和重视!普通的沟槽式水冲公厕,都是由一个自动水箱来进行冲洗的,小便槽冲洗则是通过PVC水管钻多个小孔长流水。最常见的是虹吸式水箱,它的工作原理是:将自来水不断的注入水箱内,水面不断上升,当水面淹没虹吸管时,产生虹吸现象,水箱开始放水,当水面下降低于虹吸管的进水口时,停止放水,完成了一个循环周期。普通沟槽式冲洗公厕都是24小时不间断的冲洗,造成水资源的极端浪费。厕所节水器可有人使用,机器才按指令工作,往水箱注满水才会自动冲洗,无人使用,机器则“按兵不动”,有效达到智能化节水。一、节水器由红外线热敏探测器、微电脑程控器、低压直流电磁阀组成,它的工作原理是通过红外线人体感应来完成工作的,当有人在探测器前活动,探测器立即红色灯光闪烁,表示已经感应到人,同时探测器将感应信号传送给微电脑程控器,由控制器控制电磁阀打开放水,注水时间可以自由调节。来人放水,无人停水,特别适用于学校、机关、商场、工厂等单位的沟槽式厕所,节水效率高,安全省电。人体红外线感应100%识别,可以识别不同颜色、不同材质的物体(衣物),漏冲率为零。二、在保证电压、水压正常的情况下,一般不需要维修。三、日常小问题:1、电磁阀出水正常时,无需清洗;如水中杂质较多时或注水量小,则需将过滤器螺帽卸下,清洗杂质后,再按原样装上。2、微电脑程序控制器红色指示灯不亮时,检查是否有电源。3、调节注水时间时,不要用力,轻轻慢调,顺时针注水量增加,逆时针水量减少。4、红外线探测器正常工作时LED灯会闪烁,如指示灯接受热量信号不闪烁,检查是否有电源、探测器角度是否对准、探测器和控制器之间的连接线是否接触不良等。5、使用3-5年后,检查电源线之间的连接有无氧化;探测器窗口如落灰尘过多时,用软布轻轻擦去灰尘,灵敏度即可恢复原有状态。四、节水器的主要特点:1、自动感应:微电脑全自动智能控制,按时冲洗,无人不冲,避免细菌交叉感染。2、数码调整:调整程序参数只需几秒钟,适用各种场合。3、高可靠性:微电脑控制技术,任何情况下程序绝不丢失,系统按工业化标准设计,使用寿命可达八年以上。4、适用各种恶劣环境:无论是夏季的高温,还是零下十几度的严寒,产品均能正常工作。5、使用安全:安装在2.4米以上高度,避免人员接触,也可免人为破坏。6、安装简便:无墙内预埋件,安装只需1小时。7、节能效果:节水率60%-90%,一般1~3个月可收回全部投资。五、节水器的日常维护说明厕所节水器是通过电磁阀来控制水控系统,由于长期使用,管道会有杂质,一般2-4个月清洗进水过滤网一次,如果水量变小,拆开电磁阀内部进行清先再原样装回,可延长机器的使用寿命且定期检查维护。六、厕所节水器的安装示方法(试图见右下角)1、用螺钉将微电脑程序控制器和红外线探测器固定在墙上,探测器可沿墙或吸顶安装,红外线探头感应窗口需对准便槽或便池台阶。2、将电磁阀安装在进水管上,注意进水方向,有过滤网的接进水管,箭头指示。3、将电磁阀和红外线探测器连接至微电脑控制器上的接线端子。4、再将AC220V电源连接至微电脑控制器上的接线端子2七、厕所节水器常见故障解决1、有感应(指示灯亮)不出水:检查电磁阀过滤系统是否有杂质堵塞,进行清洗,且拆开电磁阀将内部元件清洗,再原样装回,如果电磁阀没有工作,就直接放新的电磁阀线圈。2、没感应(指示灯不亮)常流水或不出水:检查线路是否接好,是否有电源供应,如果都正常,就更换红外探测器。3、有感应(指示灯亮)常水流:对过滤网和电磁阀进行清洗,原样装回。八、工作原理沟槽式公厕节水装置,是在原自动水箱的进水管上加装一个电磁阀,在公厕大便槽或小便槽入口处天花板上安装红外传感器,红外传感器对现场进行自动探测,当有人进入公厕后,红外传感器将探测到的信号送到控制箱,控制箱内的微电脑在进行预定的程序处理后发出指令,控制电磁阀往自动水箱里注水进行冲洗,如果没有人进入公厕,整个系统将“按兵不动”,始终不会冲洗。这样就实现了“无人值守,自动管理,有人则冲,无人则停”的卫生、节水的使用效果。小便槽示意图现场图1怎样选购节水器1、不轻信厂家、商家的广告宣传、产品描述,由具有相关知识的人士分析其描述是否科学、甚至是否有悖科学。譬如大家都知道“水火不容”,如果商家把产品描述成“水与火相结合的世界顶尖高科技产品”那绝对是骗人。2、收集商家不同时期的产品广告、产品宣传资料进行比较,如果商家对同一产品所采用的原理前后描述不一样、甚至前后冲突、或产品性能描述差异过大,都说明宣传不实,商家及其产品都有问题。3、不要轻信各种证书、媒体报道以及“权威机构推荐”等等东西——IT时代造假非常容易!中国的认证种类和认证机构都是全世界最多的,而假货泛滥之烈可能也是全世界最严重的!4、不要轻信专利技术与专利产品,专利技术不等于先进技术、专利产品也不等于优质产品。相反,如果一个产品对性能、原理的介绍很少而刻意强调采用的是“专利技术”,则应该提高警惕。另外,专利包括发明专利、实用新型专利和外观设计专利,其中外观设计专利与产品的技术含量无关,而只与商品的包装材料、包装方式等有关,即与产品的使用价值无关。如果对专利有疑惑可以到国家知识产权局网站下载专利文件进行分析(专利授权一段时间后资料才能上网)。5、不买侵犯专利权、商标权的产品,这对经销商和集团用户是很重要的——销售和使用侵权产品也有赔偿责任。总之,对于个人用户来讲,要多问(请教有使用经验或有相关专业知识的人)、多分析,看商家的产品广告说得是否有道理、是否有造假行为或造假嫌疑;对集团用户而言,应先买少量产品进行性能测试,认可性能、效果才批量购买。
2023-08-17 23:23:221

do shots 是什么意思?

射击
2023-08-17 23:23:238

mctaren跑车叫什么车

一般是McLaren。这是麦卡伦。凯伦是英国的汽车制造商,不仅制造民间的超跑,还制造F1赛车。麦克拉伦旗下车型有570、540C、720S、麦克拉伦GT、600LT、塞纳、P1等。迈凯伦720S是迈凯伦的热门车型。该车的纵横高度分别达到4544、1930、1194,该车轴距为2670。该车配备了一台4.0升双增压V8发动机,M840T,拥有720马力和770牛米的最大扭矩。该发动机可以在7500转每分钟内发出最大功率,在5500转到6500转每分钟内发出最大功率。该发动机搭载了气缸内直喷技术,使用铝合金气缸盖缸体。使用铝合金气缸盖可以降低发动机的重量,这样可以提高汽车的操纵性和燃料合理性。与这台发动机匹配的是7速双离合变速器。使用双离合变速器,可以提高汽车的换档速度和驾驶速度。双离合器变速器的一个离合器功能的作用是调节奇数档,另一个离合器功能的作用是调节偶数档。在底盘的设计中,该车的前悬架使用双横架,后悬架也使用双横架。
2023-08-17 23:23:241

水龙头如何调整冷热水?

感应水龙头怎么控制冷热水感应水龙头怎么控制冷热水感应式水龙头可以分为一体式自动感应水龙头和分式自动感应水龙头两种。其中一体式自动感应水龙头控制部分全部集中在一个龙头体内,不需要安装分体式的控制盒。此外,一体式自动感应水龙头还分为单冷型和冷热水型,后者可以调节冷热水。那么感应水龙头怎么控制冷热水呢?1、感应式水龙头的陶瓷阀芯底部由三个孔组成,其中两个孔一个用于进出冷水,另一个则用于进出热水。剩下的一个孔则主要是用于阀芯内部出水。2、感应式水龙头的阀芯冷、热两孔都有密封圈,保证和主体斜街处于密封状态,冷、热进水管乐主体连接后确保热水管和冷水管管道与阀芯的两个孔一一对应。3、阀芯主要控制两个孔的开合状态,当启动到手柄的出水位置,相对应的水则会出来。如何安装冷热水龙头冷热水龙头该如何安装呢,快和日隆装饰一起看看吧。一、如何安装冷热水龙头 1、检查配件 当冷热水龙头买回来以后,首先要做的肯定是先检查配件,特别是批量购买的朋友们,要知道水龙头由哪几部分组成。 2、安装进水软管 检查完配件后,第二步就是安装进水软管,把水龙头里面的塑胶垫放于洗面盆单孔里,再打上玻璃胶,以防漏水腐蚀,将进水软管穿过洗脸盆单孔再与龙头主体连接旋转固定,另一根软管安装步骤一样,再将螺纹接口由软管下端穿入与龙头主体连接。 3、固定水龙头 安装好进水软管后,第三步是固定水龙头,将白色胶垫与进水软管下端穿入,再将锁紧螺母也在两根进水软管下端套入,旋转锁紧螺母套筒就能固定龙头在单孔面盆上,拧紧。 4、连接进水口跟排水口 固定好水龙头后,就可以连接进水口跟排水口,只要记得在龙头连接时,每个螺旋位置都要加生料带防止漏水,另外要记得进水软管对应的左边冷右边热或者左边热右边冷。 二、冷热水龙头使用有哪些注意事项 1、有的家庭的厨房冷热水龙头是和面盆紧连的,因此这类水龙头下面的软管在管内是很深的,所以在拆卸的时候是要费一定的功夫的。所以我们在安装的时候,阀芯座可能用螺丝锁在龙头主体上。可以用比较长的螺丝刀从下面先逆时针旋下螺丝,再从下面往上将阀芯座和高压软管顶出来,在顶的时候要注意,避免损坏阀芯座上的密封圈然后再拆卸软管。 2、有的进水软管使用时间比较长,出现脆化或是紧固件生锈的情况导致进水软管无法一时拧下。可以在生锈的部位滴上两三滴油后,将面盆水龙头取出。 看了以上的介绍,大家都学会该如何安装和使用冷热水龙头了吗?冷热水龙头的工作原理是什么冷热水龙头是备受消费者青睐的一款产品,那么究竟该如何拆卸呢,快和日隆装饰一起看看吧。一、冷热水龙头的工作原理是什么 冷热水龙头是可同时实现冷热水的切换,以达到用户对冷水、热水的不同需求,同时可以被称为混水龙头。要想了解冷热水龙头的工作原理,我们就要首先了解一下冷热水龙头的基本构造。冷热水龙头主要由手柄、阀芯和进水编织管以及一些安装小部件组成,其中阀芯是调节水温的主要部件,阀芯多采用陶瓷或者不锈钢制作。 冷热水龙头的阀芯的底部有三个孔,一个控制冷水的进出,一个控制热水的进入,还有一个是控制阀芯内部的出水。为了保证调节水温时的准确性,冷热水龙头阀芯的冷、热两个孔,都采用了密封圈。阀芯通过转动来控制两个孔的闭合,从而控制水的温度。若以左边的孔为热水出孔,右边的孔为冷水出口,当我们把手柄拧到左边的时候,带动陶瓷阀芯的移动,使热水孔完全被打开,这时候热水自然而然的就出来了。反之,当我们把手柄拧到右边时,冷水孔被打开,热水孔闭合,冷水自然就出来了。拧到中间,冷热水孔都开合了一部分,自然而然出来的就是温水。 二、如何拆卸冷热水龙头 1、关闭总开关。找到把手下的红蓝色点。用手扣开它,就显露出一个小孔。 2、用手电观察孔内,看里面一颗固定螺丝是十字口还是一字口,用相应的螺丝刀去松动它。少数品牌的这颗螺丝是内四方孔,这就较麻烦一些。没有工具时,用能够拆卸的其它替代工具要注意不要损坏螺丝口。 3、当松开这颗螺丝后,就能取下把手。这时可看到的一颗铜质大螺母,用活动大扳手拆卸螺母后,就可以取下龙头的阀芯。 4、观察阀芯下的铜质平面有无不平整或有无细微的砂粒,清除干净。取出平面上的橡胶密封圈观察有无破损。 5、一般来说,冷热龙头的整体性好,使用可靠性高。所以,打开后往往找不到明显的问题,这时,就是换一个阀芯就行了。最后,按上述程序反向安装。 看了以上的介绍,大家了解冷热水龙头了吗?
2023-08-17 23:23:291

有首英文歌高潮是lony,can you那么long那么lone

是stronger么?
2023-08-17 23:23:323

迈凯伦多少钱一辆

迈凯伦是美国奢华跑车品牌,归属于迈凯伦集团公司。在1963年,职业赛车手布鲁斯·迈凯伦(BruceMcLaren)以自身姓式于美国开创了赛车队,自此的岁月里,迈凯伦赛车队叱诧风云F1比赛场,共获得12次赛车手冠军和8次运输队冠军等凹凸有致考试成绩,造就了车界的巅峰传奇。1993年,迈凯伦F1跑车问世,变成了有史以来第一辆碳纤维材料跑车和史上最牛快的批量生产车辆,与此同时也是史上最牛快的自吸公路跑车。2013年,迈凯伦P1发售,变成全世界适应能力最_、最灵巧和最有趣的公路跑车。做为在赛道上的大牌明星,迈凯伦一直是很多人理想有着的车辆,但是它那价格昂贵价钱只有让大部分人可望而不可即。那_你了解马迪利要多少钱一辆吗?大伙儿还可以参照接下来的车系指导价位,看一下离你的“豪华车梦”也有是多少张RMB的间距。迈凯伦570S指导价:265-299万余元迈凯伦540C指导价:239-272万余元迈凯伦570GT指导价:278-314万余元迈凯伦625C指导价:349-381万余元迈凯伦720S指导价:370-405.5万余元迈凯伦P1指导价:1260万余元看过以上的价格表,不清楚大家是否有兴致勃勃的欲望呢?我鼓足勇气看过一眼储蓄卡的账户余额,脑海中里忽然想到那句熟知的广告宣传语“可以环绕地球上三圈”,闹心。假如你要问:“迈凯伦最划算的要多少钱”,那我也只有对你说,最划算的迈凯伦540C也需要225多万元,也是一个悲伤的话题,但是下边或是为各位介绍一下吧,指不定哪天你也就功成名就了。在外观设计层面,迈凯伦540C与570S基本上保持一致。沿用了很多迈凯伦大家族设计风格,例如其窄小的汽车发动机机盖、新月形车灯、也有酷炫的蝴蝶门构造。此外,新汽车尾灯、倒车镜等设计方案均吸取了P1的设计灵感。以内饰层面,迈凯伦540C与570S保持一致,用材层面,出自于控制成本的考虑到,540C选用全牛皮包囊。汽车方向盘后边配搭TFT液晶仪表盘,中控台装有7寸触摸显示屏,并融合了中央空调、导航栏及其手机蓝牙等作用。而坐椅更有全汽车真皮座椅及其跑车筒形坐椅等挑选。在能源层面,迈凯伦540C配用3.8TV8双涡轮机,至大功率397kW/7500rpm,最高值扭距540Nm/3500-6500rpm。在7速SSG变速器相互配合下,540C可在3.5s内过百,最大车速320km/h。但在最高车速主要表现上比570S常用时0.2s。迈凯伦540C是继570S以后又一款迈凯伦健身运动跑车系列产品作品,其市场价为225万余元。做为迈凯伦现阶段市场价较低的健身运动跑车,迈凯伦生产厂家表明,之后将不容易开发设计比540C更低的车系了,生产厂家也是情系我们这批“困难家庭”,但是价钱那么“低”,我或是没钱买呀,能否再降个百八十万的,也许还能够再考虑一下。百万购车补贴
2023-08-17 23:23:341

有关表情的英语单词

happy快乐的 sad悲伤的 angry 生气的 scared害怕的 frightened恐惧的 confused迷惑不解的 embarrassed尴尬的 shy害羞的 nervous紧张的 calm冷静的 excited兴奋的 thrilled极度兴奋地 sleepy困乏的 stunned愕然的 shocked震惊的 surprised惊奇的 amazed惊喜的 blushing脸红的 shivery颤抖的 resentful怨恨的 distant 冷漠的 wistful 渴望的 vacant茫然的 wan惨淡的 funereal阴森的 guilty心虚的 droll古怪的 faraway心神恍惚的 unmeaning毫无表情的 speaking富于表情的 hangdog羞愧的 wooden木然的 serious严肃的 sympathetic 同情的 wary慎重的 covetous贪婪的 comical滑稽可笑的 gloomy 忧伤的 thoughtful 沉思的
2023-08-17 23:23:362

煤气热水器好还是电热水器好

如果说方便性,电热水器始终是不如煤气热水器的,特别是你们的人口和洗澡时间,电的烧一桶水不一定够几个人洗,后面的人可能还得重新烧水;而煤气热水器就好多了,即开即用,随时都有热水,不用等待,你想洗多长时间都可以
2023-08-17 23:23:1513

自动感应水龙头常见故障怎么修理?

一、全自动感应水龙头故障现象以及检修方法1、感应龙头出来的水量小一方面看是否由于水压和管道自身流量小,另外一方面检查水控部分进水端的过滤网是否有杂质堵塞,还一方面考虑目前管道都采用PPR,部分工程施工时管道接 头采用热熔,不排除热熔过度将管子弄变形,使通过管道的水量变小。果然是智能感应设备,随便说说都很专业的样子把,不会的话就找卫浴维修师傅帮忙。 2、没有感应(指示灯没亮)常流水这时候才发现感应水龙头不再是节约用水了,简直是水流不止啊,这是个紧急事件,我们必须先自己解决,排除感应窗口前有异物(感应器的原理是只要有物体在红 外感应距离范围内机器就响应)且指示灯正常,检查水控部分的进水端过滤网是否有泥沙或其它杂物堵塞,如有请清洁干净;如未能解决故障,请拆开电磁阀,清洗 线圈的阀芯、弹簧、膜片等再原样装回,如还关不住水则说明电磁阀已坏,更换新电磁阀部分即可。在这边有个经验之谈,关不住水的情况,用手感应后,如发现水 量大,手离开后关不住水,但水量比之前使用时小,这说明电磁阀无故障,只需要按上述方法清洗一下电磁阀内部就可以正常使用。当然,要先关闭水源! 3、有感应(指示灯亮)常流水首先检查进水处是否有杂质堵塞,如果没有,就用手感应后,仔细听电磁阀阀芯是否工作(有开启和关闭的声音,如果有工作的话,就拆开水龙头的电磁阀内部进行清洗,然后原样装回。 4、有感应(指示灯亮)不出水首先确定供电电源是否正常,现国内主要供电方式为交流220V变压为12V和6V;直流电6V(七号或五号碱性电池四节)和直流3V(两节七号或五号碱性 电池),用手挡在红外窗口前观察是否有指示灯,部分感应水龙头品牌的红外窗口采用单片机控制,电源指示灯在水控部分(装在台盆下方),如果无反应,则初步 判断感应部分有故障,可更换红外探头,在这边建议对故障无法判断的用户,如果有其它正常的感应水龙头,可采用排除法,先将其红外部分插在有故障的水龙头水 控部分(电磁阀部),这样很容易判断是感应部分故障还是水控部分的电磁阀故障,同时注意所有插头是否无接触不良或受潮的可能性。 二、选择好一点品牌的自动感应水龙头1、艾宝 作为全球最大的电热水龙头产品制造商及销售商之一,艾宝集团以研发产品、制作产品等为主营业务,旗下的电热水器以及电热水龙头等产品获得了高度评价。目前为止,艾宝为全球最大的水龙头产品制作商。旗下产品安装简单,节能环保,使用也会更加的简单。 2、飞羽 飞羽电热水龙头是宁波索顿飞羽电器有限公司生产的,公司成立于1994年,距今也有将近20年的历史了,经过不断的发展,该企业不断的扩大生产规模,至今已经拥有30多个品种的商品了。 3、奥特朗 奥特朗公司致力于节能/环保产品研发,在电器行业中小有名气,其生产的电热水龙头获得了多项奖项。该公司专注于其领域下的品牌定位,追求品质,追求时尚,为人们提供一系列先进产品,就会受到相应的改变。 4、亚乐 亚乐是型钢亚乐电动汽车国际集团有限公司的产品,相信大家对于这个企业还是有一定的了解的,它是一家集研究,开发,设计,生产,销售于一体的企业,公司一直秉承着“以诚为本”的经营原则,注重产品质量的同时,让外观也引领者时尚的潮流。 5.扬子 成立于1980年的扬子集团法门的瞬间加热水龙头,比其他水龙头具备冷热交换、省水省电、使用方便等特点。速热、恒温也是其的特性。因此解决了家庭很多麻烦,是现代家庭的必备之物。 选择一款好一点的水龙头,不仅能够使用时更加的方便,也因为售后保障完善,能够有故障不用花钱外面去修理。使用年限也更长。所以了解怎么修理感应水龙头的同事,不妨在买之前也多做做产品认知工作。
2023-08-17 23:23:141

酒吧常放的英文歌曲只知道一个很嗨,里面有唱:下下下下下下下下。。。

没听过? 下贱?= = 哈哈 不是没听过 是根本没听说过。 然后还有啊。 你给的再详细一点,下下下的歌多了去了。谁知道是哪个
2023-08-17 23:23:133

手机内录插头原理

耳机副件插头原理线路图电阻R1相当于耳机线控假麦克风是不会采集到外面声音的,也是耳机线控固定转换程序电阻,一般可选1.5K至2.2K之间,选择阻值大小,可使录音的声音大或小,电容C1和C2是将耳机LR输岀声道音送回到MIC麦克风音频输入脚,进行内录音,C1和C2选4.7uF至10uF之间 电路原理很简单,只有3个主要零件,有动手能力的人可自制,
2023-08-17 23:23:101

开车怎么漂移?自动波的车能漂移吗?

绝对经典——汽车漂移的原理、技术和方法,漂移技术不完全解析!! 一. Inertia-Drift 松油门并利用惯性使车尾甩出的过弯方式(适用于FR车种,适用于120度以上的大弯角).操作程序如下: 1.入弯前加速,入弯时松油门并同时猛切方向盘. 2.车子开始滑行后,降档并加油门,让车辆一边打滑一边出弯. 3.若只想小甩一下,可以不降档. 二.Breaking-Drift 踩刹车并利用车身重心转移,使车尾甩出的过弯方式(适用于FR车种,适用于90度以上的弯角).操作程序如下: 1.入弯时重踩刹车并降档,让车重心前移. 2.猛切方向盘使车尾甩出. 3.反打方向盘修正进弯角度. 4.保持车速以滑行到可出弯的角度. 5.配合方向盘,瞬时重踩油门出弯. 三.Sidebreak-Drift 拉手刹车使车尾甩出的过弯方式(适用于FF车种)操作程序如下: 1.尚未到一般的入弯点处,提早切方向盘,然后拉手刹车使车辆侧滑. 2.滑行时立即降档,并保持滑行状态到过弯顶点. 3.到达弯顶点时,几即大脚油门出弯. 四.Straight-Drift 入弯前的直线处,就开始甩尾的过弯方式(适用于FR车种,适用于狭窄之90度弯).操作程序如下: 1.入弯前的直线上就开始切方向盘. 2.车子开始滑行时,同时降档并保持油门深度. 3.滑行入弯点后,方向盘同时反向修正. 4.车头以朝向出弯口的姿势进入弯道. 5.车头对到出弯口时,即大脚油门直进出弯. 五.Power-Drift 利用改装后驱车的大马力,大扭力,使车尾甩出的过弯方式(适用于FR,RR车种).操作程序如下: 1.进弯前减速并降档,放油门并小切方向盘. 2.进弯后大脚油门,驱动轮会应马力抬大而抓不住地面,而让车尾甩出. 3.此时用油门控制转向程度,油门愈重,转向角度愈多,车头对到出弯点后,再顺顺地出弯. 六.Shift-Drift 利用降档使车身重心转移,并让车尾甩出的过洼方式.操作程序如下: 1.进弯前略微提升车速,进弯时切方向盘,然后踩刹车并同时降档. 2.此时车辆重心前移,车尾会突然向外甩出. 3.松开刹车并大脚油门出弯. 七.Feint Motion 利用左右重心移动使车尾甩出的过弯方式,也就是一般俗称的惯性滑移(假右甩真左甩,适用于FR,RR车种).操作程序如下: 1.进弯前不切到外侧,反而保持在中线附近. 2.方向盘在一瞬间往弯外方向切,瞬时刹车使车身重心往前移. 3.此时方向盘往前进方向用力猛切,车子会以Breaking-Drift的原理甩出. 4.滑行时退档放刹车,再大脚油门出弯. 八.4WD-Drift 四驱车过弯时稍微滑行甩尾的过弯方式.操作程序如下: 1.入弯前加速,入弯时对准弯顶点,用力切方向盘并刹车降档. 2.车子略呈Straight-Drift的方式滑行进弯. 3.过弯顶点时,大脚油门直线出弯. 漂移技术入门(10项) 弹离合(初学级):能够比较理想的直接破坏掉轮胎的抓地力。通过对离合踏板的踩击导致扭力在传动系统的不均匀传递来使后轮失去牵引力。所谓的踩击的意思就是说:迅速而有力的将离合踏板踹到底,然后再迅速的抬起。一般运用在比较窄,没有足够的空间利用重心转移造成甩尾的入弯处。在低速时进行强力的弹离合,是最直接有效能够在瞬间使节流阀完全开启的办法。而在有一定的速度的基础下或这是正在侧滑的过程中,则要轻而柔和的弹离合。只可能运用在后驱车。 手刹(初学级):最早是在拉力赛中被运用。在拉起手刹锁住后轮的同时,导致了整个后车身的侧滑开始。因为需要使车尾发生侧滑而刚好甩到一个正确的入弯角度,所以一个很流畅,力度和时间刚好的手刹使用过程是很难掌握的。拉手刹时不要太紧张,不用太狠,也不用太高,足够就好,任何时候都不要松开手刹扣,因为拉手刹的过程并不长,要保证在适当的时候,手刹能够顺畅而快速的放掉。这个基础的技术能够运用在任何速度,任何弯角,任何车,即便是专业的漂移车手也经常会运用手刹在侧滑的过程中来纠正车身侧滑的角度。 作者:熵 2005-11-11 19:36 回复此发言 2 绝对经典——汽车漂移的原理、技术和方法,漂移技术不完全解析!! 锁档(中级):这是一个在减速过程中的弹离合。以适当的引擎转速接近弯道,迅速的踩击离合器,并且降档,利用引擎的出力来使后轮急剧的减速以致发生侧滑。当然,这对你车子的传动系统来说会比较辛苦。而车子具体的动作,反映和程度,完全取决于车子的种类以及引擎的不同。因为需要有较好的技术控制引擎转速的掉落以及动力回升来达到使车身滑行,所以相对于手刹来说更难于使用。同弹离合一样,只能运用在后驱车! 重刹车(中级):一般运用于较窄的弯位和中速弯。在重踩煞车的情况下冲入弯道,使车子大绝大部分重力抛到前面,而使后轮不受重力而失去抓地力。这项技术经常被运用在赛车场上以来提高入弯的回头性,尤其是四驱(Evo和STI)。在柏油路面练习时如果发现你的车子在合适的入弯速度下严重的出现转向过度的话,那你可能在避震的设定或轮胎的选择上没有搞好,或者你应该换一台更适合的车子。 Lift off 转向过度(上级):被广泛地运用在高速弯的滑行。利用重力转移使车子从拥有抓地里的状态转变到漂移状态。和重刹车是同样的物理原理—重量转移,但不同的是这项技术被运用在非常高速的情况下,这就需要车手对车子在高速的平衡有着很好地掌握。顶级的D1车手会在漂移的过程中运用具有进攻性的lift off 转向过度来削减动力输出。 钟摆效应(上级):对头文字D熟悉的朋友应该对“钟摆”这个词有所了解了,这也是一项由拉力技术而衍生出来的。顾名思义,钟摆的意思就是说在入弯之前先将车子向弯的外侧摆动,然后再大幅度转向内侧,在重力转移的作用下破坏轮胎地抓地力而使车身发生侧抛,一般使用在入口的弧度比较小的弯位。配合lift off 转向过度,可以增强彼此的效果。在拉力过程中,钟摆是为了在没有摩擦力的路面上尽可能的增强抓地力,而漂移比赛中使用钟摆则完全因为相反的原因--让车身发生侧抛。钟摆的价值和实用性在于既可以在入弯的时候有效的减速,同时还能保证整个过程的高速状态! 摆动漂移(上级):钟摆的最终形态。速度并不快,在道路的两边进行来回的侧摆,是一种直线上的飘移,也叫做“鱼摆尾”(神龙摆尾?),但是这种摆动最难的部分不只是能将车身在高速状态下的重力装移掌握得炉火纯青,还要能够让车身的摆动角度刚好在入弯的时候处于正确的入弯角度和速度。而这一动作的熟练运用也标志着车手技术的全面以及高水准。 打滑(专业):顶级车手的伎俩,这个技术是指将车子的后轮使入赛道外的土地或者是草地上面,使之在瞬间丧失原有的抓地力,以获得更大的角度。这种特殊而有效的方法一般被运用在那些无法依靠本身引擎马力和速度来破坏抓地力的车子和情况下,或者在入弯时做出更具有攻击性的角度。更多地被运用在后驱车上面。 跳动侧滑(专业):和前一个技术一样,这个都是充分利用路面的状况而使车子侧滑。这次是让后轮压到路旁的波浪带(赛道弯位周围红白色相间的石带),通过后轮压到波浪带而产生的跳动来使车子脱离原有的抓地力,也或者利用前轮压到波浪带产生的转向过度而产生漂移。因为在运用这项技术的时候会产生相当强烈的震动和摇摆,所以不论对于车手还使车子都十分辛苦的。 长距离漂移(专业):用于顶级的竞赛中,其实质就是在离入弯还有一段距离的直线上使用手刹,提前使车子贴着边线冲入弯道。直到最近才发展成为一种独立的技术,目的是让车子在攻入弯线就已经发生漂移。与摆动漂移配合来使用,能够帮助车手一气呵成式的攻下整条线路。 最后再向大家讲一下漂移的线路,时机,位置的关系问题。上面这张图能够清楚地告诉大家基本的线路和所用到的技术,图示中,车身上的数字与前面的10种技术是相对应的,而车头的箭头表示前轮的指向,红色是减速,绿色是加速,蓝色是调节节流阀的开启。 一般来说,在学习漂移攻弯的过程中,有两点是十分值得注意的:第一点,越早的开始发生侧滑,就越容易产生更好,更大的入弯时的车身角度;第二点,越是上级的车手,比如说土屋圭市,越是会在利用入弯的前半部分用来削减速度,在车身运动到切点的时候运用马力在后半段漂移出弯,尤其是在大于90度的弯角。所以大部分技术都是让车子在减速的状态下发生漂移。每个人都可以根据自己的习惯,判断力来决定使用如何的技术攻弯,不过越好的车手,就越是能将多种技术混合在一起使用。也许你能使用各种技术来漂移过弯,但通过弯道的速度也是非常重要的。 作者:熵 2005-11-11 19:36 回复此发言 3 绝对经典——汽车漂移的原理、技术和方法,漂移技术不完全解析!! 技术这种东西,越是高级就越是融合了更多的基本技术在其中,而且越是坐在这里看就越糊涂,应该将要领记在脑中然后进行实践练习,就会更有发现。如果对一些技术的概念模糊不清的话,可以去找一些漂移的视频来看,日本的《Option》,《甩尾天国》都是不错的选择,这两个系列的DVD在很多国家都是超级受欢迎的,里面有D1最前线的日本传来的最新的漂移咨询,从改装,技术,比赛应有尽有。 汽车产生漂移的原理 归咎到底就是一种:后轮失去大部分(或者全部)抓地力,同时前轮要能保持抓地力(最多只能失去小部分,最好当然是获得额外的抓地力了),这时只要前轮有一定的横向力,车就甩尾,便会产生漂移。 令后轮失去抓地力的方法: 1.行驶中使后轮与地面间有负速度差(后轮速度相对低) 2.任何情况下使后轮与地面间有正速度差(后轮速度相对高) 3.行驶中减小后轮与地面之间的正压力。 这三项里面只要满足一项就够 实际上1,2都是减小摩擦系数的方法,将它们分开,是因为应用方法不同。 保持前轮抓地力的方法: 1.行驶中不使前轮与地面间有很大的速度差 2.行驶中不使前轮与地面间正压力减少太多,最好就是可以增大正压力。这两项要同时满足才行。 实际操作里面,拉手刹就一定同时满足行驶中使后轮与地面间有负速度差(后轮速度相对低) 行驶中不使前轮与地面间有很大的速度差 ; 漂移初状态的简单操作: 产生漂移的方法有: 1.直路行驶中拉起手刹之后打方向 2. 转弯中拉手刹 3. 直路行驶中猛踩刹车后打方向 4. 转弯中猛踩刹车 5.功率足够大的后驱车(或前后轮驱动力分配比例趋向于后驱车的四驱车)在速度不很高时猛踩油门并且打方向 其中3,4是利用重量转移(后轮重量转移到前轮上),是最少伤车的方法。 1,2只用于前驱车和拉力比赛用的四驱车,而且可免则免,除非你不怕弄坏车。 注意1和2,3和4分开, 是因为车的运动路线会有很大的不同。重要说明:漂移过弯和普通过弯一样,都有速度极限,而且漂移过弯的速度极限最多只可能比普通过弯高一点,在硬地上漂移过弯的速度极限比普通过弯还低! 至于最终能不能甩尾,跟轮胎与路面间的摩擦系数、车的速度、刹车力度、油门大小、前轮角度大小、车重分配、轮距轴距、悬挂软硬等多个因素有关。例如雨天、雪地上行车想甩尾很容易,想不甩尾反而难些;行车速度越高越容易甩尾(所以安全驾驶第一条就是不要开快车哦);打方向快,也容易甩尾(教我驾驶的师傅就叫我打方向盘不要太快哦);轮距轴距越小、车身越高,重量转移越厉害,越容易甩尾(也容易翻车!);前悬挂系统的防倾作用越弱,越容易甩尾。 有人提到多种漂移方式,实际上都在上面五种之内。 甩尾中的控制: 如果是用手刹产生漂移的,那么当车旋转到你所希望的角度后,就应该放开手刹了。 漂移的中途的任务就是要调整车身姿势。因为路面凹凸、路线弯曲程度、汽车的过弯特性等因素是会经常变化的。所以车手经常要控制方向盘、油门、刹车、甚至离合器(不推荐),以让汽车按照车手所希望的路线行驶。 先说明一点原理:要让车轮滑动距离长,就应尽量减小车轮与地面间的摩擦力;要让车轮少滑动,就应尽量增大摩擦力。减小摩擦力的方法前面说过,一个是让车轮太快或太慢地转动,一个是减小车轮与地面间正压力;增大摩擦力的方法就是相反了。 其中,让车轮太慢转动的方法即是踩脚刹或者拉手刹了(再强调一次:脚刹是作用于四个车轮,手刹是作用于后轮的。不管是否有手刹作用于其他车轮的车,我所知道的有手刹的赛车全都是我所说的情况) 踩脚刹:四个车轮都会减速,最终是前轮失去较多摩擦力还是后轮失去较多摩擦力不能一概而论。 拉手刹:前轮不会失去摩擦力而后轮就失去大量摩擦力,所以就容易产生转向过度了。因为无论脚刹、手刹都有减速的作用,所以车很快就会停止侧滑。 作者:熵 2005-11-11 19:36 回复此发言 4 绝对经典——汽车漂移的原理、技术和方法,漂移技术不完全解析!! 真正的漂移: 而如果想车轮长距离侧滑,唯一的方法就是让驱动轮高速空转,必须要装有LSD的、功率足够大的车才可以这样做。为什么要有LSD呢?因为车漂移时车身会倾斜,外侧车轮对地面的压力大,内侧的车轮压力小。没有LSD的车会出现内侧驱动轮空转,外侧驱动轮转得很慢的情况。这个转得慢的车轮与地面间摩擦力大,车的侧滑就会很快停止。 车分为前驱、后驱、四驱,没有驱动力的车轮是不可能高速空转的。那么前驱车的后轮就不能做长距离的侧滑,如果驱动轮(即是前轮)高速空转,侧滑比后轮多,漂移角度就减小,所以前驱车是不能做长距离漂移的。四驱的车很显然是可以的。后驱车呢?后驱车前轮没有驱动力,但前轮可以向车身滑动的方向摆一个角度,所以后驱车也可以作长距离漂移。 侧滑距离与侧滑开始前的速度有关,通常会越滑越慢,最后还是停下来,但如果场地允许、控制得好,理论上可以做无限长的侧滑。因为打滑的车轮仍有一定的加速所用,而侧滑的轮胎也受到地面的阻力,当这两个作用平衡时,车的速度就不会降低了。例如 Doughnut(原地转圈)就是无限长漂移中的一种,当然也可以做出转弯半径较大的无限长漂移。 上面说的都是控制驱动轮侧滑长度的方法。知道这些原理之后,再说— 调整车身姿势用到的方法: 1.控制前轮的角度,不能太大或太小,特别是对于后驱车 2.调节油门、刹车,令车有加速或减速的趋势,就产生重量转移,通过重量转移控制车头向外滑更多还是车尾向外滑更多 3.利用手刹再次产生转向过度。 注意:2中,后驱车(或动力分配比趋向于后驱的四驱车)加油所产生的效果不一定是加速,如果加油太猛,就有可能因为后轮转速太高而减小摩擦力,车尾向外滑得更多。 重要讲解: 最大漂移角度 : 最大漂移角度--在漂移中途,车头指向与车身运动方向之间夹角如果大于这个角度,就必须要停车(不停的话就撞出去)。注意不包括漂移产生时。 后轮驱动车来说,因为前轮没有驱动力,不能产生高速空转向外滑,只是*地面对前轮的侧向力控制车头运动。所以车头指向与车身运动方向之间的夹角最多只能和前轮最大摆角相等(不同的车前轮摆角不同,一般轿车的前轮摆角可以有30度左右),再大一点的话,除了停车再起步之外就没有任何方法恢复正确行驶。注意平常人提到的“大角度漂移”不是指车头指向与车身运动方向之间的夹角,而是附图红色标志出的角度,弯越急,显得角度越大。 后驱车也有前轮抓地力不够、转向不足的情况。在这样的情况下,车头指向与车身运动方向之间的夹角同样不能超越最大漂移角度,否则也必须停车才能恢复正常行驶。 前驱车因为可以保持后轮的抓地力而加大油门让前轮向外滑,所以前驱车的最大漂移角度很大,可以接近90度。 四驱车因为前后轮都可以高速空转,加油时有前轮向外滑得更多的可能性(为什么?因为加油时重量转移到后轮,前轮与地面间摩擦力小)再加上前轮可以向外摆,那么四驱车的最大漂移角度就比后驱车大。( DRIIFT : 反对意见出现,后驱车在完整的车架SET UP 下漂移角度比4WD大.) 比较三种驱动形式的车,前驱车是最容易驾驶、最安全的。(DRIIFT: 反对意见出现 ,呵呵我觉得FR最好开,停车的时候真是"感觉好极了") 漂移的出弯: 出弯的时候就应该结束漂移了,结束方法与漂移过程中减小漂移角度的方法一样。 对于前驱车, 1.加油使车头向外滑动(因为除了漂移产生的时候,前驱车基本上是转向不足的) 2.通过前轮向外摆修正车头角度 3.也可以前轮向外摆之后放一点油门。 对于四驱车,2通常是必要的,3也很有效,1则不一定奏效。 对于后驱车,最主要*2。视具体情况而定,车的重量分配、驱动力分配、之前漂移角度、路面状况等多种因素都有影响。 注意整个漂移过程中(包括产生、中途、结束)车身都是在向外滑的,所以准备出弯的时候不要把车头指向路外侧,而是应该指向内一点,让车滑到路最外侧时横向速度刚好为零,这就是完美的出弯。 作者:熵 2005-11-11 19:36 回复此发言 5 绝对经典——汽车漂移的原理、技术和方法,漂移技术不完全解析!! 后记: 开不同的车做漂移都要有一段适应过程,了解车的特性;在不同路面上也要有适应过程。在拉力赛中,因为每个弯的具体情况都是不知道的,即使在上一赛季已经跑过这赛段,路面也不会与以前相同。所以拉力赛中过弯都崇尚“慢进快出”的原则--进弯前速度慢一点,看清楚弯道之后就可以加大油门出弯。用这个原则过弯不但不会慢很多,而且安全性大大提高。 踏板的奥秘 车子是通过操纵方向盘实现转弯的,不过如果能娴熟地掌握油门和刹车踏板的技术,那么就可以更轻而易举地转弯。 刹车是为了减速,在直道上全速前进,在进入弯道时必须能在最短的距离内减速使车子安定下来。为了使车子转弯,所以必须保持刹车,而为了使车的重心向前转移,刹车时脚务必不能离开踏板,这是为了转弯而动用的刹车方法,俗称跟趾动作。由此可见,刹车其实分两种用途。 同样道理加速也有这样的效果,在加速的同时,车子后座、后部会显得沉重,相反不加速时车子的重心会前移。在高速行驶踩踏油门时如果踏板有丝毫放松,整个车子马上“阵脚大乱”,所以如果能有效地利用油门也同样能有效利用重心转移。 无论油门还是刹车都是简单而又微妙的动作,有时下脚需要快而强,有时却需轻描淡写地慢慢进行。这绝不象是开关那样操作简单。加速时脚后跟一定要紧贴地板,脚板用力。刹车也大致相同。现在的汽车许多都有ABS,初级学者只靠ABS的帮助就能充分地进行驾驶,要有意识地练习熟记自己脚步动作所带来的各项车子反应。 由于刹车与油门之间经常会交叉进行,所以鞋子要有厚厚的质感,否则极有可能因为鞋子的问题使控制失灵。 跟趾动作的基本要素中最重要的是鞋跟和脚尖。这两者与踏板的关系是指在汽车刹车的状态下进行换挡的技巧:刹车要保持一定的力度但不要拼命晃动,脚尖均匀用力踩着刹车的同时将后脚跟移到油门位。与此同时左脚踩下离合器踏板,没有发生激烈震动的情况下均匀地将速度减下来。这个动作的要点是注意对刹车的切换和轻重力量的控制。 飘移秘笈3 在赛车中希望车子能调校好,就需要将自己的意思向机械师表达清楚。虽然玩飘移不能有这么高要求,但是与负责您汽车调整的改车师傅有充分的沟通,不论对您还是汽车都是有益的。 在日本有许多环形跑道可供飘移练习,但在国内条件就没那么好了!不过重要的是要练习飘移,必须找宽阔的无人路段,千万不要危及他人。 上路前要检查 上路练习前必须要注意什么呢?如果您具有“我已经检修过汽车,万无一失!”的自信的话就OK了。但是如果有人觉得“要摆弄千斤顶去检修太麻烦了!”这种人就要小心了,因为飘移动作对刹车、轮胎造成的负担很重,如果没有充分检修,是很容易出差错的。 首先要检查机油及机油滤清器,差不多到定期更换时一定要更换(其它润滑油也一样)。其次是冷却水,要确认水是加满的。而刹车油应更换成DOT4,这样在激烈操驾下不易造成刹车失效,而在正式上路前应放空空气,以免气阻影响刹车。如果是后轮驱动的汽车,那么就特别要注意对后轮的检查,并带上备胎。因为有时会发生着地后轮飞皮的情况(警告:飘移将极度摧残轮胎,请做好心理和钱财的准备!),那就需要重新认真地检查并安装。 检查是否已经万无一失 1.要用千斤顶弄太麻烦了! 但如果不这样检查,后果不堪设想。 2.首先要更换好的油类物质 刹车油也一样 太脏的冷却水也换掉 3.其次是散热器,对液量和水温都要检查 4.最后后轮驱动车要带备胎,否则就只有推回去了!! 日落西山红霞飞,战士背车把家归…… 最初的工作是热身 热身运动包括了您自己、刹车、轮胎和方向。当然要将不必要的东西放在车外以免影响100%的注意力,否则一分心就容易造成错误。不少刚玩飘移的人头脑是一片空白,看到的只是眼前的东西。而在进入玩飘移状态时千万不能全速行驶!应该打开警示灯,先让轮胎等“热热身”,并熟记要飘移路面的弯道及路况。如果刚换上新的刹车部件,尤其要注意先磨合一下(如新刹车皮要开胶),否则将影响刹车系统的正常发挥。接着要注意感觉刹车与油门的大致情况,然后再练习进挡,做好“赛前”的热身。如果您用的是普通的街道用轮胎,那么必须注意,无论夏天还是别的什么时候,冷车和驻车后都不能一踏油门就走,特别是后胎较难加热(弄暖)的前驱车,更应先兜2-3圈让轮子充分热起来才行。(因为普通轮胎在未充分预热时就激烈进行飘移,因受热不均随时可能整块胎面飞掉,相当危险)总之轮胎及一些零部件如没有充分预热容易造成打滑乃至刹不住车造成危险,为安全起见,土屋也是先慢慢兜圈子的。 飘移前一定要热身 1.车子检查好了,全力加速! 2.没有充分进行热身就玩飘移简直跟自杀没两样! 3.热身过程中可顺便观察路面状况,如是否有水、油等 要确认水温、油温、油压都正常才行路上有油又有水太恐怖了! 4.没有热身就跑,我的轮胎爆掉了—— 充分热身后就可以全力加速攻弯了!! 切线与刹车技术的掌握 在充分预热后车子可以开足马力“冲”了。别忘了周围车辆的情况可用后视镜留意。许多习惯跑高速路段的家伙会开得非常快,初学者可慢行,没必要去与他们争强斗胜!因为无论是谁都有成长的阶段。 在弯道高速飞奔让您有一种恐惧感,横向转90度弯、转U形弯都是不可思议的事,这与卡通漫画里了解到的是完全不同的。驾驶时尽量挑选路面平整、弯度空间大的道路,这样在驾驶时只要用眼就能很快看清飘忽不定的线路,只要放松一下,将拐角当作是一种机械性熟练地去操作,就会有无穷无尽的乐趣。在拐过车身时让发动机空转,在接近下一弯道时一直踩刹车,朝一直线将车子靠近外弯道时进入。而当中别忘了一个重要的是要记住弯角大概的位置距离等。在条件成熟后,也经过好几圈的尝试后,可以镇定自若地驾驶后,就可以挑战飘移侧滑了。
2023-08-17 23:23:101

溶菌酶杀灭细菌的作用机理是

  溶菌酶杀灭细菌的作用机理是:竞争肽聚糖合成中所需的转肽酶。溶菌酶能有效地水解细菌细胞壁的肽聚糖,其水解位点是N-乙酰胞壁酸(NAM)的1位碳原子和N-乙酰葡萄糖胺(NAG)的4位碳原子间的β-1.4糖苷键。肽聚糖是细菌细胞壁的主要成分,它是由NAM、NAG和肽“尾”(由4个氨基酸残基)组成,NAM与NAG通过β-1.4糖苷键相连,肽“尾”则是通过D-乳酰羧基连在NAM的第3位碳原子上。   资料扩展:   根据酶的功能,通常将酶分为以下几类   (1)氧化还原酶类分氧化酶和脱氢酶两种。在体内参与产能、解毒和某些生理活性物质的合成。   (2)转移酶类。参与核酸、蛋白质、糖及脂肪的代谢与合成。   (3)水解酶类。这类酶催化水解反应,使有机大分子水解成简单的小分子化合物。例如,脂肪酶催化脂肪水解成甘油和脂肪酸,是人类应用最广的酶类。   (4)裂合酶类。这类酶能使复杂的化合物分解成好几种化合物。   (5)异构酶类。它专门催化同分异构化合物之间的转化,使分子内部的基因重新排列。例如,葡萄糖和果糖就是同分异构体,在葡萄糖异构酶的催化下,葡萄糖和果糖之间就能互相转化。   (6)合成酶类。这类酶使两种或两种以上的生命物质化合而成新的物质。
2023-08-17 23:23:101

找稍长的英语单词,最好积极或者美好点的

PottEveNerkoonPoxOya (都是女孩儿用的。)
2023-08-17 23:23:094

感应水龙头不出水怎么办

摘要:我国的水资源不足,分布也不均匀,严重给居民生活带来影响,为了采取节约用水的措施,在一些公共场设置了感应水龙头,它可以解决水龙头忘记关和没有关紧的问题。使用的时候遇到了感应水龙头不出水的问题,那该怎么办呢?感应水龙头一直出水怎么办?接下来就和小编一起来看看感应水龙头常见故障维修方法吧。感应水龙头常见故障维修方法一、感应水龙头不出水1、水的阀门关了:如果有人把阀门关了,那么肯定就不出水了。还有原因是阀门出了问题。2、过滤网堵塞:有些水龙头为了防止进沙加装了过滤网,拧开水龙头接口看看,是否有什么东西堵塞了过滤网。3、水压太小:如果住的楼层高的话,可能就会遇到水压不足的情况,导致水打不上去,出水很少的状况。4、水龙头出问题了:检查一下其他地方用水是否正常,如果正常的话,那么肯定水龙头的问题了。5、水管堵塞:如果是水管堵塞的话,这个就需要专业人员来处理。6、水箱里没有水了:由于停水或者水量用得过多,导致水箱没有储水,当然也不会有水了。二、感应水龙头一直出水1、没有感应(指示灯没亮)常流水首先排除感应窗口前有异物,且指示灯正常,检查水控部分的进水端过滤网是否有泥沙或其它杂物堵塞,如有请清洁干净。如未能解决故障,请拆开电磁阀,清洗线圈的阀芯、弹簧、膜片等再原样装回,如还关不住水则说明电磁阀已坏,更换新电磁阀部分即可。注意在维修过程中关闭水阀,避免浪费更多水。2、有感应(指示灯亮)常流水首先检查进水处是否有杂质堵塞,如果没有,就用手感应后,仔细听电磁阀阀芯是否工作(有开启和关闭的声音,如果有工作的话,就拆开水龙头的电磁阀内部进行清洗,然后原样装回。三、感应龙头出水量小一方面看是否由于水压和管道自身流量小,另外一方面检查水控部分进水端的过滤网是否有杂质堵塞,还一方面考虑目前管道都采用PPR,部分工程施工时管道接头采用热熔,不排除热熔过度将管子弄变形,使通过管道的水量变小。如果不会的话就找卫浴维修师傅帮忙。四、感应水龙头滴水漏水准备十字螺丝刀、一字螺丝刀、活动扳手等等,过滤网清理需要电磁阀具备前置过滤网才可使用,清洗杂质分为清洗过滤网杂质和清洗电磁阀线圈头杂质两种情况,在清理杂质前请将水量调节阀调整至关闭状态,或关闭自来水总阀开关。然后用“一”字螺丝刀旋出过滤网,用清水冲洗干净后重新装回旋紧并打开水量调节阀。特别注意,取下过滤网前须先关闭水量调节阀。感应水龙头流量要求是0.05~0.125L/s,若感应水龙头遇到流量越来越小、关不住水时考虑清理电磁阀过滤网。五、感应水龙头关不紧1、感应器的原理是只要有物体在红外感应距离范围内,机器就会响应,因此,可以排除感应窗口前是否有异物,可排除指示灯的问题,应该是正常的。2、先检查水控部分的进水端过滤网是否有泥沙、或其它杂物堵塞,若有,立即清洁干净,以后定期检查。3、也可以尝试拆开电磁阀,清洗线圈的阀芯、弹簧、膜片后,再重新装回;如果水流仍不受控制,判断电磁阀已经损坏了,需要换一个新的。
2023-08-17 23:23:061

怎样用老式热水器烧水?

你这是属于一款家用式的燃气热水器,在启用的时候如果出现不加温的情况,你可以检查一下热水器在启动的时候有没有点燃火排,如果我还没有点着的话,就应该是燃气阀门没有打开或者是脉点火器的电池没电导致点不着火而没有热水产生出来的。1。在选购的时候,首先是种类,但是每种燃气具都有适用燃气具种类的标识,一定要选购与自己家中使用的燃气种类相同的产品。但是目前我国燃气的气源主要有三种:天然气、液化石油气、人工煤气。 还有设计热水器时是根据气源的特性来决定的,由于三种气源成分不同,所以设计制造的燃烧器等部件尺寸也不相同。 在选购的时候,一种热水器一般只能适用于一种气源,广大客户在选择燃气热水器时要向专业人员询问清楚,选择与气源相适应的热水器。2。接下来在选购的时候,使用管道供气的用户,要保证燃气热水器的耗气量不大于家中燃气表的流量,还有使用钢瓶液化石油气的用户请选用相适应的减压阀,否则会造成燃气的供应不足,不能充分发挥热水器的供热水能力。3。最后说,在现在市场上有很多燃气热水器价格在八九百元,甚至更低。但是实际上,燃烧器、风机、气阀、水阀、点火系统、控制板加上机体本身,那么如果只用质量一般的材料,成本都会在千元以上,看来如果盲目图便宜并不是明智的选择。
2023-08-17 23:23:032

酶的作用原理曲线

(1)酶促反应的原理是降低化学反应的活化能,甲图中可以用ab段来表示,酶的催化效率比无机催化剂高,将酶换成无机催化剂以后,曲线应该向上移动. (2)酶的催化具有专一性,蛋白酶催化分解的底物应该是蛋白质,胃蛋白酶浓度和其他条件不变,反应液pH值由10逐渐降低到2,则酶催化反应的速度将不变,原因是胃蛋白酶的最适PH是2左右,PH为10时已经失活,再改变PH,反应速率不变. (3)①A、X轴表示pH,Y轴可以表示酶的催化效率,表示的是酶促反应与pH之间的关系;B、若X轴表示的是饭后的时间,则Y轴表示的可以是饭后血糖的浓度变化. ②A、若X轴表示反应物浓度,则Y轴可表示酶促反应的速率;B、若X轴表示光照强度,则Y轴可表示光合作用的速率. 故答案为:(1)ab 上移 (2)蛋白质 不变 胃蛋白酶的最适PH是2左右,PH为10时已经失活,再改变PH酶的活性不变 (3)①A.酶催化速率(或酶活性) B.血糖浓度  ②A.酶促反应速率 B.光合作用速率
2023-08-17 23:23:021