学生阶段第一次参加面试, 挨捶后的血泪总结

门面问题

  • 给面试官的第一印象就是我们的简历和自我介绍 如果这俩个做的不好, 真的很掉分, 比如我虽然简历有, 自我介绍也有, 但是做的不是很好, 依旧是很垮下面先看我的简历:

简历问题

在这里插入图片描述

  1. 第一个问题我的照片放置的格式不对, 位置安排的很不合理
  2. 岗位要求不像个专业人士说的话 应该是java后端开发工程师
  3. 最严重的问题我的github连接复制错了!!! 很要命应该是 https://github.com/Listen-Y
    因为我的github坚持的很好如果面试官看不到 还是因为你的失误这就血亏呀
  4. 项目演示(一) (二) 设计的不美观 这倒是小问题(只要你项目好 嘿嘿嘿)
  5. 专业技能还好 够简洁 但是不够精炼

自我介绍问题

  1. 简洁找重点 这点其实我做的还好 把重要信息一说 而且把一些简历上没有 而且重要的说一下比如你哪人, 是否单身, 爱好是啥(一个就行 别扯一大堆)就行
  2. 我的缺点就是不够流畅 其实自我介绍在面试前就应该准备好 让面试官很清楚 你说起来也是很流畅才行 不要说一大堆嗯啊 啊呀 然后 啥的

面试中具体问到的问题和回答情况

1. 简单介绍一下你的onlineMusic项目
我答: : 基于 BS 架构实现的 web 项目, 主要目的是为了让使用者只要有网络就可以听到自己喜欢 的音乐, 看到自己喜欢的 MV, 音乐时人类的精神财富, 让音乐充满生活, 就可以让乐趣充满生活(说的有文化一点, 稍微摆脱一下你的直男样子)
2. 说一下你的登录功能怎么实现的
我答: 在业务逻辑层service 我会首先去判断此时有没有session 如果有说明已经登录过了 直接跳转到主页面, 如果没有我会判断在parameter里有没有输入账号和密码, 接下来再根据账号在我的mysql数据库中去找这个有没有 如果有 再去验证密码是否正确 因为我再数据库设计中账号使用了unique 所以这账号肯定是只有一个 没有重复的 都验证完整 我会新建一个session 让浏览器记住这个user 然后跳转到主页面
3. 那你说一下session和cookie的区别
我答: Cookie以文本文件格式存储在浏览器中,而session存储在服务端, 因为每次发起 Http 请求,都要携带有效Cookie信息,所以Cookie一般都有大小限制,以防止增加网络压力,一般不超过4k, 而且可以轻松访问cookie值但是我们无法轻松访问会话(session)值,因此session方案更安全
4. 第四个问题, 你的音乐和mv播放怎么实现的
我答: 想要播放必须先上传, 那我上传的话就会在数据库中记录很多信息 比如id 名字 时间等等, 最重要的是会记录我上传到服务器文件的路径, 如果没有这个路径 我的前端html的video标签是加载不出来音乐的, 所以在播放的时候回找到这个数据库中的这个信息, 找到这个路径 输出给前端 前端根据这个路径加载文件.
5. 那你说说你在代码中是如何查询的数据库信息的
我答: 首先我会对每个功能, 对每个需求写对应的方法 在每个方法里 首先要获取connection连接 有很多方法 都要连接 所以我会把这个获取的方法封装一下 我使用的是一个懒汉模式的一个单例模式对其进行了封装 在封装里 我就通过seturl setaname setpassword连接好了我的数据库, 并且在这个类里我把资源close也封装了一下 封装close也要注意 一定是resultset最先关闭 就是说后创建的先关闭, 好接下来就需要拼装sql 在这里我们尽量不使用字符串拼接 而是使用?的形式 这样更安全更规矩化, 所以在创建Preparstatement后要进行set设置, 然后执行sql 如果是简单的插入修改删除使用executeUpdate 返回的是修改的行数 方便我们去检查核对 查询使用executeQuery 返回的是resultset结果集 要是结果集的话 我们就需要遍历结果集 , 遍历结果集就需要使用getint啥的方法 很方便 最后也是最重要的就是关闭资源 我把他放在finally 代码块中 因为这里 是必须会执行的 所以放这最合适不过了
6. 那数据库的事务有哪些特性你知道吗?
我答: 原子性(最重要): 事务中的若干个操作,要么全部执行成功,要么全部不执行(本质上不是全部不执行,那是把已执行的步骤回滚(rollback)回去,借助逆向操作,把原来操作造成的影响进行还原), 一致性: 执行事务前后,数据库始终处于一种合法的状态, 持久性: 事务一旦执行完毕, 此时对于数据库的修改是持久生效的, 隔离性有好几个级别 他们分别对应解决介绍脏读、不可重复读、幻读, 假如一个学校正在考试 学生甲在答题 学生乙在偷瞄 此时甲写了一个答案还没有写完 然后乙就偷看了甲的答案然后写在了自己的试卷上 这是乙的读操作就造成了脏读, 还是刚刚那个考试的例子 学生甲在写一道数学大题 学生乙在偷瞄 学生乙偷瞄一眼后 然后赶快将答案写在了自己的卷子上 可是在乙写的时候 甲发现自己有更好的解法就把答案全擦了 重新写了 等到乙再去看第二眼的时候 发现我靠怎么完全不一样了 乙就陷入了迷惑之中 乙这就造成了不可重复读问题, 幻读就好比 学生乙在偷瞄学生甲的大题 而此时学生甲还在写未完成的题 所以对 学生乙来说 他得到的整张试卷和学生甲还是不一样的, 隔离性就是让多个事务并发执行时,事务之间不能相互干扰 本质上就是为了线程安全, 隔离和并发是相悖的 隔离是为了保证数据的准确 并发是为了提高事务的执行效率, 如果多个事务之间的隔离性越强 并发程度就会越低 效率就会降低, 如果多个事务之间隔离性越弱 并发执行程度就会越高 效率就会越高.
7. 前几个方面还不错, 那你知道哪些http的方法
我答: GET方法:获取资源GET方法是用来请求URL指定的资源。指定资源经服务器端解析后返回响应内容。POST方法POST方法用来传输实体的主体, PUT方法:传输文件, PUT方法用来传输文件。像FTP协议的文件上传一样,要求在请求报文主体中包含文件的内容,然后保存到请求URL指定的位置。不太常用。HEAD方法:获取报文首部 , HEAD方法和GET方法一样,只是不返回报文主体部分。用于确认URL的有效性及资源更新的日期时间等。DELETE方法:删除文件 , DELETE方法用来删除文件,是PUT的相反方法。DELETE方法按请求URL删除指定的资源。也不常用。 所以我的项目中也是只用到了post和get方法
8. 那你说一下post和get的区别 还有你项目中那遇到了这俩个方法
我答: 唯一区别就是GET一般将数据放到URL中 POST一般将数据放到BODY中, 所以关于网上一些其他说法比如 get是用于服务器获取资源 POST是用于给服务器提交资源 还有get传输的数量较小POST传输的数量较大 这是设计者的初衷 但是发展到如今2020年这些早就不是问题了 目前最新版本的HTTP协议的URL可以达到几个M 虽然还是没有POST的大 但是足够了, 还有一些说post安全 其实不然 这只是对小白而言 即使是post方法 随便抓个包还是容易知道内容 在我的项目中登录的时候为了保证一定安全性我是用的post方法 在删除呀喜欢呀什么的用的是get方法
9. 那你知道servlet的生命周期吗?
我答: 这块我只是简单了解过 我只知道 Servlet 通过调用 init () 方法进行初始化。调用 service() 方法来处理客户端的请求。通过调用 destroy() 方法终止。最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。别的我还真没涉及过
10. 嗯, 你简历上说了解多线程编程 , 那多线程安全问题你知道吗
我答: 线程是抢占式执行, 这是线程不安全的万恶之源, 因为你的代码不是原子性的, 会有中途打断的情况, 如上述的increase让count++操作可以分为三步
load 将内存中的数据读取到CPU中
incr 将CPU中的数据++
save 将结果保存到内存中
当CPU执行到上面三个步骤任何一步的时候,都可能会被调度器调走,让给其他线程来执行,或者有多个CPU让修改同一个数据的俩个线程同时执行了, 这俩个都会造成线程不安全 , 还有就是多个线程尝试修改同一个变量 内存可见性导致的线程安全问题, 为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样虽然提高了效率会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题。再就是指令重排序问题, Java的编译器在编译代码时,会针对指令进行优化,调整指令的先后顺序,保存原有逻辑不变的情况下,提高程序的运行速率
现代编译器优化能力很强,优化后的代码执行速率很快,单线程的情况下不会有问题, 但是在多线程的优化是不容易实现的,可能会导致出现问题
比如下面的操作:
去校门口卖吃的
写作业
去校门口拿快递
如果是单线程就会优化为1 -> 3 -> 2 方式执行 可以优化过程 这就是指令重排序
但是如果实在多线程下就会出错 因为可能是在你写作业的时候, 快递才被送来 或者在你写作业的时候快递会被人修改一些东西的时候,如果此时进行了指令重排序 代码就会出错
11. 那如何解决线程安全问题
我答: 加锁synchronize 使用volatile 还有尽量使用线程安全的集合 锁的基本操作 加锁(获取锁)lock 解锁(释放锁) unlock , 互斥的 同一时刻只有一个线程能获取到同一把锁 , 其他线程如果尝试获取同一把锁 就会发生阻塞等待, 一直等到某个线程释放了这把锁 此时剩下的想要这把锁的线程会重新竞争这把锁, 加了volatile之后,对这个内存的读取操作肯定是从内存中读取,不加的时候,读取操作就可能不是在内存中读取而是自己读取CPU上旧的数据
12. 那你还知道哪些锁吗?
我答: 了解过一些但是记得不是很清楚了, 只记得一个ReentrantLock的加锁和解锁操作是分开的 使用更加灵活
13. 那你知道锁优化吗
我答: 锁消除本质是以编辑器和JVM代码运行的情况智能的判断当前的锁有没有必要加 如果没有必要, 就会直接把锁干掉 , 偏向锁 第一个尝试加锁的线程 不会真正的加锁 而是进入偏向锁(一种很轻量的锁) 知道其他线程也来竞争这把锁的时候 才会取消偏向锁的状态 真正的进行加锁 这个很像我去球馆打球的时候借用人家球馆里的球的时候 当人多有人和我竞争的时候我就得去花钱租 人少的时候我就可以登记一下直接玩 总而言之上述这俩个优化机制就是能不加锁就不加锁 自旋锁 当有很多线程竞争锁的时候, 偏向锁状态被消除 此时没有得到锁的线程并不会直接直接挂起放弃 而是使用自旋锁 的方式来尝试去再次获取锁 自旋锁能保证让其他想竞争锁的线程尽快得到锁 但是也相应付出了一定的cpu资源
还是上面我去球馆打球的例子 如果此时就一个人来和我竞争这个篮球 我不会立马放弃 而是会稍微等会 看我是不是快回家了 锁膨胀 当锁竞争更加激烈的时候 此时就会从自旋状态膨胀成重量级锁(挂起等待锁) 还是我去球馆打球 竞争太激烈的时候 等待的人就会回家了 不玩了 锁粗化 如果一段路基中 需要多次加锁 解锁 并且在解锁的时候没有其他线程来竞争 此时就会把多组的锁操作合并在一起 (合并后的锁的粒度很比较粗 所以叫锁粗化) 还是我去打球的例子 比如我正在玩突然想去卫生间 然后此时还没有人和我竞争这个篮球 我就没必要把他放回去 上完卫生间再去拿回来 我直接抱着篮球上卫生间多省事.
14. 嗯 不错,那你再说说tcp和udp差别是什么
我答: TCP适用于处理要求可靠性强的地方 效率自然不是很好 UDP适用于处理可靠性要求没那么高 但是要求效率高的地方 UDP 能实现广播 TCP只能一对一传输 如果TCP想实现广播就需要应用层配合 而且延迟较高
15. 那tcp为什么是可靠的
我答: 就因为三个条件 确认应答 超市重传 连接管理 包括三次握手和四次挥手
16. 那你是怎么理解滑动窗口的
我答: 刚才我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段. 这样做有一个比较大的缺点, 就是性能较差. 尤其是数据往返的时间较长的时候 滑动窗口的本质就是批量传输数据 总的传输时间: N份数据传输的时间叠加成了一份的时间 N份的应答时间 叠加成了1份的时间
17. 那么如果出现了丢包, 如何进行重传?
我答: 这里分两种情况讨论 一是数据包已经抵达, ACK被丢了 这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认; 比如的主机A,没有收到ACK1001 但是收到了ACK2001 此时主机A就可以判断出是1001的ACK丢失了 无所谓 数据已经传过去了 因为如果数据没有传过去的话 返回的ACK始终会是1001 不会出现2001这个ACK 二是数据包就直接丢了.当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001"一样; 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送; 这个时候接收端收到了1001 之后, 再次返回的ACK就是7001或者其他 接收端之前的就都已经收到了,
18. 那最后写个堆排序把 升序的
考这些七大排序啥的数据结构我还是在行的 我卡卡几下就给他写出来了

//堆排序
    public void heapSort(int[] array) {
    
    
        createHeap(array);
        //直接将数组长度减一,下次向下调整对最后一个元素不做处理,heapSize等于0的时候就只有一个元素为排序
        for (int heapSize = array.length - 1; heapSize > 0; heapSize--) {
    
    
            int tmp = array[0];
            array[0] = array[heapSize];
            array[heapSize] = tmp;
            shiftDown(array,heapSize,0);
        }
    }

    private void createHeap(int[] array) {
    
    
        //第一次减一是得到最后一个元素,在减一是为了找它的双亲结点
        for (int i = (array.length -1 - 1) / 2; i >= 0 ; i--) {
    
    
            shiftDown(array,array.length,i);
        }
    }

    private void shiftDown(int[] array, int size, int index) {
    
    
        int parent = index;
        int child  = 2 * parent + 1;
        while (child < size) {
    
    
            if(child + 1 < size && array[child + 1] > array[ child]) {
    
    
                child ++;
            }
            if(array[child] > array[parent]) {
    
    
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            }else {
    
    
                break;
            }
            parent = child;
            child  = 2 * parent + 1;
        }
    }

19. 改成降序的
我还是很熟练的又咔咔几下改好了

//堆排序
    public void heapSort(int[] array) {
    
    
        createHeap(array);
        //直接将数组长度减一,下次向下调整对最后一个元素不做处理,heapSize等于0的时候就只有一个元素为排序
        for (int heapSize = array.length - 1; heapSize > 0; heapSize--) {
    
    
            int tmp = array[0];
            array[0] = array[heapSize];
            array[heapSize] = tmp;
            shiftDown(array,heapSize,0);
        }
    }

    private void createHeap(int[] array) {
    
    
        //第一次减一是得到最后一个元素,在减一是为了找它的双亲结点
        for (int i = (array.length -1 - 1) / 2; i >= 0 ; i--) {
    
    
            shiftDown(array,array.length,i);
        }
    }

    private void shiftDown(int[] array, int size, int index) {
    
    
        int parent = index;
        int child  = 2 * parent + 1;
        while (child < size) {
    
    
            if(child + 1 < size && array[child + 1] < array[ child]) {
    
    
                child ++;
            }
            if(array[child] < array[parent]) {
    
    
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            }else {
    
    
                break;
            }
            parent = child;
            child  = 2 * parent + 1;
        }
    }

好了 时间到了就结束了我的第一次面试, 知识总结的话还是我的线程安全方面不是很好尤其一些锁策略上, 还有tcp的那十个特征也需要复习, 继续加油!!!

猜你喜欢

转载自blog.csdn.net/Shangxingya/article/details/107987498
今日推荐