面试:
先做三题,简单,但有一题做错,如下:
class Child {
String s1 = new String("123");
String s2 = "456";
char s[] = {'a' , 'b' , 'c'};
int a = 5;
public void change(String s1 , String s2 , char s[] , int a) {
s1 = "112233";
s2 = "445566";
s[0] = 'A';
a = 10;
}
public static void main(String[] args) {
Child c = new Child();
c.change(c.s1 , c.s2 , c.s , c.a);
System.out.println(c.s1);
System.out.println(c.s2);
System.out.println(c.s);
System.out.println(c.a);
}
应输出:
123
456
Abc
5
=> s1和s2未发生改变(引用传递);s发生改变(引用传递);a未发生改变(值传递)
=> String类型是引用类型,按道理说引用传递,但为什么不发生改变?
=> String对象,值先放在常量池,对象放在堆,对象指向常量池的值。
=> String传到函数里赋新值,新值再次在常量池里创建,然后实参指向新的地址,但形参未发生指向的改变。因此Java归根结底是值传递!
(1)offer情况+为什么不去+实习公司薪资+转正薪资
(2)对数据库有了解吗,主键和外键,平时设计的时候会去设置吗?
(3)你会哪一块?擅长哪一块?对哪一方面最有热情,不惜一切去完成。
(4)SpringCloud和SpringBoot有什么区别?SpringCloud各个服务之间怎么通信?你的服务是有状态还是无状态的?我回答session,存放redis(有状态的);但是忘记说token(无状态)。
(5)对Python有了解吗?我回答否,但是会C/C++,从这个开始入门的。其实之前上课了解过一点。
(6)期望月薪多少?说实话,说低了....但该行平台很不错,想干一辈子,增长空间大。
(7)实习做的什么?你们实习公司产品的定位是什么,面向的用户是?整个系统的流程是怎么实现的,你有了解吗?我说到GRPC的时候,就被打断了。
(8)无反问环节。体验良好,但愿收到体检通知。
1.C++宏。
#define PI 3.14
标识符被宏定义后,则:标识符 == 宏名
程序中出现的是宏名;
在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换。
替换后才进行编译,宏替换是简单的替换。
__LINE__ :当前程序行的行号,表示为十进制整型常量
__FILE__ :当前源文件名,表示字符串型常量
__DATE__ :转换的日历日期,表示为Mmm dd yyyy 形式的字符串常量,Mmm是由asctime产生的。
__TIME__ :转换的时间,表示"hh:mm:ss"形式的字符串型常量,是有asctime产生的。(asctime貌似是指的一个函数)
ex:
#include<iostream>
#define LINE __LINE__
#define FILE __FILE__
#define TIME __TIME__
#define DATE __DATE__
using namespace std;
int main() {
cout<<LINE<<endl;
cout<<FILE<<endl;
cout<<TIME<<endl;
cout<<DATE<<endl;
return 0;
}
2.数据库死锁特点。
产生死锁的四个必要条件:互斥;请求和保持;不可抢占;循环等待。
a. 使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。
即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表
增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,
如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),
大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。
需要注意的是,由于乐观锁机制是在我们的系统中实现,
来自外部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。
b. 使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,
如Oracle的Select … for update语句,以保证操作最大程度的独占性。
但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时
(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中
(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),
数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,
这样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。
事务回退机制:略,后续有机会补充。
3.父子类,各有构造函数,各有普通函数,各有一个成员变量x,分别调用普通函数后的,x值的变化情况。
package com.example.kevintest.demo.msh;
public class Father {
public Father() {
System.out.println("father 构造函数");
}
public void move() {
System.out.println("father move");
}
}
class Child extends Father {
public Child() {
System.out.println("child 构造函数");
}
public void move() {
System.out.println("child move");
}
public static void main(String[] args) {
Father fc = new Child();
fc.move();
System.out.println("");
Father f = new Father();
f.move();
}
}
console:
father 构造函数
child 构造函数
child move
father 构造函数
father move
4.Tomcat简述,怎么部署应用,常用开发工具?
5.JSP中上传数据到后台Servlet,用什么类对象接收的?用什么方法取出数据的?
request;
request.getParameter();
6.前中序遍历,求后序。
//层序遍历
void FloorPrint_QUEUE(pTreeNode &Tree) //层序遍历_队列实现
{
queue < pTreeNode> q;
if (Tree != NULL)
{
q.push(Tree); //根节点进队列
}
while (q.empty() == false) //队列不为空判断
{
cout << q.front()->data << " → ";
if (q.front()->leftPtr != NULL) //如果有左孩子,leftChild入队列
{
q.push(q.front()->leftPtr);
}
if (q.front()->rightPtr != NULL) //如果有右孩子,rightChild入队列
{
q.push(q.front()->rightPtr);
}
q.pop(); //已经遍历过的节点出队列
}
}
7.数据库索引采用的底层数据结构。
B+树。
索引的数据结构分为Hash和B+树。
Hash更适合单个值的查找,O(1);
B+树更适合范围查找,因为叶子节点形成了一个有序链表。
与B树的区别体现在:B+树中只有叶子节点才有value,非叶子节点只有key;B树所有节点都有key和value;
8.函数命名规范。
9.函数形参,包含指针和地址等情况的合法性。
10.SQL语句
11.Linux函数(10个左右)
消息队列的创建:
key_t ftok(char *pathname, char proj);
创建进程:fork();
读文件:read();写文件:write;打开文件:open();
函数返回从TC1970-1-1 0:0:0开始到现在的秒数 :time_t time(time_t * timer)
12.数据库类型
CREATE TABLE IF NOT EXISTS `runoob_tbl`(
`runoob_id` INT UNSIGNED AUTO_INCREMENT,
`runoob_title` VARCHAR(100) NOT NULL,
`runoob_author` VARCHAR(40) NOT NULL,
`submission_date` DATE,
PRIMARY KEY ( `runoob_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
13.创建数组的方法
14.冒泡排序最坏情况的时间复杂度
最坏的情况 => 对于 n 位的数列则有比较次数为 (n-1) + (n-2) + ... + 1 = n * (n - 1) / 2,这就得到了最大的比较次数。
15.C++指针类型的变量初始化。
16.doGet和doPost的区别?
等等。
(1)
Heap = 新生代(约1/3) + 老年代(约2/3);
新生代 = Eden(8/10)+ From Survivor(1/10) + To Survivor(1/10);8:1:1。
新生代常用标记-复制算法;老年代常用标记-整理算法。
(2)多线程:
一、corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,
他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
二、maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,
如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,
如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,
而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,
它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
三、keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,
这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
四、unit 空闲线程存活时间单位
keepAliveTime的计量单位
五、workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
jdk中提供了四种工作队列:
①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
六、threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,
这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,
jdk中提供了4中拒绝策略:
(3)单例模式:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
创建线程的三种方式:
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
1. sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。
2. yield()方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。
sleep()是使线程暂停执行一段时间的方法。wait()也是一种使线程暂停执行的方法。例如,当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。并且可以调用notify()方法或者notifyAll()方法通知正在等待的其他线程。notify()方法仅唤醒一个线程(等待队列中的第一个线程)并允许他去获得锁。notifyAll()方法唤醒所有等待这个对象的线程并允许他们去竞争获得锁。具体区别如下:
1) 原理不同。sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,他会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动苏醒。例如,当线程执行报时功能时,每一秒钟打印出一个时间,那么此时就需要在打印方法前面加一个sleep()方法,以便让自己每隔一秒执行一次,该过程如同闹钟一样。而wait()方法是object类的方法,用于线程间通信,这个方法会使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法或者notifyAll()时才醒来,不过开发人员也可以给他指定一个时间,自动醒来。
2) 对锁的 处理机制不同。由于sleep()方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通信,因此,调用sleep()方法并不会释放锁。而wait()方法则不同,当调用wait()方法后,线程会释放掉他所占用的锁,从而使线程所在对象中的其他synchronized数据可以被其他线程使用。
3) 使用区域不同。wait()方法必须放在同步控制方法和同步代码块中使用,sleep()方法则可以放在任何地方使用。sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用他的interrupt(),产生InterruptedException。由于sleep不会释放锁标志,容易导致死锁问题的发生,因此一般情况下,推荐使用wait()方法。
更多详见:https://blog.youkuaiyun.com/zenggeweiss/article/details/90342282
(4)
简单工厂模式;
单例模式;
组合模式;
需要着重看:代理模式、观察者模式