Java软件开发技术面试题总结一

Java软件开发技术面试题总结一

Java基础数据类型
byte 8位(1个字节) short16位 int32位 float32位 long64位 boolean 8位 char16位 double64位

1、计算机网络
在这里插入图片描述
TCP三次握手过程、参数;
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
SYN:同步序列编号(Synchronize Sequence Numbers)
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手.
TCP四次挥手过程、参数;
1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
TCP和UDP的区别?应用场景有何不同?
首先两者都是传输层的协议: TCP(Transmission Control Protocol),又叫传输控制协议,UDP(User Datagram Protocol),又叫用户数据报协议,它们都是传输层的协议,但两者的机制不同,它们的区别如下:
在这里插入图片描述

面向链接: 面向连接就是传输数据前先建立连接然后再传输数据
非面向连接:就是像ip,直接发送数据包,到达一个节点后找下一条路由,一跳跳的下去
TCP保证可靠,面向连接而UDP不保证可靠,非面向连接,UDP的报头长度远远小于TCP的报头长度。TCP使用了三种基础机制来实现面向连接的服务:1 使用序列号进行标记,以便TCP接收服务在向目的应用传递数据之前修正错序的报文排序;2 TCP使用确认,校验,和定时器系统提供可靠性。3 TCP在应用层数据上附加了一个报头,报头包括序列号字段和这些机制的其他一些必要信息,如叫做端口号的地址字段,该字段可以标识数据的源点和目标应用程序。
TCP阻塞控制;
TCP的拥塞控制由4个核心算法组成:“慢启动”(Slow Start)、“拥塞避免”(Congestion voidance)、“快速重传 ”(Fast Retransmit)、“快速恢复”(Fast Recovery)。
-- 慢启动
早期开发的TCP应用在启动一个连接时会向网络中发送大量的数据包,这样很容易导致路由器缓存空间耗尽,网络发生拥塞,使得TCP连接的吞吐量急剧下降。由于TCP源端一开始并不知道网络资源当前的利用状况,因此新建立的TCP连接不能一开始就发送大量数据,而只能逐步增加每次发送的数据量,以避免上述现象的发生,这里有一个“学习”的过程。 假设client要发送5120字节到server,
慢启动过程如下:
1.初始状态,cwnd=1,seq_num=1;client发送第一个segment;
2.server接收到512字节(一个segment),回应ack_num=513;
3.client接收ack(513),cwnd=1+1=2;现在可以一次发送2个数据段而不必等待ack
4.server接收到2个segment,回应ack_num=513+5122=1537
5.client接收ack(1537),cwnd=2+1;一次发送3个数据段
6.server接收到3个segment,回应2个ack,分别为ack_num=1537+1024=2561和ack_num=2561+512=3073
7.client接收ack(2561)和ack(3073),cwnd=3+2=5;一次可以发送5个数据段,但是只用4个就满足要求了
8.server接收到4个segment,回应2个ack,分别为4097,5121 9.已经发送5120字节,任务完成!
总结一下: 当建立新的TCP连接时,拥塞窗口(congestion window,cwnd)初始化为一个数据包大小。源端按cwnd大小发送数据,每收到一个ACK确认,cwnd就增加一个数据包发送量。
-- 拥塞避免 可以想象,如果按上述慢启动的逻辑继续下去而不加任何控制的话,必然会发生拥塞,引入一个慢启动阈值ssthresh的概念,当cwnd<ssthresh的时候,tcp处于慢启动状态,否则,进入拥塞避免阶段。(客户端没接收到一个ack,cwnd就会增加4个字节,超过阀值之后,立即将cwnd降为1,做极端处理)
总结一下: 如果当前cwnd达到慢启动阀值,则试探性的发送一个segment,如果server超时未响应,TCP认为网络能力下降,必须降低慢启动阀值,同时,为了避免形势恶化,干脆采取极端措施,把发送窗口降为1,个人感觉应该有更好的方法。
-- 快速重传和快速恢复
前面讲过标准的重传,client会等待RTO时间再重传,但有时候,不必等这么久也可以判断需要重传,
快速重传
例如:client一次发送8个segment,seq_num起始值为100000,但是由于网络原因,100512丢失,其他的正常,则server会响应4个ack(100512)(为什么呢,tcp会把接收到的其他segment缓存起来,ack_num必须是连续的),这时候,client接收到四个重复的ack,它完全有理由判断100512丢失,进而重传,而不必傻等RTO时间了。
快速恢复
我们通常认为client接收到3个重复的ack后,就会开始快速重传,但是,如果还有更多的重复ack呢,如何处理?这就是快速恢复要做的,事实上,我们可以把快速恢复看作是快速重传的后续处理,它不是一种单独存在的形态。
以下是具体的流程:
假设此时cwnd=70000,client发送4096字节到server,也就是8个segment,起始seq_num = _100000:
1.client发送seq_num = _100000
2.seq_num =100512的segment丢失
3.client发送seq_num = _101024
4.server接收到两个segment,它意识到100512丢失,先把收到的这两个segment缓存起来
5.server回应一个ack(100512),表示它还期待这个segment
6.client发送seq_num = _101536
7.server接收到一个segment,它判断不是100512,依旧把收到的这个segment缓存起来,并回应ack(100512) 。 。 。
8.以下同6、7,直到client收到3个ack(100512),进入快速重发阶段:
9.重设慢启动阀值ssthresh=当前cwnd/2=70000/2=35000
10.client发送seq_num = 100512 以下,进入快速恢复阶段:
11.重设cwnd = ssthresh + 3 segments =35000 + 3
512 = 36536,之所以要加3,是因为我们已经接收到3个ack(100512)了,根据前面说的,每接收到一个ack,cwnd加1
12.client接收到第四个、第五个ack(100512),cwnd=36536+2*512=37560
13.server接收到100512,响应ack_num = _104096 14.此时,cwnd>ssthresh,进入拥塞避免阶段。
OSI七层模型、各层所用到的协议;
一些常见协议的原理:ARP、ICMP、FTP等(TCP UDP更不用说啦,一定要了解)
icmp: Internet控制报文协议,网络层协议(ping命令)
arp:地址解析协议,网络层(地址解析,抓包分析)
tcp:文件传输协议,基于的传输协议是tcp,因此也具有tcp的特性,是处于应用层的协议
在这里插入图片描述

2、数据库知识
数据库有哪些索引?原理是什么?
B数索引(B+树):create ind_t on t1(id) ;如果加上unique,则表示该索引中没有重复的值
最常用的索引,各叶子节点中包括的数据有索引列的值和数据表中对应行的ROWID,简单的说,在B树索引中,是通过在索引中保存排过续的索引列值与相对应记录的ROWID来实现快速查询的目的。
位图索引:create bitmap index ind_t on t1(type);【 注:位图索引不可能是唯一索引,也不能进行键值压缩。】
有些字段中使用B树索引的效率仍然不高,例如性别的字段中,只有“男、女”两个值,则即便使用了B树索引,在进行检索时也将返回接近一半的记录。
索引中不再记录rowid和键值,而是将每个值作为一列,用0和1表示该行是否等于该键值(0表示否;1表示是)。
反向键索引:create index ind_t on t1(id) reverse;
反向键索引是一种特殊的B树索引,在存储构造中与B树索引完全相同,但是针对数值时,反向键索引会先反向每个键值的字节,然后对反向后的新数据进行索引。例如输入2008则转换为8002,这样当数值一次增加时,其反向键在大小中的分布仍然是比较平均的。
索引有什么作用?有什么特点
这是因为,创建索引可以大大提高系统的性能。
第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快 数据的检索速度,这也是创建索引的最主要的原因。
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四,在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
索引为什么用B+树?
1、非叶子节点的子树指针与关键字个数相同;
2、非叶子节点的子树指针p[i],指向关键字值属于[k[i],k[i+1]]的子树.(B树是开区间,也就是说B树不允许关键字重复,B+树允许重复);
3、为所有叶子节点增加一个链指针;
4、所有关键字都在叶子节点出现(稠密索引). (且链表中的关键字恰好是有序的);
5、非叶子节点相当于是叶子节点的索引(稀疏索引),叶子节点相当于是存储(关键字)数据的数据层;
6、更适合于文件系统;
B+树和B-树有什么区别?(左b+,右b-)
B树(B-树)
是一种多路搜索树(并不是二叉的):
1.定义任意非叶子结点最多只有M个儿子;且M>2;
2.根结点的儿子数为[2, M];
3.除根结点以外的非叶子结点的儿子数为[M/2, M];
4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
5.非叶子结点的关键字个数=指向儿子的指针个数-1;
6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的
子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
8.所有叶子结点位于同一层;
如:(M=3)
对于B+树来说:
B+树是B-树的变体,也是一种多路搜索树:
1.其定义基本与B-树同,除了:
2.非叶子结点的子树指针与关键字个数相同;
3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树
(B-树是开区间);
5.为所有叶子结点增加一个链指针;
6.所有关键字都在叶子结点出现;
如:(M=3)
mysql中MyISAM和InnoDB的区别?(两种存储引擎)
每一个MyISAM表都对应于硬盘上的三个文件。这三个文件有一样的文件名,但是有不同的扩展名以指示其类型用途:.frm文件保存表的定义,但是这个文件并不是MyISAM引擎的一部分,而是服务器的一部分;.MYD保存表的数据;.MYI是表的索引文件。
InnoDB是MySQL的另一个存储引擎,目前MySQL AB所发行新版的标准,被包含在所有二进制安装包里,5.5之后作为默认的存储引擎。较之于其它的存储引擎它的优点是它支持兼容ACID的事务(类似于PostgreSQL),以及参数 完整性(即对外键的支持)。
1>.InnoDB支持事物,而MyISAM不支持事物
2>.InnoDB支持行级锁,而MyISAM支持表级锁
3>.InnoDB支持MVCC, 而MyISAM不支持
4>.InnoDB支持外键,而MyISAM不支持
5>.InnoDB不支持全文索引,而MyISAM支持。
在使用count进行数据库计数的时候,innodb是要全部的计算一遍,而myisam的效率更快,其中有计算的方法,所以在计数方面myisam更快捷
事务的四大特性(常考)
事物是一条或者多条sql语句组成的执行序列,这个序列中的所有语句都属于同一个工作单元,要么同时完成,其中如果有一个失败,则其他操作都要回滚。
原子性 (Atomicity)
事物是一个不可分割的数据库逻辑工作单位,要么全部完成,要不失败回滚。
一致性 (Consistency)
事务执行的结果必须使数据库从一个一致性状态变到另一个一致性状态。
隔离性 (Isolation)
一个事物的执行不能被别的并发事物所干扰
持久性 (Durability)
事物一旦提交,其对数据库的改变应该是永久的。
数据库优化的一些策略;
1,表结构优化
表结构优化是数据库优化中最重要的,需要结合实际情况来看怎么设计更加的优化合理
2,sql语句优化
*sql语法优化,写出更加便捷的sql语句
*处理逻辑优化,如配合索引和缓存的使用
一个常见的做法是,将涉及到大数据量的sql语句记录下来,观察日志,有侧重点的优化
3,分区分表
分区是指将一张表的数据按照一定的规则分到不同的区来保存。若一张表中有几种类型,可以考虑分表
举一个例子,分区按照月份来分,将不同类型的字段分表,这么做的好处就是增删改查数据的时候范围已经大大缩小了
4,索引优化
索引的原理是在进行增删改的时候就预先按照指定的字段顺序排列后保存了,在查找的时候就可以从索引找到对应的指针找到数据
优点:查询效率很高 缺点:每次增删改要更新索引,降低增删改的速度
5,分离活跃数据
将活跃数据单独存放起来
比如登录网站,可以将活跃度高的用户单独存放(依据最近登录时间或者单位时间内登录次数等),查询时先从活跃数据查找,没有再去不活跃处查找
6,读写分离
读写分离的本质是对数据库进行集群,在高并发的情况下降低单台服务器的压力。
一般将写的服务器叫主服务器,写入后同步到读服务器(即从服务器),并将读请求分配到多个服务器上
增删改查要熟悉,随时可能提一个需求让你写一个SQL语句;
select * from tablename where a=1
insert into tablename(a,b,c) values (1,2,3)/insert into tablename select a,b,c from tablename1
update tablename set a=1 where id =2019
delect from tablename where id=1
数据库索引:聚集索引和非聚集索引的区别?
聚集索引的表中记录的物理顺序与索引的排列顺序一致,优点是查询速度快,因为一旦具有第一个索引值的记录被找到,具有连续索引值的记录也一定物理的紧跟其后。
缺点是对表进行修改速度较慢,这是为了保持表中的记录的物理顺序与索引 的顺序一致,而把记录插入到数据页的相应位置,必须在数据页中进行数据重排,降低了执行速度。在插入新记录时数据文件为了维持 B+Tree 的特性而频繁的 分裂调整,十分低效。
非聚集索引的记录的物理顺序和索引的顺序不一致。 非聚集索引添加记录时,不会引起数据顺序的重组。
3、编程语言基础(以Java为例)
面向对象的特性?(封装继承多态)如何体现出来?

  1. 继承: 继承都是从已有的类得到继承信息 创建新的类的过程.提供信息的类被称为父类(超类,基类),得到信息的类被称为子类(派生类).继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段.
  2. 封装: 通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口.面向对象的本质就是将现实世界描绘成一系列完全自治,封闭的对象.我们在类中编写方法就是对实现细节的一种封装;我们编写了一个类就是对数据的数据操作的封装,可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口
  3. 多态性: 多态性是指允许不同子类型的对象对同一消息作出不同响应,简单的说就是用同样的对象引用调用同样的对象引用调用同样的方法但是做了不同的事情.多态性分为编译时多态性和运行时多态性.如果将对象的方法视为对象向外界提供服务,那么运行时多态性可以解释为:当A系统访问B系统提供的服务是,B系统有多种提供服务的方式.但一切对于A系统来说都是透明的.方法重载(overload) 实现的是编译时的多态性(也称为前绑定), 而方法重写(override) 实现的是运行时的多态性(也成后绑定). 运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事: 1. 方法重写(子类继承父类并重写父类中已有的或抽象方法); 2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)
    重载和重写有什么区别?
    重载: 在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
    重载 总结:
    1.重载Overload是一个类中多态性的一种表现
    2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)
    3.重载的时候,返回值类型可以相同也可以不相同。无法以返回类型作为重载函数的区分标准
    重写:从字面上看,重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
    重写 总结:
    1.发生在父类与子类之间
    2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同
    3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
    4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
    面试时,问:重载(Overload)和重写(Override)的区别?
    答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分
    集合类有哪些?(常考)
    Set、List、Map和Queue4大体系。其中,Set代表无序的、不允许有重复元素的集合,List代表有序的、允许有重复元素的集合,Map代表具有映射关系的集合,Queue代表队列集合。Java集合类主要由2个接口派生过来:Collection接口和Map接口!
    Set和List的区别;
    List 是可重复集合,list是有序的数据结构
    Set 是不可重复集合,set是无序的数据结构
    这两个接口都实现了 Collection 父接口.
    ArrayList、Linkedlist、Vector区别?(常考,建议看下底层实现代码)
    1、Vector、ArrayList都是以类似数组的形式存储在内存中,LinkedList则以链表的形式进行存储。
    3、Vector线程同步,ArrayList、LinkedList线程不同步。
    4、LinkedList适合指定位置插入、删除操作,不适合查找;ArrayList、Vector适合查找,不适合指定位置的插入、删除操作。
    5、ArrayList在元素填满容器时会自动扩充容器大小的50%,而Vector则是100%,因此ArrayList更节省空间。
    数组:可以根据下标快速查找,所以大部分情况下,查询快。但是如果要进行增删操作的时候,会需要移动修改元素后面的所有元素,所以增删的开销比较大,数组的对增删操作的执行效率低。而采用数组作为数据存储结构的ArrayList、Vector也存在这些特性,查询速度快(可以根据下标直接取,比迭代查找更快),增删慢。
    链表:增加和删除元素方便,增加或删除一个元素,仅需处理结点间的引用即可。就像人手拉手连成一排,要增加或删除某个人只要附近的两个人换一个人牵手,对已经牵好手的人没影响。无论在哪里换人耗费的资源和时间都是一样的。但是查询不方便,需要一个个对比,无法根据下标直接查找。而采用链表结构存储的LinkedList也有这些特性,增删方便,查询慢(指的是随机查询,不是顺序查询)。
    ArrayList如何扩容?(常考)
    当数组里面的元素一个一个进行增加的时候,每次进行判断,当新增一个元素之后,数组的长度>初定义的长度的话,那么就创建一个新的数组:
    int newCapacity = oldCapacity + oldCapacity / 2,这样就是表示新的数组长度是旧的数组长度的1.5倍,然后用copyof的方法将原来数组的内容复制到新的数组中,完成arraylist的动态扩容
    Map下Hashmap、TreeMap的区别?
    (1)HashMap:适用于在Map中插入、删除和定位元素。
    (2)Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
    (3)HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap.
    (4)HashMap 非线程安全 TreeMap 非线程安全
    (5)HashMap的结果是没有排序的,而TreeMap输出的结果是排好序的。
    (6)HashMap:底层是哈希表数据结构, TreeMap:底层是二叉树数据结构
    TreeMap底层是什么?红黑树还是二叉树?
    红黑树
    Map、List下哪些类是线程安全的?(常考)
    map的实现类有:hashtable(线程安全) concurrenthashmap(线程安全)hashmap(不安全)treemap(不安全)linkedhashmap(不安全)weakhashmap(不安全)
    list的实现类:vector(线程安全)arraylist(不安全)linkendlist(不安全)
    Hashmap的扩容机制;
    HashMap使用的是懒加载,构造完HashMap对象后,只要不进行put 方法插入元素之前,HashMap并不会去初始化或者扩容table。
    当首次调用put方法时,HashMap会发现table为空然后调用resize方法进行初始化,当添加完元素后,如果HashMap发现size(元素总数)大于threshold(阈值),则会调用resize方法进行扩容
    默认的负载因子大小为0.75,当一个map填满了75%的bucket时候,就会扩容,扩容后的table大小变为原来的两倍
    重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing
    Hashmap如何解决哈希冲突?与HashTable有何不同?
    哈希冲突:通俗的讲就是首先我们进行一次 put 操作,算出了我们要在 table 数组的 x 位置放入这个值。那么下次再进行一个 put 操作的时候,又算出了我们要在 table 数组的 x 位置放入这个值
    hashmap解决哈希冲突:首先判断两者的key是不是一样的,因为hashmap是不允许有重复值得,如果两者是重复的,就进行覆盖,否则就利用链地址法,将冲突的节点放在链表的最下面。
    如果冲突过多,可以将链表转化为红黑树,时间复杂度是 O(logn)
    HashMap和HashTable主要有以下5个方面的区别:
    1.继承的父类不同
      Hashtable继承自Dictionary类,而HashMap继承自
    类。但二者都实现了Map接口。
    2.对null对象的支持不同
      HashMap是支持null键和null值的,而HashTable在遇到null时,会抛出NullPointerException异常。这并不是因为HashTable有什么特殊的实现层面的原因导致不能支持null键和null值,这仅仅是因为HashMap在实现时对null做了特殊处理,将null的hashCode值定为了0,从而将其存放在哈希表的第0个bucket中。
    3.容量大小及扩容方式不同
      HashMap和HashTable都使用哈希表来存储键值对。在数据结构上是基本相同的,都创建了一个继承自Map.Entry的私有的内部类Entry,每一个Entry对象表示存储在哈希表中的一个键值对。
    Entry对象唯一表示一个键值对,有四个属性:
    -K key 键对象
    -V value 值对象
    -int hash 键对象的hash值
    -Entry entry 指向链表中下一个Entry对象,可为null,表示当前Entry对象在链表尾部。
      HashMap/HashTable内部用Entry数组实现哈希表,而对于映射到同一个哈希桶(数组的同一个位置)的键值对,使用Entry链表来存储。
      HashMap/HashTable初始容量大小和每次扩充容量大小的不同:HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。如果在创建时给定了初始化大小,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。
    4.线程安全性不同
      HashTable是同步的(原因:公开的方法比如get都使用了synchronized描述符。而遍历视图比如keySet都使用了Collections.synchronizedXXX进行了同步包装),HashMap不是,也就是说HashTable在多线程使用的情况下,不需要做额外的同步,而HashMap则不行。
      由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap速度要慢。
      如果要保持线程安全可以选用ConcurrentHashMap,ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。Hashtable则会锁定整个map,Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map,ConcurrentHashMap比Hashtable高效。
    5.Hash值不同
      Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值,Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length取模,&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而&0x7FFFFFFF后,只有符号位改变,而后面的位都不变。
    \t 的意思是 横向跳到下一制表符位置(tab键)
    \r 的意思是 回车
    \n 的意思是回车换行。
    正则表达式;(服务端岗位常考)
    接口跟抽象类的区别?
    下面比较-下两者的语法区别:
    1.抽象类可以有构造方法,接口中不能有构造方法。
    2.抽象类中可以有普通成员变量,接口中没有普通成员变量
    3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
    4.抽象类中的抽象方法的访问类型可以是public , protected和(默认类型,虽然eclipse下不报错,但应该也不行 ) , 但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
    5.抽象类中可以包含静态方法,接口中不能包含静态方法
    6.抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变星只能是public static final类型,并且默认即为public static final类型。
    7.一个类可以实现多个接口,但只能继承一个抽象类。
    抽象(abstract)
    public abstract class people {
    public abstract void func1();
    public void func11() {
    }
    public void a() {}
    int a;
    public people() {
    int a=0;
    }
    protected void b() {}
    final void c() {}
    public static void cc() {}
    public void aaa() {
    }
    }
    package com.example.cache.test;
    /**
  • 类的抽象abstract
  • */
    public abstract class test {
    public int a;//抽象类可以有普通成员变量
    public test(){}//抽象的无参构造
    public test(int a){}//抽象的有参构造
    private int num=0;//抽象的变量
    public int num1=0;
    final int num2=0;
    public abstract int b();//不写方法主体,必须定义为abstract修饰
    public int test1(){return 0;}//抽象的非抽象普通方法
    protected int test3(){return 0;}//抽象的非抽象普通方法
    private int test4(){return 0;}//抽象的非抽象普通方法,可以是私有的,只是子类在继承的时候,没办法继承这个私有的方法
    public final int test2(){return 0;}//抽象类可以写final方法,但是不可以被重写,而且在子类中可以调用这个方法
    static int a1=0;//可以有静态的成员变量,静态变量就是独立于其他的变量,和全局变量的区别就是在多个类文件之间进行调用时,全局可以调用,静态不可以
    }

package com.example.cache.test;
/**

  • 类的接口
  • */
    public interface test1 {
    public int a;//接口没有普通成员变量,这样写是会报错的,必须要赋值
    public test1(){}//接口没有构造函数,这样写是会报错的
    abstract int test1();//接口的方法不能有主体;
    int test21();//接口不能使用final和protected进行修饰
    static int a1=0;//可以有静态的成员变量,静态变量就是独立于其他的变量,和全局变量的区别就是在多个类文件之间进行调用时,全局可以调用,静态不可以
    public int b();//不写方法主体,必须定义为abstract修饰
    }
    Java可以多继承吗?
    java不支持多继承,只支持单继承(即一个类只能有一个父类)。但是java可以实现多个接口,即一个子接口可以有多个父接口。
    JVM垃圾回收机制;(常考)
    是否符合垃圾回收的标准有两个:
    (一)给对象赋予了null,以后再没有使用过;
    (二)给对象赋予了新值,重新分配了内存空间。
    判断对象是不是需要被回收算法:
    1、引用计数法:
    给对象添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1;当引用失效时,计数器的值就减1;任何时刻计数器为0的对象就是不可能被使用的。 但是在主流的JVM中并没有选用引用计数法来管理内存,最主要的原因是它很难解决对象间互相循环引用的问题
    2、可达性分析法:
    基本思路是通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到GC Roots没通路时(也就是从GC Roots到这个对象不可达),则证明此对象是不可用的。
    垃圾处理算法:
    1、标记-清除算法:
    主要分为两个阶段:标记和清除两部分,首先就是标记上所有的需要进行回收的对象,然后在标记完成之后统一将需要进行回收的对象进行回收;
    存在两个问题:1)效率问题(效率不高),2)空间问题,标记清除之后会产生大量的不连续的内存碎片。
    2、复制算法:
    复制算法的出现是为了解决效率问题,它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次性清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑碎片等复杂情况,只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半。代价有点高了。
    3、标记-整理算法:
    标记过程和标记-清除算法是一样的,将需要进行回收的对象进行标记,然后将这些需要进行回收的对象向一端进行移动,然后清除掉端边界意外的内存
    4、分代收集算法:
    当前商业虚拟机都采用分代收集算法,将对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死亡,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外的空间对它进行分配担保,就必须使用标记 - 清除算法或者标记 - 整理算法来进行回收。
    Java中是值传递还是引用传递?
    值传递
    Java中锁机制;
    乐观锁: 总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
    悲观锁:顾名思义就是采用一种悲观的态度来对待事务并发问题,我们认为系统中的并发更新会非常频繁,并且事务失败了以后重来的开销很大,这样以来,我们就需要采用真正意义上的锁来进行实现。悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等以前的事务提交或者回滚解除锁。
    重入锁: 重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
    在JAVA环境下 ReentrantLock(轻量级) 和synchronized(重量级) 都是 可重入锁
    读写锁:相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(译者注:也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题
    Lock的底层怎么实现的?源码怎么写的?

sychronized的底层实现?

sychronized修饰静态方法和修饰普通方法有什么区别?

异常类有哪些实现类、子类?
(1)NullPointerException 当应用程序试图访问空对象时,则抛出该异常。
(2)SQLException 提供关于数据库访问错误或其他错误信息的异常。
(3)IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
(4)NumberFormatException当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
(5)FileNotFoundException当试图打开指定路径名表示的文件失败时,抛出此异常。
(6)IOException当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
(7)ClassCastException当试图将对象强制转换为不是实例的子类时,抛出该异常。
(8)ArrayStoreException试图将错误类型的对象存储到一个对象数组时抛出的异常。
(9)IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
(10)ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
(11)NegativeArraySizeException如果应用程序试图创建大小为负的数组,则抛出该异常。
(12)NoSuchMethodException无法找到某一特定方法时,抛出该异常。
(13)SecurityException由安全管理器抛出的异常,指示存在安全侵犯。
(14)UnsupportedOperationException当不支持请求的操作时,抛出该异常。
(15)RuntimeExceptionRuntimeException 是那些可能在Java虚拟机正常运行期间抛出的异常的超类。
多线程中如何保证线程安全?
1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
2.可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。

多线程有哪些常见的线程安全的类?
Timer,HashTable,StringBuffer,TimerTask,Vector,Stack
如何开启一个线程?
3 /** 4 * @desc 第一种开启线程的方式
5 * @author
6 *
7 */ 8 public class Demo1_Thread extends Thread {
9
10 public void run() {
11 for (int i = 0; i < 10; i++) {
12 System.out.println(i + " run()…");
13 }
14 }
15
16 public static void main(String[] args) {
17 Demo1_Thread demo = new Demo1_Thread();
18 demo.start();
19 for (int i = 0; i < 10; i++) {
20 System.out.println(i + " main()…");
21 }
22 }
24 }
可以继承thread类,新建一个当前类的对象,然后执行start()方法即可
get请求和post请求有什么区别?
GET产生一个TCP数据包;POST产生两个TCP数据包。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET请求会被浏览器主动cache,而POST不会
反射的原理?
java在执行的时候,先后是通过编译、加载、连接进行的
编译就是java文件编译后生成.class字节码文件
加载就是类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例
Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。总结说:反射就是把java类中的各种成分映射成一个个的Java对象,并且可以进行操作。
ClassLoader和Class.forName()这两个有什么区别?(反射源码的考察)
NIO这一块有什么了解?

4、项目框架(以Spring为例)
SSH是 struts+spring+hibernate的一个集成框架
SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)
简述Springmvc的流程;
1、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、 DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、 HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
5、 ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、 View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
控制反转(ioc):
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
  ●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  ●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
  其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
依赖注入(DI)
“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
Spring的核心特性是什么?
Ioc和aop
理解AOP、IoC的基本原理;
AOP:面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。主要功能有日志记录,性能统计,安全控制,事务处理,异常处理等等。主要意图就是把以上功能从业务逻辑代码中分离出来,进而改变这些行为的时候不影响业务逻辑的代码。说白了,就是扩展功能不修改源代码实现。
aop有两种代理方式:
java代理:采用java内置的代理API实现(写接口,设置代理)
cglib代理:采用第三方API实现
两者的区别:
cglib底层运用了asm这个非常强大的Java字节码生成框架来生成class, 比jdk代理效率要高
jdk代理只能代理被接口修饰的类,而cglib没有这个限制
IoC上面有总结:
IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤:
a. 加载配置文件,解析成 BeanDefinition 放在 Map 里。
b. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
AOP的一些场景应用;
主要在做日志记录,性能统计,安全控制,事务处理,异常处理的时候
Springmvc和Springboot有什么区别?
Spring MVC的功能
Spring MVC提供了一种轻度耦合的方式来开发web应用。
Spring MVC是Spring的一个模块,是一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。
Spring Boot的功能
Spring Boot实现了自动配置,降低了项目搭建的复杂度。
众所周知Spring框架需要进行大量的配置,Spring Boot引入自动配置的概念,让项目设置变得很容易。Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box),大部分的Spring Boot应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。
Spring Boot只是承载者,辅助你简化项目搭建过程的。如果承载的是WEB项目,使用Spring MVC作为MVC框架,那么工作流程和你上面描述的是完全一样的,因为这部分工作是Spring MVC做的而不是Spring Boot。
对使用者来说,换用Spring Boot以后,项目初始化方法变了,配置文件变了,另外就是不需要单独安装Tomcat这类容器服务器了,maven打出jar包直接跑起来就是个网站,但你最核心的业务逻辑实现与业务流程实现没有任何变化。
所以,用最简练的语言概括就是:
Spring 是一个“引擎”;
Spring MVC 是基于Spring的一个 MVC 框架 ;
Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。
Springboot为什么配置简单?(即它自动做了什么操作才能简化程序员的操作)
有很多的xxxautoconfigration,为我们配置了很多的自动配置
Spring容器的加载顺序?
@Resource 和 @Autowired 区别?分别用在什么场景?
两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
不同点
(1)@Autowired(spring的注解)
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。如下:
(2)@Resource(Java的注解)
@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
静态代理和动态代理的区别?
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成
Hibernate和mybatis的区别?
2.1 开发方面
在项目开发过程当中,就速度而言:
hibernate开发中,sql语句已经被封装,直接可以使用,加快系统开发;
Mybatis 属于半自动化,sql需要手工完成,稍微繁琐;
但是,凡事都不是绝对的,如果对于庞大复杂的系统项目来说,发杂语句较多,选择hibernate 就不是一个好方案。
2.2 sql优化方面
Hibernate 自动生成sql,有些语句较为繁琐,会多消耗一些性能;
Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能;
2.3 对象管理比对
Hibernate 是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;
Mybatis 需要自行管理 映射关系;
2.4 缓存方面
Hibernate 具有良好的管理机制,用户不需要关注SQL,如果二级缓存出现脏数据,系统会保存,;
Mybatis 在使用的时候要谨慎,避免缓存CAche 的使用。

Hibernate优势
Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。
Mybatis优势
MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
MyBatis容易掌握,而Hibernate门槛较高。

mybatis是如何工作的?
1、加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着<select | update | delete | insert>标签项。
2、SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。
3、SqlSession对象完成和数据库的交互:
a、用户程序调用mybatis接口层api(即Mapper接口中的方法, crud)
b、SqlSession通过调用api的Statement ID找到对应的MappedStatement对象
c、通过Executor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbc Statement对象
d、JDBC执行sql。
e、借助MappedStatement中的结果映射关系,将返回结果转化成HashMap、JavaBean等存储结构并返回。
Hibernate对象有几个状态值?
Hibernate 把对象分为三种状态:Transient(临时状态)、Persistent(持久化状态)、Detached(游离状态)。
1)Transient:刚刚new出来的对象,就是Transient状态的,此时他没有OID。
2)Persistent:有持久化标志OID,已经被纳入到session对象的管理
3) Detached: --有持久化标志OID,但是没有被纳入到Session对象的管理
数据库的锁机制:
数据库锁出现的目的:处理并发问题
并发控制的主要采用的技术手段:乐观锁、悲观锁和时间戳。
锁分类
从数据库系统角度分为三种:排他锁、共享锁、更新锁。
从程序员角度分为两种:一种是悲观锁,一种乐观锁。
redirect和forward的区别
redirect是间接请求转发: 有时也叫重定向,它一般用于避免用户的非正常访问
response.sendRedirect("…/user-jsp/login.jsp");
forward是直接请求转发: 在请求对象request中,保存的对象对于每个信息资源是共享的。
request.getRequestDispatcher("/main.jsp").forward(request, response);
oracle数据库的数据表的交集表现用intersect(从两个表中查一些要合在一起的数据)
SELECT CODE FROM EMPLOYEE WHERE GENDER = ‘M’
INTERSECT
SELECT CODE FROM SALARY WHERE SALARY > 2500
jsp页面传递参数的几种方法:
1、可以使用a标签进行传递,例如: 点击 //设置参数和赋值,
然后在后台就可以使用requset.getParameter的方式进行接收:String name=request.getParameter(“name”)
2、可以用request来传递参数
请求作用域,客户端的一次请求。生命周期为一次请求或使用forward方式执行请求转发,也就是使用forward方式跳转多个jsp,在这些页面里你都可以使用这个变量。但是只要刷新页面,这个变量就失效了。

<%request.setCharacterEncoding("UTF-8"); %> <%request.setAttribute("name", "李白"); %> 然后,在另外进行接收 String name= request.getAttribute("name"); 3.可以使用session进行传递参数: session的作用时间简单的说就是从浏览器打开到浏览器关闭这个过程中有效。 <%session.setAttribute("name", "李白"); %> 另外进行接收:String name= session.getAttribute("name");

4.可以用application进行参数的传递(方法类似session)
appllication是全局作用范围,整个应用程序共享,生命周期为从应用程序启动到停止,在这个过程中application里的变量一直在累加,除非你重启tomcat或是人工删除,否则它会一直变大。
在一边: <%application.setAttribute(“name”, “李白”); %>
在另一边进行接收:String name= application.getAttribute(“name”);
5、使用jstl的方式,前提是引入jstl的jar包:
在一端:
<c:redirect url=“main.jsp”> //负责跳转
<c:param name=“user” value=“wgh”></c:param> //赋值
</c:redirect>
在另一端接收:{param.user}]您好,欢迎访问我公司的网
String和StringBuffer的区别
String:是对象不是原始类型.
为不可变对象,一旦被创建,就不能修改它的值.
对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.
String 是final类,即不能被继承.
StringBuffer:
是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象
它只能通过构造函数来建立,
StringBuffer sb = new StringBuffer();
对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.通过它的append方法向其赋值.
sb.append(“hello”);
字符串连接操作中StringBuffer的效率要明显比String高:
String对象是不可变对象,每次操作String 都会重新建立新的对象来保存新的值.
StringBuffer对象实例化后,只对这一个对象操作。
并且StringBuffer的字符操作效率比String的效率高很多。
删除索引:(下面两者等价)
DROP INDEX index_name ON talbe_name
ALTER TABLE table_name DROP INDEX index_name
建立索引的原则:
定义主键的数据列一定要建立索引。
定义有外键的数据列一定要建立索引。
对于经常查询的数据列最好建立索引。
对于需要在指定范围内的快速或频繁查询的数据列;
经常用在WHERE子句中的数据列。
经常出现在关键字order by、group by、distinct后面的字段,建立索引。如果建立的是复合索引,索引的字段顺序要和这些关键字后面的字段顺序一致,否则索引不会被使用。
对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
对于定义为text、image和bit的数据类型的列不要建立索引。
对于经常存取的列避免建立索引
限制表上的索引数目。对一个存在大量更新操作的表,所建索引的数目一般不要超过3个,最多不要超过5个。索引虽说提高了访问速度,但太多索引会影响数据的更新操作。
对复合索引,按照字段在查询条件中出现的频度建立索引。在复合索引中,记录首先按照第一个字段排序。对于在第一个字段上取值相同的记录,系统再按照第二个字段的取值排序,以此类推。因此只有复合索引的第一个字段出现在查询条件中,该索引才可能被使用,因此将应用频度高的字段,放置在复合索引的前面,会使系统最大可能地使用此索引,发挥索引的作用。
数据库的外键( foreign key)
数据库的外键创建的必要性:
1、表的组织结构复杂不清晰
2、浪费空间
3、扩展性极差
为了解决上述的问题,就需要用多张表来存放数据。
表与表的记录之间存在着三种关系:一对多、多对多、一对一的关系。
处理表之间关系问题就会利用到FOREIGN KEY
create table dep(
id int primary key auto_increment,
dep_name char(10),
dep_comment char(60)
);
create table emp(
id int primary key auto_increment,
name char(16),
gender enum(‘male’,‘female’) not null default ‘male’,
dep_id int,
foreign key(dep_id) references dep(id)
这样在创建的时候,就可以将emp中的dep_id对应上emp中中的id
使用的语法就是
foreign key(xxxb) references tablename(xxxa)
public/private/protected
1、public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用

2、private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,私有财产神圣不可侵犯嘛,即便是子女,朋友,都不可以使用。

3、protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。
Java是否存在内存泄漏问题
内存泄漏是指一个不再被程序使用的对象或变量还在内存中占有存储空间
在java语言中,判断一个内存空间是否符合垃圾回收的标准有两个:
(一)给对象赋予了null,以后再没有使用过;
(二)给对象赋予了新值,重新分配了内存空间。
在java语言中,内存泄漏主要有两种情况:
(一)是在堆中申请的空间没有被释放;
(二)是对象已经不再被使用,但仍然在内存中保留。
垃圾回收可以解决(一),但是无法保证不再被使用的对象会被释放。
在java语言中,内存泄漏的原因很多:
1.静态集合类:例如static HashMap和static Vector,由于它们的生命周期与程序一致,那么容器中的对象在程序结束之前将不能被释放。
2.各种连接:例如数据库连接、网络连接和IO连接等,当不再使用时,需调用close()方法来释放连接。
3.监听器:在释放对象的同时没有删除相应的监听器。
4.变量不合理的作用域:一个变量定义的作用范围大于其使用范围(例如一个本可以定义为方法内局部变量的变量,却被定义为程序对象内的全局变量),并且在使用完后没有及时地把它设为null。

栈:后进先出,从一端进,从一端出
队列:先进先出,从队尾进,从队头出
几种常见的排序算法总结: https://www.cnblogs.com/hokky/p/8529042.html
性能比较:
在这里插入图片描述
直接插入排序: 直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
在这里插入图片描述
嵌套两层循环:
第一层循环:遍历待比较的所有数组元素
第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。
如果:selected > ordered,那么将二者交换
#直接插入排序def insert_sort(L):
#遍历数组中的所有元素,其中0号索引元素默认已排序,因此从1开始
for x in range(1,len(L)):
#将该元素与已排序好的前序数组依次比较,如果该元素小,则交换
#range(x-1,-1,-1):从x-1倒序循环到0
for i in range(x-1,-1,-1):
#判断:如果符合条件则交换
if L[i] > L[i+1]:
temp = L[i+1]
L[i+1] = L[i]
L[i] = temp
希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。(步长gap是自己进行设定的,最后必须是1,并且步长的设定必须小于数组的长度)
在这里插入图片描述
嵌套三层循环:
同样的:从上面的描述中我们可以发现:希尔排序的总体实现应该由三个循环完成:
第一层循环:将gap依次折半,对序列进行分组,直到gap=1
第二、三层循环:也即直接插入排序所需要的两次循环。具体描述见上。
def insert_shell(L):
#初始化gap值,此处利用序列长度的一般为其赋值
gap = (int)(len(L)/2)
#第一层循环:依次改变gap值对列表进行分组
while (gap >= 1):
#下面:利用直接插入排序的思想对分组数据进行排序
#range(gap,len(L)):从gap开始
for x in range(gap,len(L)):
#range(x-gap,-1,-gap):从x-gap开始与选定元素开始倒序比较,每个比较元素之间间隔gap
for i in range(x-gap,-1,-gap):
#如果该组当中两个元素满足交换条件,则进行交换
if L[i] > L[i+gap]:
temp = L[i+gap]
L[i+gap] = L[i]
L[i] =temp
#while循环条件折半
gap = (int)(gap/2)
简单选择排序算法:简单选择排序的基本思想:比较+交换。
从待排序序列中,找到关键字最小的元素;
如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
在这里插入图片描述
两层嵌套循环:
第一层循环:依次遍历序列当中的每一个元素
第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。

简单选择排序def select_sort(L):#依次遍历序列中的每一个元素

for x in range(0,len(L)):
#将当前位置的元素定义此轮循环当中的最小值
minimum = L[x]
#将该元素与剩下的元素依次比较寻找最小元素
for i in range(x+1,len(L)):
if L[i] < minimum:
temp = L[i];
L[i] = minimum;
minimum = temp
#将比较后得到的真正的最小值赋值给当前位置
L[x] = minimum
堆排序:(堆:本质是一种数组对象。特别重要的一点性质:任意的叶子节点小于(或大于)它所有的父节点。对此,又分为大顶堆和小顶堆,大顶堆要求节点的元素都要大于其孩子,小顶堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求。利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现【大顶堆的根节点一定是最大的数字】)
基本思想:
堆排序可以按照以下步骤来完成:
首先将序列构建称为大顶堆;
(这样满足了大顶堆那条性质:位于根节点的元素一定是当前序列的最大值)
取出当前大顶堆的根节点,将其与序列末尾元素进行交换;
(此时:序列末尾的元素为已排序的最大值;由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质)
对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质;
重复2.3步骤,直至堆中只有1个元素为止
(说白了就是每次找一个最大的,然后将它放在最后,然后在剩余的元素中在找最大的,放在后面,依次类推)
冒泡排序:
将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素;
( 第一轮结束后,序列最后一个元素一定是当前序列的最大值;)
对序列当中剩下的n-1个元素再次执行步骤1。
对于长度为n的序列,一共需要执行n-1轮比较
(利用while循环可以减少执行次数)
在这里插入图片描述
#冒泡排序def bubble_sort(L):
length = len(L)
#序列长度为length,需要执行length-1轮交换
for x in range(1,length):
#对于每一轮交换,都将序列当中的左右元素进行比较#每轮交换当中,由于序列最后的元素一定是最大的,因此每轮循环到序列未排序的位置即可
for i in range(0,length-x):
if L[i] > L[i+1]:
temp = L[i]
L[i] = L[i+1]
L[i+1] = temp
快速排序:
快排使用的是分治的思想:将要排序的数列分为两部分,设定一个分解值,将小于等于分解质的数放在左边,大于的放在右边,然后在从左边的数列里面设定一个分解值,然后在进行排序,右边同理,然后在拍完的时候就是从小到大的序列顺序
快速排序的基本思想:挖坑填数+分治法
从序列当中选择一个基准数(pivot)
在这里我们选择序列当中第一个数最为基准数
将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧
重复步骤1.2,直到所有子集当中只有一个元素为止。
用伪代码描述如下:
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中
在这里插入图片描述
#快速排序#L:待排序的序列;start排序的开始index,end序列末尾的index#对于长度为length的序列:start = 0;end = length-1def quick_sort(L,start,end):
if start < end:
i , j , pivot = start , end , L[start]
while i < j:#从右开始向左寻找第一个小于pivot的值
while (i < j) and (L[j] >= pivot):
j = j-1#将小于pivot的值移到左边
if (i < j):
L[i] = L[j]
i = i+1
#从左开始向右寻找第一个大于pivot的值
while (i < j) and (L[i] < pivot):
i = i+1#将大于pivot的值移到右边
if (i < j):
L[j] = L[i]
j = j-1#循环结束后,说明 i=j,此时左边的值全都小于pivot,右边的值全都大于pivot#pivot的位置移动正确,那么此时只需对左右两侧的序列调用此函数进一步排序即可#递归调用函数:依次对左侧序列:从0 ~ i-1//右侧序列:从i+1 ~ end
L[i] = pivot
#左侧序列继续排序
quick_sort(L,start,i-1)
#右侧序列继续排序
quick_sort(L,i+1,end)

归并排序: https://blog.csdn.net/khwkhwkhw/article/details/51254467
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个典型的应用。它的基本操作是:将已有的子序列合并,达到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
归并排序其实要做两件事:
分解----将序列每次折半拆分
合并----将划分后的序列段两两排序合并
因此,归并排序实际上就是两个操作,拆分+合并
如何合并?
L[first…mid]为第一段,L[mid+1…last]为第二段,并且两端已经有序,现在我们要将两端合成达到L[first…last]并且也有序。
首先依次从第一段与第二段中取出元素比较,将较小的元素赋值给temp[]
重复执行上一步,当某一段赋值结束,则将另一段剩下的元素赋值给temp[]
此时将temp[]中的元素复制给L[],则得到的L[first…last]有序
如何分解?
在这里插入图片描述
在这里,我们

归并排序#这是合并的函数# 将序列L[first…mid]与序列L[mid+1…last]进行合并def mergearray(L,first,mid,last,temp):#对i,j,k分别进行赋值

i,j,k = first,mid+1,0#当左右两边都有数时进行比较,取较小的数
while (i <= mid) and (j <= last):
if L[i] <= L[j]:
temp[k] = L[i]
i = i+1
k = k+1
else:
temp[k] = L[j]
j = j+1
k = k+1#如果左边序列还有数
while (i <= mid):
temp[k] = L[i]
i = i+1
k = k+1#如果右边序列还有数
while (j <= last):
temp[k] = L[j]
j = j+1
k = k+1#将temp当中该段有序元素赋值给L待排序列使之部分有序
for x in range(0,k):
L[first+x] = temp[x]

这是分组的函数def merge_sort(L,first,last,temp):

if first < last:
mid = (int)((first + last) / 2)
#使左边序列有序
merge_sort(L,first,mid,temp)
#使右边序列有序
merge_sort(L,mid+1,last,temp)
#将两个有序序列合并
mergearray(L,first,mid,last,temp)

归并排序的函数def merge_sort_array(L):#声明一个长度为len(L)的空列表

temp = len(L)*[None]
#调用归并排序
merge_sort(L,0,len(L)-1,temp)

泊松分布: 若X服从参数为λ的泊松分布,则EX=DX=λ ,所以在题目中说”EX=DX“得到的结果 为true
ER图中四种基本成分:实体(矩形框),关系(菱形框),属性(椭圆形框),连接(直线)
Java的try,catch,finally的return问题:
先来看一段代码:
public abstract class Test {
public static void main(String[] args) {
System.out.println(beforeFinally());
}
public static int beforeFinally(){
int a = 0;
try{
a = 1;
return a;
}finally{
a = 2;
}
}
}
/**output:
1
*/
从结果上看,貌似finally 里的语句是在return 之后执行的,其实不然,实际上finally 里的语句是在在return 之前执行的。那么问题来了,既然是在之前执行,那为什么a 的值没有被覆盖了?
实际过程是这样的:当程序执行到try{}语句中的return方法时,它会干这么一件事,将要返回的结果存储到一个临时栈中,然后程序不会立即返回,而是去执行finally{}中的程序, 在执行a = 2时,程序仅仅是覆盖了a的值,但不会去更新临时栈中的那个要返回的值 。执行完之后,就会通知主程序“finally的程序执行完毕,可以请求返回了”,这时,就会将临时栈中的值取出来返回。这下应该清楚了,要返回的值是保存至临时栈中的。
再来看一个例子,稍微改下上面的程序:
public abstract class Test {
public static void main(String[] args) {
System.out.println(beforeFinally());
}

public static int beforeFinally(){
    int a = 0;
    try{
        a = 1;
        return a;
    }finally{
        a = 2;
        return a;
    }
}

}
/**output:
2
*/
在这里,finally{}里也有一个return,那么在执行这个return时,就会更新临时栈中的值。同样,在执行完finally之后,就会通知主程序请求返回了,即将临时栈中的值取出来返回。故返回值是2.
继承初始化问题
java初始化顺序。初始化子类必先初始化父类。子类的构造方法会隐式去调用 父类无参的构造方法(不会在代码中显示)。但如果父类没有无参的构造方法,就必须在子类构造方法第一行显示调用父类的有参构造方法。否则编译失败
折半查找判定( 具有12个关键字的有序表,折半查找的平均查找长度())
在这里插入图片描述
一些推荐的小Tips:
JVM推荐的书籍:周志明的《深入了解Java虚拟机》,我觉得讲得比较明白比较细,我就看了前一部分已经完全够应付所有的面试问到的JVM问题了;
Spring书籍:《Spring源码深度解析》,我个人觉得不是很好啃下来,可能需要一些Spring项目开发经验的会好理解一些,硬啃的话很多地方可能看不太懂,建议更多地与实践相结合;
Java并发:《Java并发编程实战》,我觉得这一本讲得也很好,也建议反复地看反复消化,对于面试问到的一些底层原理讲解得很清楚;
数据库:《高性能MySQL》,很厚,慢慢看吧。其实数据库的话,更多问到的是索引机制这一块,还有性能调优之类等,问的方向比较固定。
算法及代码:建议牛客网或者LeeCode,我面试的时候坚持一天一道题,只要消化理解了,其实进步还是特别大的,特别是思路上的提高真的很快。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当谈到Java的高频面试题时,以下是一些常见的问题和答案: 1. 什么是JavaJava是一种面向对象的编程语言,具有跨平台的特性,可以在不同的操作系统上运行。它由Sun Microsystems(现在是Oracle)于1995年开发,并且在软件开发领域广泛应用。 2. Java的特点有哪些? Java具有以下特点: - 简单易学:Java语法相对简单,与C++相比更易于学习和理解。 - 面向对象:Java是一种纯粹的面向对象编程语言,支持封装、继承和多态等特性。 - 跨平台性:Java程序可以在不同的操作系统上运行,只需在目标平台上安装Java虚拟机(JVM)。 - 安全性:Java提供了安全机制,如内存管理、异常处理和安全检查等,以防止潜在的安全漏洞。 - 多线程:Java支持多线程编程,可以同时执行多个任务。 - 高性能:通过即时编译器和垃圾回收器等技术Java可以实现高性能的应用程序。 3. 什么是Java虚拟机(JVM)? Java虚拟机(JVM)是Java程序运行的环境。它负责将Java字节码翻译成机器码,并提供内存管理、垃圾回收和安全检查等功能。JVM的跨平台性使得Java程序可以在不同的操作系统上运行。 4. 什么是Java的垃圾回收机制? Java的垃圾回收机制是自动管理内存的一种机制。它通过监测不再使用的对象,并释放其占用的内存空间,以避免内存泄漏和资源浪费。垃圾回收器会定期执行垃圾回收操作,清理不再使用的对象。 5. 什么是Java中的异常处理机制? Java的异常处理机制用于处理程序运行时可能出现的错误情况。当程序发生异常时,可以使用try-catch语句块捕获异常,并采取相应的处理措施。常见的异常类型包括NullPointerException、ArrayIndexOutOfBoundsException和IOException等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值