HuangYunfei's Blog


  • 首页

  • 分类

  • 归档

  • 标签

hexo博客图片

发表于 2020-03-07 | 分类于 博客
字数统计 414 | 阅读时长 2

hexo在文章中添加图片

在hexo博客中的文章插入图片

1.安装插件

在hexo的目录下执行

1
npm install https://github.com/CodeFalling/hexo-asset-image --save

2.修改配置

  • 在_config.yml配置文件中,修改为 post_asset_folder: true

  • 打开/node_modules/hexo-asset-image/index.js,将内容更换为下面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    'use strict';
    var cheerio = require('cheerio');

    // http://stackoverflow.com/questions/14480345/how-to-get-the-nth-occurrence-in-a-string
    function getPosition(str, m, i) {
    return str.split(m, i).join(m).length;
    }

    var version = String(hexo.version).split('.');
    hexo.extend.filter.register('after_post_render', function(data){
    var config = hexo.config;
    if(config.post_asset_folder){
    var link = data.permalink;
    if(version.length > 0 && Number(version[0]) == 3)
    var beginPos = getPosition(link, '/', 1) + 1;
    else
    var beginPos = getPosition(link, '/', 3) + 1;
    // In hexo 3.1.1, the permalink of "about" page is like ".../about/index.html".
    var endPos = link.lastIndexOf('/') + 1;
    link = link.substring(beginPos, endPos);

    var toprocess = ['excerpt', 'more', 'content'];
    for(var i = 0; i < toprocess.length; i++){
    var key = toprocess[i];

    var $ = cheerio.load(data[key], {
    ignoreWhitespace: false,
    xmlMode: false,
    lowerCaseTags: false,
    decodeEntities: false
    });

    $('img').each(function(){
    if ($(this).attr('src')){
    // For windows style path, we replace '\' to '/'.
    var src = $(this).attr('src').replace('\\', '/');
    if(!/http[s]*.*|\/\/.*/.test(src) &&
    !/^\s*\//.test(src)) {
    // For "about" page, the first part of "src" can't be removed.
    // In addition, to support multi-level local directory.
    var linkArray = link.split('/').filter(function(elem){
    return elem != '';
    });
    var srcArray = src.split('/').filter(function(elem){
    return elem != '' && elem != '.';
    });
    if(srcArray.length > 1)
    srcArray.shift();
    src = srcArray.join('/');
    $(this).attr('src', config.root + link + src);
    console.info&&console.info("update link as:-->"+config.root + link + src);
    }
    }else{
    console.info&&console.info("no src attr, skipped...");
    console.info&&console.info($(this));
    }
    });
    data[key] = $.html();
    }
    }
    });

3.新建文章

1
hexo new post hexo博文图片

会自动生成一个.md文件 和与md文件同名的的文件夹

image-20200305135549127

将图片放在文件夹中,采用相对路径插入

image-20200305135654502

设计模式

发表于 2020-06-27 | 分类于 设计模式
字数统计 53 | 阅读时长 1

设计模式

单例模式

饿汉式

(线程安全),全局的单例实例在类装载时构建。

1
2
3
4
5
6
class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}

懒汉式

OS

发表于 2020-04-16
字数统计 0 | 阅读时长 1

JVM复习

发表于 2020-04-05 | 分类于 JVM
字数统计 4.5k | 阅读时长 15

运行时数据区域

Java虚拟机运行时数据区

程序计数器

一块较小的内存空间,当前线程所执行的字节码的行号指示器。通过改变它可以选取下一条需要执行的字节码指令。程序控制流程的指示器,分支、循环、跳转、异常处理、线程恢复等功能都依赖这个计数器来完成。

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。

堆


Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。

在 JDK 7 版本及JDK 7 版本之前,堆内存被通常被分为下面三部分:

  1. 新生代内存(Young Generation)
  2. 老生代(Old Generation)
  3. 永生代(Permanent Generation)

大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

方法区


JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小

1
2
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemory

运行时常量池

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。

JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。

HotSpot 虚拟机对象探秘


image-20200411130808719

Step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

Step2:分配内存

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

内存分配的两种方式:(补充内容,需要掌握)

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是”标记-清除”,还是”标记-整理”(也称作”标记-压缩”),值得注意的是,复制算法内存也是规整的

内存分配并发问题(补充内容,需要掌握)

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:

  • CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

Step5:执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

JVM 垃圾回收


eden 区、s0(“From”) 区、s1(“To”) 区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1(“To”),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。经过这次GC后,Eden区和”From”区已经被清空。这个时候,”From”和”To”会交换他们的角色,也就是新的”To”就是上次GC前的“From”,新的”From”就是上次GC前的”To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,”To”区被填满之后,会将所有对象移动到老年代中。

1.2 大对象直接进入老年代

大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。

为什么要这样呢?

为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。

1.3 长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。

如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

1.4 动态对象年龄判定

大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

2 对象已经死亡?

image-20200411134817976

2.1 引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

1
2
3
4
5
6
7
8
9
10
11
12
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;

}
}

2.2 可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

Java中可以作为GC Roots的对象


  1. 虚拟机(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(即一般说的native方法)中引用的对象

2.3 再谈引用

无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。

JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。

JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)

1.强引用(StrongReference)

以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

2.软引用(SoftReference)

如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。

3.弱引用(WeakReference)

如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

4.虚引用(PhantomReference)

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

虚引用主要用来跟踪对象被垃圾回收的活动。

虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

2.4 不可达的对象并非“非死不可”

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。

被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

2.5 如何判断一个常量是废弃常量

运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?

假如在常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,”abc” 就会被系统清理出常量池。

注意:我们在 可能是把 Java 内存区域讲的最清楚的一篇文章 也讲了 JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。

2.6 如何判断一个类是无用的类

方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?

判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” :

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

Java并发

发表于 2020-04-05 | 分类于 Java并发
字数统计 1.3k | 阅读时长 5

Java并发

并发容器

ConcurrentHashMap的分段锁的实现原理

ConcurrentHashMap成员变量使用volatile 修饰,免除了指令重排序,同时保证内存可见性,另外使用CAS操作和synchronized结合实现赋值操作,多线程操作只会锁住当前操作索引的节点。

为什么说HashMap线程不安全?

1、put的时候导致的多线程数据不一致。
这个问题比较好想象,比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。

2、另外一个比较明显的线程不安全的问题是HashMap的get操作可能因为resize而引起死循环(cpu100%),具体分析如下:

下面的代码是resize的核心内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void transfer(Entry[] newTable, boolean rehash) {  
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {

while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

image-20200412141956431

我们假设有两个线程同时需要执行resize操作,我们原来的桶数量为2,记录数为3,需要resize桶到4,原来的记录分别为:[3,A],[7,B],[5,C],在原来的map里面,我们发现这三个entry都落到了第二个桶里面。
假设线程thread1执行到了transfer方法的Entry next = e.next这一句,然后时间片用完了,此时的e = [3,A], next = [7,B]。线程thread2被调度执行并且顺利完成了resize操作,需要注意的是,此时的[7,B]的next为[3,A]。此时线程thread1重新被调度运行,此时的thread1持有的引用是已经被thread2 resize之后的结果。线程thread1首先将[3,A]迁移到新的数组上,然后再处理[7,B],而[7,B]被链接到了[3,A]的后面,处理完[7,B]之后,就需要处理[7,B]的next了啊,而通过thread2的resize之后,[7,B]的next变为了[3,A],此时,[3,A]和[7,B]形成了环形链表,在get的时候,如果get的key的桶索引和[3,A]和[7,B]一样,那么就会陷入死循环。

如果在取链表的时候从头开始取(现在是从尾部开始取)的话,则可以保证节点之间的顺序,那样就不存在这样的问题了。

综合上面两点,可以说明HashMap是线程不安全的。

ReentranLock


AQS核心是自旋、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//可重入的体现
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
1
2
3
4
5
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

入队,AQS的对头所对应的Node里的Thread始终为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

入队,第二个线程的话入队后先判断能不能拿到锁(自旋一次)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

多自旋一次,上一个节点,判断的是上一个节点,ws = 0,先改成-1,再死循环进来判断

自己不改自己的状态,因为自己休眠了

ws = 0,必要的状态,并且尽量不park, 因为park比较重量级

Java锁事


https://tech.meituan.com/2018/11/15/java-lock.html

数据库

发表于 2020-04-05 | 分类于 数据库
字数统计 5.6k | 阅读时长 20

MySQL

1.MVCC

mvcc

每行记录记录后面保存两个隐藏的列,两个列,一个保存行的创建时间,一个保存行的过期时间(或删除时间),保当然存储的不是实际的时间,而是系统版本号。每开始一个一个新的事务,系统版本号就会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。

索引

MySQL索引使用的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。

MySQL的BTree索引使用的是B树中的B+Tree,但对于主要的两种存储引擎的实现方式是不同的。

  • MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
  • InnoDB: 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。

Innodb与Myisam引擎的区别与应用场景

\1. 区别:

(1)事务处理:

MyISAM是非事务安全型的,而InnoDB是事务安全型的(支持事务处理等高级处理);

(2)锁机制不同:

MyISAM是表级锁,而InnoDB是行级锁;

(3)select ,update ,insert ,delete 操作:

MyISAM:如果执行大量的SELECT,MyISAM是更好的选择

InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表

(4)查询表的行数不同:

MyISAM:select count() from table,MyISAM只要简单的读出保存好的行数,注意的是,当count()语句包含 where条件时,两种表的操作是一样的

InnoDB : InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行

(5)外键支持:

mysiam表不支持外键,而InnoDB支持

  1. 为什么MyISAM会比Innodb 的查询速度快。

INNODB在做SELECT的时候,要维护的东西比MYISAM引擎多很多;
1)数据块,INNODB要缓存,MYISAM只缓存索引块, 这中间还有换进换出的减少;
2)innodb寻址要映射到块,再到行,MYISAM 记录的直接是文件的OFFSET,定位比INNODB要快
3)INNODB还需要维护MVCC一致;虽然你的场景没有,但他还是需要去检查和维护

MVCC ( Multi-Version Concurrency Control )多版本并发控制

\3. 应用场景

MyISAM适合:(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。

InnoDB适合:(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且行锁定的机会比较大的情况。

串行化(SERIALIZABLE):所有事务都一个接一个地串行执行,这样可以避免幻读(phantom reads)。对于基于锁来实现并发控制的数据库来说,串行化要求在执行范围查询(如选取年龄在10到30之间的用户)的时候,需要获取范围锁(range lock)。如果不是基于锁实现并发控制的数据库,则检查到有违反串行操作的事务时,需要滚回该事务。

可重复读(REPEATABLE READ):所有被Select获取的数据都不能被修改,这样就可以避免一个事务前后读取数据不一致的情况。但是却没有办法控制幻读,因为这个时候其他事务不能更改所选的数据,但是可以增加数据,因为前一个事务没有范围锁。

读已提交(READ COMMITED):被读取的数据可以被其他事务修改。这样就可能导致不可重复读。也就是说,事务的读取数据的时候获取读锁,但是读完之后立即释放(不需要等到事务结束),而写锁则是事务提交之后才释放。释放读锁之后,就可能被其他事物修改数据。该等级也是SQL Server默认的隔离等级。

读未提交(READ UNCOMMITED):这是最低的隔离等级,允许其他事务看到没有提交的数据。这种等级会导致脏读(Dirty Read)。

索引

四、缺点

1.虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行insert、update和delete。因为更新表时,不仅要保存数据,还要保存一下索引文件。

2.建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会增长很快。 索引只是提高效率的一个因素,如果有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

五、注意事项

使用索引时,有以下一些技巧和注意事项:

1.索引不会包含有null值的列 只要列中包含有null值都将不会被包含在索引中,复合索引中只要有一列含有null值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为null。

2.使用短索引 对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

3.索引列排序 查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

4.like语句操作 一般情况下不推荐使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。 5.不要在列上进行运算 这将导致索引失效而进行全表扫描,例如

1
SELECT * FROM table_name WHERE YEAR(column_name)<2017;

6.不使用not in和<>操作

Explain 详解

在日常工作中,我们会有时会开慢查询去记录一些执行时间比较久的SQL语句,找出这些SQL语句并不意味着完事了,些时我们常常用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看。所以我们深入了解MySQL的基于开销的优化器,还可以获得很多可能被优化器考虑到的访问策略的细节,以及当运行SQL语句时哪种策略预计会被优化器采用。

1
2
3
4
-- 实际SQL,查找用户名为Jefabc的员工
select * from emp where name = 'Jefabc';
-- 查看SQL是否使用索引,前面加上explain即可
explain select * from emp where name = 'Jefabc';

img

expain出来的信息有10列,分别是id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra

概要描述:
id:选择标识符
select_type:表示查询的类型。
table:输出结果集的表
partitions:匹配的分区
type:表示表的连接类型
possible_keys:表示查询时,可能使用的索引
key:表示实际使用的索引
key_len:索引字段的长度
ref:列与索引的比较
rows:扫描出的行数(估算的行数)
filtered:按表条件过滤的行百分比
Extra:执行情况的描述和说明

下面对这些字段出现的可能进行解释:

一、 id

SELECT识别符。这是SELECT的查询序列号

我的理解是SQL执行的顺序的标识,SQL从大到小的执行

\1. id相同时,执行顺序由上至下

\2. 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

\3. id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

1
2
-- 查看在研发部并且名字以Jef开头的员工,经典查询
explain select e.no, e.name from emp e left join dept d on e.dept_no = d.no where e.name like 'Jef%' and d.name = '研发部';

img

二、select_type

\示查询中每个select子句的类型**

(1) SIMPLE(简单SELECT,不使用UNION或子查询等)

(2) PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)

(3) UNION(UNION中的第二个或后面的SELECT语句)

(4) DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)

(5) UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)

(6) SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)

(7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)

(8) DERIVED(派生表的SELECT, FROM子句的子查询)

(9) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

三、table

显示这一步所访问数据库中表名称(显示这一行的数据是关于哪张表的),有时不是真实的表名字,可能是简称,例如上面的e,d,也可能是第几步执行的结果的简称

四、type

对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。

常用的类型有: ALL、index、range、 ref、eq_ref、const、system、**NULL(从左到右,性能从差到好)**

ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行

index: Full Index Scan,index与ALL区别为index类型只遍历索引树

range:只检索给定范围的行,使用一个索引来选择行

ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件

const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system

NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。

五、possible_keys

指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(该查询可以利用的索引,如果没有任何索引显示 null)

该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询

六、Key

key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中

如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

七、key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)

不损失精确性的情况下,长度越短越好

八、ref

列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

九、rows

估算出结果集行数,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数

十、Extra

该列包含MySQL解决查询的详细信息,有以下几种情况:

Using where:不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤

Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by ; order by

Using filesort:当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”

1
2
-- 测试Extra的filesort
explain select * from emp order by name;

Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。

Impossible where:这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)。

Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行

No tables used:Query语句中使用from dual 或不含任何from子句

数据库隔离级别与锁

事务的四种隔离级别

在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库锁,也是为了构建这些隔离级别存在的。

隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)
未提交读(Read uncommitted) 可能 可能 可能
已提交读(Read committed) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
可串行化(Serializable ) 不可能 不可能 不可能
  • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
  • 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
  • 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读
  • 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

Read Uncommitted这种级别,数据库一般都不会用,而且任何操作都不会加锁,这里就不讨论了。

Read Committed(读取提交内容)

在RC级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。效果如下

事务A 事务B
begin; begin;
update class_teacher set class_name=‘初三二班’ where teacher_id=1; update class_teacher set class_name=‘初三三班’ where teacher_id=1;
commit;

为了防止并发过程中的修改冲突,事务A中MySQL给teacher_id=1的数据行加锁,并一直不commit(释放锁),那么事务B也就一直拿不到该行锁,wait直到超时。

Repeatable Read(可重读)

很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。

如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。

所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。

上文说的,是使用悲观锁机制来处理这两种问题,但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。

悲观锁和乐观锁

  • 悲观锁

正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。

  • 乐观锁

相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

要说明的是,MVCC的实现没有固定的规范,每个数据库都会有不同的实现方式,这里讨论的是InnoDB的MVCC。

MVCC在MySQL的InnoDB中的实现

在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在可重读Repeatable reads事务隔离级别下:

  • SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。
  • INSERT时,保存当前事务版本号为行的创建版本号
  • DELETE时,保存当前事务版本号为行的删除版本号
  • UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

通过MVCC,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。

美团

https://tech.meituan.com/2014/08/20/innodb-lock.html

Java基础

发表于 2020-04-05
字数统计 1.8k | 阅读时长 7

###Object有哪些公用方法?

1.clone方法

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法。

2.getClass方法

final方法,获得运行时类型。

3.toString方法

该方法用得比较多,一般子类都有覆盖。

4.finalize方法

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

5.equals方法

该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。

6.hashCode方法

该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

一般必须满足obj1.equals(obj2)==true。可以推出obj1.hashCode()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。

如果不重写hashCode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。

7.wait方法

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

(1)其他线程调用了该对象的notify方法。

(2)其他线程调用了该对象的notifyAll方法。

(3)其他线程调用了interrupt中断该线程。

(4)时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

8.notify方法

该方法唤醒在该对象上等待的某个线程。

9.notifyAll方法

该方法唤醒在该对象上等待的所有线程。

Java 基本数据类型

变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。

内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。

img

因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。

Java 的两大数据类型:

  • 内置数据类型
  • 引用数据类型

内置数据类型

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

byte:

  • byte 数据类型是8位、有符号的,以二进制补码表示的整数;
  • 最小值是 -128(-2^7);
  • 最大值是 127(2^7-1);
  • 默认值是 0;
  • byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
  • 例子:byte a = 100,byte b = -50。

short:

  • short 数据类型是 16 位、有符号的以二进制补码表示的整数
  • 最小值是 -32768(-2^15);
  • 最大值是 32767(2^15 - 1);
  • Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是 0;
  • 例子:short s = 1000,short r = -20000。

int:

  • int 数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是 -2,147,483,648(-2^31);
  • 最大值是 2,147,483,647(2^31 - 1);
  • 一般地整型变量默认为 int 类型;
  • 默认值是 0 ;
  • 例子:int a = 100000, int b = -200000。

long:

  • long 数据类型是 64 位、有符号的以二进制补码表示的整数;
  • 最小值是 -9,223,372,036,854,775,808(-2^63);
  • 最大值是 9,223,372,036,854,775,807(2^63 -1);
  • 这种类型主要使用在需要比较大整数的系统上;
  • 默认值是 0L;
  • 例子: long a = 100000L,Long b = -200000L。 “L”理论上不分大小写,但是若写成”l”容易与数字”1”混淆,不容易分辩。所以最好大写。

float:

  • float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float 在储存大型浮点数组的时候可节省内存空间;
  • 默认值是 0.0f;
  • 浮点数不能用来表示精确的值,如货币;
  • 例子:float f1 = 234.5f。

double:

  • double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
  • 浮点数的默认类型为double类型;
  • double类型同样不能表示精确的值,如货币;
  • 默认值是 0.0d;
  • 例子:double d1 = 123.4。

boolean:

  • boolean数据类型表示一位的信息;
  • 只有两个取值:true 和 false;
  • 这种类型只作为一种标志来记录 true/false 情况;
  • 默认值是 false;
  • 例子:boolean one = true。

char:

  • char类型是一个单一的 16 位 Unicode 字符;
  • 最小值是 \u0000(即为0);
  • 最大值是 \uffff(即为65,535);
  • char 数据类型可以储存任何字符;
  • 例子:char letter = ‘A’;。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//8位
byte bx = Byte.MAX_VALUE;
byte bn = Byte.MIN_VALUE;
//16位
short sx = Short.MAX_VALUE;
short sn = Short.MIN_VALUE;
//32位
int ix = Integer.MAX_VALUE;
int in = Integer.MIN_VALUE;
//64位
long lx = Long.MAX_VALUE;
long ln = Long.MIN_VALUE;
//32位
float fx = Float.MAX_VALUE;
float fn = Float.MIN_VALUE;
//64位
double dx = Double.MAX_VALUE;
double dn = Double.MIN_VALUE;
//16位
char cx = Character.MAX_VALUE;
char cn = Character.MIN_VALUE;
//1位
boolean bt = Boolean.TRUE;
boolean bf = Boolean.FALSE;
`127`
`-128`
`32767`
`-32768`
`2147483647`
`-2147483648`
`9223372036854775807`
`-9223372036854775808`
`3.4028235E38`
`1.4E-45`
`1.7976931348623157E308`
`4.9E-324`
`�`

`true`
`false`

引用类型

  • 在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。
  • 对象、数组都是引用数据类型。
  • 所有引用类型的默认值都是null。
  • 一个引用变量可以用来引用任何与之兼容的类型。
  • 例子:Site site = new Site(“Runoob”)。

Java 常量

常量在程序运行时是不能被修改的。

在 Java 中使用 final 关键字来修饰常量,声明方式和变量类似:

1
final double PI = 3.1415927;

虽然常量名也可以用小写,但为了便于识别,通常使用大写字母表示常量。

字面量可以赋给任何内置类型的变量。例如:

1
2
byte a = 68;
char a = 'A'

自动拆箱和装箱

自动装箱与自动拆箱机制,方便基本类型与包装类型的相互转换操作

计算机网络

发表于 2020-03-10 | 分类于 计算机网络
字数统计 211 | 阅读时长 1

七层体系结构图

从输入URL到页面加载发生了什么

https://segmentfault.com/a/1190000006879700

TCP三次握手四次挥手

image-20200412200113765

image-20200412200541794

https://www.bilibili.com/video/BV1h7411A7tG?from=search&seid=2537784259408108642

HTTPS

  1. 客户端生成一个随机数,然后将随机数、版本协议号、支持的加密方法传给服务器
  2. 服务器确认加密方法,生成一个随机数,还有将数字证书传给客户端
  3. 客户端确认数字证书有效,生成一个新的随机数,并用数字证书里的公钥来加密这个随机数,发给服务器
  4. 服务器用自己的私钥来获取客户端发来的随机数
  5. 服务器和客户端根据约定的加密方法,使用之前生成的三个随机数,生成对话秘钥,来加密接下来的整个对话过程。

Linux复习

发表于 2020-03-07 | 分类于 linux
字数统计 8.1k | 阅读时长 30

第一章

1、简述 Linux 系统的应用领域。

  • Linux服务器
  • 嵌入式Linux系统
  • 软件开发平台
  • 桌面应用

2、简述 Linux 系统的特点。

  • 开放性
  • 多用户
  • 多任务
  • 良好的用户界面
  • 设备独立性
  • 丰富的网络功能
  • 可靠的系统安全
  • 良好的可移植性

3、简述 Linux 系统的组成。

  1. 内核
  2. Shell
  3. 文件系统
  4. 应用程序

4、简述主流的 Linux 系统发行版本。

  • Red Hat
  • SUSE
  • Oracle
  • CentOS
  • Ubuntu
  • Debian
  • Mandriva
  • Gentoo
  • Slackware
  • Fedora

5、简述 RHEL 7 系统的新特性。

  • 包含 Kernel 3.10 版本,支持 swap 内存压缩可保证显著减少 I/O 并提高性能,采用 NUMA(统一内存访问)的调度和内存分配,支持APIC(高级程序中断控制器)虚拟化, 全面的 DynTick 支持,将内核模块列入黑名单,kpatch 动态内核补丁等。
  • 在存储和文件系统方面,RHEL 7 使用 LIO 内核目标子系统,支持快速设备为较慢的块设备提供缓存,引进了 LVM 缓存,将 xfs作为默认的文件系统。
  • 引进网络分组技术作为链路聚集的捆绑备用方法,对 NetworkManager 进行大量改进,提供动态防火墙守护进程 firewalld,加入 DNSSEC 域名系统安全扩展,附带 OpenLMI 用来管理 Linux 系统提供常用的基础设施,引进 了可信网络连接功能等。
  • 对 KVM(基于内核的虚拟化)提供了大量改进,诸如使用 virtio-blk-data-plane 提高快 I/O 性能 (技术预览),支持 PCI 桥接,QEMU 沙箱,多队列 NIC,USB 3.0 支持等。
  • 引入 Linux 容器 Docker。
  • 编译工具链方面,RHEL 7 包含 GCC 4.8.x、glibc 2.17、GDB 7.6.1。
  • 包含 Performance Co-Pilot、SystemTap 2.4、Valgrind 3.9.0 等性能工具。
  • 包含 Ruby 2.0.0、Python 2.7.5、Java 7 等编程语言。
  • 包含 Apache 2.4、MariaDB 5.5、PostgreSQL 9.2 等软件包。
  • 在系统和服务方面,RHEL 7 使用 systemd 替换了 SysV。
  • 引入 Pacemaker 集群管理器,同时使用 keepalived 和 HAProxy 替换了负载均衡程序 Piranha。
  • 对安装程序 Anaconda 进行了重新设计和增强,并使用 GRUB 2 引导装载程序。

第二章

1、简述安装 Linux 系统的硬件要求。

  • CPU:主流计算机和服务器都能达到要求
  • 内存:安装 Linux 系统至少需要 1GB 内存(建议使用 2GB 甚至更高内存)
  • 硬盘空间:若要安装所有软件包至少需要 10GB 以上硬盘空间
  • 显示器和显卡
  • DVD光驱

2、在你的计算机上设计一个合理的分区规划。

  • 最简单的分区规划
swap 分区 即交换分区,实现虚拟内存,建议大小是物理内存的 1~2 倍。
/boot 分区 用来存放与 Linux 系统启动有关的程序,比如引导装载程序等,最少 200MB。
/ 分区 建议大小至少在 10GB 以上。
  • 合理的分区规划
swap 分区 实现虚拟内存,建议大小是物理内存的 1~2 倍。
/boot 分区 建议大小最少为 200MB。
/usr 分区 用来存放 Linux 系统中的应用程序,其相关数据较多,建议大小最少为 8GB。
/var 分区 用来存放 Linux 系统中经常变化的数据以及日志文件,建议大小最少为 1GB。
/ 分区 Linux 系统的根目录,所有的目录都挂在这个目录下面,建议大小最少为 1GB。
/home 分区 存放普通用户的数据,是普通用户的宿主目录,建议大小为剩下的空间。

3、简述分区命名方案。

  • Linux 系统使用字母和数字的组合来指代硬盘分区,使用一种更加灵活的命名方案,该命名方案是基于文件的,文件名的格为 /dev/xxyN。
/dev 这是 Linux 系统下所有设备文件所在的目录名。
xx 分区名的前两个字母表示分区所在设备的类型,通常是 hd(IDE硬盘)或 sd(SCSI硬盘)。
y 这个字母表示分区所在的设备。
N 最后的数字N代表分区。

4、简述在安装 Linux 系统时设置计算机 IP 地址的方法。

5、FirewallID 防火墙的默认连接区域是什么?

  • 默认情况下 FirewallD 防火墙的连接区域为 public,public 在公共区域内使用,指定外部连接可以进入内部网络或主机。

第三章

1、进入字符界面有哪些方式?

  1. 可以通过使用如下命令修改为进入字符界面
    • systemctl set-default multi-user.target
  2. 打开终端命令行界面的方式,终端方式允许用户通过输入命令来管理计算机。
  3. 如果用户在图形界面下,那么可以使用 [Ctrl+Alt+F2]~[Ctrl+ Alt+F6] 组合键切换字符虚拟控制台,使用 [Ctrl+Alt+F1] 可以切换到图形界面。

2、可以使用哪些命令关闭计算机系统?

  • shutdown
    • 可以安全地关闭或重启 Linux 系统。
  • halt
    • halt 命令就是调用 shutdown –h 命令执行关机任务。
  • init
    • 所有进程的祖先,其中 init 0 为关闭系统。

3、简述 Linux 系统中的目标概念。

  • 在 RHEL 7 之前的版本,使用运行级别代表特定的操作模式。运行级别被定义为七个级别,用数字 0~6 表示,每个运行级别可以启动特定的一些服务。
  • RHEL 7 使用目标(target)替换运行级别。目标使用目标单元文件描述,目标单位文件扩展名是 .target,目标单元文件的唯一目标是将其他 systemd 单元文件通过一连串的依赖关系组织在一起。比如 graphical.target 单元,用于启动一个图形会话 systemd 会启动像 GNOME 显示管理(gdm.service)、帐号服务(axxounts-daemon)这样的服务,并且会激活 multi-user.target 单元。相似的 multi-user.target 单元,会启动必不可少的 NetworkManager.service、dbus.service 服务并激活basic.target 单元。
  • 每一个目标都有名字和独特的功能,并且能够同时启用多个。一些目标继承其他目标的服务,并启动新服务。systemd 提供了一些模仿 System V init 启动级别的目标,仍可以使用旧的 telinit 启动级别命令切换。
运行级别 目标 目标的链接文件 功能
0 poweroff.target runlevel0.target 关闭系统
1 rescue.target runlevel1.target 进入救援模式
2 multi-user.target runlevel2.target 进入非图形界面的多用户方式
3 multi-user.target runlevel3.target 进入非图形界面的多用户方式
4 multi-user.target runlevel4.target 进入非图形界面的多用户方式
5 graphical.target runlevel5.target 进入图形界面的多用户方式
6 reboot.target runlevel6.target 重启系统

4、在 Linux 系统中获取帮助有哪些方式?

  1. 使用 man 手册页
    • man [选项] [命令名称]
  2. 使用 help 命令
    • [命令] --help
  3. 使用 whereis 命令
    • whereis [选项] [命令名]

5、有哪些重定向方式?

输出重定向 command > file
输入重定向 command < file
错误重定向 command 2> file
追加重定向 command >> file
同时实现输出和错误的重定向 command &> file

6、简述 vi 编辑器的工作模式。

vi编辑器有3种基本工作模式,分别是命令行模式、插入模式和末行模式。

  • 命令行模式
    • 控制屏幕光标的移动,字符、字或行的删除,移动、复制某区域及进入插入模式,或者到末行模式。
  • 插入模式
    • 只有在插入模式下,才可以做文字输入,按 Esc 键可回到命令行模式。
  • 末行模式
    • 将文件保存或退出 vi 编辑器,也可以设置编辑环境,如寻找字符串、列出行号等。

第四章

1、Linux 系统中有哪些文件类型?

普通文件、目录文件、设备文件、管道文件和符号链接文件等。

2、简述软链接文件和硬链接文件的区别。

  • 硬链接记录的是目标的 inode,软链接记录的是目标的路径。
  • 软链接就像是快捷方式,而硬链接就像是备份。
  • 软链接可以做跨分区的链接,而硬链接由于 inode 的缘故,只能在本分区中做链接。
  • 所以软链接的使用频率要高得多。

3、简述 Linux 系统中的文件目录。

  • Linux 系统都有根文件系统,它包含系统引导和使其他文件系统得以挂载所必要的文件,根文件系统需要有单用户状态所必须的足够的内容,还应该包括修复损坏系统、恢复备份等工具。

  • Linux 系统的目录结构是分层的树形结构,都是挂载在根文件系统 / 下。

4、简述使用 ls -l 命令显示的详细信息。

-rw-r--r-- 1 h2p staff 230 Nov 25 21:33 test.sh

第 1 列 第 1 个字符表示文件的类型
第 2~4 个字符表示文件所有者对此文件的访问权限
第 5~7 个字符表示用户组对此文件的访问权限
第 8~10 个字符表示其他用户对此文件的访问权限
第 2 列 文件的连接数
第 3 列 文件的所有者
第 4 列 文件的用户组名
第 5 列 文件所占的字节数
第 6~8 列 文件上一次的修改时间
第 9 列 文件名

5、使用什么命令可以删除具有子目录的目录?

rm -r {dirPath}

第五章

1、常用的文本内容显示命令有哪些?区别是什么?

这些命令有:cat、more、less、head、tail。

命令 作用
cat 显示文本文件的内容,也可以把几个文件内容附加到另一个文件中。
more 分页显示文本文件的内容。类似于 cat 命令,不过是以分页方式显示文件内容,方便使用者逐页阅读。
less 回卷显示文本文件的内容。less 命令的作用与 more 十分相似,都可以用来浏览文本文件的内容,不同的是 less 命令允许使用者往回卷动。
head 显示指定文件的前若干行文件内容。如果没有给出具体行数值,默认缺省设置为 10 行。如果没有指定文件,head 就从标准输入读取。
tail 查看文件的末尾数据,默认显示指定文件的最后 10 行到标准输出。

2、常用的文本处理命令有哪些?区别是什么?

命令 作用
sort 对文件中的数据进行排序,并将结果显示在标准输出上。
uniq 将文件内的重复行数据从输出文件中删除,只留下每条记录的惟一样本。
cut 从文件的每行中输出选定的字节、字符或字段(域)。
comm 比较两个已排过序的文件,并将其结果显示出来。
diff 逐行比较两个文本文件,列出其不同之处。

3、使用什么命令能显示当前计算机的内核版本?

uname -r

4、使用什么命令能清除计算机屏幕信息?

clear

5、使用什么命令可以以倒序方式排序文件内容?

sort -t

第六章

1、简述一个简单 Shell 程序的创建过程。

  1. 使用 vi 编辑文件,文件第一行为 #!/bin/bash (#! 后面表示用来执行该文件的程序)。
  2. 执行 chmod +x 命令设置可执行权限。
  3. 执行 Shell 程序。

2、简述执行 Shell 程序的方法。

  • 执行 chmod +x 设置文件执行权限,输入文件完整路径执行 Shell 程序。
  • 如果不设置可执行权限,需要执行 bash {filePath} 来执行 bash 程序。

3、简述常见的 Shell 环境变量。

$HOME 用户主目录的完全路径名
$PATH 用于保存用冒号分隔的目录路径名,Shell 将按 PATH 变量中给出的顺序搜索这些目录,
找到的第一个与命令名称一致的可执行文件将被执行。
$TERM 终端的类型
UID 当前用户的 UID,由数字构成
PWD 当前工作目录的绝对路径
PS1 主提示符。在 root 用户下默认的主提示符是 #,在普通用户下默认的主提示符是 $
PS2 辅助提示符,提示用户继续输入命令的其余部分,默认的辅助提示符是 > 。
在 Shell 接收用户输入命令的过程中,如果用户在输入行的末尾输入 \ 然后按回车键,
或者当用户按回车键时 Shell 判断出用户输入的命令没有结束时,就显示这个辅助提示符。

4、简述常用的字符串比较符号。

  • 字符串比较是用来测试字符串是否相同、长度是否为0、字符串是否为 null。
字符串比较符号 描述
= 比较两个字符串是否相同,相同则为“是”
!= 比较两个字符串是否相同,不同则为“是”
-n 比较字符串的长度是否大于 0,如果大于 0 则为“是”
-z 比较字符串的长度是否等于 0,如果等于 0 则为“是”
数字比较符号 描述
-eq $=$
-ge $\ge$
-le $\le$
-ne $\ne$
-gt $\gt$
-lt $\lt$
逻辑测试符号 描述
! 非
-a 与
-o 或
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
myZero=0
myOne=1
myTwo=2

test $myZero -eq 0 -a $myOne -eq 2
echo $?

test $myZero -eq 0 -a $myOne -eq 1
echo $?

# 1
# 0

5、Linux 系统中有哪些条件判断语句和循环控制流程语句?

  • if 条件语句

    • linux shell 之if——-用if做判断

    • if-then-fi

      • if {command}
        then
        {command}
        fi
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        - `if-then-else-fi`

        - ```bash
        if {command}
        then
        {command}
        else
        {command}
        fi
    • #!/bin/bash
      
      echo -n "Input 0 or 1 : "
      read inputNum
      
      if [ $inputNum -eq 0 ]
      then
      echo 'Input is 0.'
      elif [ $inputNum = 1 ]; then
      echo "Input is 1."
      else
      echo "Invalid num : $inputNum."
      fi
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - `case` 条件语句

      - ```bash
      case {param} in
      {exp-1}) {command};;
      {exp-2}) {command};;
      ...;;
      *) {command}
      esac
      1
      2
      3
      4
      5
      6
      7
      8
      #!/bin/bash
      myStr=abc
      case $myStr in
      a) echo 'myStr is a';;
      ab) echo 'myStr is ab';;
      abc) echo 'myStr is abc';;
      *) echo $myStr
      esac
    • 通过计算字符串 param 的值,将其结 果依次与运算式 exp-1 和 exp-2 等进行比较, 直到找到一个匹配的运算式为止。如果找到了匹配项,则执行它下面的命令直到遇到一对分号 ;; 为止。

6、简述 if 条件语句和 case 条件语句的区别。

  • 使用 case 语句的选项比较多

第七章

1、在 Linux 系统中用户账户有哪些分类?

  • root用户、系统用户和普通用户

2、管理用户账户的配置文件有哪些?并描述这些文件各字段的含义。

  • /etc/passwd
    • 每一行表示的是一个用户账户的信息,一行有 7 个段位,每个段位用 : 分隔。
    • zhangsan:x:1000:1000:张三:/home/zhangsan:/bin/bash
字段 字段含义
用户名 也称为登录名,在系统内用户名应该具有惟一性。在本例中, zhangsan 就是用户名。
密码 存放加密用户的密码,看到的是一个 x,其实密码已被映射到 /etc/shadow 文件中。
用户标识号(UID) 在系统内用一个整数标识用户 ID 号,每个用户的 UID 都是惟一 的,root 用户的 UID 是 0,普通用户的 UID 默认从 1000 开始,本例中的用户 zhangsan 的 UID 是 1000。
群组标识号(GID) 在系统内用一个整数标识用户所属的主要组群 ID 号,每个组群的 GID 都是惟一的。
用户名全称 用户名描述,可以不设置。在本例中,zhangsan 用户的用户名全称是张三。
主目录 用户登录系统后首先进入的目录,zhangsan 用户的主目录是 /home/zhangsan。
登录 Shell 用户使用的 Shell 类型,Linux 系统默认使用的 Shell 是 /bin/bash。
  • /etc/shadow
    • zhangsan:$6$E/xvWMmh$rhYLQwwffEqIudVLFzMlvkb0iN4.0Ol uk6H.UovEYN0/99dVoHXcaCNGZZkFY1S3QHYgm7e6JPzEew6ybmN 4e0:16364:0:99999:7:::
字段 字段含义
用户名 这里的用户名和 /etc/passwd 中的用户名是相同的。
加密密码 密码已经加密,如果有些用户在这里显示的是“!!”,则表示这个用户还没有设置密码,不能登录到系统。
用户最后一次更改密码的日期 从1970年1月1日算起到最后一次修改密码的时间间隔天数。
密码允许更换前的天数 如果设置为 0,则禁用此功能。该字段是指用户可以更改密码的天数。
密码需要更换的天数 如果设置为 0,则禁用此功能。该字段是指用户必须更改密码的天数。
密码更换前警告的天数 用户登录系统后,系统登录程序提醒用户密码将要过期。
账户被取消激活前的天数 表示用户密码过期多少天后,系统会禁用此用户,也就是说系统会不让此用户登录,也不会提示用户过期,是完全禁用的。
用户账户过期日期 指定用户账户禁用的天数(从1970年的1月1日开始到账户被禁用的天 数),如果这个字段的值为空,账户永久可用。
保留字段 目前为空,以备将来Linux系统发展时使用。

3、管理组群账户的配置文件有哪些?并描述这些文件各字段的含义。

  • /etc/group
    • zhangsan:x:1000:
字段 字段含义
组群名 组群名称,如组群名 root。
组群密码 存放加密的组群密码,看到一个 x,密码已被映射到 /etc/gshadow 文件中。
组群标识号(GID) 在系统内用一个整数标识组群 GID,每个组群的 GID 都是惟一的,默认普通组群的 GID 从 1000 开始, root 组群 GID 是 0。
组群成员 属于这个组群的成员,如 root 组群的成员有 root 用户。
  • /etc/gshadow
    • beijing:$6$E/xvWMmh$rhYLQwwffEqIudVLFzMlv1::ou
字段 字段含义
组群名 组群的名称
组群密码 密码已经加密,如果有些组群在这里显示的是 “!”,表示这个组群没有密码。本例中组群 shanghai 没有密码,组群 beijing 已设置密码。
组群管理员 组群的管理者,有权在该组群中添加、删除用户。
组群成员 属于该组群的用户成员列表,如有多个用户用逗号分隔。本例中 beijing 组群的成员是 ou。

4、默认情况下新创建的第一个用户账户 UID 是多少?

  • 新创建的用户账户默认是被锁定的,无法使用,需要使用 passwd 命令设置密码以后才能使用。
    • useradd zhangsan
    • passwd zhangsan
    • usermod zhangsan
    • userdel zhangsan
  • 默认情况下新创建的第一个用户账户 UID 是 1000。

5、简述对用户账户设置密码和不设置密码的区别。

  • 用户账户未设置密码,不能登录到系统。

第八章

1、简述磁盘分区的含义。

  • 磁盘分区是指对硬盘物理介质的逻辑划分。
  • 分区就是磁盘的“段落”,如果用户希望在计算机上安装多个操作系统,将需要更多的分区。

2、简述格式化的含义。

  • 格式化是指对磁盘分区进行初始化的一种操作,这种操作通常会导致现有的分区中所有的数据被清除。
  • 格式化是在磁盘中建立磁道和扇区,建立好之后,计算机才可以使用磁盘来储存数据。

3、fdisk 命令有哪些子命令?其含义分别是什么?

子命令 功能
m 显示所有能在fdisk中使用的子命令
p 显示磁盘分区信息
a 设置磁盘启动分区
n 创建新的分区
e 创建扩展分区
p 创建主分区
t 更改分区的系统ID(也就是分区类型ID)
d 删除磁盘分区
q 退出fdisk,不保存磁盘分区设置
l 列出已知的分区类型
v 验证分区表
w 保存磁盘分区设置并退出 fdisk

4、Linux 系统中常用的文件系统有哪些?

  • XFS、ext4、JFS、ReiserFS、ext2、 ext3、ISO9660、MSDOS,VFAT、NFS 等

5、使用新磁盘存储数据一般要经过哪些操作?

  1. 对磁盘进行分区
  2. 格式化分区
  3. 创建文件系统
  4. 挂载文件系统

6、要实现开机自动挂载文件系统,可以通过哪些方式来实现?

  1. 修改 /etc/fstab 文件
    • /dev/sda5 /mnt/kk ext4 defaults 0 0
      • 设备、挂载目录、文件系统类型、挂载选项、转储选项(0 表示文件系统不需要备份)、文件系统检查选项(0 表示不检查文件系统)
      • 其中设备
        • 使用设备名:/dev/sda5
        • 使用 UUID:UUID=f084b8a6-c9ab-425b-99da-b64cb35e4fc2
        • 使用卷标:LABEL=www
          • xfs_admin -l /dev/sda5 查看卷标信息

第九章

1、使用 RPM 软件包管理的用途是什么?

  • RPM(Red Hat Package Manager,Red Hat 软件包管理器)是一种开放的软件包管理系统,按照 GPL 条款发行,可以运行于各种 Linux 系统上。
  • RPM 简化了 Linux 系统安装、卸装、更新和升级的过程,只需要使用简短的命令就可完成。

2、简述升级 RPM 软件包和刷新 RPM 软件包的区别。

  • 使用 rpm -Uvh 命令可以在 Linux 系统中升级 RPM 软件包,升级软件包是删除和安装的组合。不管该软件包的早期版本是 否已被安装,升级选项都会安装该软件包。
  • 使用 rpm -Fvh 命令可以在 Linux 系统中刷新 RPM 软件包。使用 RPM 刷新软件包时,系统会比较指定的软件包的版本和系统上已安装的版本。当 RPM 的刷新选项处理的版本比已安装的版本更新,它就会升级到更新的版本。如果软件包先前没有安装,RPM 的刷新选项将不会安装该软件包,这和 RPM 的升级选项不同。

3、简述在本地磁盘上创建本地软件仓库的步骤。

  1. 安装 deltarpm、python-deltarpm 和 createrepo 软件包。
  2. 复制 Linux 系统安装光盘中的软件包。
  3. 创建软件仓库配置文件。
  4. 使用 createrepo 命令创建软件仓库。

4、tar 命令可以调用哪些压缩程序?

  • tar cvf {name}.tar {filePath}
  • tar xvf {name}.tar
  • 调用 gzip
    • 使用 -z 选项来调用 gzip
    • tar -zcvf {name}.tar.gz {filePath}
  • 调用 bzip2
    • 使用 -j 选项来调用 bzip2
    • tar -jcvf {name}.tar.gz {filePath}
  • 调用 xz
    • 使用 -J 选项来调用
    • tar -Jcvf {name}.tar.gz {filePath}

第十章

1、文件有哪些权限?其含义分别是什么?

  • 三种用户访问
    • 文件所有者、文件的组群所有者、系统中的其他用户
基本权限 含义
r 对文件而言,该用户具有读取文件内容 的权限;对目录来说,该用户具有浏览目录的权 限;
w 对文件而言,该用户具有新增、修改 文件内容的权限;对目录来说,该用户具有删除、移动目录内文件的权限;
x 对文件而言,该用户具有执行文件的 权限;对目录来来说,该用户具有进入目录的权限;
特殊权限 含义
SUID(u+s) 对一个可执行文件,不是以发起者身份来获取资源,而是以可执行文件的用户所有者身份来执行;对一个目录无影响。
SGID(g+s) 对一个可执行文件,不是以发起者身份来获取资源,而是以可执行文件的组群所有者身份来执行;对一个目录,在该目录中创建的任意新文件的所属组与该目录的所属组相同。
Sticky(o+t) 对一个可执行文件无影响;对目录设置 Sticky 之后,尽管其它用户有写权限,也必须由文件所有者执行删除和移动等操作。

2、可以使用哪些方法设置文件的权限?

  1. 文字设定法
    • chmod [操作对象] [操作符号] [权限] [文件|目录]
    • chmod [ugoa] [+-=] [rwx] [file|dir]
    • chmod u+w a.txt
  2. 数字设定法
    • chmod [n1n2n3] [文件|目录]
    • n1 表示用户所有者的权限 ,n2 表示组群所 有者的权限,n3 表示其它用户的权限。

第十一章

1、简述 Linux 系统的进程分类。

  • 交互式进程
    • 一个由 Shell 启动并控制的进程,交互式进程既可在前台运行,也可在后台运行。
  • 批处理进程
    • 与终端无关,安排在指定时刻完成的一系列进程。
  • 守护进程
    • 在引导系统时启动,以执行即时的操作系统任务,比如 crond、rsyslogd、named 等。

2、简述 Linux 系统的启动过程。

  1. BIOS 自检
  2. 启动 GRUB 2
  3. 加载内核
  4. 执行 systemd 进程
  5. 初始化系统环境
  6. 执行 /bin/login 程序

3、简述 GRUB 2 所具有的新功能。

  • 采用模块化动态加载的思想。
  • 图形接口。
  • 使用模块机制,通过动态加载需要的模块来扩展功能。
  • 支持脚本语言,比如条件判断、循环、变量和函数。
  • 支持救援模式,可以用于系统无法引导的情况。
  • 国际化语言。包括支持非 ASCII 的字符集和类似 gettext 的消息分类、字体、图形控制台等。
  • 有一个灵活的命令行接口。如果不存在配置文件, GRUB 2 会自动进入命令模式。
  • 针对文件系统、文件、设备、驱动、终端、命令、分 区表、系统加载的模块化、层次化、基于对象的框架。
  • 支持多种文件系统格式。
  • 可以访问已经安装在设备上的数据。
  • 支持自动解压。

4、简述 GRUB 2 密码支持的两种格式。

  • 明文密码
    • 密码数据没有经过加密,安全性差。
  • PBKDF2 加密密码
    • 密码经过 PBKDF2 哈希算法进行加密,在文件中存储的是加密后的密码数据,安全性较高。

第十二章

1、简述网卡配置文件的内容。

  • 网卡配置文件保存在 /etc/sysconfig/network-scripts 目录下,一个网卡对应一个配置文件,以 ifcfg- 开头。
关键字 含义
DEVICE 网卡名称
HWADDR Mac 地址、硬件地址
TYPE 网络类型
BOOTPROPT 网卡获取 IP 地址的方式
IPADDR ip 地址
NETMASK 子网掩码
GATEWAY 网关

2、测试网络连通可以使用哪些命令?

  • ping [选项] [目标]
  • netstat -antu | grep [端口号]
  • traceroute [目标]

3、DNS 服务使用什么端口号?

  • DNS 协议运行在 UDP 协议之上,使用端口号 53。

第十三章

1、简述 OpenSSH 替代 telnet 的主要原因。

  • 使用 OpenSSH 工具将增进系统安全性,在使用 OpenSSH 软件进行通信时,登录验证口令将会被加密。
  • OpenSSH 提供了服务端后台程序和客户端工具,用来加密远程控件和文件传输过程中的数据。
  • Telnet 使用纯文本口令,并以明文发送。这些信息可能被截取,口令 kennel 会被检索。
  • OpenSSH 会自动把 DISPLAY 变量转发给客户主机。

2、简述 VNC 软件的组成部分。

  • VNC 软件要由两个部分组成
    • 服务端的 VNC server
    • 客户端的 VNC viewer
  • 用户需要先将 VNC server 安装在被远程操控的计算机上后,才能在主控端执行 VNC viewer 进行远程操控。

第十四章

1、简述 NFS 的含义。

  • NFS(Network File System,网络文件系统)对于在同一个网络上的多个用户间共享目录和文件很有用途。
  • 通过使用 NFS,用户和程序可以像访问本地文件一样访问远程系统上的文件。

2、简述 /etc/exports 文件内容的格式。

  • /etc/exports 文件控制着 NFS 服务器要导出的共享目录以及访问控制。
  • 文件内容的格式:共享目录 客户端 [导出选项]
  • 客户端机器可以是一台计算机,也可以是一个网段,甚至是一个域。
  • 导出选项用来设置客户端访问 NFS 服务器共享目录的权限

补充习题

1、 什么是 LVM?

  • Logical Volume Manager 逻辑卷管理
  • 它是 Linux 环境下对磁盘分区进行管理的一种机制。屏蔽了底层磁盘布局,便于动态调整磁盘容量。

2、 什么是 PV、VG、LV?

PV(Physical Volume) 物理卷:整个磁盘,或使用 fdisk 等工具建立的普通分区
VG(Volume Group) 卷组:一个或多个物理卷组合而成的整体
LV(Logical Volume) 逻辑卷:从卷组中分割出的一块空间,用于建立文件系统

3、 PV、VG、LV 之间的相互关系?

  • PV 基本单元为 4MB 大小的 PE(Physical Extent)。
  • VG 由 N 个 PV 组成。
  • LV 在 VG 的基础上进行分割。

4、 逻辑卷是否可以在线扩容、缩小?

  • 逻辑卷扩容可以在线操作但是缩减必须是离线执行,需要先卸载已挂载的逻辑卷。
    • lvextend -L +大小 /dev/卷组名/逻辑卷名
    • lvreduce -L +大小 /dev/卷组名/逻辑卷名

5、 文件系统是否可以在线扩容和缩小?

  • 逻辑卷扩容可以在线操作但是缩减必须是离线执行,需要先卸载已挂载的逻辑卷。
    • resize2fs -p 逻辑卷设备名

6、 缩小逻辑卷或文件系统应该注意哪些?

  • 扩容逻辑卷我们是先扩大逻辑卷再扩大文件系统,而缩小逻辑卷就需要我们反着来,我们要先缩小文件系统,再去缩小逻辑卷。
  • 缩小文件系统前我们要先对文件系统做个扫描检测,如果我们直接使用 resize2fs 系统会提示让你先对文件系统进行检测,这是为了保护缩减文件系统时不对数据损害。

7、 如何创建逻辑卷?

PV -> VG -> LV -> 格式化,挂载使用文件系统
转换物理卷 -> 创建卷组 -> 创建逻辑卷 -> 格式化并挂载
pvcreate 设备名
vgcreate -s 8M 卷组名 物理卷名1 物理卷名2
lvcreate -L 大小 -n 逻辑卷名 卷组名
umount 文件夹:卸载文件系统
mkfs -t 文件系统 文件夹: 重新格式化
vim /etc/fstab: 修改fstan文件
mount -a: 重新挂载

8、 卷组的 PE 默认大小是多少?

  • 4MB

9、 什么是 SELinux?有什么优点?

  • SELinux 是一组可确定哪个进程能访问文件、目录、端口等的安全规则。是美国国家安全局对于强制访问控制的实现,是 Linux 上最杰出的新安全子系统。
  • 优点
    • 对访问的控制彻底化,对于所有文件、目录和端口都可以设定访问策略。
    • 对于进程只赋予最小的权限。
    • 防止权限升级。

10、 SELinux 的两种状态分别是什么?

  • 状态 0:permissive
    • 不会阻止访问,但会记录日志
  • 状态 1:enforcing
    • 强制开启,每个受限的进程都必然受限

11、 如何修改 SELinux 模式?

  • setenforce 0|1

12、 SELinux 安全上下文的作用是什么?

  • 在 SELinux 中访问控制属性基本上就是安全上下文。
  • 所有的客体和主体有一个相关的安全上下文。
  • SELinux 使用进程和客体的安全上下文来进行访问控制。

13、 SELinux 安全上下文格式是什么?

  • 一个安全上下文由三部分组成:用户、角色和类型标识符

14、 如何修改 SELinux 安全上下文?

  • chcon -t 安全上下文 文件

15、 SELinux 布尔值的概念?

  • SELinux 布尔值是更改 SELinux 策略行为的开关。
  • SELinux 布尔值是可以启用或禁用的规则。
  • 安全管理员可以使用 SELinux 布尔值来调整策略。

16、 SELinux 如何修改布尔值?

  • setsebool P 功能bool值 on|off

passwdtest

发表于 2020-03-07
字数统计 0 | 阅读时长 1
12<i class="fa fa-angle-right"></i>
HuangYunfei

HuangYunfei

11 日志
8 分类
9 标签
GitHub E-Mail
Links
  • WaterH2P
  • Shelton Lee
© 2020 HuangYunfei
本站访客数:
博客全站共22.5k字