时间:2018.10.14-2018.10.24
地点:广州
一、闲话
18年刚毕业的我,签了一家桂林的公司,本来是图家近,并且以后会到南宁分公司去,这样逢年过节回家也方便。
桂林是个好地方啊,物价低得简直像是在天堂,空气也都很好,风景优美。哪曾想却被坑的好苦,我是用的安卓简历去应聘的,校招,结果聊得还好,谁知道去之后竟然是让做小程序,那个主管也不懂技术,先做着吧,但是关键是整个部门就只有我在做软件。在做完之后主管假装好意得问我想学什么技术,可以调其他部门学习,我脱口而出区块链、架构、大数据。结果呵呵。插曲就是原先约定好的五千工资竟然被压榨成四千五,并且说希望我自己凭能力提上去,食屎啦你!后来我真去其他部门了,拧了一个多星期的螺丝钉,没错,是用螺丝刀拧的那种!发展方向估计是偏硬件方面。然后八月底提离职了,然后原主管就拖着我啊,说什么我走了项目没人接,需要找人,我TM!原则上试用期三天就能走人的,后来我是用了将近半个月。还好那十天工资发了,就是待了一个多月社保只交了一个月。
原先我的方向是安卓开发,后来校招发现招安卓的真的是太少了,然后转战java,看了黑马的视频。桂林那段时间简直就是闭关,尤其是离职后的日子。我是9月10号离职,然后看日子,觉得至少该学个一两周吧,但是一周后就快到中秋了,中秋一周后就又到国庆了。。。然后就安心闭关吧,国庆后再去找工作。事实证明我是对的,因为过一遍黑马的视频并不是一两天就能办到的。泪奔。
买的10月13号的动车,当天是周六,室友骑电动车搭我去车站,然而我真是有毒。其实入职前去了趟深圳求职java,从家去的路上遇上了道路塌方,错过火车,住宿的地方又吵,睡不安稳,吃了假早餐。。。。。我是中了砒霜的毒,并且在深圳的那一周是碰了一鼻子灰的,然后决定先回桂林工作了,有机会再出来。谁知道回去后第二天就接到复试通知了,含泪拒绝。刚才说到我有毒,因为室友送我的时候路上就下雨了,下大雨!我还是掐着点去的车站,时间不充裕,结果到站了雨就停了,还好赶上了火车。室友说他回去时发现家里都是干的,雨都没下过一样。。。。
来到广州先是找住的地方,这边没什么男的朋友,而且上次去深圳感觉不找一个月应该是找不到工作的,所以怀揣着微薄的工资决定来一场持久战。
来之前选好了地理位置,天河棠下。这边附近就是一个软件园区,属于城中村,租房便宜。原先在咸鱼看好了一个房子550元,家具齐全。但是到棠下后假装站着看租房公告栏,结果一堆人过来问租不租房,500的房子但是家具确实没咸鱼的全,然后决定再看看。但是在我看最后一家的时候,天都黑了,她把我带到了一个深巷中,我都不记得来时路,最后谈好了房价470,算是被坑了吧,但是转念一想,工作地点还不确定,可能会换,不如先租个便宜点的。
周六就这样过去了。周天投了一波简历,没人睬,有点失望。
转机在周一,估计hr们上班了。本来接到第一个面试的时候推到了周二,结果我还真是正确的,周一一共接到了五家面试,面试排到了周四。。。。
但是一直到周四,都没有再接到其他面试,好无语。这一周就笔试面试来说都不是很好,唯独有一家笔试七十多,面试的两个人技术还不怎么的,他们公司做的挺多项目,见我既会安卓、java,又会小程序、php、h5,觉得很对口,然后就查我背景了,可是我留的是我室友的电话。。。但是他们还是提出要复面了。可是看准网上他们公司的评价真的是贼强!瞬间拒绝复试!
第二周似乎转运了,周六周天投了一波简历,周一又收到面试,但是我照例推到了周二。周二一共面了三家,第一家是一个今年八月刚起步的小公司,我说我初级他瞬间想让我走,然后让我做了套卷子,全是框架题,并且如果框架了解的不是特别深入的话,确实不好答,还有就是sql题。
下午第一家是做微信小游戏的,老板很时尚,做的后端,hr小姐姐热情,另一个小姐姐很漂亮,你懂得。初试老板就跟我谈好了薪资6k,其实面试了这么多家,薪资实际上5.5k这样,有一家说6k左右,其实他的意思根本没有右,是低于6k。所以对于我这种游戏白痴来说,给我6k不错了。而且我也急于稳定一下(其实是小姐姐真的很漂亮),主要一点是公司离住所只需走路十五分钟。
下午最后一家公司才是我最想去的,面试官问了就简历先是问了一些项目的问题,然后问了设计模式,数据库优化等等。但是来的时候我接到了另一个offer,我从没想过那家公司会录取我,笔试59分,面试一般般,喊的7.5k,要我当天决定要不要去,被我推迟了一天。所以这家公司面试的时候还是很放松的,老子有offer了(得意)。技术面完,hr小姐姐介绍了下公司和福利,公司技术大牛,哇,瞬间觉得应该来这,但是ceo去校招了,周五才能回来,天意啊。
最后我去了59分的那家。
深圳的面试面的比较深,会问框架原理,广州问的都是基础题和简历上的项目,接下来总结下笔试吧。
二、笔试总结
1、笔试选择题基本上都是比较细节的基础知识,比如类方法和实例方法的区别。类的加载,编译后是.class文件还是什么文件。这些问题如果不深究还真的很容易错。
2、编程题
A.去掉字符串中连续的重复字母,只保留一个。
比如aacskjfllkdcc,会变成acskjflkdc
我当时的做法如下:
public static String fun(String s) {
StringBuilder builder = new StringBuilder(s.charAt(0));
char index = s.charAt(0);//指针
int num = 0;
for (int i = 1; i < s.length(); i++) {
if (index == s.charAt(i)) {
num++;//遇到相同字符做标记,num++
} else {
//突然遇上不同字符了
if (num > 0) {//如果此时num大于0说明前面是碰上了相同字符的
builder.append(index);//所以只加上指针指向的字符
num = 0;//初始化
index = s.charAt(i);//指向另一个字符
i--;//回退一位
} else {//没碰上相同字符
builder.append(s.charAt(i));//直接加上
index = s.charAt(i);//指针变换指向
}
}
}
builder.append(index);//把最后一位加上
return builder.toString();
}
B.快速排序
public static void sort(int[] a, int left, int right) {
if (left > right)//索引左》右遍历结束
return;
int pivot = a[left];//选择最左边作为参照
int i = left;
int j = right;
while (i < j) {
while (a[j] >= pivot && i < j) {
j--;//右边大于参照往左移
}
while (a[i] <= pivot && i < j) {
i++;//左边大于参照往右移
}
if (i < j) {//其它的调换位置,达到比参照小的在左边,比参照大的在右边
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
//把参照放在左跟右的中间
a[left] = a[i];
a[i] = pivot;
sort(a, left, i - 1);//以上方式遍历左边
sort(a, i + 1, right);//以上方式遍历右边
}
C.二分查找
二分查找是建立在已经排序好的数组上的。
直接挑最中间的数出来比较,相等则直接返回,大了说明右边都是比要查找的大,所以去左边找,小了说明左边所有的都小。接着就是递归左边或右边了。
D.选择排序
选择排序是每遍历一次就选最小的跟指针位置交换。就比如56139,初始指针 对着5,在后面发现1最小,然后跟指针交换位置,变成16539;接着指针移到6,然后发现后面3是最小的,跟指针交换位置变成13569。。。。。。
E.冒泡排序
冒泡跟选择排序很像。还是拿56139,首先指针指着5,发现56,顺序是对的,接着看61,发现不对,调换变成51639;再看63,顺序不对调换变成51369;这时候最大的数9已经冒泡到最后了,所以接着比较51,顺序不对,调换15639;比较56,对了,比较63,顺序不对15369,这时候不用比较69了,因为上一轮已经知道9是最大的了,这一轮6是最大的。第三轮,15,不变;53,大了13569;下一轮。。。。
F.选择排序和冒泡区别
选择排序是每遍历一轮就把其中的老大认出来,直接放到最后面;冒泡排序是从头开始选择老大,每次都要pk(交换),舞林大会,胜者为王。
G.分割字符串
传入一个字符串和分隔符,将其分割成字符串数组。
比如aa_h_o,会被分割成[“aa”,”h”,”o”]
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
char ch = 0;
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}
Pattern.compile(regex).split(this, limit)方法如下:
public String[] split(CharSequence input, int limit) {
int index = 0;
boolean matchLimited = limit > 0;
ArrayList<String> matchList = new ArrayList<>();
Matcher m = matcher(input);
// Add segments before each match found
while(m.find()) {
if (!matchLimited || matchList.size() < limit - 1) {
if (index == 0 && index == m.start() && m.start() == m.end()) {
// no empty leading substring included for zero-width match
// at the beginning of the input char sequence.
continue;
}
String match = input.subSequence(index, m.start()).toString();
matchList.add(match);
index = m.end();
} else if (matchList.size() == limit - 1) { // last one
String match = input.subSequence(index,
input.length()).toString();
matchList.add(match);
index = m.end();
}
}
// If no match was found, return this
if (index == 0)
return new String[] {input.toString()};
// Add remaining segment
if (!matchLimited || matchList.size() < limit)
matchList.add(input.subSequence(index, input.length()).toString());
// Construct result
int resultSize = matchList.size();
if (limit == 0)
while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
resultSize--;
String[] result = new String[resultSize];
return matchList.subList(0, resultSize).toArray(result);
}
以上是String源码。
我当时的做法是:
挨个遍历,如果匹配上分隔符(不一定只有一个字符),就将前半部分存入list,最后转成数组。
public static String[] split(String s, String split) {
List<String> list = new ArrayList<>();
if (s==null||split == null || split.length() == 0 || split.length() > s.length()) {
// 分隔符为空
return new String[] { s };
}
int len = s.length();
char index = 0;//当前的头一个字符
int last = 0;//上一次的指向(从这里开始截取)
for (int i = 0; i < s.length(); i++) {
index = s.charAt(i);
if (index == split.charAt(0) && (len - i) >= split.length()) {
boolean isSplit = true;//标志位
// 一旦检测到两者头字符相同,立马判断接下来的是否都想同
int k = i;
for (int j = 0; j < split.length(); j++) {
if (s.charAt(k++) != split.charAt(j)) {
isSplit = false;//不匹配直接退出循环
break;
}
}
if (isSplit) {
//全部匹配
list.add(s.substring(last, i));//加入list
last = i + split.length();//变换指针
}
}
}
list.add(s.substring(last));//记得加上,否则漏掉最后一项
return list.toArray(new String[list.size()]);
}
F.还有一个递归题,题目忘了,用了循环和递归两种方式实现。
三、数据库
主要考察多表查询。广州给人的感觉就是数据库要求比较高。
参考另一篇文章。
四、数据库优化
老生常谈的话题了。
这次面试最深刻的问题就是,有一个字段status,只有0,1,2三个值,是否需要建立索引?如果数据量达到千万级呢?
当时记得这样是不需要建立索引的,但是最后问面试官时,他说当达到千万级别,并且0的值占80%,这样建立索引是值得的。
- 在查询字段上建索引
- 尽量不使用不等于查询、判断是否为NULL查询,因为会造成全表查询
- 尽量在整型上建索引
- 只查询一条数据用limit 1
- 可以使用insertTime当作下一次查询的条件
- 分页查询使用limit a,b 时,如果a过大会导致查询缓慢,可以使用自增id代替分页。
- 索引为char时只会匹配开头,所以索引字段查询时使用%key是不会使用索引查询的。
- 先group by 再排序
- Union 多表时可以使用多线程分别查询再合起来
- 使用exists代替in
- 将or 改成 union
- 连续的使用between代替in
- 不在sql中使用参数
- Where中不使用表达式操作,计算
- Where中不使用函数操作使用联合索引必须使用第一个索引,并且其它索引顺序同如果只更改一两个字段不要全部字段都改
- 大表分页再join
- 不使用count(*)
- 索引不要太多,
- 不要返回用不到的字段
- 避免频繁创建和删除临时表
- 避免大事务操作
- 避免一次返回大数据量
- 尽量少排序
- 使用union all 代替union
- 不使用not in,使用left join代替
- 磁盘速度越快越好
- 使用group by 代替distinct
- 使用use index