基础
1. 面向对象的三大特性:
(1)封装:
封装是把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口;
(2)继承:
继承是指从已有的类中继承该类的信息(方法、成员等)创建新类的过程;
(3)多态:
多态是同一个行为有多个不同表现形式的能力,(初始化一个对象,可能会根据使用的子类不同而结果不同)分为编译时多态(方法重载)和运行时多态(方法重写),实现多态的两个条件:一是子类继承父类并重写父类中 的方法,二是父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同表现出不同的行为;
2. 重载和重写的区别:
重载反映的是:同一项功能,根据数据类型的不同,会采用不同的处理方式;
重写反映的是:子类的不同,具体继承父类的方法也可能不同;
-
方法重写:
- 概念:重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变,即外壳不变,核心重写;
- 好处:子类可以根据需要,定义属于自己的特性,可根据自己的需要实现父类的方法;
- 重写规则:
a. 参数列表必须完全与被重写的方法的相同;
b. 返回值类型必须与被重写的方法相同;
c. 访问权限不能比父类中被重写的方法权限更低;
d. 声明为final的方法不能重写;
e. 声明为static的方法不能被重写,但可以再次声明;
f. 构造方法不能被重写;
-
方法重载:
- 概念:重载是在同一个类中,方法,名字相同,参数不同,返回值可相同或不同;
- 注意:每个重载的方法(或构造函数)都必须有一个独一无二的参数类型列表(最常用的地方是构造器额方法重载);
- 重载规则:
a. 被重载的方法必须改变参数列表(参数类型或个数不同);
b. 被重载的方法可以改变返回类型;
c. 被重载的方法可以改变访问权限;
d. 方法能在同一个类或在一个子类中被重载;
直接区别:
3. 抽象类和接口的区别:
- 抽象类中可定义构造方法,接口不可;
- 抽象类中可以有抽象方法和具体方法,而接口中只能有抽象方法(1.8后一样);
- 抽象类中可以包含静态方法,而接口中不可(1.8后一样);
- 抽象类中的成员权限可以是public、默认、protected(抽象类中的方法就是为了被重写,所以不能被private修饰),接口中的成员只可以是public(方法默认public abstract,成员变量默认public static final);
4. String、StringBuilder、StringBuffer的区别:
String是字符串常量,一旦创建对象后该对象不可改变,但后两者的对象是变量,可以改变;
-
String:用于字符串操作,他的值是不可变的,每次对String的操作都会生成新的String对象(String不是基本数据类型,是引用类型,底层用char数组实现){使用于少量的字符串操作};
-
StringBuilder:适用于单线程下在字符缓冲区进行大量操作;
-
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
5. final、finally、finalize的区别:
- final:用于声明属性、方法和类,表示属性不可改变、方法不可覆盖、被修饰的类不可被继承,是唯一的;
- finally:处理异常的语法,表示总是执行;
- finalize:Object类的一个方法,在垃圾回收时会调用被回收对象的finalize;
6. ==和equals的区别:
- ==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同;(String、Integer等重写了equals方法,把他变成了比较值的大小);
7. 为什么重写equals()就一定要重写hashCode()方法?
前提是要用到HashMap、HashSet,这样节省了时间复杂度,若两个对象的HashCode不相同,就不用再equals了;
8.深克隆和浅克隆的区别:
- 浅克隆:拷贝对象和原始对象的引用类型引用同一个对象,浅克隆只是复制了对象引用的地址,两个对象指向同一个内存地址,所以修改其中一个值另一个值也会随之变化;
- 深克隆:拷贝对象和原始对象的引用类型引用不同对象,是将对象及值复制过来,两个对象任一个改变值另一个也不会改变;
9. JavaWeb环境搭建:
Web服务器主要是用来接收客户端发送的请求和响应客户端请求;一般常用的Web服务器是Tomcat;
- 在Tomcat安装目录bin目录下双击startup.bat启动;双击shutdown.bat停止;
- 配置端口号:打开Tomcat安装目录下的conf/server.xml
集合
1. ArrayList和LinkedList的区别:
ArrayList:底层是基于数组实现的,查找快,增删慢;
LinkedList:底层是基于链表实现的(循环双向链表),查找慢,增删快;
2. HashMap的实现原理/底层数据结构;
JDK1.7:Entry数组(包含一个key-value键值对) + 链表;
JDK1.8:Node数组(包含一个key-value键值对) + 链表/红黑树(当链表上的元素个数超过8个,并且数组长度 >= 64时自动转化成红黑树);
3. HashMap的put执行过程:
当往一个HashMap中添加一对key-value时,系统首先会计算key的hash值,然后根据hash值确认在table中的存储位置,若该位置没有元素,则直接插入,否则会遍历该链表/红黑树,并依次比较key的hash值,若两个hash值相等,且key值相等则用新的Entry覆盖原来节点的value;如果两个Hash值相等但key不等,则将该节点插入该链表的链头;
4. HashMap的get方法执行过程:
通过key的hash值找到在table数组中的索引处的Entry,然后返回该key对应的value即可;
在这里根据key快速取到value除了和hashMap的数据结构密不可分外,还和Entry有莫大的关系,HashMap存储过程中并没有将key,value分开来存储,而是当做一个整体key-value来处理,这个整体就是Entry对象,在存储过程中,系统根据key的HashCode来决定Entry在table中的位置;
5. HashMap中的resize方法的执行过程:
- 第一次调用HashMap的put方法时,会调用resize方法对table数组进行初始化,如果不传入指定值,默认大小为16;
- 扩容时会调用resize;
每次扩容之后容量都是翻倍的,扩容后用到队列来对原始table中的节点进行保存,之后再放入新的table中;
6. ConcurrentHashMap的实现原理****:
7. JDK和JRE的区别:
- JDK:Java开发工具包,提供了Java的开发环境和运行环境;
- JRE:为Java运行提供了所需环境;
JDK其实包含了JRE,同时还包含了编译Java源码的编译器javac,还包含了很多java程序调试和分析的工具;
并发
1. sleep和wait的区别:
- sleep()方法正在执行的线程主动让出cpu(然后cpu就可以去执行其他任务),在sleep指定时间后cpu再回到该线程继续往下执行(注意:sleep方法只是让出了cpu,而并不会释放同步资源锁)////wait()方法则是当前线暂时退让同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait方法的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行(notify的作用相当于叫醒睡着的人,而不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);
- sleep放法可以再任何地方使用,而wait方法则只能在同步方法或同步块中使用;
- sleep()是线程类(Thread)的方法,调用会暂停此线程指定额时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait是Object的放法,调用会放弃对象锁,进入等待队列,待调用notify唤醒指定的线程或者热所有线程才会进入锁池,不再次获得对象锁才会进行运行状态;
2.对synchronized关键字的了解:
synchronized关键字解决的是多个线程之间访问资源的同步性,他可以保证被他修饰的方法或者代码块在任意时刻只能有一个线程执行;
3. synchronized关键字的底层原理:
他属于JVM层面,synchronized同步语句块的实现使用的是monitorenter和monitorexit指令,其中monitorenter指向同步代码块开始的位置,monitorexit指向同步代码块结束的位置;当执行monitorenter指令时,线程试图获取锁也就是monitor持有权,monitor对象存在每个java对象的对象头中,synchronized就是通过这种方式获取锁的,这就是为什么java中任意对象可以作为锁的原因;当计数器为0则可以成功获取,获取后将锁计数器设为1,相应的在执行monitorexit指令后,将计数器设为0,表明锁被释放;
4.JDK1.6之后synchronized关键字的 底层做出来哪些优化*****:
5. AQS原理***:
6. 线程池:
线程池的作用就是限制系统中执行线程的数量,根据系统的环境可以自动或手动设置线程数量,达到最佳运行效果,用线程池控制线程数量,其他线程排队等待,一个任务执行完毕,再从队列中取最前面的任务开始执行;
7. 为什么要用线程池:
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;
- 可以根据系统的承受能力,调整线程池线程的数目,防止因为消耗过多的内存,而使服务器崩溃;
8. 创建线程池的参数有哪些:
- corePoolSize(线程池的基本大小),当提交一个任务到线程池时,如果poolSize < coreSize时,线程池会创建一个线程来执行任务;
- maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数;
- keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活时间。
- TimeUnit(线程活动保持时间的单位);
- workQueue(任务队列):用于保存等待执行的任务的阻塞队列;
- threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建的线程设置名称;
JAVA虚拟机(JVM)
1. Jvm的主要组成部分:
- 类加载器(ClassLoader):会把Java代码转换成字节码;
- 运行时数据区(Runtime Data Area):再把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器“执行引擎”;
- 执行引擎(Execution Engine):将字节码文件翻译成底层系统指令;
- 本地库接口(Native Interface):调用该接口(其他语言的本地接口),交由Cpu去执行;
2. 谈谈对运行时数据区的理解:
首先,运行时数据区包括:Java栈、本地方法栈、程序计数器、方法区、堆;
-
程序计数器:是一块较小的内存空间,可以看做当前线程所执行的字节码的行号指示器;字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,程序的分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成;
由于Java虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式进行的,在任何一个时刻,一个处理器都只会执行一条线程中的命令,因此,为了能使线程切换后回到正确的执行位置,每条线程都要有一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,称这块内存区域为“线程私有”的内存; -
Java虚拟机栈:描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量、操作数栈、动态链接、方法出口等信息;每个方法从调用到执行完成的过程,就对应着一个栈帧入栈出栈的过程;他的生命周期与线程相同(首先方法被编译成了字节码文件,并生成了可执行命令,通过程序计数器,虚拟机会一行一行的执行命令,直到进入一个新的方法入口,这对应虚拟机栈也就是新的栈帧入栈,当前栈帧改变,或者遇到返回指令或出现异常结束了方法,对应虚拟机也就是出栈);
-
本地方法栈:与虚拟机栈的作用非常相似,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务;
-
Java堆:是被所有线程所共享的一块内存区域,在虚拟机启动时创建,此区域唯一的目的就是:存放对象实例;
-
方法区:与Java堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
3. 谈谈对内存泄漏的理解:
在Java中,内存泄漏指的是存在一些不会再被使用却没有没有回收的对象,会占用内存,这些对象有两个特点:
- 可达的,即在有向图中,存在通路可以与其相连;
- 无用的,即程序以后不会在使用它;
4. 常用的垃圾收集算法:
- 标记-清除算法:从根集合进行扫描,对存活的对象进行标记,标记完毕后。再扫描整个空间中未被标记的对象,进行回收;本算法不需要进行对象移动,只需要处理未被标记的对象,在存活对象比较多的情况下极为高效,但由于这是直接回收不存活对象,会造成内存碎片;
- 复制算法:复制算法的核心就是将内存一分为二,每次只用其中一块,在垃圾回收时,将正在使用的对象复制到另外一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾回收;如果内存中需要回收的垃圾过多,需要复制的对象就较少,此方法效率便较高;但在垃圾较少的情况下不合适;
- 标记整理算法:和标记清除算法一致对对象进行标记,但在回收垃圾对象后,还要将所有的存活对象往左端空闲空间移动,因此成本更高。但解决了内存碎片问题;
- 分代收集算法:大部分JVM采用此算法,他的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域;分别为老年代、新生代、永久代,老年代每次只有少量垃圾需要回收,新生代每次有大量垃圾需要回收,那么就可以根据不同代的特点采用不同的收集算法;
5. 常用的垃圾收集器:
- Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
- Serial Old收集器(标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本;
- ParNew收集器(停止-复制算法):新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
- Parallel Scavenge收集器(停止-复制算法):并行收集器,追求高吞吐量,高效利用CPU;
- G1收集器:G1收集器采用标记整理算法,不会产生内存空间碎片;
6. 类加载机制:
我们写好的Java代码(.Java)会通过javc.exe编译成为字节码文件(.class文件 / 二进制数据),这个文件并不能直接被JVM使用,而是通过类加载器将其加载到JVM内存中去,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是类加载机制;他的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载 7个阶段;其中验证、准备。解析 3 个部分统称为连接;
7. 双亲委派原则:
如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类无法完成这个加载请求,子类才会尝试自己加载;
优点: Java类随着他的类加载器一起具备了一中带有优先级的层次关系;无论哪个加载器加载一个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同一个Object对象。
8. Java内存模型的理解:
- Java内存模型:屏蔽掉了各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致性的内存访问效果;
- Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节;
计算机网络
1. 五层网络协议体系结构的理解:
- 应用层:通过应用进程间的交互来完成特定网络应用;
- 运输层:负责向两台主机进程间的通信提供通用的数据传输服务;
- 网络层:在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。
- 数据链路层:两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。
- 物理层:在物理层上所传送的数据单位是比特;
2. TCP和UDP:
- TCP协议:传输控制协议,就是要对数据的传输进行一定的控制;
3. TCP的三次握手(简述):
握手需要在客户端和服务器之间交换三个TCP报文段;
三次握手一般由客户端调用函数发起,TCP三次握手后就打开了数据收发通道,即建立了连接;
- 第一次握手:客户端向服务器发出连接请求报文段;TCP客户进程进入同步已发送状态;
- 第二次握手:服务器收到连接报文请求后,如果同意建立连接,则向客户端发送确认;TCP服务端进程进入同步收到状态;
- 第三次握手:TCP客户进程收到服务器的确认后,还要向服务器给出确认;这是客户端进入已建立连接状态;
4. TCP的四次挥手(简述):
这就是终止TCP连接,是指断开一个TCP连接时,需要客户端和服务器总共发送4个包来确认连接的断开;是由客户端或服务端任一方执行close来触发;
- 第一次挥手:(A)客户端的应用进程先向其TCP发出连接释放报文段,并停止再次发送数据,主动关闭TCP连接;这时A进入终止等待1状态;
- 第二次:(B)服务器收到连接释放的报文后立即发出确认,A收到来自B的确认后,进入了终止等待2状态,等待B发出的连接释放报文段;
- 第三次:若B已经没有要向A发送的数据,其应用进程就通知TCP释放连接;这时B进入最后确认状态,等待A的确认;
- 第四次:A在收到B的连接释放报文后,必须对此进行确认;所以在释放连接时,B结束TCP连接的时间要早于A;
5. HTTP协议:
超文本传输协议,是应用层协议;Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求,Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,http会立即将TCP连接断开,这个过程是很短的,所以Http连接是一种短连接,是一种无状态连接;
计算机操作系统:
1. 进程与线程:
- 进程:进程是系统进行资源分配和调度的一个独立单位,是系统中的并发执行单位;
- 线程:线程是进程的一个实体,也是CPU调度和分配的基本单位,他是比进程更小的能独立运行的基本单位,有时又被称为轻权进程或轻量级进程;
2. 进程与线程的区别:
- 进程是资源分配的最小单位,而线程是CPU调度的最小单位;
- 创建或销毁进程,系统都要为之分配或回收资源,操作系统开销远大于创建或销毁线程的开销;
- 不同进程地址空间相互独立,同一进程的线程共享同一地址空间,一个进程的线程在另一个进程内是不可见的;
- 进程间不会相互影响,而一个线程挂掉将可能导致整个进程挂掉;
3. 进程间的通信方式:
- 管道:
- 他是半双工的,具有固定的读端和写端;
- 他只能用于父子进程或兄弟进程的通信;
- 他可以看成是一种特殊的文件,对于他的读写也可以用普通的read、write等函数,但他不是普通文件,并不属于其他任何文件系统,并且只存在于内存中;
- 命名管道:
- FIFO可以再无关的进程之间交换数据,与无名管道不同;
- FIFO有路径名与之相关联,他以一种特殊设备文件形式存在于文件系统中;
- 消息队列:
- 消息队列是消息的链接表,存放在内核中;
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;
- 消息队列独立于发送与接收进程,进程结束时,消息队列及其内容都不会被删除;
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取;
- 信号量:
- 信号量是一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据;
- 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存;
- 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作;
- 每次对信号量的PV操作不仅限于对信号量值加1或减1,而且可以加减任意正整数;
- 支持信号量组;
- 共享内存:
- 指两个或多个进程共享一个给定的存储区;
- 共享内存是最快的一种进程通信方式,因为进程是直接对内存进行存取;
4. 死锁:
是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程进入这种僵局,若无外力作用,则无法再向前推进;
MySQL:
1. 索引:
索引是为了提高数据的查询效率,他是一种特殊文件(一般存放在磁盘中),其中包含着对数据表里所有记录的引用指针,通俗的讲,数据库索引就像是一本书的目录;
1. 普通索引(单列索引):
最基本的索引,没什么限制,最常用的索引;
2. 复合索引(组合索引):
复合索引是在多个字段上创建的索引。复合索引遵守“最左前缀”原则,即在查询条件中使用了复合索引的第一个字段,索引才会被使用。
3. 唯一索引:
和普通索引类似,但索引列值必须唯一;(允许有空值)
4. 全文索引(FULLTEXT):
一般情况下,模糊查询都是通过like的方式进行查询,全文索引主要用来查询文本中的关键字,而不是直接与索引中的值比较;
5. 主键索引:
主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引