面经:大厂面试一(wpf+go) - Go语言中文社区

面经:大厂面试一(wpf+go)


一、面试通用能力相关

今天和大家聊聊面试的相关问题:

面试是一个老大难的问题,这个问题和公司的口味有关系,不过我们可以从众多的面试中,总结出公司选拔人才的通用的标准,或者说公司看中面试者的那些方面的能力。

重要能力排行(这个只是代表个人意见):

  1. 沟通理解能力

    沟通理解能力是面试中非常重要的能力,在没有正式进入工作岗位之前,面试官了解一个人能力的主要途径就是问答,在一问一答的过程中,对一个人进行综合的评估,所以,如果沟通理解能力不行,你的能力再好,也不能很好的呈现给面试官,这样你和公司谈薪水的时候,对自己是很不利的。(提高沟通理解能力是一个长期的过程,需要自己平时多积累才行)

  2. 面试能力

    这个能力其实主要是针对面试的,由于面试的时间很短,面试官想在几十分钟的时间全面的了解一个人的能力,然后给出评估,这个时候,我们就要有针对性的组织语言,将自己各个方面的能力体现在这几十分钟的时间里面。

    比如:在每次面试的时候都会有个简单的自我介绍的环节,这个环节对于不同阶段的面试关注点是不同的,一面主要关注技术,也就是用人部门关注你能不能干活,这个时候,你可以介绍自己的技术栈,平时主要使用开发语言、数据库、缓存、消息中间件,做过哪些项目,项目中你负责的部分和重点攻克的重难点技术,这个要是说清楚,你的一面肯定是高分,二面大多数情况是用工部门的技术总监或者组长、领导这样,是对一面的核实,然后考察综合能力、以及学习能力,这个时候,我们的重点和一面差不多,但是这个环节可能会问一下拓展能力,比如你对某个擅长技术的理解,这样面试官就能够大概了解到你的技术深度;三面,我碰到最多的是部门主任级别等,这个环节,重点关注的是项目经验,以及抗压能力,要是有的单位加班比较严重,这个时候就会问一下抗压能力的问题;最后,就是hr面,谈工资,一般的时候,用工部门会有个评估,提交到hr手上,给出这个人的能力及表现情况,和大致的工资范围,记住是范围,也就是说,你和hr之间是有商量的余地的,但是在商量的时候,不要表现出不尊重和轻视,或者恃才傲物这种,要真诚的对待、和面试官说清楚你的情况、你的理由等。

  3. 技术能力

    这个就不用多说了,是一个面试者的与其他人PK的本钱,越是过硬的基本功,越是加分,这个没有上限,所以,有技术追求的朋友,多多苦练自己的基本功。

好啦,上面说了这么多,开始今天的面经汇总!!!

二、面试相关

下面聊一下三一重工的面试体验,三一重工是一个上市公司,但是从总体的面试体验来看,面试风格上面有点偏国企的风格(偏实用,目的就是让你过来干活,你的技术栈和他们有80%一样,你就是他们要找的人,剩下的就是谈工资)。

我在三一面试的是前端的开发岗位混合后端开发,前端面的是wpf,后端面的是go,前后端一起,类似全栈工程师。

三一重工一面

我对面试中提出的问题进行了分类,总的来说,面试考察的还是综合能力,没有对某一方面的能力进行死磕。

三一重工:面试聊项目,聊了聊技术栈,redis,mysql,sqlite、wpf

数据库

redis的几种数据类型(这个就不多说了,网上一大堆)

redis每种数据类型的主要应用场景,每种数据类型的底层实现原理是什么(这个问题非常常见,很多面试官都问)

redis数据的持久化方式(这个有两种)

缓存相关的几个问题

  1. 缓存雪崩
  2. 缓存击穿
  3. 缓存穿透

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:大厂知识储备imgwpf方便记忆.png)]

方便访问:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2i5wwRtd-1616244624933)(E:大厂知识储备imgwpf方便访问.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的记录。

感兴趣的朋友可以关注下面的公众,号同步更新,每天分享一点知识,成长看得见,感谢支持!!
在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/xiazhipeng1000/article/details/115034567
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢