hashMap源码部分转载自:https://me.csdn.net/weixin_42340670
其他集合部分转载自:https://blog.csdn.net/luyanc?t=1
本文转载自JavaGuide,地址:https://github.com/Snailclimb/JavaGuide,作者:SnailClimb
1.1java基础知识
1.1.0谈一下java面向对象
这是一种"万物皆对象的编程思想",在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例。面向对象有三大特性,封装、继承和多态。
封装就是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化。这样做使得代码的复用性更高。
继承则是进一步将一类事物共有的属性和行为抽象成一个父类,而每一个子类是一个特殊的父类–有父类的行为和属性,也有自己特有的行为和属性。这样做扩展了已存在的代码块,进一步提高了代码的复用性。而多态就是这种关系在实际场景的运用。简单点说,多态就是把做什么和怎么做分开了;其中,做什么是指调用的哪个方法,我是去吃饭(方法a)还是去睡觉(方法b),怎么做是指实现方案,如果我选择吃饭,那么我是吃米饭还是吃面条,”分开了“则是指两件事不在同一时间确定。说的学术点,多态就是父类的引用指向子类的对象。这样做的好处就是可以消除类型之间的耦合关系。
根据模板而捏造不同实现细节的类的行为称为多态
由于绑定动作发生在运行时,所以多态也称运行时绑定或者后期绑定,相比于静态的编译期,多态也称为动态绑定(确定方法调用以及调用方法对象之间的关系,那么这个关系的确定就称为绑定)
动态绑定的过程可以理解为下面几个过程:
1 当虚拟机创建Circle类(子类)对象的时候,会创建一个类的方法列表,同时包含父类的方法列表。
2 子类型circle对象的引用向上转换成shape(父类)引用,确定引用与对象之间的关系。
3.虚拟机会找到参数引用实际指向的地址,也就是shape引用实际指向circle对象,并查询对象的方法列表,如果在circle对象中找到这个方法,就直接调用,否则查询父类shape方法并调用。
注意步骤2中发生了类型转换。我们知道基本数据类型可以发生类型转换,如byte,short,char和int之间;那么引用类型之间也可以,但需要满足一定的条件(有继承关系)。那么这里发生的转换是从circle对象转到shape对象,即从子类转换父类,是向上转换。
Java中除了static方法和final方法(private方法属于final方法,因为从最终方法的定义可以看出,不能被子类覆盖的成员方法就是最终方法)是前期绑定,其他所有方法都是后期绑定。
①final关键字修饰的方法不能被重写,即防止被其他人覆盖该方法,同时也能避免动态绑定。
②private关键字修饰的方法对子类不可见,也就不能被子类继承,那么也就无法通过子类对象调用private级别的方法;唯一调用的途径就是在类内通过其对象本身。所以说,private级别的方法和定义这个方法的类绑定在一起了。
③static关键字修饰的方法属于类而非对象,所以不能被重写,但static方法可以被子类继承(不能表现出多态性,因为父类的静态方法会被子类覆盖)。
public class Test1 {
public static void main(String[] args) {
new Circle().change(); // ① shape change
new Square().change(); // ② square change
method(new Circle()); // ③ shape change
method(new Square()); // ④ shape change
}
static void method(Shape shape) {
shape.change();
}
}
class Shape {
public static void change() {
System.out.println("shape change");
}
}
class Circle extends Shape {}
class Square extends Shape {
public static void change() {
System.out.println("square change");
}
}
从上面的运行结果可以得出:
类Circle没有重写change()方法,但①表明Circle类继承了Shape类的change()方法,所以static方法可以被子类继承。
类Square”重写“了父类change()方法,结果②表明square对象调用的是自身对象的change()方法;注意这里的”重写“加了引号。
结果③和结果④表明,当子类向上转换成父类时,不论子类中有没有定义该静态方法,引用都会调用父类的静态方法。如果子类square真的重写父类change()方法,那么结果④就不对了,所以这里的重写只是表面的重写,而不是真正意义上的重写,即static方法并不能被子类重写。(可以尝试在子类的change()方法加上@Override注解)
结果④进一步表明,子类的change()方法被父类shape隐藏了。可以简单理解为,调用该引用类型内的static方法。
多态的缺陷
①类的属性没有多态性(属性根据引用类型来决定)
②构造函数没有多态性(构造函数实际上是static方法,所以构造函数不具备多态性)
详细见:https://blog.csdn.net/nobody_1/article/details/93670681
1.1.0多态的底层实现
多态的前提:1.继承 2.重写 3.向上转型(即父类型的引用指向子类型的实例)
多态的底层实现是动态绑定,即在运行时才把方法调用与方法实现关联起来。invokevirtual 和 invokeinterface 用于动态绑定。可以看出,动态绑定主要应用于虚方法和接口方法。以一个例子为例:
class Father {
public void test(){
System.out.println("This is Father");
}
}
class Son extends Father {
@Override
public void test(){
System.out.println("This is Son");
}
}
public class TestDemo {
public static void main(String[] args) {
Father s = new Son();
s.test();
}
}
结果是“This is Son”,我们在这里要讨论的是这个过程是如何实现的
如上图所示,当我们在执行代码的时候,首先根据我们所写的语法在栈内存上会创建相对应的引用变量s,相应地在堆内存上开辟空间创建Son的实例对象,并且引用s指向它的实例Son,由类的加载过程我们可知道我们所编写的Class文件会在JVM方法区上建立储存它所含有的类型信息(成员变量、类变量、方法等)并且还会得到一个Class对象(通过反射机制)建立在堆区上,该Class对象会作为方法区访问数据的入口。
方法区
方法区是与多态的实现最密切相关的区域,该类的类型信息会被保存在方法区中,并且为了优化对象调用方法的速度,方法区中的类型信息中会增加一个指针,该指针会去指向一张记录该类方法入口的表,即方法表,并且方法表中的每一项都是指向相应方法的指针。
JAVA语言是单继承机制,一个类只能继承一个父类,而所有的类又都继承自Object类。方法表有自己的存储机制:方法表中最先存放的是Object类的方法,接下来是父类的方法,最后才是自身特有的方法。这里的关键点在于如果子类重写了父类的方法,那么子类和父类的同名方法共享一个方法表项,都被认作是父类的方法(仅仅只有非私有的实例方法才行,静态方法是不行的),即同名(子类重写的)方法在相对应类的方法表中的偏移量是相同的。
所以我要说重点了:
结合同名方法偏移量相同且是固定的,则在调用方法时,首先会对实例方法的符号引用进行解析,解析的结果就是方法表的偏移量。当我们把子类对象声明为父类类型时,明面上虚拟机通过对象引用的类型得到该类型方法区中类型信息的入口,去查询该类型的方法表(即例中的Father),得到的是父类型的方法表中的test方法的偏移量,但实际上编译器通过类加载过程获取到Class对象知道了实例对象s的真正类型,转而进入到了真正的子类类型(例中的Son)的方法表中用偏移量寻找方法,恰好两者偏移量是相等的,我们就顺利成章的拿到了Son类型方法表中的test方法进而去指向test方法入口。嘻嘻。
多态的好处:
1.应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。(继承保证)
2.派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,
可以提高可扩充性和可维护性。(多态保证)
多态的实现过程
多态的实现过程,就是方法调用动态分派的过程,通过栈帧的信息去找到被调用方法的具体实现,然后使用这个具体实现的直接引用完成方法调用。
1.1.0静态的成员变量与非静态的成员变量的区别:
1. 作用上的区别:
1. 静态的成员变量的作用共享一个 数据给所有的对象使用。
2. 非 静态的成员变量的作用是描述一类事物的公共属性。
2. 数量与存储位置上的区别:
1. 静态成员变量是存储方法 区内存中,而且只会存在一份数据。
2. 非静态的成员变量是存储在堆内存中,有n个对象就有n份数据。
3. 生命周期的区别:
1. 静态的成员变量数据是随着类的加载而存在,随着类文件的消失而消失。
2.非静态的成员数据是随着对象的创建而存在,随着 对象被垃圾回收器回收而消失。 静态函数注意事项:
1. 静态函数是可以调用类名或者对象进行调用的,而非静态函数只能使用对象进行调用。
2. 静态的函数可以直接访问静态的成员,但是不能直接访问非静态的成员。
原因:静态函数是可以使用类名直接调用的,这时候可能还没有存在对象,
而非静态的 成员数据是随着对象 的存在而存在的。
3. 非静态的函数是可以直接访问静态与非静态的成员。
原因:非静态函数只能由对象调用,当对象存在的时候,静态数据老早就已经存在了,而非静态
数据也随着对象的创建而存在了。
4. 静态函数不能出现this或者super关键字。
原因:因为静态的函数是可以使用类名调用的,一旦使用类名调用这时候不存在对象,而this关键字是代表了一个函数 的调用者对象,这时候产生了冲突。
静态的数据的生命周期:静态的成员变量数据是优先于对象存在的。
static关键字的作用(修饰类、方法、变量、静态块)
(static修饰的内部类相当于一个普通的类,访问方式为(new 外部类名.内部类的方法() )
普通内部类的访问方式为 (外部类对象.new 内部类().内部类方法())
)
内部类的定义及使用
内部类特点:
1.破坏了程序的结构;
2.方便进行私有属性的访问;不受private 限制
1.1.1重载和重写的区别
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同、方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
1.1.2String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?
可变性
简单的来说:String类中使用final关键字字符数组保存字符串,private final char value[],所以String对象是不可变的。而StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串char[]value但是没有用final修饰,所以这两种对象都是可变的。
StringBuilder与StringBuffer的构造方法都是调用父类构造方法也就是AbstractStringBuilder实现的,可以查看源码。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
线程安全性
String中的对象是不可变的,也可以理解为常量,线程安全。AbatractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expendCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
性能、
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。
StrignBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险。
对于三者的使用的总结:
1.操作少量的数据:String
2.单线程操作字符串缓冲区下操作大量数据:StringBuilder
3.多线程操作字符串缓冲区下操作大量数据:StringBuffer
1.1.3自动装箱与拆箱
装箱:将基本类型用他们对应的引用类型包装起来
拆箱:将包装类型转换为基本数据类型
1.1.4==与equals
双等号:他的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型双等号比较的是值,引用数据类型比较的是内存地址)
equals():它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过"=='比较这两个对象。
情况2:类覆盖了equals()方法。一般,我们都覆盖equas()方法来比较俩个对象的内容是否相等;若他们的内容相等,则返回true(即,认为这两个对象相等)
举个例子:
public static void main(String[] args) {
String a = new String("ab");
String b=new String("ab");
String aa="ab";//放在常量池中
String bb="ab";//从常量池中查找
if(aa==bb)//true
System.out.println("aa=bb");
if(a==b)//false
System.out.println(a==b);
if(a.equals(b))//true
System.out.println("aEQb");
if(42==42.0){//true
System.out.println("true");
}
}
说明:
String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有,就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象
1.1.5关于final关键字的一些总结
final关键字主要用在三个地方:变量、方法、类
1.对于一个final变量,如果是基本类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
2.当使用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式的指定为final方法。
3.使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的java版本已经不需要final方法进行这些优化了)类中所有的private方法都隐式地指定为final。
1.1.6Object类的常见方法总结
Object类是一个特殊的类,是所有类的父类。它主要提供了一下11个方法:
//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写
public final native Class<?>getClass()
//native方法,返回对象的哈希码,主要使用再哈希表中,比如JDK中的HashMap
public native int hashCode()
//用于比较比较两个对象的内存地址是否相等,String类对该方法进行了重写,用户比较字符串的值是否相等
public boolean equals(Object obj)
//native方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象x,表达式x.clone()!=x为true,
// x.clone().getClass()==x.getClass()为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且调用的话会发生CloneNotSupportedException异常
protected native Object clone() throws CloneNotSupportedException
//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法
public String toString()
//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
public final native void notify()
//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程
public final native void notifyAll()
//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁。timeout是等待时间
public final native void wait(long timeout)throws InterruptedException
//多了nanos参数,这个参数表示额外时间(以毫秒为单位,范围是0-999999)所以超时的时间还需要加上nanos毫秒
public final void wait(long timeout,int nanos)throws InterruptedException
//跟之前的两个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
public final void wait()throws InterruptedException
//实例被垃圾回收器回收的时候触发的操作
public void finalize()throws Throwable{}
面试题1:hashcode相等两个类一定相等吗?equals呢?相反呢?
hashCode相等,equals也不一定相等, 两个类也不一定相等
equals相同, 说明是同一个对象, 那么hashCode一定相同
hashCode方法的实现一般是“通过将该对象的内部地址转换成一个整数来实现的”,这个返回值就作为该对象的哈希码值返回。哈希表是结合了直接寻址和链式寻址两种方式,所需要的就是将需要加入哈希表的数据首先计算哈希值,其实就是预先分个组,然后再将数据挂到分组后的链表后面,随着添加的数据越来越多,分组链上会挂接更多的数据,同一个分组链上的数据必定具有相同的哈希值,java中的hash函数返回的是int类型的,也就是说,最多允许存在2^32个分组,也是有限的,所以出现相同的哈希码就不稀奇了
由此可知默认情况下:
两个对象== 相等,则其hashcode一定相等,反之不一定成立。
两个对象equals相等,则其hashcode一定相等,反之不一定成立。(和上一条等价,因为Object的equals实现用的就是对象的 == 来判断的)
如果equals方法和hashCode方法被重写,则需满足hashCode 的常规协定:
1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
所以按规定重写的情况下:
两个对象equals相等,则它们的hashcode必须相等,反之则不一定。
两个对象 == 相等,则它们的hashcode必须相等,反之则不一定。【==相等,则equals必然相等】
所以总的来说,只要按照规定,则有:
两个对象equals相等,则它们的hashcode必须相等,反之则不一定
1.1.7Java中的异常处理
在java中,所有的异常都有一个共同的祖先java.lang包中的Throwable类。Throwable:有两个重要的子类:Exception(异常)和Error(错误),二者都是java异常处理的重要子类,各自都包含大量子类。
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重的问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM(java虚拟机)出现的问题。例如,java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,java虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为他们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。在java中,错误通过Error的子类描述。
Exception(异常):是程序本身可以处理的异常。Exception类有一个重要的子类RuntimeException.RuntimeException异常由java虚拟机抛出。NullPointerException:(要访问变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以0时,抛出该异常)和 ArrayIndexOutOfBoundsException(下标越界异常)
注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理
Throwable类常用方法
public String getMessage():返回异常发生时的详细信息
public String toString():返回异常发生时的简要描述
public String getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMesage()返回的结果相同
public void printStackTrace():在控制台上打印Throwable对象封装的异常信息
异常处理总结
try块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
catch块:用于处理try捕获到的异常
finally块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块在方法返回之前被执行。
在以下4中特殊情况下,finally块不会被执行:
1.在fianlly语句块中发生了异常
2.在前面代码中用来System.exit()退出程序
3.程序所在的线程死亡
4.关闭CPU
1.1.8获取键盘输入常用的两种方法
方法1:通过Scanner
Scanner input=new Scanner(System.in);
String s=input.nextLine();
input.close();
方法2:通过BufferedReader
BufferedReader input=new BufferedReader(new InputStreamReader(System.in));
String s=input.readLine();
1.1.9接口和抽象类的区别是什么
1.接口的方法默认是public,所有方法在接口中不能有实现(java8开始接口方法可以有默认实现),抽象类可以有非抽象的方法。
2.接口中的实例变量默认是final类型的,而抽象类中则不一定
3.一个类可以实现多个接口,但是最多只能实现一个抽象类
4.一个类实现接口的话要实现接口的所有方法,而抽象类不一定
5.接口不能用new实例化,但可以声明。但是必须引用一个实现该接口的对象,从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为规范。
备注:在JDK1.8中,接口也可以定义静态方法,可以直接用接口名调用。实现类是不可以调用的。如果同时实现俩个接口,接口中定义了一样的默认方法,必须重写,不然会报错
关于抽象类
JDK 1.8以前,抽象类的方法默认访问权限为protected
JDK 1.8时,抽象类的方法默认访问权限变为default
关于接口
JDK 1.8以前,接口中的方法必须是public的
JDK 1.8时,接口中的方法可以是public的,也可以是default的
JDK 1.9时,接口中的方法可以是private的
jdk1.8后,接口方法可以有方法体,需要用default或static修饰。实现类无需重写。(接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。)当然default方法重写也是可以的。如果一个类实现多个接口,多个接口有相同default方法,则子类必须重写该方法
注意:
类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。即抽象类可以在不提供接口方法实现的情况下实现接口。
interface B{
public void eat();
int a=0;
}
abstract class C implements B{//抽象类可以在不提供接口方法实现的情况下实现接口。
}
问题1.Java接口中声明的变量默认都是final的。(为什么)
interface中的变量是当作常量来设计的,它不但是final,而且还是public static的,也即interface中的变量一定是public static final的,换言之,这个变量实际上已经是个“常量”。
解答:java接口中成员变量必须是final类型的原因如下:
- 接口中的数据对所有实现类只有一份,所以是static
2.要使实现类为了向上转型成功,所以必须是final的.这个举例子很好理解.比如接口A,A有变量value.实现类A1,A2,可以向上转型.假如代码中有一句:
A a=null;
a=....(2)实际实现类
System.out.println(a.value);
利用向上转型,可以得到接口a的值,在第2步中,我不关你是实现类A1,还是new A2(),通过转型,我们可以得到正确的值.要是类中可以更改,我们得不到一个统一的值,接口也没有了意义.
接口中如果可能定义非final的变量的话,而方法又都是abstract的,这就自相矛盾了,有可变成员变量但对应的方法却无法操作这些变量,虽然可以直接修改这些静态成员变量的值,但所有实现类对应的值都被修改了,这跟抽象类有何区别?
子类没有 与父类声明同名的static变量
我们知道static类型的变量 是归类所有,不是类的实例所有,一般用类名引用。以下代码中,ClassOne.count++;其实是是ClassOne自己没有count这个声明,这样编译器做的事情ClassOne.count++;之所以输出结果为1,实际是Base.count++;的效果
class Base {
public static int count = 0;
}
class ClassOne extends Base {
}
class TestN {
public static void main(String args[]) {
System.out.println(Base.count);//0
System.out.println(ClassOne.count);//0
//ClassOne自己没有count这个声明,这样编译器做的事情ClassOne.count++;实际是Base.count++;的效果
ClassOne.count++;
System.out.println(Base.count);//1
}
}
但是假如ClassOne有自己的count变量,那么执行ClassOne.count++;根本不影响父类的count值,两者在这个时候static的类变量独立的内存空间,互不影响
class Base {
public static int count = 0;
}
class ClassOne extends Base {
public static int count=1;
}
class Test {
public static void main(String args[]) {
System.out.println(Base.count);//0
System.out.println(ClassOne.count);//1
ClassOne.count++;
System.out.println("Base的count值:"+Base.count);//0
System.out.println("ClassOne的count值:"+ClassOne.count);//2
}
}
小结:
1)子类是不继承父类的static变量和方法的。因为这是属于类本身的。但是子类是可以访问的。
2)子类和父类中同名的static变量和方法都是相互独立的,并不存在任何的重写的关系。
拓展问题:
1.String类和常量池
2. 8种基本类型的包装类和常量池
1.5设计模式
三、Linux
3.1 简单介绍一下 Linux 文件系统
Linux文件系统简介
在Linux操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文 件或是目录都被看作是一个文件。
也就是说在LINUX系统中有一个重要的概念:一切都是文件。其实这是UNIX哲学的一个体现,而Linux是重写UNIX而 来,所以这个概念也就传承了下来。在UNIX系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬 件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。
文件类型与目录结构
Linux支持5种文件类型 :
Linux的目录结构如下:
Linux文件系统的结构层次鲜明,就像一棵倒立的树,顶层是其根目录:
常见目录说明:
- /bin: 存放二进制可执行文件(ls,cat,mkdir等),常用命令一般都在这里;
- /etc: 存放系统管理和配置文件;
- /home: 存放所有用户文件的根目录,是用户主目录的基点,比如用户user的主目录就是/home/user,可以 用~user表示;
- /usr : 用于存放系统应用程序;
- /opt: 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把tomcat等都安装到这里;
- /proc: 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息;
- /root: 超级用户(系统管理员)的主目录(特权阶级o);
- /sbin: 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程 序。如ifconfig等;
- /dev: 用于存放设备文件;
- /mnt: 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统;
- /boot: 存放用于系统引导时使用的各种文件;
- /lib : 存放着和系统运行相关的库文件 ;
- /tmp: 用于存放各种临时文件,是公用的临时文件存储点;
- /var: 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启 动日志等。)等;
- /lost+found: 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在 这里。
3.2 一些常见的 Linux 命令了解吗
目录切换命令 - cd usr : 切换到该目录下usr目录
- cd …(或cd…/): 切换到上一层目录
- cd / : 切换到系统根目录
- cd ~ : 切换到用户主目录
- cd -: 切换到上一个所在目录
目录的操作命令(增删改查) - mkdir 目录名称: 增加目录
- ls或者ll (ll是ls -l的缩写,ll命令以看到该目录下的所有目录和文件的详细信息):查看目录信息
- find 目录 参数: 寻找目录(查)
- mv 目录名称 新目录名称: 修改目录的名称(改) 注意:mv的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv命令用 来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到mv命令的另一个用法。
- mv 目录名称 目录的新位置: 移动目录的位置—剪切(改) 注意:mv语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作。另外mv与cp的结果不 同,mv好像文件“搬家”,文件个数并未增加。而cp对文件进行复制,文件个数增加了。
- cp -r 目录名称 目录拷贝的目标位置: 拷贝目录(改),-r代表递归拷贝 注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归
- rm [-rf] 目录: 删除目录(删)
注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文 件,都直接使用 rm -rf 目录/文件/压缩包
文件的操作命令(增删改查)
1.touch 文件名称: 文件的创建(增)
2.cat/more/less/tail 文件名称 文件的查看(查)
cat : 只能显示后一屏内容
more : 可以显示百分比,回车可以向下一行, 空格可以向下一页,q可以退出查看 less:可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看
tail-10 : 查看文件的后10行,Ctrl+C结束
注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变 化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化
3.vim 文件: 修改文件的内容(改)
vim编辑器是Linux中的强大组件,是vi编辑器的加强版,vim编辑器的命令和快捷方式有很多,但此处不一一阐 述,大家也无需研究的很透彻,使用vim编辑修改文件的方式基本会使用就可以了。 在实际开发中,使用vim编辑器主要作用就是修改配置文件,下面是一般步骤: vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输 入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。)
4.rm -rf 文件: 删除文件(删)
同目录删除:熟记 rm -rf 文件 即可
压缩文件的操作命令
Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.gz结尾的。 而一般情况下打包和压缩是一起进行的,打包并压缩后的文件的后名一般.tar.gz。 命令: tar -zcvf 打包压缩后的文件名 要打包压缩的文件 其中:
z:调用gzip压缩命令进行压缩
c:打包文件
v:显示运行过程
f:指定文件名
比如:加入test目录下有三个文件分别是 :aaa.txt bbb.txt ccc.txt,如果我们要打包test目录并指定压缩后的压缩包名 称为test.tar.gz可以使用命令: tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt 或: tar -zcvf test.tar.gz /test/
2)解压压缩包:
命令:tar [-xvf] 压缩文件
其中:x:代表解压
示例:
1 将/test下的test.tar.gz解压到当前目录下可以使用命令: tar -xvf test.tar.gz
2 将/test下的test.tar.gz解压到根目录/usr下: tar -xvf xxx.tar.gz -C /usr (- C代表指定解压的位置)
其他常用命令 - pwd : 显示当前所在位置
- grep 要搜索的字符串 要搜索的文件 --color : 搜索命令,–color代表高亮显示
- ps -ef / ps aux : 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看
特定的进程可以使用这样的格式: ps aux|grep redis (查看包括redis字符串的进程)
注意:如果直接用ps((Process Status))命令,会显示所有进程的状态,通常结合grep命令查看某进程的 状态。 - kill -9 进程的pid : 杀死进程(-9 表示强制终止。) 先用ps查找进程,然后用kill杀掉
- 网络通信命令:
- 查看当前系统的网卡信息:ifconfig
- 查看与某台机器的连接情况:ping
- 查看当前系统的端口使用:netstat -an
- shutdown : shutdown -h now : 指定现在立即关机; shutdown +5 “System will
shutdown after 5 minutes” :指定5分钟后关机,同时送出警告信息给登入用户。 - reboot : reboot : 重开机。 reboot -w : 做个重开机的模拟(只有纪录并不会真的重开机)。
yarn查看某一日志的指令:
yarn logs -applicationId application_1517538889175_2550 > logs.txt
通过vim进行查看logs.txt文件
四、Mysql
4.1 说说自己对于 MySQL 常见的两种存储引擎:MyISAM与 InnoDB的理解
关于二者的对比与总结:
- count运算上的区别:因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好 的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存。
- 是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型 更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务 (commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
- 是否支持外键: MyISAM不支持,而InnoDB支持。 MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作 为主库的存储引擎。 一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大,所以 当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压 缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是好的选择。
5.2数据库索引了解吗?
1.为什么要使用索引?
1)使用索引可以大大加快数据检索速度(大大减少检索的数据量),这也是创建索引的最主要原因。
2)帮助服务器避免排序和临时表
3)将随机IO变为顺序IO
4)通过创建唯一索引,可以保证数据库表中每一行数据的唯一性
5)可以加速表和表之间的连接,特别是在实现数据库的参考完整性方面特别有意义
(前四点为主要)
2.索引这么多优点,为什么不对表中的每一列创建一个索引呢?
1)当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
2)索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
3)创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
3.索引是如何提高查询速度的?
将无序的数据变成相对有序的数据(就行查目录一样)
4.使用索引的注意事项
1)在经常需要搜索的列上,可以加快搜索的速度
2)在经常使用WHERE子句的列上面创建索引,加快条件的判断速度
3)在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询的时间
4)对于中到大型表索引都是非常有效的,但是特大型表的话维护开销会很大,不适合建索引
5)在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度
6)避免where子句对字段施加函数,这会造成无法命中索引
7)在使用InnoDB时使用与业务无关的自增主键作为索引,即 使用逻辑主键,而不要使用业务主键
8)将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描
9)删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗MySQL5.7可以通过查询sys库的chema_unused_indexes视图来查询哪些索引从未被使用
10)在使用limit offset查询缓慢时,可以借助索引来提高性能
5.Mysql索引主要使用的两种数据结构:
1)哈希索引
对于哈希索引来说,底层的数据结构是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引(哈希索引不适合于范围查找)
2)BTree索引
Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。
6.MyISAM和InnoDB实现BTree索引方式的区别:
MyISAM:
B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址读取相应的数据记录。这被称为“非聚簇索引”
InnoDB:
其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被成为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,和也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
7.覆盖索引介绍
1)什么是覆盖索引?
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,就称之为“覆盖索引”。在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作
2)覆盖索引的使用实例
比如说现在创建了索引(username,age),在查询数据的时候:
select usename,age from user where username=‘Java’ and age=22.要查询出的列在叶子节点都存在!所以就不用回表
8.选择索引和编写利用这些索引的查询的3个原则
1)单行访问是很慢的。特别是在机械硬盘存储中(SSD的随机I/O要快很多,不过这一点仍然成立)。如果服务器从存储中读取一个数据块只是未来获取其中一行,那么就浪费了很多工作。最好读取的块中能包含尽可能多所需的行。使用索引可以创建位置引用以提升效率。
2.按顺序访问范围数据是很快的,这又两个原因。第一,顺序I/O不需要多次磁盘寻址,所以比随机I/O要快很多(特别是对机械硬盘)。第二,如果服务器能够按需要顺序读取数据,那么就不再需要额外的排序操作,并且GROUPBY查询也无须再做排序和将行按组进行聚合计算了。
3)索引覆盖查询是很快的。如果一个索引包含了查询需要的所有列,那么存储引擎就不需要再回表查找行。这避免了大量单行访问,而上面的第一点已经写明单行访问是很慢的。
1.NoSQL数据库与关系型数据库的优缺点
- 关系型数据库
优点:以完善的关系代数理论为基础,有严格的标准,支持事务ACID四性,借助索引机制可以实现高效的查询。
缺点:可扩展性差,无法较好地支持海量数据存储,数据模型过于死板,事务机制影响了系统的整体性能,全文搜索功能较弱。 - NoSQL数据库
优点:数据之间无关系,易扩展。有非常高的读写性能,支持大量数据,性能高。有灵活的数据模型,无须事先为要存储的数据建立字段,随时可以存储自定义的数据格式。
缺点:复杂查询性能不高,一般都不能实现事务的强一致性。
2.redis与mongoDB的区别
- 内存管理
Redis 数据全部存在内存,定期写入磁盘,当内存不够时,可以选择指定的 LRU 算法删除数据。 MongoDB数据会优先存于内存,当内存不够时,只将热点数据放入内存,其他数据存在磁盘。
需要注意的是Redis 和mongoDB特别消耗内存,一般不建议将它们和别的服务部署在同一台服务器上。 - 数据结构
Redis 支持的数据结构丰富,包括hash、set、list等。
MongoDB 数据结构比较单一(他支持的数据结构非常松散,是类似Json的Bson格式,因此可以存储比较复杂的数据类型。),但是支持丰富的数据表达,索引,最类似关系型数据库,支持的查询语言非常丰富。 - 数据量和性能
当物理内存够用的时候,性能:redis>mongodb>mysql
数据量:mysql>mongodb>redis
注意mongodb可以存储文件,适合存放大量的小文件,内置了GirdFS 的分布式文件系统。 - 可靠性
mongodb从1.8版本后,采用binlog方式(MySQL同样采用该方式)支持持久化,增加可靠性;
Redis依赖快照进行持久化;AOF增强可靠性;增强可靠性的同时,影响访问性能。
可靠性上MongoDB优于Redis。
Redis主要把数据存储在内存中,其“缓存”的性质远大于其“数据存储“的性质,其中数据的增删改查也只是像变量操作一样简单;
MongoDB却是一个“存储数据”的系统,增删改查可以添加很多条件,就像SQL数据库一样灵活,这一点在面试的时候很受用。
3.为什么mysql不建议存储海量数据
- 1.数据量大,在进行DDL时,几乎是毁灭级别的,虽然现在online DDL在逐渐完善,但仍有没办法进行online的操作,开销很大,由于业务需求,为表增加列,往往是不可避免的,虽然可以用预留字段的方式一定程度上规避这个问题,但不可能为每张表都预留字段,预留字段也不可能无限多
- 2.随着数据量的增长,索引的效率会越来越低(如insert的场景,insert当然和索引有关系,之后再单独写一篇文章吧),这个问题即使在Oracle上也是尤其明显的;
- 3.备份恢复难,几千万甚至上亿的表,备份与恢复的时间都会非常长,在出现异常需要恢复时业务恢复时间变得不可控。
越来越多的用户意识到了这个问题,开始去使用分布式关系型数据库
3种方式解决多线程并发访问资源的安全,线程安全,线程不安全
https://blog.csdn.net/qq_35389417/article/details/80594524