1. 基本数据类型和包装类
基本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | (byte)0 | Byte |
short | 2 | (short)0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0d | Double |
boolean | 1 | false | Boolean |
char | 2 | \u0000(null) | Character |
2. int 和Integer所占字节大小一样吗?
int和integer 占用内存一样,都是4个字节。
丢失精度
不能直接使用Bigdecimal的构造函数传double进行转换,部分数值会丢失精度,因为计算机是二进制的,Double无法精确的储存一些小数位。
解决方法 Double转BIgdecimal的两种常用方式:
==
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是同一个对象。比较的是真正意义上的指针操作。
equals
equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。
1. 定义
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
2. 反射的实现方式
获取Class对象,有4中方法:
3. 获取对象的信息
4. 反射机制的优缺点
优点
缺点
性能低的解决方案:
1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度;
2、多次创建一个类的实例时,有缓存会快很多
3、ReflectASM工具类,通过字节码生成的方式加快反射速度
1. 对象克隆
使用clone()方法,这个对象必须要实现Cloneable接口,并且重写clone方法
2. 浅拷贝和深拷贝
浅拷贝:被复制对象的所有值都与原对象相同,而所有的对象引用仍然指向原来的对象。
深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象。
直接使用对象的clone方法,执行的是浅拷贝;
要实现深拷贝,除了调用Object中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来。这就要求被引用的对象也必须要实现Cloneable接口,重写clone方法。
1. 序列化
在类定义时实现java.io.serializable接口即可,Java会自动序列化。需要注意的,被标为transient和static的属性是不会被序列化的。
2. 序列化对象
ObjectOutputStream类用来序列化一个对象,writeObject()方法 可以将Java对象序列化到一个文件中(.ser)。
3. 反序列化对象
ObjectInputStream类用来反序列化一个对象,readObject()方法 可以将.ser文件反序列化为Java对象。
readObject() 方法中的 try/catch代码块需尝试捕获 ClassNotFoundException 异常。对于 JVM 可以反序列化对象,它必须是能够找到字节码的类
使用final修饰变量,要注意:
1. static 关键字的使用场景
2. static变量、成员变量、构造函数 三者的初始化顺序
先初始化父类static --> 再初始化子类的static --> 再初始化父类的其他成员变量 -->父类构造方法 -->子类其他成员变量 -->子类的构造方法。
示例:这段代码的输出结果是什么?
public class Test {
Person person = new Person("Test");
static{
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
static{
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static{
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
过程分析:
1.找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类
2.加载Test类的时候,发现Test类有static块,先执行static块,输出test static结果
3.然后执行new MyClass(),执行此代码之前,先加载MyClass类,发现MyClass类继承Test类,要先加载Test类,Test类之前已加载
4.加载MyClass类,发现MyClass类有static块,先执行static块,输出myclass static结果
5.然后调用MyClass类的构造器生成对象,在生成对象前,需要先初始化父类Test的成员变量,执行Person person = new Person(“Test”)代码,发现Person类没有加载
6.加载Person类,发现Person类有static块,先执行static块,输出person static结果
7.接着执行Person构造器,输出person Test结果
8.然后调用父类Test构造器,输出test constructor结果,这样就完成了父类Test的初始化了
9.再初始化MyClass类成员变量,执行Person构造器,输出person MyClass结果
10.最后调用MyClass类构造器,输出myclass constructor结果,这样就完成了MyClass类的初始化了。
3. static的访问限制
在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。
4. static的垃圾回收
static方法是属于类的,非实例对象,在JVM加载类时,就已经存在内存中,不会被虚拟机GC回收掉,这样内存负荷会很大。非static方法会在运行完毕后被虚拟机GC掉,减轻内存压力。
5. static关键字的误区
抽象类的特点
接口的特点
接口可以包含变量、方法;变量被隐式指定为public static final,方法被隐式指定为public abstract(JDK1.8之前);
接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
JDK1.8中对接口增加了新的特性:
默认方法(default method)
:JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;
静态方法(static method)
:JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。
抽象类和接口的区别
区别 | 抽象类 | 接口 | |
---|---|---|---|
成员区别 | 成员变量 | 可以变量,也可以常量 | 只可以常量 |
构造方法 | 有 | 无 | |
成员方法 | 可以抽象,也可以非抽象 | 只可以抽象 (JDK1.8后提供了默认方法和静态方法) | |
关系区别 | 类与类 | 继承(extends),单继承 | |
类与接口 | 实现(implements),单实现,多实现 | ||
接口与接口 | 继承(extends),单继承,多继承 | ||
设计理念区别 | 被继承体现的是:”is a”的关系。抽象类中定义的是该继承体系的共性功能 | 被实现体现的是:”like a”的关系。接口中定义的是该继承体系的扩展功能 |
List(列表)
List的元素以线性方式存储,可以存放重复对象,List主要有以下两个实现类:
ArrayList
: 长度可变的数组,可以对元素进行随机的访问,向ArrayList中插入与删除元素的速度慢。JDK8中ArrayList扩容的实现是通过grow()方法里使用语句newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍扩容)计算容量,然后调用Arrays.copyof()方法进行对原数组进行复制。
LinkedList
: 采用链表数据结构,插入和删除速度快,但访问速度慢。
Set(集合)
Set中的对象不按特定(HashCode)的方式排序,并且没有重复对象,Set主要有以下两个实现类:
HashSet
:HashSet按照哈希算法来存取集合中的对象,存取速度比较快。当HashSet中的元素个数超过数组大小*loadFactor(默认值为0.75)时,就会进行近似两倍扩容(newCapacity = (oldCapacity << 1) + 1)。
TreeSet
:TreeSet实现了SortedSet接口,能够对集合中的对象进行排序。
Map(映射)
Map是一种把键对象和值对象映射的集合,键对象不可重复,它的每一个元素都包含一个键对象和值对象。Map主要有以下实现类:
HashMap
:HashMap基于散列表实现,其插入和查询
LinkedHashMap
:类似于HashMap,但是迭代遍历它时,取得
TreeMap
:TreeMap基于红黑树实现。查看
1. 在 for 循环中使用 entrySet (最常见和最常用的)
for (Map.Entry<String, String> entry : map.entrySet()) {
String mapKey = entry.getKey();
String mapValue = entry.getValue();
System.out.println(mapKey + ":" + mapValue);
}
2. 使用 for循环遍历 key 或者 values,一般适用于只需要 Map 中的 key 或者 value 时使用。性能上比 entrySet 较好。
// 打印键集合 keySet
for (String key : map.keySet()) {
System.out.println(key);
}
// 打印值集合 values
for (String value : map.values()) {
System.out.println(value);
}
3. 使用迭代器(Iterator)遍历
Iterator<Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> entry = it.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ":" + value);
}
4. 通过键找值遍历,这种方式的效率比较低
for(String key : map.keySet()){
String value = map.get(key);
System.out.println(key+":"+value);
}
HashMap | HashTable | |
---|---|---|
父类 | AbstractMap | Dictionary |
线程安全性 | 线程不安全 | 线程安全 |
效率 | 效率高 | 效率低 |
对null的支持 | key可以为null,且只能有一个;可以有多个value为null | key和value都不能为null |
初始容量和扩容 | 默认大小11,扩容变为2n+1 | 默认大小16,扩容变为2n |
计算hash值的方式 | (h = key.hashCode()) ^ (h >>> 16) | key.hashCode() |
1. 存储结构
在JDK1.7 中,由“数组+链表”组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突。
在JDK1.8 中,由“数组+链表+红黑树”组成。当链表过长,则会严重影响 HashMap 的性能,红黑树搜索时间复杂度是 O(logn),而链表是 O(n)。链表和红黑树在达到一定条件会进行转换:
2. 不用红黑树,用二叉查找树可以么?
可以。但是二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),数据量大时遍历查找会非常慢。
3. 默认加载因子是多少?为什么是 0.75,不是其他值?
table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳键值对的最大值。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。
默认的loadFactor是0.75,0.75是对空间和时间效率的一个平衡选择,较高的值会降低空间开销,但提高查找成本,一般不要修改,除非在时间和空间比较特殊的情况下 :
4. HashMap 中 key 的存储索引是怎么计算的?
5. HashMap 的put方法流程?
6. HashMap 的扩容方式?
Hashmap 在容量超过负载因子所定义的容量之后,就会扩容。Java 里的数组是无法自动扩容的,方法是将 Hashmap 的大小扩大为原来数组的两倍,并将原来的对象放入新的数组中。
JDK1.7中,使用头插法,需要重新计算hash值
JDK1.8中,使用尾插法,不需要重新计算hash值,扩容后元素的位置在原来的位置,或者原来的位置 +oldCap (原来哈希表的长度)上。
7. HashMap 链表是怎么转红黑树的?
①使用treeifyBin()方法转红黑树判断。先根据数组长度判断是进行扩容还是转红黑树操作。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 数组长度小于64时,就先进行扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 如果新增的node 要插入的数组位置已经有node存在了,取消插入操作
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 步骤一:遍历链表中每个节点,将Node转化为TreeNode
// hd指向头节点,tl指向尾节点
TreeNode<K,V> hd = null, tl = null;
do {
// 将链表Node转换为红黑树TreeNode结构
TreeNode<K,V> p = replacementTreeNode(e, null);
// 以hd为头结点,将每个TreeNode用prev和next连接成新的TreeNode链表
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
// 步骤二:如果头结点hd不为null,则对TreeNode双向链表进行树型化操作
if ((tab[index] = hd) != null)
// 执行链表转红黑树的操作
hd.treeify(tab);
}
}
②treeify()方法真正将链表转成红黑树;大体上分为三步:
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
// 最开始的x表示TreeNode双向链表的头结点
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
// 构建树的根节点
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else {
// 第一部分
K k = x.key;
int h = x.hash;
Class<?> kc = null;
// p 表示parent节点
for (TreeNode<K,V> p = root;;) {
// dir表示x节点在parent节点的左侧还是右侧
int dir, ph; // ph表示parent节点的hash值
K pk = p.key; // pk表示parent节点的key值
// x节点在parent节点的左侧
if ((ph = p.hash) > h)
dir = -1;
// x节点在parent节点的右侧
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
// 第二部分
TreeNode<K,V> xp = p; // xp表示x的父节点
// 如果p节点的左节点/右节点不为空,则令p = p.left/p.right,继续循环
// 直到p.left/p.right为空
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 令待插入节点x的父节点为xp, 即p
x.parent = xp;
// 根据dir判断插入到xp的左子树(<0)还是右子树(>0)
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 往红黑树中插入节点后,进行树的平衡操作
root = balanceInsertion(root, x);
break;
}
}
}
}
// 将root节点插入到table数组中
moveRootToFront(tab, root);
}
③在往红黑树中插入一个节点之后,会调用 balanceInsertion()方法进行树的平衡操作
对红黑树进行平衡操作时,主要分两部分:直接返回、变色旋转后返回;
第一部分:直接返回根节点:
1、如果待插入节点x没有parent节点,直接把x的节点变为黑色,并作为根节点返回;
2、如果x的父节点为黑色、或者没有祖父节点,则直接返回根节点root;
第二部分:根据x节点在父节点xp的左侧还是右侧、xp节点再其父节点xpp的左侧还是右侧进行旋转、变色;
- 如果x的父节点xp是祖父节点xpp的左子节点:
0)若x的祖父节点xpp的右子节点xppr存在并且是红色,直接交换祖父节点和其子节点的颜色、并返回。
1)若x是其父节点xp的右子节点,交换x和xp的位置,然后对x进行左旋;
2)接着设置父节点xp为黑色、祖父节点xpp为红色,以xpp节点为轴右旋。- x的父节点xp是祖父节点的右子节点:
这里和上面的类似,只是左旋变右旋、右旋变左旋;
0)若x的祖父节点xpp的左子节点xppl并在并且是红色,直接交换祖父节点和其子节点的颜色、并返回。
1)若x是其父节点xp的左子节点,交换x和xp的位置,然后对x进行右旋;
2)接着设置父节点xp为黑色、祖父节点xpp为红色,以xpp节点为轴左旋。
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
// 将待插入节点x的节点颜色置为红色
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
// 1、如果x没有父节点,自己变为黑色节点、作为根节点返回
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// 2、如果x的父节点为黑色或者没有祖父节点,则直接返回root
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 3、如果x的父节点xp是祖父节点的左子节点
if (xp == (xppl = xpp.left)) {
// 1)仅变色:
// 如果xp和xppr都是红色节点,则把xppr、xp置为黑色、zpp置为红色。
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
// 2)旋转 + 变色:
else {
// (1)如果x 是其父节点xp的右子节点。令当前节点为其父节点,然后对当前节点x进行左旋
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// (2)如果x的父节点xp不为空
if (xp != null) {
// 令xp的颜色为黑色
xp.red = false;
// 如果x的祖父节点xpp不为空
if (xpp != null) {
// 令xpp的颜色为红色
xpp.red = true;
// 对祖父节点xpp进行右旋
root = rotateRight(root, xpp);
}
}
}
}
// 4、x的父节点xp是祖父节点的右子节点
else {
// 逻辑和3、类似,只是单纯的左变右、右变左
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
HashMap 链表转红黑树的源码解析,参考:https://blog.csdn.net/Saintmm/article/details/121582015
Java可抛出(Throwable)的结构分为三种类型:被检查的异常(CheckedException
),运行时异常(RuntimeException
),错误(Error
)。
1、运行时异常 RuntimeException及其子类都被称为运行时异常。
ArithmeticException
异常IndexOutOfBoundsException
异常ConcurrentModificationException
异常NullPointerException
(空指针异常)ClassCastException
(类转换异常)2、被检查异常 Exception类及其子类中除了"运行时异常"之外的其它子类都属于被检查异常。
Java编译器会检查它。 此类异常,要么通过throws
进行声明抛出,要么通过try-catch
进行捕获处理,否则不能通过编译。
CloneNotSupportedException
异常FileNotFoundException
异常SQLException
异常IOException
异常3、错误 Error类及其子类。
是程序无法处理的错误,表示代码运行时 JVM出现的问题。
VirtualMachineError
)OutOfMemoryError
NoClassDefFoundError
)线程独占:方法栈,本地方法栈,程序计数器
线程共享:堆,方法区
1. 方法栈
线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方法出口等信息。调用方法时执行入栈,方法返回式执行出栈。
2. 本地方法栈
与方法栈类似。执行Java方法是使用栈,执行Native方法时使用本地方法栈。
3. 程序计数器
保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行Native方法时,程序计数器为空。
4. 堆
被线程共享,目的是存放对象的实例,当堆没有可用空间时,会抛出OOM异常。根据对象的存活周期不同,JVM把对象进行分代管理。由垃圾回收器进行垃圾的回收管理。
5. 方法区
用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据。JDK1.7的永久代和JDK1.8的元空间都是方法区的一种实现。
类加载过程分为加载、连接、初始化。其中连接包括验证、准备、解析
加载:通过类的完全限定名,查找此类字节码文件,利用字节码文件创建Class对象。
验证:确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全。
准备:进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null)。不包含final修饰的静态变量,因为final变量在编译时分配。
解析:将常量池中的符号引用替换为直接引用的过程。
初始化:主要完成静态块执行以及静态变量的赋值。先初始化父类,再初始化当前类。只有对类主动使用时才会初始化。
加载机制-双亲委派模式
双亲委派模式,即加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器。父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载。
使用java -XX:+PrintCommandLineFlags -version查看一下
-XX:InitialHeapSize=132500864 //初始堆大小
-XX:MaxHeapSize=2120013824 //最大堆大小
-XX:+PrintCommandLineFlags //程序运行前打印出用户手动设置或者JVM自动设置的XX选项,因为我们执行时间加上了这个选项,所以这里会打印出来
-XX:+UseCompressedClassPointers // 默认开启类指针压缩
-XX:+UseCompressedOops // 默认开启对象指针压缩
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC // 默认使用Parallel垃圾收集器
java version "1.8.0_221" // jdk版本
Java(TM) SE Runtime Environment (build 1.8.0_221-b11) // jre
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode) // Hotspot虚拟机,Server模式,混合编译
JDK1.8中默认使用ParallelGC垃圾收集器,包括Parallel Scavenge(新生代) 和Parallel Old(老年代) 收集器组合。
1. 基础:标记 - 清除算法
基本思想:
不足:
2. 解决效率问题:复制算法
新生代(Young)中使用的GC就是使用的复制算法。
-XX:MaxTenuringThreshold //设置对象在新生代中存活的代数,默认是15
基本思想:
不足:
3. 解决空间碎片问题:标记 - 整理算法
基本思想:
不足:
4. 进化:分代收集算法
参考文章:https://www.toobug.cn/post/4990.html
设定堆内存大小
-Xmx:堆内存最大限制。
设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代占比
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比
设定垃圾回收器
线程通常都有五种状态, 创 建 、 就 绪 、 运 ⾏ 、 阻 塞 和 死 亡 \color{red}{创建、就绪、运⾏、阻塞和死亡} 创建、就绪、运⾏、阻塞和死亡。
Java通过Executors提供四种线程池,分别为:
CAS适用于多读场景,冲突较少的情况,synchronized适用于多写场景,冲突较多的情况
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 可以尝试获得锁,线程不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入,不可中断,非公平 | 可重入,可中断,可公平/非公平 |
性能 | 少量同步 | 大量同步 |
LOCK的优势有:
总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能。
实现原理 JVM层面
1. synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1。相应的在执行monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
2. synchronized 方法使用的是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
注意:uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
1.为 uniqueInstance 分配内存空间
2.初始化 uniqueInstance
3.将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在多线程环境下会导致一个线程获得还没有初始化的实例。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
JDK1.7的实现
在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成。
Segment数组的意义就是将一个大的table分割成多个小的table来进行分段加锁,每一个Segment元素存储的是HashEntry数组+链表。
1. put操作
对于ConcurrentHashMap的数据插入,进行两次Hash去定位数据的存储位置。Segment继承了ReentrantLock,也就带有锁的功能,当执行put操作时,会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后进行第二次hash操作,找到相应的HashEntry的位置,这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(链表的尾端),会通过继承ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁。
2. get操作
ConcurrentHashMap第一次需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表。
3. size操作
在并发操作时,计算size的时候,其他线程还在并发的插入数据,可能会导致计算出来的size和实际的size有相差,JDK1.7版本用两种方案解决此问题:
JDK1.8的实现
JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
即:在并发处理中使用的是乐观锁CAS,当有冲突的时候才进行并发处理synchronized
1. Node
Node是ConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据。是一个链表,但是只允许对数据进行查找,不允许进行修改。
2. TreeNode
TreeNode继承与Node,但是数据结构换成了二叉树结构,它是红黑树的数据的存储结构,用于红黑树中存储数据,当链表的节点数大于8且table长度大于64时会转换成红黑树的结构,链表转换为红黑树的最终目的,是为了解决在map中元素过多,hash冲突较大,而导致的读写效率降低的问题。
3. TreeBin
TreeBin是封装TreeNode的容器,它提供转换黑红树的一些条件和锁的控制。
4. put操作
对当前的table进行无条件自循环直到put成功,可以分成以下六步流程来概述:
5. get操作
注意:volatile不能保证原子性,要是说能保证,也只是对单个volatile变量的读/写具有原子性,但是对于类似volatile++这样的复合操作就无能为力了
实现原理:lock前缀指令。
lock前缀指令实际相当于一个内存屏障,内存屏障提供了以下功能:
重排序时不能把后面的指令重排序到内存屏障之前的位置
使得本CPU的Cache写入内存
写入动作也会引起别的CPU或者别的内核无效化其Cache,相当于让新写入的值对别的线程可见。
使用场景:状态量标记和双检锁(DCL)的单例模式
IOC(控制反转)
控制反转也叫依赖注入。利用了工厂模式
将对象交给容器管理,你只需要在spring配置文件中配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。
AOP(面向切面编程)
使用代理模式
实现。(Aspect-Oriented Programming)AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。AOP将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。
实现AOP的技术,主要分为两大类:一是采用动态代理技术
,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式
,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码。
Spring AOP中的动态代理主要有两种方式,JDK动态代理
和CGLIB动态代理
:
①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口
和Proxy类
,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
异同 | @Autowired | @Resource | |
---|---|---|---|
共同点 | 两者都可以写在字段或者setter方法上。如果写在字段上,那么就不需要再写setter方法。 | ||
区别 | 注解来源不同 | Spring提供的注解 | 由J2EE提供 |
注入方式不同 | 按照类型(byType)注入,若想使用按照名称(byName)来装配,可以结合@Qualifier注解 | 默认按照名称(byName)注入,可以使用name和type属性指定注入方式 | |
装配可选 | 提供了required属性(默认值为true)以避免注入为空抛出异常,设置@Autowired为false | 没有提供可选择装配的特性,一旦无法装配则会抛出异常 | |
构造函数注入 | 可以写在构造函数上 | 不可以写在构造函数上 |
一、构造器注入
将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。
优点:
缺点:
二、setter方法注入
IoC Service Provider通过调用成员变量提供的setter方法将被依赖对象注入给依赖类。
优点:
缺点:
三、接口注入
依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象。
优点
缺点:
@Component、@Controller、@Service、@Repository
@Bean
:声明并注入实例对象
@Scope
:设置Bean的作用域
@Import
:导入其他组件到容器中
@Autowired
:依赖注入
@Value
:属性注入
@PostConstruct
:初始化方法,注解由java提供
@PreDestory
:销毁方法,注解由java提供
@Configuration
:声明当前类为配置类
@ComponentScan
:用于对Component进行扫描
@Aspect
:声明一个切面
@After
:在方法执行之后执行(方法上)
@Before
:在方法执行之前执行(方法上)
@Around
:在方法执行之前与之后执行(方法上)
@PointCut
:声明切点
@Enable***
:这些注解主要是用来开启对xxx的支持
前端控制器DispatcherServlet
。HandlerMapping处理器映射器
。Handler
处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。HandlerAdapter处理器适配器
。Controller
处理器(也叫后端控制器)。ModelAndView
。返回给DispatcherServlet
。ViewReslover视图解析器
。View
。渲染视图
(即将模型数据填充至视图中)响应
用户@Controller
:用于控制层注解
@Service
:用于对业务逻辑层进行注解
@Repository
:对Dao实现类进行注解
@Component
:在类定义之前添加@Component注解,他会被spring容器识别,并转为bean
@RequestMapping
:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径
@RequestParam
:用于获取传入参数的值
@PathViriable
:用于定义路径参数值
@RequestBody
:注解实现接收http请求的json数据,将json转换为java对象
@ResponseBody
:注解实现将conreoller方法返回值转化为json对象响应给用户
@CookieValue
:用于获取请求的Cookie值
@ModelAttribute
:用于把参数保存到model中
@SessionAttributes
:使得model中的数据存储一份到session域中
工厂模式
:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象单例模式
:Bean默认为单例模式策略模式
:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略代理模式
:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术模板方法
:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate适配器模式
:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller观察者模式
:Spring事件驱动模型就是观察者模式的一个经典应用。桥接模式
:可以根据客户的需求能够动态切换不同的数据源。比如项目需要连接多个数据库@ControllerAdvice / @RestControllerAdvice 有三方面的功能
1. 全局异常处理
@ExceptionHandler
注解用来指明处理异常的类型2. 全局数据绑定
@ModelAttribute
注解标记该方法的返回数据是一个全局数据,name属性用来指定数据的keyModel
对象直接获取该数据model.asMap()
将数据转成map3. 全局数据预处理
比如给实体类的对象属性增加前缀
Spring Boot用来简化spring应用开发,约定大于配置,去繁从简
优点
独立运行
Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。自动配置
无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解
完成的,这也是Spring4.x的核心功能之一。应用监控
Spring Boot Actuator提供一系列端点可以监控服务及应用,做健康检测。缺点
启动类上面的核心注解是@SpringBootApplication
主要组合包含了以下3 个注解:
@SpringBootConfiguration
:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration
:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源
自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan
:Spring组件扫描。
Spring Boot 的核心配置文件是 application
配置文件 和 bootstrap
配置文件。
1、使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
2、一些固定的不能被覆盖的属性;
3、一些加密/解密的场景;
yml与properties配置文件格式的区别
区别 | .yml | .properties |
---|---|---|
语法不同 | 配置以“:”进行分割,缩进使用两个空格,不能使用 TAB键 | 配置以“.”进行分割 |
数据格式不同 | 通过“: ”赋值,且Key的冒号后面一定要加一个空格 | 通过“=”赋值 |
数据类型不同 | 支持键值对数据,支持数组格式 "- "表示数组,支持对象格式 | 只支持键值对数据 |
配置加载顺序 | 加载有先后顺序,有序 | 不保证加载顺序,无序 |
配置加载方式 | 无法使用@PropertySource注解加载自定义yml文件 | @PropertySource |
profile配置,指定配置文件
spring.profiles.active=dev
日志配置
推荐使用:Slf4j + logbak
logging.level.com.example.mapper=debug
logging.file.name=D:\logs\demo.log
logging.file.path=D:\logs
ApplicationRunner
接口CommandLineRunner
接口ServletContextListener
接口Starters可以理解为启动器
,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成
Spring及其他技术,而不需要到处找示例代码和依赖包。如你想使用 Spring JPA 访问数据库,只要加入spring-boot-starter-data-jpa 启动器依赖就能使用了。
spring-boot-starter-parent主要提供了如下默认配置:
注解 @EnableAutoConfiguration
, @Configuration
, @ConditionalOnXxx条件注解
就是自动配置的核心,首先它得是一个配置文件,其次根据类路径下是否满足这个条件去自动配置。
由两个组件组成:Eureka服务器和Eureka客户端。Eureka服务器用作服务注册服务器。Eureka客户端是一个java客户端,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持。
Eureka详细介绍可以参考文章:SpringCloud 服务注册与发现-Eureka
Ribbon,主要提供客户端的软件负载均衡算法。Ribbon客户端组件提供一系列完善的配置选项,比如连接超时、重试、重试算法等。Ribbon内置可插拔、可定制的负载均衡组件。
断路器可以防止一个应用程序多次试图执行一个操作,即很可能失败,允许它继续而不等待故障恢复或者浪费 CPU 周期,而它确定该故障是持久的。断路器模式也使应用程序能够检测故障是否已经解决。如果问题似乎已经得到纠正,应用程序可以尝试调用操作。
Hystrix详细介绍可以参考文章:SpringCloud 熔断器-Hystrix
Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul。而为了提升网关的性能,Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的通信框架Netty。 旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
OpenFeign,它是 Spring 官方推出的一种声明式服务调用与负载均衡组件,对 Ribbon 进行了集成,利用 Ribbon 维护了可用服务清单,并通过 Ribbon 实现了客户端的负载均衡。它具有 Feign 的所有功能,并在 Feign 的基础上增加了对 Spring MVC 注解的支持。
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持,可以方便的实现分布式统一配置管理。分为Config Server和Config Client两部 分。Config Server负责读取配置文件,并且暴露Http API接口,Config Client通过调用Config Server的接口来读取配置文件。
Spring Cloud Config是静态的,得配合Spring Cloud Bus实现动态的配置更新。
Spring Cloud Bus 使用轻量级的消息代理来连接微服务架构中的各个服务,可以将其用于广播状态更改(例如配置中心配置更改)或其他管理指令
Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。
目前 Spring Cloud Bus 支持两种消息代理:RabbitMQ
和 Kafka
。
优点:
缺点:
同步调用。也就是我们常说的服务的注册与发现,直接通过远程过程调用来访问别的service。
RestTemplate
OpenFegin
优点: 简单,常见,因为没有中间件代理,系统更简单
缺点:
异步调用
Spring Cloud Bus
MQ消息中间件
优点:
缺点: 消息中间件有额外的复杂
RPC,全称 Remote Procedure Call(远程过程调用)
,即调用远程计算机上的服务。大致分4个步骤:
建立通信
。首先需要有处理网络连接通讯的模块,负责连接建立、管理和消息的传输。服务寻址
。需要Registry来注册服务的地址。网络传输
。需要有编解码的模块。因为网络通讯都是传输的字节码,需要将我们使用的对象序列化和反序列化。服务调用
。服务器端暴露要开放的服务接口;客户端调用服务接口的一个代理实现,这个代理实现负责收集数据、编码并传输给服务器然后等待结果返回。PRC架构组件
客户端(Client)
: 服务调用方(服务消费者)
客户端存根(Client Stub)
:存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端
服务端存根(Server Stub)
:接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理
服务端(Server)
:服务的真正提供者
RPC具体调用过程
1、服务消费者(client客户端)通过调用本地服务的方式调用需要消费的服务;
2、客户端存根(client stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体;
3、客户端存根(client stub)找到远程的服务地址,并且将消息通过网络发送给服务端;
4、服务端存根(server stub)收到消息后进行解码(反序列化操作);
5、服务端存根(server stub)根据解码结果调用本地的服务进行相关处理;
6、本地服务执行具体业务逻辑并将处理结果返回给服务端存根(server stub);
7、服务端存根(server stub)将返回结果重新打包成消息(序列化)并通过网络发送至消费方;
8、客户端存根(client stub)接收到消息
9、客户端存根(client stub)对收到的消息进行解码(反序列化);
10、服务消费方(client客户端)得到最终结果;
RPC框架的实现目标则是将上面的第2-10步完好地封装起来,也就是把调用、编码/解码的过程给封装起来
,让用户感觉上像调用本地服务一样的调用远程服务。
使用#{}可以有效的防止SQL注入,提高系统安全性。
Mapper 接口的工作原理是JDK动态代理
,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。
Mapper接口里的方法,是不能重载
的,因为是使用 全限定名+方法名 的保存和寻找策略
。
Mybatis使用RowBounds对象进行分页
,它是针对ResultSet结果集执行的内存分页
,而非物理分页。
可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
PerpetualCache
的 HashMap 本地缓存
,其存储作用域为Session
,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。PerpetualCache,HashMap 存储
,不同在于其存储作用域为 Mapper(Namespace)
,并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口
(可用来保存对象的状态),可在它的映射文件中配置 ;且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上。
1、mysql:limit关键字
两个参数分别表示查询初始位置(从0开始)和查询长度,一个参数表示查询长度
2、oracle:rownum隐藏列
3、SQLServer:top
多条sql语句,要么全部成功,要么全部失败。
1. 事务的特性
原子性(Atomic):组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有操作都成功,整个事务才会提交。任何一个操作失败,已经执行的任何操作都必须撤销,让数据库返回初始状态。
一致性(Consistency):事务操作成功后,数据库所处的状态和它的业务规则是一致的。即数据不会被破坏。如A转账100元给B,不管操作是否成功,A和B的账户总额是不变的。
隔离性(Isolation):在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对彼此产生干扰
持久性(Durabiliy):一旦事务提交成功,事务中的所有操作都必须持久化到数据库中。
2. MySQL执行事务的语法和流程
InnoDB 存储引擎事务主要通过 UNDO日志
和 REDO日志
实现,MyISAM 存储引擎不支持事务。
开始事务
BEGIN
;或START TRANSACTION
;这个语句显式地标记一个事务的起始点。
提交事务
COMMIT
; 表示提交事务,将事务中所有对数据库的更新都写到磁盘上的物理数据库中,事务正常结束。一旦执行了该命令,将不能回滚事务。
回滚(撤销)事务
ROLLBACK
; 表示撤销事务,即在事务运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库的所有已完成的操作全部撤销,回滚到事务开始时的状态。这条语句也标志着事务的结束。
3. 事务之间相互影响的种类
脏读
:一个事务读取了另一个事务未提交的数据。不可重复读
:就是在一个事务范围内,两次相同的查询会返回两个不同的数据,是因为在此间隔内有其他事务对数据进行了修改。幻读
:指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。丢失更新
:两个事务同时读取同一条记录,A先修改记录,B也修改记录(B是不知道A修改过),B提交数据后覆盖了A的修改结果。4. 事务隔离级别
数据库的事务隔离级别(TRANSACTION ISOLATION LEVEL)是为了尽可能的避免上述事务之间的影响而产生的隔离级别。
隔离级别 | 脏读 | 丢失更新 | 不可重复读 | 幻读 | 并发模型 | 更新冲突检测 |
---|---|---|---|---|---|---|
未提交读: Read Uncommited | 是 | 是 | 是 | 是 | 悲观 | 否 |
已提交读: Read commited | 否 | 是 | 是 | 是 | 悲观 | 否 |
可重复读: Repeatable Read | 否 | 否 | 否 | 是 | 悲观 | 否 |
可串行读: Serializable | 否 | 否 | 否 | 否 | 悲观 | 否 |
事务隔离是通过锁来实现的,通过阻塞
来隔离上述影响,级别越高,加的锁越多,效率越低下
。
未提交读
:在读取数据时不会加任何锁,也不会进行检测,可能会读到没有提交的数据。已提交读
:只读取提交的数据等待其他事务释放排他锁,读数据的共享锁在读操作完成后会立即释放。这个隔离级别是大多数数据库默认的隔离级别。可重复读
:像已提交读一样,但共享锁会保持到事务结束才会释放。MySQL数据库默认的隔离级别。可串行读
:类似于可重复读,但锁不仅会锁定所查询的数据,也会锁定所查询的范围,这样就阻止了新数据插入所查询的范围。索引是对数据库表中一个或多个列的值进行排序的结构,建立索引有助于快速获取信息。
1. mysql 有4种索引类型
主键索引(PRIMARY)
数据列不允许重复,不允许为NULL,一个表只能有一个主键索引。
ALTER TABLE table_name ADD PRIMARY KEY (column_name)
唯一索引(UNIQUE)
数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
-- 创建唯一索引
ALTER TABLE table_name ADD UNIQUE (column_name);
-- 创建唯一组合索引
ALTER TABLE table_name ADD UNIQUE (column1,column2);
普通索引(INDEX)
-- 使用 CREATE INDEX 语句创建索引
CREATE INDEX indexName ON table_name (column_name)
-- 修改表结构(添加索引)方式
ALTER TABLE table_name ADD INDEX indexName (column_name)
-- 创建组合索引
ALTER TABLE table_name ADD INDEX indexName (column1,column2,column3);
全文索引(FULLTEXT)
-- 创建全文索引
ALTER TABLE table_name ADD FULLTEXT (column_name);
索引一经创建不能修改,如果要修改索引,只能删除重建。
-- 删除索引
DROP INDEX indexName ON table_name;
2. 索引优缺点
3. 索引设计的原则
1. 查看索引的使用情况
通过SHOW STATUS LIKE 'Handler_read%'
;查看索引的使用情况:
Handler_read_key
:如果索引正在工作,Handler_read_key的值将很高。
Handler_read_rnd_next
:数据文件中读取下一行的请求数,如果正在进行大量的表扫描,值将较高,则说明索引利用不理想。
2. 索引优化规则
3. explain 分析SQL的执行计划
通过 explain
命令获取 select 语句的执行计划,通过 explain 我们可以知道以下信息:表的读取顺序,数据读取操作的类型,哪些索引可以使用,哪些索引实际使用了,表之间的引用,每张表有多少行被优化器查询等信息。
需要重点关注type、rows、filtered、Extra。
type
由上至下,效率越来越高
Extra
4. SQL优化规则
5. 确定问题并采用相应的措施
限定数据查询的范围
。禁止不带任何限制数据范围条件的查询语句。读/写分离
。经典的数据库拆分方案,主库负责写,从库负责读;垂直分区
。垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。水平分区
。水平拆分是指数据表行的拆分,保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分最好分库 。补充一下数据库分片的两种常见方案:
SQL中的drop、delete、truncate都表示删除,但是三者有一些差别
内联接(Inner Join)
:匹配2张表中相关联的记录。左外联接(Left Outer Join)
:除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。右外联接(Right Outer Join)
:除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。数据类型 | 可以存储的值 | 操作 | 应用场景 |
---|---|---|---|
STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作;对整数和浮点数执行自增或者自减操作 | 做简单的键值对缓存 |
LIST | 列表 | 从两端压入或者弹出元素;对单个或者多个元素进行修剪,只保留一个范围内的元素 | 存储一些列表型的数据结构 |
SET | 无序集合 | 添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素 | 交集、并集、差集的操作 |
HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对;获取所有键值对;检查某个键是否存在 | 结构化的数据,比如一个对象 |
ZSET | 有序集合 | 添加、获取、删除元素;根据分值范围或者成员来获取元素;计算一个键的排名 | 去重但可以排序,如获取排名前几名的用户 |
应用场景
Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:
RDB:(Redis DataBase)快照
按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
优点
1、只有一个文件 dump.rdb,方便持久化。
2、容灾性好,一个文件可以保存到安全的磁盘。
3、性能最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能。
4、相对于数据集大时,比 AOF 的启动效率更高。
缺点
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
AOF持久化:(Append Only File)
将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
优点
1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点
1、AOF 文件比 RDB 文件大,且恢复速度慢。
2、数据集大的时候,比 rdb 启动效率低。
RDB和 AOF 机制对比
过期键的删除策略
Redis中同时使用了惰性过期和定期过期两种过期策略。
缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2、一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
3、给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
1、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2、从缓存取不到数据,在数据库中也没有取到,也将key放入缓存中,值设置为null,缓存有效时间可以设置短点。需要定期的清理空值的key。避免内存被恶意占满。
缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
1、设置热点数据永远不过期。
2、加互斥锁。
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
1、一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
2、警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
3、错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
4、严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
缓存降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
缓存更新
缓存服务(Redis)和数据服务(底层数据库)是相互独立且异构的系统,在更新缓存或更新数据的时候无法做到原子性的同时更新两边的数据,因此在并发读写或第二步操作异常时会遇到各种数据不一致的问题。如何解决并发场景下更新操作的双写一致是缓存系统的一个重要知识点。
缓存更新的设计模式有四种:
1、Cache aside:查询:先查缓存,缓存没有就查数据库,然后加载至缓存内;更新:先更新数据库,然后让缓存失效;或者先失效缓存然后更新数据库;
2、Read through:在查询操作中更新缓存,即当缓存失效时,Cache Aside 模式是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载;
3、Write through:在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后由缓存自己更新数据库;
4、Write behind caching:俗称write back,在更新数据的时候,只更新缓存,不更新数据库,缓存会异步地定时批量更新数据库;
Cache aside
为了避免在并发场景下,多个请求同时更新同一个缓存导致脏数据,因此不能直接更新缓存而是令缓存失效。
1、先更新数据库后失效缓存:并发场景下,推荐使用延迟失效(写请求完成后给缓存设置1s过期时间),在读请求缓存数据时若redis内已有该数据(其他写请求还未结束)则不更新。当redis内没有该数据的时候(其他写请求已令该缓存失效),读请求才会更新redis内的数据。这里的读请求缓存数据可以加上失效时间,以防第二步操作异常导致的不一致情况。
2、先失效缓存后更新数据库:并发场景下,推荐使用延迟失效(写请求开始前给缓存设置1s过期时间),在写请求失效缓存时设置一个1s延迟时间,然后再去更新数据库的数据,此时其他读请求仍然可以读到缓存内的数据,当数据库端更新完成后,缓存内的数据已失效,之后的读请求会将数据库端最新的数据加载至缓存内保证缓存和数据库端数据一致性;在这种方案下,第二步操作异常不会引起数据不一致,例如设置了缓存1s后失效,然后在更新数据库时报错,即使缓存失效,之后的读请求仍然会把更新前的数据重新加载到缓存内。
推荐使用先失效缓存,后更新数据库,配合延迟失效来更新缓存的模式
1. Redis 事务概念
Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
2. Redis事务的三个阶段
事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队。
3. Redis事务相关命令
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的。
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
Redis 不支持回滚,Redis 在事务失败时不进行回滚,而是继续执行余下的命令。
如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
如果在一个事务中出现运行错误,那么正确的命令会被执行。
4. Redis事务特性
一致性
和隔离性
耐久性
。Redis 集群方案有主从复制(master-slave)、哨兵模式(sentinel) 和集群(Cluster) 三种方式。
主从复制(master-slave)
一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
SYNC
命令和master全量同步
(初次连接)RDB持久化
过程),并将期间接收到的写命令缓存起来先写入本地磁盘,然后再从本地磁盘加载到内存中
2. 主从复制优点
3. 主从复制缺点
Sentinel(哨兵)模式
哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立运行的进程。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个 Redis 实例。
1. 哨兵模式的工作原理
PING
命令。down-after-milliseconds
选项所指定的值, 则这个实例会被 Sentinel进程标记为主观下线(SDOWN)
客观下线(ODOWN)
INFO
命令。2. 哨兵的作用
哨兵(sentinel)是 redis 集群机构中非常重要的一个组件,哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。主要有以下功能:
3. 哨兵模式的优点
4. 哨兵模式的缺点
Cluster 集群模式(Redis官方)
Redis Cluster是一种服务器 Sharding(分片)
技术,3.0版本开始正式提供。
Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在 redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,每台 Redis 节点上存储不同的内容。
1. Cluster 集群的数据分片
Redis Cluster 集群没有使用一致性 hash,而是引入了哈希槽(hash slot)
的概念。Redis 集群有16384 个(2^14)哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽。集群的每个节点负责一部分hash槽
。
这种结构很容易添加或者删除节点。从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
在 Redis 的每一个节点上,都有这么两个东西,一个是插槽(slot)
,它的的取值范围是:0-16383。还有一个就是cluster
,可以理解为是一个集群管理的插件。当我们的存取的 Key到达的时候,Redis 会根据 CRC16 的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
2. Cluster 集群的优点
gossip 协议,用于节点间进行高效的数据交换
)优化传输速度和带宽。3. Cluster 集群的缺点
1. SETNX命令
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系可以使用SETNX
命令实现分布式锁。SETNX 是『SET if Not Exists
』(如果不存在,则 SET)的简写。
返回值:设置成功,返回 1 。设置失败,返回 0 。
SETNX流程及事项如下
2. RedLock
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
优点:
异步处理
:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;应用解耦
:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;限流削峰
:避免流量过大导致应用系统挂掉的情况;缺点:
系统可用性降低
:系统引入,MQ崩溃,整套系统崩溃。系统复杂度提高
:会产生其他问题。如重复消费、消息丢失、消息传递的顺序性等问题。数据一致性问题
。特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic 数量对吞吐量的影响 | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | ||
时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 |
可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ |
功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
生产者
:消息的创建者,负责创建和推送数据到消息服务器;消费者
:消息的接收方,用于处理数据和确认消息;代理
:就是 RabbitMQ 本身,用于扮演“快递”的角色,本身不生产消息,只是扮演“快递”的角色。ConnectionFactory(连接工厂)
:应用程序与Rabbit之间建立连接的管理器,程序代码中使用。Channel(信道)
:消息推送使用的通道。Exchange(交换器)
:用于接受、分配消息。Queue(队列)
:用于存储生产者的消息。RoutingKey(路由键)
:用于把生成者的数据分配到交换器上。BindingKey(绑定键)
:用于把交换器的消息绑定到队列上。首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息,客户端和 rabbit server 之间会创建一个 tcp 连接
,一旦 tcp 打开并通过了认证(认证就是你发送给 rabbit 服务器的用户名和密码),你的客户端和 RabbitMQ 就创建了一条 amqp 信道(channel)
,信道是创建在“真实” tcp 上的虚拟连接,amqp 命令都是通过信道发送出去的,每个信道都会有一个唯一的 id,不论是发布消息,订阅队列都是通过这个信道完成的。
1. 生产者发出后保证到达了MQ
RabbitMQ引入了事务机制
和发送方确认机制
(publisher confirm),由于事务机制过于耗费性能所以一般不用。发送方确认机制就是消息发送到MQ那端之后,MQ会回一个确认收到的消息给我们。
2. MQ收到消息保证分发到了消息对应的Exchange
消息找不到对应的Exchange。找不到对应的Queue。这两种情况都可以用RabbitMQ提供的mandatory
参数来解决,它会设置消息投递失败的策略
,有两种策略:自动删除或返回到客户端。
3. Exchange分发消息入队之后保证消息的持久性
消息持久化
,以便MQ重新启动之后消息还能重新恢复过来。消息的持久化要做,还要做队列的持久化
和Exchange的持久化
。创建Exchange和队列时只要设置好持久化,发送的消息默认就是持久化消息。如果出现服务器宕机或者磁盘损坏则上面的手段统统无效,必须引入镜像队列,做异地多活来抵御这种不可抗因素。
4. 消费者收到消息之后保证消息的正确消费
消费者的消息确认
RabbitMQ 保证消息的顺序性
RabbitMQ 的问题是由于不同的消息都发送到了同一个 queue
中,多个消费者都消费同一个 queue 的消息。解决这个问题,我们可以给 RabbitMQ 创建多个 queue,每个消费者固定消费一个 queue 的消息,同一个 queue 的消息是一定会保证有序的。
Kafka 保证消息的顺序性
对于 Kafka 来说,一个 topic 下同一个 partition
中的消息肯定是有序的,导致最终乱序是由于消费者端需要使用多线程并发处理消息来提高吞吐量。可以在线程处理前增加个内存队列,每个线程只负责处理其中一个内存队列的消息。
RocketMQ 保证消息的顺序性
对于 RocketMQ 来说,每个 Topic 可以指定多个 MessageQueue
,当我们写入消息的时候,会把消息均匀地分发到不同的 MessageQueue 中。要解决 RocketMQ 的乱序问题,我们只需要想办法让同一个Topic 进入到同一个 MessageQueue 中就可以了。因为同一个 MessageQueue 内的消息是一定有序的,一个 MessageQueue 中的消息只能交给一个 Consumer 来进行处理,所以 Consumer 消费的时候就一定会是有序的。
消息的幂等性
:就是即使多次收到了消息,也不会重复消费。
mq内部可以为每条消息生成一个全局唯一的消息id,当mq接收到消息时,会先根据该id判断消息是否重复发送,mq再决定是否接收该消息。
即使MQ重复发送了消息,消费者拿到了消息之后,要判断是否已经消费过,如果已经消费,直接丢弃。
1、从MQ拿到数据存到数据库,可以根据数据创建唯一约束。
2、拿到的数据是直接放到redis的set中
index 索引
:索引类似于mysql中的数据库,是存数据的地方,包含一堆有相似结构的文档数据。document 文档
:类似于mysql中的一行,不同之处在于 ES 中的每个文档可以有不同的字段。文档是es中的最小数据单元,可以认为一个文档就是一条记录。Field 字段
:Field是Elasticsearch的最小单位,一个document里面有多个fieldshard 分片
:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。replica 副本
:任何一个服务器随时可能故障或宕机,此时 shard 可能会丢失,因此可以为每个 shard 创建多个 replica 副本。多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个 shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。传统的检索方式是通过文章,逐个遍历找到对应关键词的位置。
倒排索引
,是通过分词策略,形成了词和文章的映射关系表,也称倒排表,这种词典 + 映射表即为倒排索引。 其中词典中存储词元,倒排表中存储该词元在哪些文中出现的位置。时间复杂度O(1)
倒排索引的底层实现是基于:FST(Finite State Transducer)
数据结构。
ES集群部署可以参考 elasticsearch环境集群部署,此文介绍比较详细。
ES基本操作可以参考 elasticsearch入门基本操作,此文介绍比较详细。
两个的区别主要分词
的区别:keyword
类型是不会分词的,直接根据字符串内容建立倒排索引,keyword类型的字段只能通过精确值搜索到;text
类型在存入 Elasticsearch 的时候,会先分词,然后根据分词后的内容建立倒排索引
query
:查询操作不仅仅会进行查询,还会计算分值,用于确定相关度;filter
:查询操作仅判断是否满足查询条件,不会计算任何分值,也不会关心返回的排序问题,同时,filter 查询的结果可以被缓存,提高性能。/etc/init.d
./
../
~/
cd
cd ~
和cd $HOME
cd -
pwd
ps aux
a:显示当前终端下的所有进程信息,包括其他用户的进程。
u:使用以用户为主的格式输出进程信息。
x:显示当前用户在所有终端下的进程。
ps -elf
-e:显示系统内的所有进程信息。
-l:使用长(long)格式显示进程信息。
-f:使用完整的(full)格式显示进程信息。
ps命令可以配合管道命令grep 一起使用 ps -elf | grep java
lsof
,此命令需要安装lsof -i:端口号
结束进程
kill -9 PID
kill -9 'lsof -t -u tt'
lsof -u tt 是列出tt用户所有打开的文件,加上 -t 选项之后表示结果只列出PID列
Linux常见的处理目录的命令:
ls
(list files): 列出目录及文件名cd
(change directory):切换目录pwd
(print work directory):显示目前的目录mkdir
(make directory):创建一个新的目录rmdir
(remove directory):删除一个空的目录cp
(copy file): 复制文件或目录rm
(remove): 删除文件或目录mv
(move file): 移动文件与目录,或修改文件与目录的名称ls (list files)命令:用于列出目前工作目录所含文件及子目录。
ls [-alrtAFR] [name...]
-a 显示所有文件及目录 (. 开头的隐藏文件也会列出)
-l 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出 可简写为ll
-r 将文件以相反次序显示(原定依英文字母次序)
-t 将文件依建立时间之先后次序列出
-A 同 -a ,但不列出 “.” (目前目录) 及 “…” (父目录)
-F 在列出的文件名称后加一符号;例如可执行档则加 “*”, 目录则加 “/”
-R 若目录下有文件,则以下之文件亦皆依序列出
mkdir(make directory):创建一个新的目录
mkdir [-mp] 目录名称
-m :配置文件的权限
-p :递归创建多级目录
rmdir:删除空的目录
rmdir [-p] 目录名称
-p :从该目录起,递归删除多级空目录
cp:复制文件或目录
cp [options] source1 source2 .... destination
-a
:相当于 -pdr 的意思,至于 pdr 请参考下列说明;(常用)
-d:若来源档为连结档的属性(link file),则复制连结档属性而非文件本身;
-f:为强制(force)的意思,若目标文件已经存在且无法开启,则移除后再尝试一次;
-i:若目标档(destination)已经存在时,在覆盖时会先询问动作的进行(常用)
-l:进行硬式连结(hard link)的连结档创建,而非复制文件本身;
-p:连同文件的属性一起复制过去,而非使用默认属性(备份常用);
-r
:递归持续复制,用于目录的复制行为;(常用)
-s:复制成为符号连结档 (symbolic link),亦即『捷径』文件;
-u:若 destination 比 source 旧才升级 destination !
scp:用于 Linux 之间复制文件和目录
scp [可选参数] [[user@]host1]file_source [[user@]host1]file_target
rm:删除文件或目录
rm [-fir] 文件或目录
-f
:强制删除,忽略不存在的文件,不会出现警告信息;
-i :互动模式,在删除前会询问使用者是否动作
-r
:递归删除
mv:移动文件与目录,或修改名称
mv [-fiu] source destination
-u :若目标文件已经存在,且 source 比较新,才会执行 (update)
Linux系统中使用以下命令来查看文件的内容:
cat
由第一行开始显示文件内容tac
从最后一行开始显示,可以看出 tac 是 cat 的倒着写!nl
显示的时候,顺道输出行号!more
一页一页的显示文件内容less
与 more 类似,但是比 more 更好的是,他可以往前翻页!head
只看头几行tail
只看尾几行cat:由第一行开始显示文件内容
cat [options] 文件
-A
:相当于 -vET 的整合选项,可列出一些特殊字符而不是空白而已;
-b :列出行号,仅针对非空白行做行号显示,空白行不标行号!
-E :将结尾的断行字节 $ 显示出来;
-n
:列出行号,连同空白行也会有行号,与 -b 的选项不同;
-T :将 [tab] 按键以 ^I 显示出来;
-v :列出一些看不出来的特殊字符
more:一页一页的显示文件内容
more 文件
less:一页一页的显示文件内容,可以向上翻页和搜索
less 文件
head:查看文件前面几行
head [-n number] 文件
-n :后面接数字,代表显示几行
tail:查看文件后面几行,一般用于查看日志信息
tail [-fn number] 文件
-f :循环读取
-n :后面接数字,代表显示几行
linux查找文件的命令:
find
命令,可以查找任何想要的文件;locate
命令,查不到最新变动过的文件;whereis
命令,只搜索二进制文件which
命令,只搜索二进制文件grep
命令find [指定目录] [指定条件] [指定动作]
1. 根据名称查找,可以使用*,?通配符
-name
区分大小写-iname
不区分大小写find /etc -name init
2. 根据文件大小查找 -size
find / -size +204800
这条命令的功能是,在根目录下查找大于 100MB 的文件。因为它的单位是数据块
,而一个数据块是 0.5KB,所以 100MB 是 204800 个数据块,所以要写 204800。
若把「+」换成「-」,就是查找小于 100MB 的文件;若换成「=」,就是查找等于 100MB 的文件。
3. 根据所有者查找 -user
4. 根据所有组查找 -group
5. 根据时间查找
find /etc -cmin -5
这条命令的功能是,在 /etc 下查找 5 分钟内被修改过属性的文件和目录。
-amin
访问时间(a - access)-cmin
文件属性(c - change)-mmin
文件内容 (m - modify)6. 根据文件类型查找 -type
find /etc -type f
这条命令的功能是查找 etc 目录下的所有文件。
-type f
,文件-type d
,目录-type l
,软链接7. 连接符 -a、-o
若查找的条件有多个,可通过连接符将不同的选项连接起来。其中,-a表示 and,即通过「-a」连接的多个条件要同时满足,-o表示 or,通过「-o」连接的多个条件只满足其中一个即可。
locate 文件
find 命令是通过遍历磁盘来查找文件,搜索速度相对较慢,而 locate 命令是在 Linux 系统内的一个文件数据库中查找你所需要的文件,查找速度比 find 快很多。
# 查找init文件,区分大小写。若想忽略大小写 加 -i
locate init
locate 的缺点
updatedb
手动更新一下数据库which 命令
which 是查找命令文件的命令,可以查找命令所在位置。
which ls
whereis 命令
whereis 也是查找命令文件的命令,可以查找命令所在位置,和其帮助文档所在位置。
whereis ls
grep 字串 [目录/文件]
grep是查找文件内容
grep multiuser /etc/inittab
忽略大小写 -i
grep -i multiuser /etc/inittab
排除指定字符串 -v
如果想看配置文件的内容,但是不想看注释,就可以在搜索文件内容时排除「#」所在的行。但是有的注释并不是单独一行,而是写在配置语句的后面,这样的话单纯地排除「#」所在的行就会把配置语句也排除掉,造成误伤。也就是说我们只能排除掉以「#」开头的行。-v ^#
grep -v ^# /etc/inittab
ping
:查看网络是否连通netstat
:检验本机各端口的网络连接情况ifconfig
:查看 ip 地址hostname
:显示主机名字ssh
:登录到其他系统1、.tar
解包:tar -xvf FileName.tar
打包:tar -cvf FileName.tar DirName
(注:tar是打包,不是压缩)
2、.gz
解压1:gunzip FileName.gz
解压2:gzip -d FileName.gz
压缩:gzip FileName
3、.tar.gz 和 .tgz
解压:tar -zxvf FileName.tar.gz
压缩:tar -zcvf FileName.tar.gz DirName
4、.zip
解压:unzip FileName.zip
压缩:zip FileName.zip DirName
5、.rar
解压:rar -x FileName.rar
压缩:rar -a FileName.rar DirName
chgrp
:修改文件和目录的所属组chgrp [-R] 所属组 文件或目录
chown
:修改文件和目录的所有者和所属组chown [-R] 所有者 文件或目录
chown [-R] 所有者:所属组 文件或目录
chmod
:修改文件或目录的权限chmod [-R] 权限值 文件名
export
unset
readonly
,不能用unset删除env
env $HOME
或echo $HOME
Linux变量可分为两类:
永久的
:需要修改配置文件,变量永久生效。临时的
:使用export命令声明即可,变量在关闭shell时失效。设置变量的三种方法
/etc/profile
文件中添加变量【对系统所有用户生效(永久的)】vi /etc/profile
export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib
注:修改文件后要想马上生效还要运行source /etc/profile
不然只能在下次重进此用户时生效。
.bash_profile
文件中增加变量【对单一用户生效(永久的)】vi /home/zhangsan/.bash.profile
export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib
注:修改文件后要想马上生效还要运行source /home/zhangsan/.bash_profile
不然只能在下次重进此用户时生效。
export
命令定义变量【只对当前shell(BASH)有效(临时的)】在shell的命令行下直接使用export 变量名=变量值
定义变量,该变量只在当前的shell(BASH)或其子shell(BASH)下是有效的,shell关闭了,变量也就失效了,再打开新shell时就没有这个变量,需要使用的话还需要重新定义。
chkconfig
命令用于检查,设置系统的各种服务chkconfig [param][系统服务] 或 chkconfig [系统服务][on/off/reset]
--add
增加所指定的系统服务,让 chkconfig 指令得以管理它,并同时在系统启动的叙述文件内增加相关数据。
--del
删除所指定的系统服务,不再由 chkconfig 指令管理,并同时在系统启动的叙述文件内删除相关数据。
--list
列出chkconfig 所知道的所有命令。
service
命令的作用是去 /etc/init.d
目录下寻找相应的服务,可以启动、停止、重启系统服务,还可以显示所有系统服务的当前状态。service 系统服务 status/start/stop/restart
systemctl
来管理系统服务,启动、停止、重启、禁用、查看系统服务,该命令集成了命令 service、chkconfig、setup、init 的大部分功能于一身。systemctl status/start/stop/restart 系统服务.service
yum 查找、安装、删除某一个、一组甚至全部软件包
yum [options] [command] [package ...]
options:可选,选项包括 -y(当安装过程提示选择全部为 “yes”),-q(不显示安装的过程)等等。
command:要进行的操作。
package:安装的包名。
yum check-update
yum update
yum -y install
yum update
yum list
yum remove
yum search
rpm 查找、安装、删除某一个、一组甚至全部软件包
rpm [options] [package ...]
-a 查询所有套件。
-e 删除指定的套件。
-h 套件安装时列出标记。
-i 显示套件的相关信息。
-l 显示套件的文件列表。
-p 查询指定的RPM套件档。
-q 使用询问模式,当遇到任何问题时,rpm指令会先询问用户。
-v 显示指令执行过程。
--nodeps
不验证套件档的相互关联性。
rpm -qa | grep
查看软件安装版本信息rpm -qi
查看软件安装详细信息rpm -ql
查看软件安装目录rpm -ivh
安装软件rpm -evh
卸载软件ln -s slink source
ln link source
history
:查看用过的命令列表df -h
:检查文件系统的磁盘空间占用情况。su
:切换用户sudo
:以系统管理者的身份执行指令