一、面试通用能力相关
今天和大家聊聊面试的相关问题:
面试是一个老大难的问题,这个问题和公司的口味有关系,不过我们可以从众多的面试中,总结出公司选拔人才的通用的标准,或者说公司看中面试者的那些方面的能力。
重要能力排行(这个只是代表个人意见):
-
沟通理解能力
沟通理解能力是面试中非常重要的能力,在没有正式进入工作岗位之前,面试官了解一个人能力的主要途径就是问答,在一问一答的过程中,对一个人进行综合的评估,所以,如果沟通理解能力不行,你的能力再好,也不能很好的呈现给面试官,这样你和公司谈薪水的时候,对自己是很不利的。(提高沟通理解能力是一个长期的过程,需要自己平时多积累才行)
-
面试能力
这个能力其实主要是针对面试的,由于面试的时间很短,面试官想在几十分钟的时间全面的了解一个人的能力,然后给出评估,这个时候,我们就要有针对性的组织语言,将自己各个方面的能力体现在这几十分钟的时间里面。
比如:在每次面试的时候都会有个简单的自我介绍的环节,这个环节对于不同阶段的面试关注点是不同的,一面主要关注技术,也就是用人部门关注你能不能干活,这个时候,你可以介绍自己的技术栈,平时主要使用开发语言、数据库、缓存、消息中间件,做过哪些项目,项目中你负责的部分和重点攻克的重难点技术,这个要是说清楚,你的一面肯定是高分,二面大多数情况是用工部门的技术总监或者组长、领导这样,是对一面的核实,然后考察综合能力、以及学习能力,这个时候,我们的重点和一面差不多,但是这个环节可能会问一下拓展能力,比如你对某个擅长技术的理解,这样面试官就能够大概了解到你的技术深度;三面,我碰到最多的是部门主任级别等,这个环节,重点关注的是项目经验,以及抗压能力,要是有的单位加班比较严重,这个时候就会问一下抗压能力的问题;最后,就是hr面,谈工资,一般的时候,用工部门会有个评估,提交到hr手上,给出这个人的能力及表现情况,和大致的工资范围,记住是范围,也就是说,你和hr之间是有商量的余地的,但是在商量的时候,不要表现出不尊重和轻视,或者恃才傲物这种,要真诚的对待、和面试官说清楚你的情况、你的理由等。
-
技术能力
这个就不用多说了,是一个面试者的与其他人PK的本钱,越是过硬的基本功,越是加分,这个没有上限,所以,有技术追求的朋友,多多苦练自己的基本功。
好啦,上面说了这么多,开始今天的面经汇总!!!
二、面试相关
下面聊一下三一重工的面试体验,三一重工是一个上市公司,但是从总体的面试体验来看,面试风格上面有点偏国企的风格(偏实用,目的就是让你过来干活,你的技术栈和他们有80%一样,你就是他们要找的人,剩下的就是谈工资)。
我在三一面试的是前端的开发岗位混合后端开发,前端面的是wpf,后端面的是go,前后端一起,类似全栈工程师。
三一重工一面
我对面试中提出的问题进行了分类,总的来说,面试考察的还是综合能力,没有对某一方面的能力进行死磕。
三一重工:面试聊项目,聊了聊技术栈,redis,mysql,sqlite、wpf
数据库
redis的几种数据类型(这个就不多说了,网上一大堆)
redis每种数据类型的主要应用场景,每种数据类型的底层实现原理是什么(这个问题非常常见,很多面试官都问)
redis数据的持久化方式(这个有两种)
缓存相关的几个问题
- 缓存雪崩
- 缓存击穿
- 缓存穿透
mysql的事务 这个回答的还可以
sqlite的事务
sqlite在使用的过程中怎样保障数据的私密性
消息中间件
消息中间件有没有用过??
在什么样的业务场景下使用的??
为什么要使用消息中间件?使用消息中间件之后,业务系统有哪些提升??
rabbitmq:(我使用的是这个)
kafka:这个也是应用广泛的消息中间件
c#专题
委托实现原理?
你平时是怎么使用委托的?
事件和委托的关系和区别?
对c#的垃圾回收了解不?谈一下c#的垃圾回收 (这个垃圾回收,我觉得必问,有垃圾回收的语言,这个话题逃不过,所以提前准备好)
tuple和valuetuple有没有用过??(这个我真的是头一次听说):tuple是在.net4.0的时候提出来的,可以用于多个值返回和多个值传参
tuple是C#4.0之后可以用,.net framework4.0之后可以使用
默认情况.Net Framework元组仅支持1到7个元组元素,如果有8个元素或者更多,需要使用Tuple的嵌套和Rest属性去实现。另外Tuple类提供创造元组对象的静态方法。
var testTuple6 = new Tuple<int, int, int, int, int, int>(1, 2, 3, 4, 5, 6);
Console.WriteLine($"Item 1: {testTuple6.Item1}, Item 6: {testTuple6.Item6}");
var testTuple10 = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int>(8, 9, 10));
Console.WriteLine($"Item 1: {testTuple10.Item1}, Item 10: {testTuple10.Rest.Item3}");
如下创建一个元组表示一个学生的三个信息:名字、年龄和身高,而不用单独额外创建一个类。
var studentInfo = Tuple.Create<string, int, uint>("Bob", 28, 175);
Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
元组的作用:可以使用多返回值和多值传参
多返回值:
static Tuple<string, int, uint> GetStudentInfo(string name)
{
return new Tuple<string, int, uint>("Bob", 28, 175);
}
static void RunTest()
{
var studentInfo = GetStudentInfo("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
}
多值传参:
static void WriteStudentInfo(Object student)
{
var studentInfo = student as Tuple<string, int, uint>;
Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
}
static void RunTest()
{
var t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(WriteStudentInfo));
t.Start(new Tuple<string, int, uint>("Bob", 28, 175));
while (t.IsAlive)
{
System.Threading.Thread.Sleep(50);
}
}
尽管元组有上述方便使用的方法,但是它也有明显的不足:
- 访问元素的时候只能通过ItemX去访问,使用前需要明确元素顺序,属性名字没有实际意义,不方便记忆;
- 最多有八个元素,要想更多只能通过最后一个元素进行嵌套扩展;
- Tuple是一个引用类型,不像其它的简单类型一样是值类型,它在堆上分配空间,在CPU密集操作时可能有太多的创建和分配工作。
ValueTuple是C# 7.0的新特性之一,.Net Framework 4.7以上版本可用。
值元组也是一种数据结构,用于表示特定数量和元素序列,但是是和元组类不一样的,主要区别如下:
- 值元组是结构,是值类型,不是类,而元组(Tuple)是类,引用类型;
- 值元组元素是可变的,不是只读的,也就是说可以改变值元组中的元素值;
- 值元组的数据成员是字段不是属性。
var testTuple6 = new ValueTuple<int, int, int, int, int, int>(1, 2, 3, 4, 5, 6);
Console.WriteLine($"Item 1: {testTuple6.Item1}, Item 6: {testTuple6.Item6}");
var testTuple10 = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple <int, int, int>(8, 9, 10));
Console.WriteLine($"Item 1: {testTuple10.Item1}, Item 10: {testTuple10.Rest.Item3}");
优化区别:当构造出超过7个元素以上的值元组后,可以使用接下来的ItemX进行访问嵌套元组中的值,对于上面的例子,要访问第十个元素,既可以通过testTuple10.Rest.Item3访问,也可以通过testTuple10.Item10来访问。
var testTuple10 = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple<int, int, int>(8, 9, 10));
Console.WriteLine($"Item 10: {testTuple10.Rest.Item3}, Item 10: {testTuple10.Item10}");
static ValueTuple<string, int, uint> GetStudentInfo(string name)
{
return new ValueTuple <string, int, uint>("Bob", 28, 175);
}
static void RunTest()
{
var studentInfo = GetStudentInfo("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
}
优化区别:返回值可以不明显指定ValueTuple,使用新语法(,)代替,如(string, int, uint):
static (string, int, uint) GetStudentInfo1(string name)
{
return ("Bob", 28, 175);
}
static void RunTest1()
{
var studentInfo = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
}
优化区别:返回值可以指定元素名字,方便理解记忆赋值和访问:
static (string name, int age, uint height) GetStudentInfo1(string name)
{
return ("Bob", 28, 175);
}
static void RunTest1()
{
var studentInfo = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.name}], Age [{studentInfo.age}], Height [{studentInfo.height}]");
}
方便记忆赋值:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rz8YBiiO-1616244624932)(E:\大厂知识储备\img\wpf\方便记忆.png)]
方便访问:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2i5wwRtd-1616244624933)(E:\大厂知识储备\img\wpf\方便访问.png)]
可以通过var (x, y)或者(var x, var y)来解析值元组元素构造局部变量,同时可以使用符号”_”来忽略不需要的元素。
static (string name, int age, uint height) GetStudentInfo1(string name)
{
return ("Bob", 28, 175);
}
static void RunTest1()
{
var (name, age, height) = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{name}], Age [{age}], Height [{height}]");
(var name1, var age1, var height1) = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{name1}], Age [{age1}], Height [{height1}]");
var (_, age2, _) = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Age [{age2}]");
}
- ValueTuple支持函数返回值新语法”(,)”,使代码更简单;
- 能够给元素命名,方便使用和记忆,这里需要注意虽然命名了,但是实际上value tuple没有定义这样名字的属性或者字段,真正的名字仍然是ItemX,所有的元素名字都只是设计和编译时用的,不是运行时用的(因此注意对该类型的序列化和反序列化操作);
- 可以使用解构方法更方便地使用部分或全部元组的元素;
- 值元组是值类型,使用起来比引用类型的元组效率高,并且值元组是有比较方法的,可以用于比较是否相等
三一重工二面
go相关的内容:go的线程模型 这个没有答好
go的线程模型
MPG的概念
M
指的是Machine
,一个M
直接关联了一个内核线程。由操作系统管理。
P
指的是”processor”,代表了M
所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。
G
指的是Goroutine
,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。
go中怎样查看目前运行了多少个goroutine
go的chan的关闭,这个我感觉回答的还可以
1 个发送者 1个接收者(最简单的)
发送端直接关闭channel
package main
import (
"fmt"
"time"
)
func main() {
notify := make(chan int)
datach := make(chan int, 100)
go func() {
<-notify
fmt.Println("2 秒后接受到信号开始发送")
for i := 0; i < 100; i++ {
datach <- i
}
fmt.Println("发送端关闭数据通道")
close(datach)
}()
time.Sleep(2 * time.Second)
fmt.Println("开始通知发送信息")
notify <- 1
time.Sleep(1 * time.Second)
fmt.Println("通知1秒后接收到数据通道数据 ")
for {
if i, ok := <-datach; ok {
fmt.Println(i)
} else {
fmt.Println("接收不到数据中止循环")
break
}
}
time.Sleep(5 * time.Second)
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
1个发送者 N个接收者(进行扩展)
func main(){
notify := make(chan int)
datach := make(chan int, 100)
go func() {
<-notify
fmt.Println("2 秒后接受到信号开始发送")
for i := 0; i < 100000; i++ {
datach <- i
}
fmt.Println("发送端关闭数据通道")
close(datach)
}()
time.Sleep(2 * time.Second)
fmt.Println("开始通知发送信息")
notify <- 1
time.Sleep(1 * time.Second)
fmt.Println("3秒后接受到数据通道数据 此时datach 在接收端已经关闭")
for i := 0; i < 5; i++ {
go func() {
for {
if i, ok := <-datach; ok {
fmt.Println(i)
} else {
break
}
}
}()
}
time.Sleep(5 * time.Second)
}
N 个发送者 1个接收者
添加一个 停止通知 接收端告诉发送端不要发送了
package main
import (
"fmt"
"time"
"math/rand"
)
type T int
func main() {
dataCh := make(chan T, 1)
stopCh := make(chan T)
//notifyCh := make(chan T)
for i := 0; i < 10000; i++ {
go func(i int) {
for {
value := T(rand.Intn(10000))
select {
case <-stopCh:
fmt.Println("接收到停止发送的信号")
return
case dataCh <- value:
}
}
}(i)
}
time.Sleep(1 * time.Second)
fmt.Println("1秒后开始接收数据")
for {
if d, ok := <-dataCh; ok {
fmt.Println(d)
if d == 1111 {
fmt.Println("当在接收端接收到1111时告诉发送端不要发送了")
close(stopCh)
return
}
} else {
break
}
}
}
m对n的这种形式:
package main
import (
"time"
"math/rand"
"sync"
"log"
"strconv"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
// ...
const MaxRandomNumber = 100000
const NumReceivers = 10
const NumSenders = 1000
wgReceivers := sync.WaitGroup{}
wgReceivers.Add(NumReceivers)
// ...
dataCh := make(chan int, 100)
stopCh := make(chan struct{})
// stopCh is an additional signal channel.
// Its sender is the moderator goroutine shown below.
// Its reveivers are all senders and receivers of dataCh.
toStop := make(chan string, 1)
// The channel toStop is used to notify the moderator
// to close the additional signal channel (stopCh).
// Its senders are any senders and receivers of dataCh.
// Its reveiver is the moderator goroutine shown below.
var stoppedBy string
// moderator
go func() {
stoppedBy = <- toStop
close(stopCh)
}()
// senders
for i := 0; i < NumSenders; i++ {
go func(id string) {
for {
value := rand.Intn(MaxRandomNumber)
if value == 0 {
// Here, a trick is used to notify the moderator
// to close the additional signal channel.
select {
case toStop <- "sender#" + id:
default:
}
return
}
// The first select here is to try to exit the goroutine
// as early as possible. This select blocks with one
// receive operation case and one default branches will
// be optimized as a try-receive operation by the
// official Go compiler.
select {
case <- stopCh:
return
default:
}
// Even if stopCh is closed, the first branch in the
// second select may be still not selected for some
// loops (and for ever in theory) if the send to
// dataCh is also unblocked.
// This is why the first select block is needed.
select {
case <- stopCh:
return
case dataCh <- value:
}
}
}(strconv.Itoa(i))
}
// receivers
for i := 0; i < NumReceivers; i++ {
go func(id string) {
defer wgReceivers.Done()
for {
// Same as the sender goroutine, the first select here
// is to try to exit the goroutine as early as possible.
select {
case <- stopCh:
return
default:
}
// Even if stopCh is closed, the first branch in the
// second select may be still not selected for some
// loops (and for ever in theory) if the receive from
// dataCh is also unblocked.
// This is why the first select block is needed.
select {
case <- stopCh:
return
case value := <-dataCh:
if value == MaxRandomNumber-1 {
// The same trick is used to notify
// the moderator to close the
// additional signal channel.
select {
case toStop <- "receiver#" + id:
default:
}
return
}
log.Println(value)
}
}
}(strconv.Itoa(i))
}
// ...
wgReceivers.Wait()
log.Println("stopped by", stoppedBy)
}
go的文件读写
dockerfile的创建镜像
怎样将dockerfile产生的镜像推到私有仓库中
使用dockerfile创建镜像后,使用命令将docker push到私有仓库
写好的代码打包到docker镜像中
三一重工部门责任人面试
三面主要问项目,在项目中自己攻克的重难点问题?以及问题的解决办法
问项目中难点的地方
项目觉得自己出彩的地方
sql在使用的时候有哪些需要注意的地方
1.只返回需要的列,避免用select * from t1
2.避免笛卡尔乘积,select * from a,b
–使用参数化查询,where col1=?,减少编译时间3避免对查询条件计算,where salary*2>xx 改为salary > xx/2
4.尽量避免在索引列上使用not,!=和<>,索引只能告诉什么在表中,而不能告诉什么不在表中,
当数据库遇上以上几种符号时,将不再使用索引,使用全表扫描5.in/exist, not in/not exist
可以用exists代替in,可以提高查询的效率.其实也是分情况的:in与exists的使用取决于子查询集合大小,IN适合于外表大而内表小的情况;
EXISTS适合于外表小而内表大的情况(前提是内表字段有索引).
所以结论: 如果子查询得出的结果集记录较少,外层主查询中的表较大且又有索引时应该用in,
反之如果外层的主查询记录较少,子查询中的表大,又有索引时使用exists。
举例,以下两个sql是高效的:
select * from big_tab where id in (select id from small_tab); --内表small_tab是小表,而外表big_tab是大表且有索引
select * from small_tab a where exists(select 1 from big_tab b where b.id = s.id); --内表big_tab是大表且有索引,而外表small_tab是小表原因: in 是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询。
一直以来认为exists比in效率高的说法是不准确的。6无论任何情况: not exists > not in;
原因:
如果查询语句使用了not in 那么内外表都进行全表扫描,没有用到索引;
而not extsts 的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。7.任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。
8.注意LIKE模糊查询的使用,避免%%。
–使用for read only或for fetch only
9.注意SQL的where条件书写顺序:
对于大部分数据库而言,sql语句的解析都是有顺序的
比如Oracle数据库采用自下而上的顺序解析where字句,所以那些可以滤过大量纪录的条件应该写在where字句的末尾,例如:
(DB2就不需要,因为DB2的优化器足够智能,能够根据实际情况来对sql进行优化来决定合理的执行顺序)
select * from table e
where 25<(select count(*)
from table
where count=e.count);
and h>500
and d=‘001’;
说明: d='001’能够过滤掉绝大多数数据,所以这样写更高效–避免使用HAVING字句
10.用>=替代>
高效:
SELECT * FROM EMP WHERE DEPTNO >=4
低效:
SELECT * FROM EMP WHERE DEPTNO >3两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录。
感兴趣的朋友可以关注下面的公众,号同步更新,每天分享一点知识,成长看得见,感谢支持!!