秋招结束面试和面经的总结(个人向)-阿里云开发者社区

开发者社区> 游客3gxjduqhpgz42> 正文

秋招结束面试和面经的总结(个人向)

简介: 主要是自己秋招的个人总结(太菜了呀我)
+关注继续查看

自我介绍:

文案:

参考:

面经总结:

项目中可以装逼的:

1.弹幕娱乐:

直播是眼下最为火爆的行业,而弹幕无疑是直播平台中最流行、最重要的功能之一。本文将讲述如何实现兼容 PC 浏览器和移动浏览器的弹幕。

基本功能

并发与队列

一般来说,弹幕数据会通过异步请求或 socket 消息传到前端,这里会存在一个隐患——数据量可能非常大。如果一收到弹幕数据就马上渲染出来,在量大的时候:

  • 显示区域不足以放置这么多的弹幕,弹幕会堆叠在一起;
  • 渲染过程会占用大量 CPU 资源,导致页面卡顿。

所以在接收和渲染数据之间,要引入队列做缓冲。把收到的弹幕数据都存入数组(即下文代码中的 this._queue),再通过轮询该数组,把弹幕逐条渲染出来:

弹幕的滚动

弹幕的滚动本质上是位移动画,从显示区域的右侧移动到左侧。前端实现位移动画有两种方案——DOM 和 canvas。

DOM 方案实现的动画较为流畅,且一些特殊效果(如文字阴影)较容易实现(只要在 CSS 中设置对应的属性即可)。 Canvas 方案的动画流畅度要差一些,要做特殊效果也不那么容易,但是它在 CPU 占用上有优势。

本文将以 DOM 方案实现弹幕的滚动,并通过 CSS 的 transition 和 transform 来实现动画,这样可以利用浏览器渲染过程中的「合成层」机制(有兴趣可以查阅这篇文章),提高性能。弹幕滚动的示例代码如下:

弹幕的渲染

在 DOM 方案下,每条弹幕对应一个 HTML 元素,把元素的样式都设定好之后,就可以添加到 HTML 文档里面:

由于元素的 left 样式值设置为 100%,所以它在显示区域之外。这样可以在用户看到这条弹幕之前,做一些“暗箱操作”,包括获取弹幕的尺寸、占用的轨道数、总位移、位移时间、位移速度。接下来的问题是,要把弹幕显示在哪个位置呢?

首先,弹幕的文字大小不一定一致,从而占用的高度也不尽相同。为了能充分利用显示区域的空间,我们可以把显示区域划分为多行,一行即为一条轨道。一条弹幕至少占用一条轨道。而存储结构方面,可以用二维数组记录每条轨道中存在的弹幕。下图是弹幕占用轨道及其对应存储结构的一个例子:

img

其次,要防止弹幕重叠。原理其实非常简单,请看下面这题数学题。假设有起点站、终点站和一条轨道,列车都以匀速运动方式从起点开到终点。列车 A 先发车,请问:如果在某个时刻,列车 B 发车的话,会不会在列车 A 完全进站之前撞上列车 A?

img

聪明的你可能已经发现,这里的轨道所对应的就是弹幕显示区域里面的一行,列车对应的就是弹幕。解题之前,先过一下已知量:

  • 路程 S,对应显示区域的宽度; - 两车长度 la 和 lb,对应弹幕的宽度;
  • 两车速度 va 和 vb,已经计算出来了;
  • 前车已行走距离 sa,即弹幕元素当前的位置,可以通过读取样式值获取。

那在什么情况下,两车不会相撞呢?

  • 其一,如果列车 A 没有完全出站(已行走距离小于车长),则列车 B 不具备发车条件;
  • 其二,如果列车 B 的速度小于等于列车 A 的速度,由于 A 先发车,这是肯定撞不上的;
  • 其三,如果列车 B 的速度大于列车 A 的速度,那就要看两者的速度差了:
    • 列车 A 追上列车 B 所需时间 tba = (sa - la) / (vb - va);
    • 列车 A 完全到站所需时间 tad = (s + la - sa) / va; - tba > tad 时,两车不会撞上。

有了理论支撑,就可以编写对应的代码了。

class Danmaku {
  // 省略 N 行代码...

  // 把弹幕数据放置到合适的轨道
  _addToTrack(data) {
    // 单条轨道
    let track;
    // 轨道的最后一项弹幕数据
    let lastItem;
    // 弹幕已经走的路程
    let distance;
    // 弹幕数据最终坐落的轨道索引
    // 有些弹幕会占多条轨道,所以 y 是个数组
    let y = [];

    for (let i = 0; i < this._tracks.length; i++) {
      track = this._tracks[i];

      if (track.length) {
        // 轨道被占用,要计算是否会重叠
        // 只需要跟轨道最后一条弹幕比较即可
        lastItem = track[track.length - 1];

        // 获取已滚动距离(即当前的 translateX)
        distance = -getTranslateX(lastItem.node);

        // 计算最后一条弹幕全部消失前,是否会与新增弹幕重叠
        // (对应数学题分析中的三种情况)
        // 如果不会重叠,则可以使用当前轨道
        if (
          (distance > lastItem.width) &&
          (
            (data.rollSpeed <= lastItem.rollSpeed) ||
            ((distance - lastItem.width) / (data.rollSpeed - lastItem.rollSpeed) >
              (this._totalWidth + lastItem.width - distance) / lastItem.rollSpeed)
          )
        ) {
          y.push(i);
        } else {
          y = [];
        }

      } else {
        // 轨道未被占用
        y.push(i);
      }

      // 有足够的轨道可以用时,就可以新增弹幕了,否则等下一次轮询
      if (y.length >= data.useTracks) {
        data.y = y;
        y.forEach((i) => {
          this._tracks[i].push(data);
        });
        break;
      }
    }
  }

  // 省略 N 行代码...
}

只要弹幕成功入轨(data.y 存在),就可以显示在对应的位置并执行动画了:

class Danmaku {
  // 省略 N 行代码...

  _renderToDOM {
    const data = this._queue[0];
    let node = data.node;

    if (!data.node) {
      // 省略 N 行代码...
    }

    this._addToTrack();

    if (data.y) {
      this._queue.shift();

      // 轨道对应的 top 值
      node.style.top = data.y[0] * this._trackSize + 'px';
      // 动画参数
      node.style.transition = `transform ${data.rollTime}s linear`;
      node.style.transform = `translateX(-${data.totalDistance}px)`;
      // 动画结束后移除
      node.addEventListener('transitionend', () => {
        this._removeFromTrack(data.y, data.autoId);
        this._container.removeChild(node);
      }, false);
    }
  }

  // 省略 N 行代码...
}

至此,渲染流程结束,此时的弹幕效果见此 demo 页。为了能够让大家看清楚渲染过程中的“暗箱操作”,demo 页中会把显示区域以外的部分也展示出来。

完善

上一节已经实现了弹幕的基本功能,但仍有一些细节需要完善。

跳过弹幕

仔细观察上文的弹幕 demo 可以发现,同一条轨道内,弹幕之间的距离偏大。而该 demo 中,队列轮询的间隔为 150ms,理应不会有这么大的间距。

回顾渲染的代码可以发现,该流程总是先检查第一条弹幕能不能入轨,倘若不能,那后续的弹幕都会被堵塞,从而导致弹幕密集度不足。然而,每条弹幕的长度、速度等参数不尽相同,第一条弹幕不具备入轨条件不代表后续的弹幕都不具备。所以,在单次渲染过程中,如果第一条弹幕还不能入轨,可以往后多尝试几条。

相关的代码改动也不大,只要加个循环就行了:

_renderToDOM() {
  // 根据轨道数量每次处理一定数量的弹幕数据。数量越大,弹幕越密集,CPU 占用越高
  let count = Math.floor(totalTracks / 3), i;
  while (count && i < this._queue.length) {
    const data = this._queue[i];
    // 省略 N 行代码...
    if (data.y) {
      this._queue.splice(i, 1);
      // 省略 N 行代码...
    } else {
      i++;
    }
    count--;
  }
}

改动后的效果见此 demo 页,可以看到弹幕密集程度有明显改善。

弹幕已滚动路程的获取

防重叠检测是弹幕渲染过程中执行得最为频繁的部分,因此其优化显得特别重要。JavaScript 性能优化的关键是:尽可能避免 DOM 操作。而整个防重叠检测算法中涉及的唯一一处 DOM 操作,就是弹幕已滚动路程的获取:

distance = -getTranslateX(data.node);

而实际上,这个路程不一定要通过读取当前样式值来获取。因为在匀速运动的情况下,路程=速度×时间,速度是已知的,而时间嘛,只需要用当前时间减去开始时间就可以得出。先记录开始时间:

_renderToDOM() {
  // 根据轨道数量每次处理一定数量的弹幕数据。数量越大,弹幕越密集,CPU 占用越高
  let count = Math.floor(totalTracks / 3), i;
  while (count && i < this._queue.length) {
    const data = this._queue[i];
    // 省略 N 行代码...
    if (data.y) {
      this._queue.splice(i, 1);
      // 省略 N 行代码...
      node.addEventListener('transitionstart', () => {
        data.startTime = Date.now();
      }, false);
      // 从设置动画样式到动画开始有一定的时间差,所以加上 80 毫秒
      data.startTime = Date.now() + 80;

    } else {
      i++;
    }

    count--;
  }
}

注意,这里设置了两次开始时间,一次是在设置动画样式、绑定事件之后,另一次是在 transitionstart 事件中。理论上只需要后者即可。之所以加上前者,还是因为兼容性问题——并不是所有浏览器都支持 transitionstart 事件

然后,获取弹幕已滚动路程的代码就可以优化成:

distance = data.rollSpeed * (Date.now() - data.startTime) / 1000;

别看这个改动很小,前后只涉及 5 行代码,但效果是立竿见影的(见此 demo 页):

浏览器getTranslateX匀速公式计算
ChromeCPU 16%~20%CPU 13%~16%
Firefox能耗影响 3能耗影响 0.75
SafariCPU 8%~10%CPU 3%~5% IE

暂停和恢复

首先要解释一下为什么要做暂停和恢复,主要是两个方面的考虑。

第一个考虑是浏览器的兼容问题。弹幕渲染流程会频繁调用到 JS 的 setTimeout 以及 CSS 的 transition,如果把当前标签页切到后台(浏览器最小化或切换到其他标签页),两者会有什么变化呢?请看测试结果:

浏览器setTimeouttransition
Chrome/Edge延迟加大如果动画未开始,则等待标签页切到前台后才开始
Safari/IE 11正常如果动画未开始,则等待标签页切到前台后才开始
Firefox正常正常

可见,不同浏览器的处理方式不尽相同。而从实际场景上考虑,标签页切到后台之后,即使渲染弹幕用户也看不见,白白消耗硬件资源。索性引入一个机制:标签页切到后台,则弹幕暂停,切到前台再恢复

let hiddenProp, visibilityChangeEvent;if (typeof document.hidden !== 'undefined') {  hiddenProp = 'hidden';  visibilityChangeEvent = 'visibilitychange';} else if (typeof document.msHidden !== 'undefined') {  hiddenProp = 'msHidden';  visibilityChangeEvent = 'msvisibilitychange';} else if (typeof document.webkitHidden !== 'undefined') {  hiddenProp = 'webkitHidden';  visibilityChangeEvent = 'webkitvisibilitychange';}document.addEventListener(visibilityChangeEvent, () => {  if (document[hiddenProp]) {    this.pause();  } else {    // 必须异步执行,否则恢复后动画速度可能会加快,从而导致弹幕消失或重叠,原因不明    this._resumeTimer = setTimeout(() => { this.resume(); }, 200);  }}, false);

先看下暂停滚动的主要代码(注意已滚动路程 rolledDistance,将用于恢复播放和防重叠):

this._eachDanmakuNode((node, y, id) => {  const data = this._findData(y, id);  if (data) {    // 获取已滚动距离    data.rolledDistance = -getTranslateX(node);    // 移除动画,计算出弹幕所在的位置,固定样式    node.style.transition = '';    node.style.transform = `translateX(-${data.rolledDistance}px)`;  }});

接下来是恢复滚动的主要代码:

this._eachDanmakuNode((node, y, id) => {  const data = this._findData(y, id);  if (data) {    // 重新计算滚完剩余距离需要多少时间    data.rollTime = (data.totalDistance - data.rolledDistance) / data.rollSpeed;    data.startTime = Date.now();    node.style.transition = `transform ${data.rollTime}s linear`;    node.style.transform = `translateX(-${data.totalDistance}px)`;  }});this._render();

防重叠的计算公式也需要修改:

// 新增了 lastItem.rolledDistancedistance = lastItem.rolledDistance + lastItem.rollSpeed * (now - lastItem.startTime) / 1000;

修改后效果见此 demo 页,可以留意切换浏览器标签页后的效果并与前面几个 demo 对比。

丢弃排队时间过长的弹幕

弹幕并发量大时,队列中的弹幕数据会非常多,而在防重叠机制下,一屏能显示的弹幕是有限的。这就会出现“供过于求”,导致弹幕“滞销”,用户看到的弹幕将不再“新鲜”(比如视频已经播到第 10 分钟,但还在显示第 3 分钟时发的弹幕)。

为了应对这种情况,要引入丢弃机制,如果弹幕的库存比较多,而且这批库存已经放了很久,就扔掉它。相关代码改动如下:

while (count && i < this._queue.length) {  const data = this._queue[i];  let node = data.node;  if (!node) {    if (this._queue.length > this._tracks.length * 2 &&      Date.now() - data.timestamp > 5000    ) {      this._queue.splice(i, 1);      continue;    }  }  // ...}

修改后效果见

2.显示计数器;

要求:

1.通过首页来访问的时候开始计时,为用户生成id(全局唯一)

2.每五分钟动态模拟登陆人数,并在0点重置.在同一时刻同一个用户访问时需要和计数器实现一致性

3.不适用消息中间件,仅用数据库的方式实现,

3.实时上升热点(仿照微博的排行榜)

更具点赞和好评评论数来对喜爱的景点经行排名

其他字节的面经:

介绍一些web服务器的 项目 ,都做了什么

介绍select,poll,epoll的区别,特点

说一下升序 链表 的实现思路,有什么优化的地方吗

进程和线程的区别,分别占有什么

协程了解吗,协程的主要作用?协程有什么优点

户自己控制切换的时机,不再需要陷入系统的内核态
  协程的执行效率非常高。因为子程序切换不是线程切换,而是由程序自身控制。因此,没有线程切换的开销,和多线程相比,线程数量越多,相同数量的协程体现出的优势越明显。
  不需要多线程的锁机制

说一下进程间通信的方式?详细说一下管道和消息队列

信号、管道、消息队列、共享内存**

\1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
\2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

\4. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

\5. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

\6. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

\7. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

\8. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

说一下TCP三次握手,能否两次握手

两次握手会发生什么?

客户端单方面认为通

服务端不同

三次握手有什么不好的地方吗?有点懵,面试官提示我DDOS攻击角度

(SYN洪泛)

timewait状态,是干嘛的

1.收到最后应答的ack

2.之前存活的报文死亡。

等到确认

说一下输入url之后的全过程

1.dns解析为ip(dns协议)

2.发送HTTP请求: tcp建立连接,ip传输请求,http协议,arp协议将ip转mac ,ospf负责最近转发

3.接受http响应

4.解析渲染

说一下OSI七层参考模型,HTTP在那一层,dns在哪一层,tcp、udp在哪一层。

路由器工作在哪一层

说一下mysql和innodb和myisam的区别

mysql索引的存储方式

b,b+

mysql的最左前缀法则?举了个例子问我能够匹配上吗

select id,name,stuNo( select )

讲一下事务的四种隔离级别,他们分别解决了什么问题

说一下脏读,不可重复读,幻读(插入)

说一下mysql的默认隔离级别?可重复读 通过什么实现的?mvcc

讲一下mvcc的理解,如何实现的mvcc(undo,版本,锁)

讲一下mysql有哪几种锁,讲一下间隙所间隙锁都加在哪了

讲一下mvcc的事务id,他是怎么判断哪些事务能够访问到哪些版本的

熟悉 redis 吗?讲一下 redis 的数据类型

了解常用的消息队列吗,kafka之类的?

能来实习吗?

翻转 链表 区间元素

二:

一面

  1. JAVA的堆和栈的区别

    栈内存储的是局部变量,堆内存储的是实体

​ 栈内存存储的是局部变量而堆内存存储的是实体

​ 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短

​ 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收

2.JAVA的GC垃圾回收机制

垃圾回收机制就是JVM利用一些列算法对内存进行管理
回收内存空间,删除无用实例(没有被引用的对象)
自动进行,减少了内存溢出.但同时也牺牲了一定的性能
优化:将不用的变量和指针置位null

3.垃圾回收机制和调用 System.gc()的区别?

gc函数的作用是程序员提醒虚拟机,希望进行一次垃圾回收,但是虚拟机并不保证垃圾回收一定会进行。什么时候进行依然取决于虚拟机

4.什么是多态

一个程序中存在多的同名的不同方法,包括
通过子类对父类的覆盖来实现(重写)
通过在一个类中方法的重载来实现(重载)
向上继承:通过子类对象转化为父类对象来实现

5.重写和重载的区别

重写:子类和父类的一种关系,子类继承父类的方法
重载:同一个类中(包括父类)有多个同名的不同方法

6.进程和线程的区别,线程独有什么?

进程是程序的一次执行,进程包含线程,线程是进程中的代码段。

进程通过线程来实现同时运行不同段的代码。

线程的内存范围不允许越过进程。

线程共有的部分:方法区和堆

线程独有的部分:虚拟机栈,本地方法栈,程序计数器

7.你在项目中做过哪些性能优化

代码优化:
尽量重用对象,出现字符串连接时应该使用StringBuilder/StringBuffer代替
尽量使用局部变量(用过释放)
将常量声明为static final
字符串变量和字符串常量equals的时候将字符串常量写在前面
数据库优化:建立一个具有良好表结构的数据库

8.如何优化页面卡顿

拆分代码段
尽量减少使用layout
简化DOM结构

9.Handler消息机制

将耗时的操作放在子线程中处理,handler用来在主线程和子线程中传递信息

10.Android自定义View

View是用于创建交互式用户界面组件(按钮、文本等)的基础类
View是所有控件(包括ViewGroup)的父类,它里面有一些常见的方法,如果我们要自定义View,最简单的只需要重写onDraw(android.graphics.Canvas)即可。

11.常用的开源框架及其原理
Spring框架

12.MVC模式与MVP模式

13.MVC和MVVC怎么理解,开发中怎么用的

14.HTTP常见的请求方式及主要操作

15.HTTP和HTTPS

14.TCP的三次握手是什么?四次挥手

15.用到过的加密算法有哪些?

CA(RSA非对称的) MD5

16.设计模式有哪些?

17.如何实现AOP,用到什么技术?

18.JAVA自带的代理类和cglib有啥不同?

19.消息队列的作用和场景
客户端:

作者:字节研发内推
链接:https://www.nowcoder.com/discuss/712069?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

1.编程基础知识:java语言相关, 同步,互斥,单例,线程池等等

2.计算机基础知识: 堆,栈,编译,运行,进程,线程,操作系统,IO 等等

3.数据结构& 算法

4.网络: 7层模型, tcp/ip/ http/https , 要融汇贯通,结合实际场景能理解整个流程。

5.数据库:有的面试官会问,有的不会问, 建议也准备一下这部分, 客户端开发一般不会聊特别深.

客户端大牛:

作者:0118101437
链接:https://www.nowcoder.com/discuss/642137?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

本人双非非科班大三

----------------------------------------------------------------

一面:

自我介绍

介绍你参与比较多的[项目]()

说说解决的问题

聊聊加密[算法](),你在[项目]()中怎么用的?

加密[算法]()你了解多深(介绍了MD5,非对称加密DES,AES)(我从加密[算法]()提了RSA,CA机构加密的就是用的RSA)

怎么保证非对称加密的安全性(原话)

如果让你设计你会怎样做?

TCP和UDP的区别(后面所有话题都由这个衍生)

  • 我提了可靠传输的问题(怎么保证可靠?你说的几个点怎么实现?如果我发了几个包但是丢了后面几个怎么办?重传?计数器)
  • 效率问题
  • HTTP都是基于TCP的吗?(不是,HTTP3.0基于QUIC,介绍了QUIC的优化问题以及具体实现,我还提了http的扩展性,可以重写传输层协议)
  • 那HTTP是一个TCP建立连接以后就一个请求就断开吗?(不是,HTTP1.1开始支持长连接,只要在字段collection设为[keep]()-alive)
  • 那就是连接以后就不会断了?
  • 如果让你设计,你会怎样控制这个连接的开闭
  • http是一个线程只能处理一个连接吗?(2.0的多路复用)
  • 假设我有多个连接但是那些数据一起涌进来怎么办?
  • 多路复用让你设计你会怎样设计?
  • 怎么控制TCP连接数?(他提了个设定最大TCP连接数)
  • 如果让你做你会怎样去断开没用的连接(我提了可以用OS的调度[算法](),设计一个类似LRU的模型)
  • 能介绍一下LRU吗?(我提了Java的实现妄想让他让我写LRU)
  • 具体怎么做?
  • 你怎么知道断开哪个连接?(如果有新连接进来,直接断开双向[链表]()最后一个连接,记录每个连接的占用然后放hashmap里可以达到O(1)查询)
  • Hashmap怎样get
  • 平常用的什么语言?

Java几个修饰符(protect public那些)

final的作用

[算法]():无序数组三数之和,输出下标

  • [排序]()过后下标会乱你怎么处理?(hashmap记录原来的索引)
  • 那假设我有重复元素怎么办?
  • value用arraylist存放?(我写了个大概
  • [两数之和]()会吗?(动态Hashmap)
  • [两数之和]()提了几个特殊用例 答上来了
  • 优化三数之和[算法]()

    反问:评价一下我的这次面试给意见

  • 基础知识挺好,数据结构计算机网络都学得不错,[算法]()能力还可以

    建议:多去实习,最好来字节实习,一两天会有反馈

隔天下午收到预约一周后二面的电话

--------------------------------------------------------------------------

二面:

进程和线程有啥区别?(围绕这块问了一会操作系统)
写DCL
请你设计一个系统,将长域名转化为短链接,中途的处理[算法]()和解析流程,大概怎么解析
[算法]():[把一个奇数位升序偶数位降序的单链表变成升序的,空间复杂度O(1)]()

隔天下午收到预约一周后三面的电话

---------------------------------------------------------------------------

三面,最让我想不明白的一面......

自我介绍

(这里面试官说一二面问基础为主,面试官反馈不错,三面就不问基础了)

[算法题]():N叉树,求走M步走到节点x的概率,如果到了目的节点但是步数没用完算走不到,只有走到叶子节点而且还没用完步数的情况才能原地走

JavaGC

[项目]()相关

能实习多久?

在学校成绩如何

作者:Ctz
链接:https://www.nowcoder.com/discuss/739501?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

部分借鉴

2.OS

(1)页面置换[算法]()?(FIFO、LRU、LFU)

(2)LRU怎么实现的?(双向[链表]() + [哈希表]())

3.网络

(1)[客户端]()请求资源,如何实现断点续传?(使用HTTP请求的if-range字段+range字段)

(2)如果资源发生变化,怎么判断?(时间戳或ETAG)

(3)HTTP劫持?(不会)

贴小广告的

事前加密https

事中规避

事后屏蔽

(4)DNS劫持?(用HTTPDNS解决)

输入百度进入渣渣灰

用httpdns解决

(5)通过HTTPDNS请求域名时,是使用IP还是域名?

将域名替换为ip

(6)如果用IP去请求,如何去实现容灾?

(7)HTTP1.0、2.0、3.0的区别?

(8)HTTP3.0用的UDP怎么做到可靠的?(自己实现了可靠传输的机制,例如流量控制、重传等)

(9)流量控制时怎么实现的?

(10)假如让你设计一个类似微信的离线消息机制,例如接收方离线,发送方给他发消息,如何实现?(服务器缓存发送方的消息,当接收方上线时通过请求来获取离线消息)

4.DB

(1)索引优缺点和原理?

(2)什么时候索引会失效?

5.iOS

(1)UITableView的用途、如何展示数据、复用策略?(给几分钟时间线上搜相关资料,然后回答)

6.代码

(1)(Leetcode3)给一个字符串,求无重复字符的最长子串长度

(2)(Leetcode1)[两数之和]()

作者:Ctz
链接:https://www.nowcoder.com/discuss/725266?source_id=profile_create_nctrack&channel=-1
来源:牛客网

2.网络

(1)TCP三次握手中,SYN、ACK、seq、ack四个字段的含义?

(2)HTTP和HTTPS的区别?

(3)HTTPS密钥协商的过程?

(4)如何实现HTTP长连接?(1.0里Connection:Keep-Alive,1.1里默认开启长连接)

(5)浏览器断点续传,分段下载时,HTTP用的哪个字段?(不会,答了个content-length字段,懂得大佬麻烦说一下)

3.OS

(1)进程和线程的区别?

(2)进程间通信机制?

(3)Linux如何创建进程?(fork,写时复制)

(4)什么是僵尸进程?如何处理?(wait/waitpid)

(5)线程同步的方式?(互斥锁、自旋锁、读写锁、条件变量、信号量)

(6)互斥锁和自旋锁的区别?

(7)死锁的四个必要条件?

(8)如何避免死锁?(银行家[算法]())

(9)线程池如何设计的?(线程池、任务队列、互斥锁、条件变量)

(10)线程池里面的线程是如何实现执行完函数后不结束的(while)

(11)那会不会造成CPU空转的情况呢?(不会,条件变量,条件不满足时会自动释放锁,然后进入阻塞)

4.DB

(1)从Person表中查询Age在18-25之间的数据,根据身高Tall由高到低[排序]()

(2)接着(1)增加 Name 包含A(查询包含A的Name)

5.代码

(1)如何判断两个[链表]()是否相交?

(2)(Leetcode162)一组数据,不存在num[i] = num[i+1],返回任意一个峰值。峰值:左右都比该值小则该值称为峰值。

数组测试样例:
(1) 1232143
(2) 321
(3) 123

最近的部分借鉴::

作者:小辣鸡0.0
链接:https://www.nowcoder.com/discuss/735790?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

  • 进程与线程的区别
  • 进程之间的通信方式
  • 用户态和内核态的区别
  • 守护线程是什么
  • 产生死锁的原因
  • 1.
  • 2.
  • 3
  • 4
  • TCP和UDP的区别
  • HTTP是基于哪个协议的

    • 1.0、1.1、2.0、3.0
  • HashMap、HashTable、ConcurrentHashMap底层数据结构,底层原理
  • Java的引用类型,强引用、软引用、弱引用、虚引用的区别
  • JVM垃圾回收[算法]()
  • GC ROOT有哪些对象
  • Java就问这么多吧(太熟练了)
  • MySQL索引为什么要有B+ Tree
  • [算法]():判断[二叉树]()和为n的路径是否存在

二面:(1h 30min)

  • MySQL事务特点
  • 索引何时失效
  • MySQL如何抵抗高并发(详细)
  • 如何设计高并发的系统(详细)
  • 流量非常大的页面,如何统计PV、UV,保证高并发不出问题(详细)
  • 如何优化SQL
  • Java中抽象类和接口区别
  • Java中类加载器有哪些
  • JVM双亲委派机制
  • Java中如何实现C++中的多重继承
  • Java常见的垃圾回收[算法]()
  • Java反射机制
  • 为啥C++可以多继承而Java只有单继承
  • 为什么String在Java中是不可变的
  • 为什么要有字符串常量池,为什么要这么设计
  • 为什么Java不支持运算符重载
  • 为什么Char数组比String数组更适合存储密码
  • 你对并发和并行的理解
  • [源码]()如何变成可执行文件
  • Java类加载机制 详细
  • 什么是守护进程、僵尸进程、孤儿进程
  • 说一下常见的内存分配错误
  • 内存已经释放了,但是继续使用这个区域的内存,会出现什么问题
  • 说一下什么是缓冲区溢出,它有什么危害
  • 智力题:10分钟等车的概率是99%,5分钟等车的概率是多少?
  • 智力题:A和B轮流丢硬币,谁先丢出正面谁赢,A赢的概率是多少
  • [算法题]()手撕:两个栈实现队列,使用线程安全实现(Synchornized 和 ReentrantLock 都实现出来)

    • 先手撕一个线程不安全的
    • 再撕一个Synchronized实现的

      • Synchronized底层原理
      • 锁升级机制
    • 再撕一个ReentrantLock实现的
  • [算法题]()口述思路:投n个骰子,最后计算和为m,有多少种投法(DP)

三面:(1h)

  • 学校成绩怎么样
  • 之前[项目]()和实习都是学习大数据,对[客户端]()有什么了解吗
  • 希望转[客户端]()吗
  • 深挖[项目]()
  • Kafka数据的同步,有了解吗
  • Zoo[keep]()er在[项目]()中做什么用的
  • Zoo[keep]()er集群选举[算法]()
  • 还了解哪些分布式协调[算法]()(2PC Paxos)?
  • Python脚本 a.py 文件 生成的a.pyc是什么(不知道)
  • javac具体怎么做的(不知道)
  • 编译原理是没学过吗 (没学过)
  • 智力题:1000个苹果放进 10个盒子,如何分配可以让任何一个人想取多少苹果就可以通过几个盒子加起来拿到
  • 场景[算法](): 最大的 Top K 变形题,维护小顶堆实现(讨论 10min,写代码 20min)
  • 反问

较全的借鉴:

作者:Darreni
链接:https://www.nowcoder.com/discuss/700214?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

一面

  1. 给出[二叉树]()前序、中序遍历,写出树的后序遍历
  2. 操作系统:内存管理、虚拟内存
  3. Java内存管理:分区、JMM、垃圾回收
  4. 类的加载:静态变量什么时候被初始化
  5. 登陆用户数据表的设计,考虑安全性、性能、扩展性,什么是加盐

其他忘记了...
最后还有一道原创[算法]()(不得不吐槽一下,之前面字节实习也是原创[算法](),看别人都是[牛客]()网字节题库原题羡慕~)

二面

  1. 介绍[项目]()
  2. 操作系统怎么划分内存?堆、栈有什么区别?
  3. 操作堆和栈谁比较快?为什么?
  4. 为什么随机IO的速度会比随机IO慢?
  5. 如何解决电商系统的常见问题?
  6. 如何解决缓存雪崩、缓存穿透?
  7. Rand5 实现 Rand7
  8. 什么是内核态和用户态,陷入内核态的方式?什么是中断?

两道[算法](),验证IP、O(1)

空间翻转URL(www.bytedance.com->com.bytedance.www)

三面

1.狂问[项目](),介绍[项目]()难点,抓住[项目]()中的某些点,深入的问相关的知识~
2.单例模式,没有列举直接写了双重校验锁,引起了面试官的不满,问我是不是单例都要这样实现。
这一面基本在问[项目]()相关的东西,具体问啥忘记了....

四面

这一面是我经历过的最难的一次面试(难度感觉比[腾讯]()、字节后端的面试都难,本以为[客户端]()会比较友好...)全程重点在操作系统和计算机网络,给一个具体的场景,分析原理

  1. 完整描述一次复制操作的过程(面试官强调复制不只是文件复制),不是简单的用Java实现文件复制,而是描述这次复制操作中的整个过程(操作系统做了什么?怎么做?什么系统调用?有什么区别?)
  2. UDP具体应用场景?为什么DNS用UDP,UDP很脆弱,用UDP会出现什么问题?具体举个例子?具体描述DNS劫持的过程
  3. Http2.0多路复用具体是怎么实现的?(我说了二进制分帧,然后有流标识符,所以实现多路复用,面试官不满,表示他想知道的是具体流程)
  4. 整个网络链路的性能指标分析,在5层协议的基础上,逐层分析每一层有那些影响网络性能的指标,如何提高性能(这个题真的难,我怕了~)
  5. 开放性题目,如何完善抖音推荐[算法](),让推荐更加准确?
    还有其他一些具体的场景,忘记了...基本上都是关于计网和操作系统的某个场景,描述整个流程,如何实现,所背的Http、TCP全套八股文一点没用上,一度以为我已经凉了~

总结

总体的面试体验还是很不错,就是别人都是三面,然后我可能表现不好被加面,然后四面被暴捶。总的来说,八股文并不是很多,挺多背了很久的八股文完全没用上。面试前,基本翻了所有字节[客户端]()的[面经](),感觉实际面试跟[牛客]()网上的[面经]()内容差距较大,感觉看到的[面经]()多数以八股文为主~ 还是不要过度相信[面经]()。

最后想问问,[客户端]()真的有那么劝退吗 ~ 挂后端被[客户端]()捞,还要不要接着卷卷其他厂的后端??

还有一个:

作者:thyxsl
链接:https://www.nowcoder.com/discuss/714525?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

一面 (1小时)

  1. 手撕[二叉树]()Z遍历(力扣)
  2. 编译原理
  3. JVM回收[算法]()
  4. CPP如何实现垃圾回收
  5. UDP与TCP,以及UDP如何保证可靠传输(quic)
  6. 介绍[项目]()技术栈,深挖[项目]()
  7. 强引用弱引用软引用虚引用对比
  8. 标记对象可回收[算法]()
  9. JVM内存模型
  10. JVM类加载方式:双亲委派详细介绍过程
  11. 介绍[google]() inject(依赖注入框架)及依赖注入的优点
  12. LRU[算法]()及其实现
  13. LinkedList HashMap 底层原理

二面(1小时)

  1. 递归的处理过程
  2. 设计模式(单例模式,建造者模式,代理模式,工程模式等)
  3. [项目]()技术栈,深挖[项目]()
  4. 常用[算法]()对比(DP, 回溯, 贪心,分支限界)
  5. 递归写个斐波那契
  6. [项目]()中遇到的难题
  7. 数组[链表]()区别
  8. 页面置换[算法](),内部碎片产生
  9. 进程线程
  10. 设计操作系统需要实现哪些功能:进程管理,文件管理,内存管理,IO管理等
  11. 未来发展移动端发展趋势

三面(1.5小时)

  1. 介绍[项目]()技术栈,深挖[项目]()
  2. [项目]()中使用的Hash[算法]()生成token过程
  3. HashMap 结构,扩容,负载因子,putVal过程
  4. 手撕删除[链表]()中所有重复结点(递归写出来了,迭代没有)
  5. 介绍MQ(RabbitMQ与Kafka)
  6. 介绍微服务
  7. 是否愿意转[客户端]()
  8. hashTable是否线程安全,如何实现线程安全
  9. 场景题: 不使用消息队列,如何实现服务之间数据通信(Http长轮询 和 类似于实现发布订阅机制的中间件如[redis]())
  10. [redis]() 与 MC 对比, 为何选在[redis]()
  11. 介绍小程序的优缺点
  12. 未来[职业规划]()

同部门的:

作者:三七二十一°
链接:https://www.nowcoder.com/discuss/752059?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

一面:

聊[项目]()

tcp在哪层,ip在哪层

tcp四次挥手为什么四次

https握手过程【这里答的不太好,面试官也一直扣,后面帮我纠正了下

页置换[算法]()有哪些

belady现象了解吗

进程线程的区别,进程和线程的切换

线程共享资源的保护

cpu怎么实现原子操作的 **

1、使用总线锁

2、使用缓存锁

3、CAS

页面置换算法

内存管理

垃圾回收

数据库事物

事物隔离级别

脏读幻读的解决

提高隔离界别

智力题:有 9 个球,其中 8 个球质量相同,有 1 个球比较重。要求用 2 次天平,找出比较重的那个球

[算法题]():两个栈实现队列

二面:

抠[项目]()

[算法题]():口述前中后层序遍历,mod5转mod7【口述,用1-5随机数生成1-7】,bash博弈【也是口述】,力扣5【[最长回文子串](),这个让写代码了】

tcp如何进行流量控制的

tcp保活计时器【[客户端]()挂掉时一段时间之后自动释放那个】

还是https,对称加密还是非对称加密,证书认证

进程之间的通信方式

1.管道:速度慢,容量有限,只有父子进程能通讯

2.FIFO:任何进程间都能通讯,但速度慢

3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

4.信号量:不能传递复杂消息,只能用来同步

5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

自旋锁和互斥锁的区别【这块好像没答好

消息队列的作用,使用场景

子网掩码的作用,给两对ip地址和子网掩码,判断他们是不是在同一个网段

DNS的作用

ARP协议的作用,协议原理

恒生:

自我介绍

问[项目]()

Java基本数据类型有哪些?分别占多少字节?什么时候用int,什么时候用long?

在Java中一共有8种基本数据类型,其中有4种整型,2种浮点类型,1种用于表示Unicode编码的字符单元的字符类型和1种用于表示真值的boolean类型。

反射了解吗?说一下应用场景

反射:运行状态

对于任意一个类,都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意方法和属性

4.JDBC 的数据库的连接

  1. 通过Class.forName()加载数据库的驱动程序 (通过反射加载,前提是引入相关了Jar包)
  2. 通过 DriverManager 类进行数据库的连接,连接的时候要输入数据库的连接地址、用户名、密码
  3. 通过Connection 接口接收连接

CLass.forName()字节码已经加载到java虚拟机中,去得到字节码;java虚拟机中还没有生成字节码 用类加载器进行加载,加载的字节码缓冲到虚拟机中。

5.泛型擦除中

1.getclass.getmethod.invoke();

1、可以用于改进设计模式

工厂模式的改进

要新增实现类的话,只要将传入的参数改变就好,无需更改工厂内的代码。

2、 反射机制的典型应用---Tomcat服务器(SSM框架中使用)

(1). Tomcat(App)首先读取配置文件web.xml中配置好的Servlet的子类名称

(2).Tomcat根据读取到的客户端实现的Servlet子类的类名字符串去寻找对应的字节码文件。如果找到就将其加载到内存。

(3).Tomcat通过预先设置好的Java反射处理机制解析字节码文件并创建相应的实例对象。之后调用所需要的方法。

【最后】Tomcat一启动,用户自定义的Servlet的子类通过Tomcat内部的反射框架也随之运行。

3、在项目中处处可见,AOP和IOC都是基于反射实现的

例:拦截器,注解、XML文档、token登录拦截

Java异常有哪些?介绍下你遇到过的异常

集合类框架:

1.ArrayList:和Vector对比

默认创建的是一个空集合,它的初始容量是10

调用add()方法先判断大小

扩容的源码:

grow()方法中:

Vector2倍

​ 加锁,线程安全

HashMap

JDK1.8 之前HashMap 底层 数组和链表结合形成链表散列

HashMap 通过key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (数组长度 - 1) & hash 判断当前元素
存放的位置(这⾥的 n 指的是数组的⻓度)

为什么是(n - 1):

因为在hash值和 那个长度做按位与运算 最后一位都是0,需要充分利用空间.

如果当前位置存在元素的话,就判断该元素与要存
⼊的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲
突。

所谓扰动函数指的就是 HashMap 的 hash ⽅法。使⽤ hash ⽅法也就是扰动函数是为了防⽌⼀
些实现⽐较差的 hashCode() ⽅法 换句话说使⽤扰动函数之后可以减少碰撞。

源码中:

1.8

右移16位扰动4次

介绍下static关键字的使用场景。什么时候要用到static?

静态方法的好处就是不用生成类的实例就可以直接调用。

static方法修饰的成员不再属于某个对象,而是属于它所在的类。只需要通过其类名就可以访问,不需要再消耗资源反复创建对象。

在类第一次加载的时候,static就已经在内存中了,直到程序结束后,该内存才会释放。

如果不是static修饰的成员函数,在使用完之后就会立即被JVM回收。

使用:

1.写工具类(JDBC中 getConnection)

2.单例模式:双重校验. SingletonInstance

介绍下JVM内存区域以及每个区域的作用。类信息存储在哪个区域?常量存储在哪个区域?字节码存储在哪个区域?

1、程序计数器

  1. 字节码指令的地址
  2. native【底层方法】,那么计数器为空
  3. ⽣命周期:

    ​ 随着线程的创建⽽创建,随着线程的结束⽽死亡。

    4.可以切换线程且之间独立.

2、虚拟机栈

存储局部变量表,操作栈,动态链接,方法出口

编译期完成,不会改变大小.

存放编译期可知的各种 数据类型 对象引⽤

抛出异常:

1.StackOverflowError

2.OutOfMemory

方法函数调用:

1.return

2.抛出异常

主要是桢栈被弹出

3、本地方法栈

本地方法栈则为虚拟机使用到的native方法服务

4、堆(GC收集的主要区域)

堆是java虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的,所以多线程的时候也需要同步机制。

所有对象实例及数组都要在堆上分配内存

1、-Xms:表示java虚拟机堆区内存初始内存分配的大小,通常为操作系统可用内存的1/64大小即可,但仍需按照实际情况进行分配。
2、-Xmx:表示java虚拟机堆区内存可被分配的最大上限,通常为操作系统可用内存的1/4大小。

5.方法区

存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。

Spring里面用到了哪些设计模式?

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。(JDK CGlib)
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

你用过哪些SpringBoot的注解?

自动装配原理:

@EnableAutoConfigration 注解会导入一个自动配置选择器去扫描每个jar包的META-INF/xxxx.factories 这个文件,这个文件是一个key-value形式的配置文件,里面存放了这个jar包依赖的具体依赖的自动配置类。这些自动配置类又通过@EnableConfigurationProperties 注解支持通过xxxxProperties 读取application.properties/application.yml属性文件中我们配置的值。如果我们没有配置值,就使用默认值,这就是所谓约定>配置的具体落地点。

Spring Boot 中如何解决跨域问题 ?

同源策略:

JSONP 仅仅有GET不符合

CORS 有跨域的:

​ 通过WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

当用户退出登录状态时或者token过期时,由于拦截器和跨域的顺序有问题,出现了跨域的现象。

一个http请求,先走filter,到达servlet后才进行拦截器的处理,如果我们把cors放在filter里,就可以优先于权限拦截器执行。

类加载过程

加载-->验证--> 准备-->解析-->初始化-->使用-->卸载
  1. 项目 中pagehelper的原理是什么?

    1. 项目 中使用aop是如何实现降低耦合和提高拓展性的?

      1. 项目 细节等
        4.java的基本数据类型详细说说

        5.一个空字符串占几个字节

        编码决定大小 GBK6

        源码时8*int

        6.一个java文件里有多个类,编译后生成几个class文件

        一个java文件中有多个类,除去内部类,剩下的每个普通类都会生成一个class文件

        7.volatile的作用是什么,举例使用场景

        8.hash碰撞的解决方法有哪些

        9.讲一讲jdk里的hashmap有哪些值得借鉴的细节

        10.垃圾回收 算法 有哪些

        11.年轻代老年代分别用的什么 算法

        12.gc会停止用户进程吗?cms和g1的哪些阶段会stw?

        13.抽象类和接口的区别

        14.jmm讲一讲

        15.线程和进程的区别

        16.并行和并发的区别

        17.怎么去结束一个正在运行的线程

        18.乐观锁与悲观锁是什么?举个例子

        19.linux了解吗,怎么查看占用cpu最高的进程

        20.时间复杂度为O(nlogn)的 排序 有哪些

        21.快速 排序 最坏情况

        22.外部 排序 ,找一亿个数中的top50

        23.一组数中找到出现次数为奇数的那个数

        24.文件系统和数据库系统的索引用什么结构?

        \25. 红黑树 和 平衡二叉树 的区别、优点,b+树和 红黑树 比较

        26.熟悉哪些数据库,mysql, redis

        27.sql左连接右连接特点分析

        28.对工作地点有什么看法

        29.未来的发展方向

        30.反问

然后就开始问我[项目](),一开始太紧张答得不太好,面试官似乎对我做的[项目]()好像也不是很感兴趣,然后就开始问八股文了,方方面面基本都有问到。

1、Java自身带的线程池有哪些问题(答了OOM面试官就问我Java堆溢出怎么排查,这块不是很熟没答出来)

2、几种垃圾回收[算法]()

3、怎么查看sql查询效率

4、MySQL左外连接和内连接的区别

5、问了spring的AOP,然后就问我动态代理的几种实现

6、问了我spring中有用到那些设计模式(就问了下也没问我怎么实现的)

JVM

讲一下有哪些垃圾回收器,说一下吞吐量优先垃圾回收器怎么使用???

什么时候会进行full GC(答着答着发现CMS那个failure忘了叫啥了,离谱)

1.jdk动态代理

2.避免死锁怎么做

3.索引

4.分布式session

5.mybatis怎么分页的,PageHelper实现分页

6.怎么保证字节码指令的顺序,禁止指令重排,voliate关键字

7.设计模式,讲了单例的实现

8.reids宕机了怎么办,使用过[redis]()集群吗

9.怎么设计接口限流的

10.mybatis的三级缓存

11.怎么保证mysql和[redis]()的数据一致性

嘿嘿,还有一些忘记了哈

\1. 在校活动。

\2. 介绍[项目](),团队分工

\3. 了解哪些[前端]()技术

\4. 研究生阶段最有成就感的一件事

\5. 你认为完成一个[项目]()的完整流程有哪些,分别会用到哪些工具?

\6. 你认为5中最重要的一个环节是?

\7. 6的环节你的[项目]()花了多少总工程时间

\8. [项目]()有没有考虑安全问题,如何解决?

感觉有点儿上来就是技术终面的味道。

二面(32min)

\1. 介绍[项目](),[项目]()难点

\2. [项目]()给你最大的成长

\3. 团队怎么分工

\4. [项目]()维护的细节(举例)

\5. 提高用户体验的细节(举例)

\6. 了解哪些技术栈,哪些比较熟练

问了一些JVM相关的、分布式的ZAB协议、binlog和redolog数据同步等问题。

JVM问了原先公司用什么垃圾回收器、为什么新生代分为eden、s0、s1区

数组扩容死循环问题

讲讲集合(讲了得10分钟,从ArrayList到LinkedList,Set,从HashMap到HashTable到ConcurrentHashMap,数据结构,线程安全问题,哈希碰撞,全讲了一遍)

恒生
1.一分钟自我介绍
2.集合框架 每个怎么使用 区别
3.JVM内存结构,垃圾回收

4.Java多线程,线程池
5.Servlet作用是什么
6.Spring的俩个特性 IOC AOP
7.SpringMVC的工作流程
8.学习框架遇到的困难,重来一次你会怎么学习
9.大学期间自我感觉做的好的事情
10.反问
框架问的比较多

二面:

自我介绍补充简历
为什么选这个专业
[项目]()是自己做的吗
为什么不考研,为什么不,实习工作
[项目]()的职务,做了多久,流程
遇到的困难
理论和实践差距在哪里
怎么学习的
为什么有科目考60分
团队中你作为组长怎么统筹,怎么解决问题,[项目]()时间,
出现的差错
工作看中什么
怎么对比两份offer
期望薪资
证书
压力最大的时间
反问

1 自我介绍

2 八股文环节
2.1 创建一个对象有哪些方式?
2.2 重载和重写区别?
2.3 如果想阻止一个对象序列化,可以采用什么方法?
2.4 反射机制是什么,有哪些实现方法,优点缺点?
2.5 HashMap底层数据结构,扩容机制
2.6 [红黑树]()是什么?如何进行自调整?与HashTable区别?
2,7 String、String StringBuffer 和 StringBuilder 的区别是什么?
2.8 什么叫线程安全?ConcurrentHashMap做了哪些优化?
2.9 Synchronized 和 ReentrantLock 有什么不同?
2.10 线程池是什么?构造方法有哪些参数?有哪些包和策略?有哪些线程池?
2.11 sleep()和wait() 有什么区别?
2.12 为什么线程没有运行态(只有runnable)?
2,13 GC机制
2.14 双亲委派机制是什么?类加载过程是怎样的?
2.15 存储过程是什么,优点?
2.16 数据库索引
2.17 事务隔离级别?Mysql默认隔离级别是什么?
2.18 数据库最左匹配原则?Sql怎么优化?
2.19 缓存雪崩与缓存穿透,解决方法?
2.20 Redis为什么是单线程?
2.21 缓存有哪几个实现方式?
2.22 请你谈谈消息队列?有哪些应用场景?
2.23 HTTP和HTTPs区别?
2.24 简述DNS过程
2.25 TcP三次握手具体过程

3 反问环节
自行提问即可

09-15-二面[面经]():

1 自我介绍
2 我看你是做人工智能的,为什么不搞[算法]()?
3 你在[项目]()中遇到哪些问题,怎么解决?
4 你的[职业规划]()?
5 你对恒生有哪些了解?
6 请用两个词形容你自己?
7 为什么你选择恒生?
8 你对我们部门有什么了解?
9 最后反问

作者:披萨大王
链接:https://www.nowcoder.com/discuss/761047?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

一面: 60分钟 2021.9.10
  • 先介绍了面试流程,自我介绍--》基础知识的了解---》[项目]()经历or实习经历---》反问
  1. 自我介绍
  2. 能实习了这么久(一年半),是没课了吗?有课。那你的课程完成了吗
  3. 哪门计算机的专业课学的最好?
  4. 判断相同,除了重写equals,还要重写什么?为什么要重写这两个?equals意义是啥?
  5. final关键字的作用?
  6. 什么是对象的深拷贝和浅拷贝?实现深拷贝怎么做?或者说两个相同类型的对象做到深拷贝怎么实现?除了序列化还有其他方法吗?
  7. 什么是反射?反射的意义?你能想到的在什么场景下用到了反射机制?
  8. 用过哪些类型的数据库?索引是用哪种数据结构存储的?
  9. 说说B+树
  10. 为什么不用B-树而用B+树呢?说说B+树的其他特征?
  11. 聚集索引和非聚集索引?
  12. 说说内连接和外连接
  13. 索引失效是什么原因?
  14. 你知道的Mysql里面的锁?
  15. 遇到过死锁的情况吗?简述了一下,说我说的不够严谨,顺序性没照顾到。
  16. 说说HTTP协议
  17. HTTP协议除了get和post还有其他请求吗?了解head请求吗?面试官还给我解释了一下head
  18. 了解跨域问题吗?怎么解决?为什么后端上加了注解就好用了呢?
  19. 了解短连接和长链接的概念吗?我在发出一个请求的时候,我在请求头里设置什么可以将其设置成长链接?
  20. 有操作过服务器上的东西吗?假如说你在服务器上去维护的时候对吧?各种状态,比如 CPU 运营状态,网络的这一个状态对吧?然后你看到了说这个服务器上你会发现这个因为每一个假如说你是服务端,有[客户端]()跟你好多[客户端]()跟你这进行建链对吧?你会发现比如说线下这个服务器上,你去查的时候,你会发现好多他的网络的连接状态它是 close wait 的一个,这是什么?可能是什么原因导致的?
  21. 聊聊限流[算法](),为什么选择令牌桶的方式?
  22. 秒杀[项目]()中哪里还用到了Redis?
  23. 怎么才能保证不超卖?
  24. [redis]()里面做库存扣减用的哪个方法?使用[redis]()的incrby特性来扣减库存。
  25. 这个[项目]()是作业还是自己尝试着去研究的?
  26. 最近是刚开始面试还是已经面了一段时间了?
  27. 投递简历筛选公司的原则?为什么
  28. 你是内推的,那你应该对我们公司有过了解,不需要再跟你多花时间去介绍我们公司主要做什么?
  29. 场景题:可能你在学校里面或者在实习的时候也会遇到这样的事情。其实比如说你的老师或者你的上级领导给你安排了一个任务,这个任务要的非常的急,以你初步评估来讲的,可能在这么短的时间内没有办法很高质量去完整的。这个时候你会去怎么抉择?一个是我延期去交付,我保证它质量会好一些对吧。一个按时交付,但是我没有办法保证这个质量多好,这让你怎么抉择呢?
  30. 再确认几个信息,本科对吧?大二开始实习,那课程是实习期间自学的吗?学习允许吗?学分修够了吗?
  31. ​ 反问环节
  32. ​ 当天下午打电话约了二面时间
二面: 20+分钟 2021.9.16
  1. 自我介绍
  2. 为什么选择软件工程专业
  3. 这个专业女生比较少,以后加班熬夜没问题吧
  4. 在[项目]()中用过hashmap吗?说说
  5. 在什么场景下用到ArrayList?为什么用到ArrayList?放数组里不也是一样吗?那说说ArrayList的扩容机制吧
  6. 多线程在[项目]()中用到过吗?
  7. 一个线程启动之后占多少内存?
  8. Concurrenthashmap和hashmap的区别?
  9. 设计模式了解过吗?一共有多少种?常用哪些?用模板型设计模式解决什么问题?
  10. 什么样的事情能让你获取成就?
  11. 讲讲最近的那个实习的[项目]()?
  12. 实习[项目]()里面比较困难的技术点?怎么解决的?
  13. 我们用的不是Java,知道其他的一些语言吗?
  14. 对[北森]()了解过吗?
  15. 你认为[北森]()是你心目中比较期望的这种这个类型的公司吗?
  16. [二分查找]()法的原理?
  17. 一面给你讲过[北森]()的一些技术栈吧?
  18. 什么是缓存穿透?怎么避免?
  19. 这些技术是哪里学到的?自己尝试过吗?
  20. 秒杀做的时候主要注意那个点?
  21. ​ 反问:介绍一下技术栈

作者:牛客737673632号
链接:https://www.nowcoder.com/discuss/750286?type=post&order=time&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack
来源:牛客网

一面:(8月31号进行的一面。大概四十多分钟五十分钟吧)

1、 自我介绍

2、 你有没有体现你价值的东西,或者是惑你比较满意的[项目]()

3、 介绍[项目]()

4、 介绍一下服务器实现的流程,i/o模型

5、 Epoll的建立流程,线程池

6、 当你接收到数据的时候,用线程池去处理数据,他们之间的交互是怎么样的

7、 Epoll的工作原理,各个函数的作用,从监听树到就绪[链表]()是由什么触发的呢,是怎么将就绪的事件放到就绪[链表]()的。

8、 MD5加密会出现什么问题,如果我现在对它有一个更高的安全要求,可以怎么做。

9、 中介者模式

10、 map是放在进程里的?当用户量过大的时候会出现什么问题?那你这个问题可以怎么解决?

11、 数组和[链表]()的区别

12、 队列和栈两个数据结构的区别

13、 树的广度优先遍历

14、 [哈希表]()的实现原理、哈希冲突的解决

15、 生成1-1000的数放到数组里,但是是随机的不能重复的不能有序。应该怎么做

16、 TCP的三次握手、四次挥手,连接断开的时候会发送什么、[客户端]()和服务器分别处于什么状态

17、 滑动窗口

18、 在网络通信,发送端发送数据包的时候,是怎么把数据包传送给接收端的,也就是他是怎么找到接收端然后发送包的(具体的过程)

19、 在网路中是如何根据ip和端口号找到对应的主机的。

20、 什么是阻塞i/o、什么是非阻塞i/o。阻塞的什么?

21、 当我们调用sleep的时候,他调用的线程会变成什么状态

22、 当我们访问淘宝的时候,从浏览器到网络到对方的服务器中心,中间的具体实现过程。

23、 做[项目]()的时候遇到的最大的挑战是什么?是怎么解决的?

整体面试感觉挺好的,面试官也没有咄咄逼人的状态。前面的问题主要是基于[项目]()展开的。问题相对来说都还是比较基础的,也会问一些比较底层是实现(比如epoll是怎么把就绪的I/O事件放到就绪[链表]()的),但是不多。

过一段时间整理一下二面。二面的时候都被问懵逼了...

二面(一面完了第二天就打电话约二面了,约的是3号面试,结果面试官没来推到了后面。也是五十分钟左右

一面的时候问了数据库的知识,我说对数据库不是很熟悉。。。结果二面傻眼了

没有自我介绍,上来直接问的

1、 线程间同步互斥有哪些方式

2、 互斥锁有的是能跨进程使用的,有的是不能跨进程使用的,为什么有的能跨进程使用有的不能

3、 条件变量用到了什么场景?用信号量是不是也可以?(可以,那你为什么用条件变量+互斥锁)

4、 [二分查找]()的时间复杂度是多少?哪些数据结构支持[二分查找]()

5、 除了有序的数据结构还有其他的吗?

6、 数据结构的字典,它的查找过程,是怎么根据key值找到value的?

7、 map是线程安全的吗

8、 假设一个数据结构不是线程安全的,我们要调用它进行读操作和写操作,可以选择读加锁和写加锁或者读写都加锁去保证它,那你觉得应该选哪个

9、 当我们读加锁的时候,我们读的时候是不是也会被阻塞?

10、 数据库的ACID,你理解的一致性是什么

11、 隔离级别

12、 隔离级别有哪些实现方式

13、 Vector和list有什么区别?它是怎么根据索引下标去得到对应的值的。(纠正了一个问题vector查找的时候时间复杂度不是o(1),当我们查找的时候还是得遍历,它是根据索引下标去得到)

14、 MD5加密

15、 生产者消费者是干什么的

16、 你为什么要进行10个线程的创建和销毁

17、 你觉得10个10个创建有什么不好的地方

18、 假设一个场景,我去请求mysql或者[redis](),它报了一个socket超时的异常,有可能是哪的问题。

19、 检测网络是否畅通用什么命令

20、 TCP的三次握手过程,每一次[客户端]()和服务器的交互发送了什么

21、 怎么保证缓存的数据和数据库的数据是一致的。

22、 假设我写的时候先写数据库然后再写缓冲区,你觉得这个方法能不能保持一致性?考虑一下它的并发,有多个线程同时执行写操作?多个线程要对数据进行修改呢?

复习:

计算机网络:

加密算法:

散列函数hash

常见的有 MD5、SHA1、SHA256,该类函数特点是函数单向不可逆、对输入非常敏感、输出长度固定,针对数据的任何修改都会改变散列函数的结果,用于防止信息篡改并验证数据的完整性; 在信息传输过程中,散列函数不能单独实现信息防篡改,因为明文传输,中间人可以修改信息之后重新计算信息摘要,因此需要对传输的信息以及信息摘要进行加密;

对称加密

常见的有AES-CBC、DES、3DES、AES-GCM等,相同的密钥可以用于信息的加密和解密,掌握密钥才能获取信息,能够防止信息窃听,通信方式是1对1; 对称加密的优势是信息传输1对1,需要共享相同的密码,密码的安全是保证信息安全的基础,服务器和 N 个客户端通信,需要维持 N 个密码记录,且缺少修改密码的机制;

非对称加密

即常见的 RSA 算法,还包括 ECC、DH 等算法,算法特点是,密钥成对出现,一般称为公钥(公开)和私钥(保密),公钥加密的信息只能私钥解开,私钥加密的信息只能公钥解开。因此掌握公钥的不同客户端之间不能互相解密信息,只能和掌握私钥的服务器进行加密通信,服务器可以实现1对多的通信,客户端也可以用来验证掌握私钥的服务器身份。 非对称加密的特点是信息传输1对多,服务器只需要维持一个私钥就能够和多个客户端进行加密通信,但服务器发出的信息能够被所有的客户端解密,且该算法的计算复杂,加密速度慢。

http相关:

http1.0和1.1:

HTTP1.0和HTTP1.1的一些区别
HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。

主要区别主要体现在:

1.缓存处理:

​ 在HTTP1.0中主要使用header里的 lf-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entitytag,lf-Unmodified-Since, If-Match, lf-None-Match等更多可供选择的缓存头来控制缓存策略。
2.带宽优化及网络连接的使用:

​ HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206 (Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
3.错误通知的管理:

​ 在HTTP1.1中新增了24个错误状态响应码,如409 (Conflict)表示请求的资源与资源的当前状态发生冲突;410 (Gone)表示服务器上的某个资源被永久性的删除。
4.Host头处理:

​ 在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request) .
5.长连接:

​ HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,其中长连接也就是对应在HTTP1.1中的Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
HTTP1.0 和 1.1 现存的一些问题

​ HTTP1.0和HTTP1.1可以称做HTTP1.x,正如上面提到过的,HTTP1.x在传输数据时,每次都需要重新建立连接,无疑增加了大量的延迟时间,特别是在移动端更为突出。
​ HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性。
​ HTTP1.x在使用时,header里携带的内容过大,在—定程度上增加了传输的成本,并且每次请求header基本不怎么变化,尤其在移动端会增加用户流量。
虽然HTTP1.1支持了keep-alive,来弥补多次创建连接产生的延迟,但是keep-alive 使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。同样keep-alive 也无法解决线头阻塞(Head-of-line blocking, HOL)问题。

https:

HTTPS与HTTP的一些区别
HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。
HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的TLS加密传输协议。
HTTP和HTTPS使用的是完全不同的连接方式,用的默认端口也不一样,前者是80,后者是443.
HTTPS的连接很简单,HTTPS协议是由TLS+HTTP协议构建的 可进行加密传输、身份认证的网络协议,比HTTP协议安全。

https"中间人":

为什么需要 CA 认证机构颁发证书?

HTTP 协议被认为不安全是因为传输过程容易被监听者勾线监听、伪造服务器,而 HTTPS 协议主要解决的便是网络传输的安全性问题。

首先我们假设不存在认证机构,任何人都可以制作证书,这带来的安全风险便是经典的 “中间人攻击” 问题。

“中间人攻击”的具体过程如下:

WX20191126-212406@2x.png

过程原理:

  1. 本地请求被劫持(如DNS劫持等),所有请求均发送到中间人的服务器
  2. 中间人服务器返回中间人自己的证书
  3. 客户端创建随机数,通过中间人证书的公钥对随机数加密后传送给中间人,然后凭随机数构造对称加密对传输内容进行加密传输
  4. 中间人因为拥有客户端的随机数,可以通过对称加密算法进行内容解密
  5. 中间人以客户端的请求内容再向正规网站发起请求
  6. 因为中间人与服务器的通信过程是合法的,正规网站通过建立的安全通道返回加密后的数据
  7. 中间人凭借与正规网站建立的对称加密算法对内容进行解密
  8. 中间人通过与客户端建立的对称加密算法对正规内容返回的数据进行加密传输
  9. 客户端通过与中间人建立的对称加密算法对返回结果数据进行解密

由于缺少对证书的验证,所以客户端虽然发起的是 HTTPS 请求,但客户端完全不知道自己的网络已被拦截,传输内容被中间人全部窃取。

http2.0:

HTTP2.0可以说是SPDY的升级版(其实原本也是基于SPDY设计的)。但是,HTTP2.0跟SPDY仍有不同的地方,主要是以下两点:

HTTP2.0消息头的压缩算法采用HPACK算法,而非SPDY采用的DEFLATE算法。
HTTP2.0设计初期支持明文HTTP传输,而SPDY强制使用HTTPS,到后期两者都需要使用HTTPS。

http3.0:

HTTP 3.0而QUIC是基于传输层UDP上的协议,可以定义成:HTTP3.0基于UDP的安全可靠的HTTP2.0协议。

QUIC 协议针对基于TCP和TLS的HTTP2.0协议解决了下面的问题。

1.1 减少了TCP三次握手及TLS握手时间
不管是HTTP1.0/1.1还是HTTPS,HTTP2.0,都使用了TCP进行传输。HTTPS和HTTP2还需要使用TLS协议来进行安全传输。这就出现了两个握手延迟,而基于UDP协议的QUIC,因为UDP本身没有连接的概念,连接建立时只需要一次交互,半个握手的时间。区别如下图:

1.2 多路复用丢包的线头阻塞问题
QUIC保留了HTTP2.0多路复用的特性,在之前的多路复用过程中,同一个TCP连接上有多个stream,假如其中一个stream丢包,在重传前后的stream都会受到影响,而QUIC中一个连接上的多个stream之间没有依赖。所以当发生丢包时,只会影响当前的stream,也就避免了线头阻塞问题。

1.3 优化重传策略
以往的TCP丢包重传策略是:在发送端为每一个封包标记一个编号(sequence number),接收端在收到封包时,就会回传一个带有对应编号的ACK封包给发送端,告知发送端封包已经确实收到。当发送端在超过一定时间之后还没有收到回传的ACK,就会认为封包已经丢失,启动重新传送的机制,复用与原来相同的编号重新发送一次封包,确保在接收端这边没有任何封包漏接。这样的机制就会带来一些问题,假设发送端总共对同一个封包发送了两次(初始+重传),使用的都是同一个sequence number:编号N。之后发送端在拿到编号N封包的回传ACK时,将无法判断这个带有编号N的ACK,是接收端在收到初始封包后回传的ACK。这就会加大后续的重传计算的耗时。QUIC为了避免这个问题,发送端在传送封包时,初始与重传的每一个封包都改用一个新的编号,unique packet number,每一个编号都唯一而且严格递增,这样每次在收到ACK时,就可以依据编号明确的判断这个ACK是来自初始封包或者是重传封包。

1.4 流量控制
通过流量控制可以限制客户端传输资料量的大小,有了流量控制后,接收端就可以只保留相对应大小的接收 buffer ,优化记忆体被占用的空间。但是如果存在一个流量极慢的stream ,光一个stream就有可能估用掉接收端所有的资源。QUIC为了避免这个潜在的HOL Blocking,采用了连线层(connection flow control)和Stream层的(streamflow control)流量控制,限制单一Stream可以占用的最大buffer size。

1.5 连接迁移
TCP连接基于四元组(源IP、源端口、目的IP、目的端口),切换网络时至少会有一个因素发生变化,导致连接发生变化。当连接发生变化时,如果还使用原来的TCP连接,则会导致连接失败,就得等原来的连接超时后重新建立连接,所以我们有时候发现切换到一个新网络时,即使新网络状况良好,但内容还是需要加载很久。如果实现得好,当检测到网络变化时立刻建立新的TCP连接,即使这样,建立新的连接还是需要几百毫秒的时间。QUIC的连接不受四元组的影响,当这四个元素发生变化时,原连接依然维持。QUIC连接不以四元组作为标识,而是使用一个64位的随机数,这个随机数被称为Connection lD,对应每个stream,即使IP或者端口发生变化,只要Connection ID没有变化,那么连接依然可以维持。

大文件传输:

HTTP断点续传(分块传输)(HTTP头格式非常清楚)

简述

断点续传:指的是在上传/下载时,将任务(一个文件或压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。可以节省时间,提高速度。

断点续传的用途

有时用户上传/下载文件需要历时数小时,万一线路中断,不具备断点续传的 HTTP/FTP 服务器或下载软件就只能从头重传,比较好的 HTTP/FTP 服务器或下载软件具有断点续传能力,允许用户从上传/下载断线的地方继续传送,这样大大减少了用户的烦恼。

常见的支持断点续传的上传/下载软件:QQ 旋风、迅雷、快车、电驴、酷6、土豆、优酷、百度视频、新浪视频、腾讯视频、百度云等。

在 Linux/Unix 系统下,常用支持断点续传的 FTP 客户端软件是 lftp。

Range & Content-Range

HTTP1.1 协议(RFC2616)开始支持获取文件的部分内容,这为并行下载以及断点续传提供了技术支持。它通过在 Header 里两个参数实现的,客户端发请求时对应的是 Range ,服务器端响应时对应的是 Content-Range。

Range

用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:

Range:(unit=first byte pos)-[last byte pos]

Range 头部的格式有以下几种情况:

Range: bytes=0-499 表示第 0-499 字节范围的内容
Range: bytes=500-999 表示第 500-999 字节范围的内容
Range: bytes=-500 表示最后 500 字节的内容
Range: bytes=500- 表示从第 500 字节开始到文件结束部分的内容
Range: bytes=0-0,-1 表示第一个和最后一个字节
Range: bytes=500-600,601-999 同时指定几个范围

Content-Range

用于响应头中,在发出带 Range 的请求后,服务器会在 Content-Range 头部返回当前接受的范围和文件总大小。一般格式:

Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]

例如:

Content-Range: bytes 0-499/22400

0-499 是指当前发送的数据的范围,而 22400 则是文件的总大小。

而在响应完成后,返回的响应头内容也不同:

HTTP/1.1 200 Ok(不使用断点续传方式)
HTTP/1.1 206 Partial Content(使用断点续传方式)

增强校验

在实际场景中,会出现一种情况,即在终端发起续传请求时,URL 对应的文件内容在服务器端已经发生变化,此时续传的数据肯定是错误的。如何解决这个问题了?显然此时需要有一个标识文件唯一性的方法。

在 RFC2616 中也有相应的定义,比如实现 Last-Modified 来标识文件的最后修改时间,这样即可判断出续传文件时是否已经发生过改动。同时 FC2616 中还定义有一个 ETag 的头,可以使用 ETag 头来放置文件的唯一标识。

Last-Modified

If-Modified-Since,和 Last-Modified 一样都是用于记录页面最后修改时间的 HTTP 头信息,只是 Last-Modified 是由服务器往客户端发送的 HTTP 头,而 If-Modified-Since 则是由客户端往服务器发送的头,可以看到,再次请求本地存在的 cache 页面时,客户端会通过 If-Modified-Since 头将先前服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则返回 304 告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。

Etag

Etag(Entity Tags)主要为了解决 Last-Modified 无法解决的一些问题。

  1. 一些文件也许会周期性的更改,但是内容并不改变(仅改变修改时间),这时候我们并不希望客户端认为这个文件被修改了,而重新 GET。
  2. 某些文件修改非常频繁,例如:在秒以下的时间内进行修改(1s 内修改了 N 次),If-Modified-Since 能检查到的粒度是 s 级的,这种修改无法判断(或者说 UNIX 记录 MTIME 只能精确到秒)。
  3. 某些服务器不能精确的得到文件的最后修改时间。

为此,HTTP/1.1 引入了 Etag。Etag 仅仅是一个和文件相关的标记,可以是一个版本标记,例如:v1.0.0;或者说 “627-4d648041f6b80” 这么一串看起来很神秘的编码。但是 HTTP/1.1 标准并没有规定 Etag 的内容是什么或者说要怎么实现,唯一规定的是 Etag 需要放在 “” 内。

If-Range

用于判断实体是否发生改变,如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。一般格式:

If-Range: Etag | HTTP-Date

也就是说,If-Range 可以使用 Etag 或者 Last-Modified 返回的值。当没有 ETage 却有 Last-modified 时,可以把 Last-modified 作为 If-Range 字段的值。

例如:

If-Range: “627-4d648041f6b80”
If-Range: Fri, 22 Feb 2013 03:45:02 GMT

If-Range 必须与 Range 配套使用。如果请求报文中没有 Range,那么 If-Range 就会被忽略。如果服务器不支持 If-Range,那么 Range 也会被忽略。

如果请求报文中的 Etag 与服务器目标内容的 Etag 相等,即没有发生变化,那么应答报文的状态码为 206。如果服务器目标内容发生了变化,那么应答报文的状态码为 200。

用于校验的其他 HTTP 头信息:If-Match/If-None-Match、If-Modified-Since/If-Unmodified-Since。

工作原理

Etag 由服务器端生成,客户端通过 If-Range 条件判断请求来验证资源是否修改。请求一个文件的流程如下:

第一次请求:

  1. 客户端发起 HTTP GET 请求一个文件。
  2. 服务器处理请求,返回文件内容以及相应的 Header,其中包括 Etag(例如:627-4d648041f6b80)(假设服务器支持 Etag 生成并已开启了 Etag)状态码为 200。

第二次请求(断点续传):

  1. 客户端发起 HTTP GET 请求一个文件,同时发送 If-Range(该头的内容就是第一次请求时服务器返回的 Etag:627-4d648041f6b80)。
  2. 服务器判断接收到的 Etag 和计算出来的 Etag 是否匹配,如果匹配,那么响应的状态码为 206;否则,状态码为 200。

检测服务器是否支持断点续传

CURL 实现检测:

[root@localhost ~]# curl -i --range 0-9 http://www.baidu.com/img/bdlogo.gifHTTP/1.1 206 Partial ContentDate: Mon, 21 Nov 2016 05:26:29 GMTServer: ApacheP3P: CP=" OTI DSP COR IVA OUR IND COM "Set-Cookie: BAIDUID=0CD0E23B4D4F739954DFEDB92BE6CE03:FG=1; expires=Tue, 21-Nov-17 05:26:29 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1Last-Modified: Fri, 22 Feb 2013 03:45:02 GMTETag: "627-4d648041f6b80"Accept-Ranges: bytesContent-Length: 10Cache-Control: max-age=315360000Expires: Thu, 19 Nov 2026 05:26:29 GMTContent-Range: bytes 0-9/1575Connection: Keep-AliveContent-Type: image/gifGIF89a[root@localhost ~]#

能够找到 Content-Range,则表明服务器支持断点续传。有些服务器还会返回 Accept-Ranges,输出结果 Accept-Ranges: bytes ,说明服务器支持按字节下载。

http://blog.csdn.net/liang19890820/article/details/53215087

Java基础

设计模式:

这里主要讲解简单工厂模式,代理模式,适配器模式,单例模式4中设计模式.

1、简单工厂模式

主要特点是需要在工厂类中做判断,从而创造相应的产品,当增加新产品时,需要修改工厂类。使用简单工厂模式,我们只需要知道具体的产品型号就可以创建一个产品。

缺点:工厂类集中了所有产品类的创建逻辑,如果产品量较大,会使得工厂类变的非常臃肿。

2、代理模式

代理模式:为其它对象提供一种代理以控制这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

优点:

职责清晰。真实的角色只负责实现实际的业务逻辑,不用关心其它非本职责的事务,通过后期的代理完成具体的任务。这样代码会简洁清晰。

代理对象可以在客户端和目标对象之间起到中介的作用,这样就保护了目标对象。

扩展性好。

3、适配器模式

适配器模式可以将一个类的接口转换成客户端希望的另一个接口,使得原来由于接口不兼容而不能在一起工作的那些类可以在一起工作。通俗的讲就是当我们已经有了一些类,而这些类不能满足新的需求,此时就可以考虑是否能将现有的类适配成可以满足新需求的类。适配器类需要继承或依赖已有的类,实现想要的目标接口。

缺点:过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

4、单例模式

单例模式顾名思义,保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。实现单例模式必须注意一下几点:

单例类只能由一个实例化对象。

单例类必须自己提供一个实例化对象。

单例类必须提供一个可以访问唯一实例化对象的接口。

单例模式分为懒汉和饿汉两种实现方式。

JVM中:

说一下堆栈的区别?

物理地址

堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)

栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。

内存分别

堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。

栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。

存放的内容

堆存放的是对象的实例和数组。因此该区更关注的是数据的存储

栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。

PS:

静态变量放在方法区
静态的对象还是放在堆。
程序的可见度

堆对于整个应用程序都是共享、可见的。

栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

队列和栈是什么?有什么区别?

队列和栈都是被用来预存储数据的。

操作的名称不同。队列的插入称为入队,队列的删除称为出队。栈的插入称为进栈,栈的删除称为出栈。
可操作的方式不同。队列是在队尾入队,队头出队,即两边都可操作。而栈的进栈和出栈都是在栈顶进行的,无法对栈底直接进行操作。
操作的方法不同。队列是先进先出(FIFO),即队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(不能从中间插入),每次离开的成员总是队列头上(不允许中途离队)。而栈为后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除。

HotSpot虚拟机对象探秘

对象的创建

说到对象的创建,首先让我们看看 Java 中提供的几种对象创建方式:

Header 解释
使用new关键字 调用了构造函数
使用Class的newInstance方法 调用了构造函数
使用Constructor类的newInstance方法 调用了构造函数
使用clone方法 没有调用构造函数
使用反序列化 没有调用构造函数
下面是对象创建的主要流程:

虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。类加载通过后,接下来分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行方法。

为对象分配内存

类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:

指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。
选择哪种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

处理并发安全问题
对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:

对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);
把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。

对象的访问定位
Java程序需要通过 JVM 栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有 句柄 和 直接指针 两种方式。

指针: 指向对象,代表一个对象在内存中的起始地址。

句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。

句柄访问
Java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,具体构造如下图所示:

优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改。

直接指针
如果使用直接指针访问,引用 中存储的直接就是对象地址,那么Java堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。

优势:速度更快,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。

垃圾回收器:

Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用对交互相应要求不高的场景;

Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;

Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

MYSQL:

MyISAM和InnoDB的区别?

  1. 是否支持行级锁 : MyISAM 只有表级锁,而InnoDB 支持行级锁和表级锁,默认为行级锁。
  2. 是否支持事务和崩溃后的安全恢复MyISAM 注重性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。而InnoDB提供事务支持,具有事务、回滚和崩溃修复能力。
  3. 是否支持外键: MyISAM不支持,而InnoDB支持。
  4. 是否支持MVCCMyISAM不支持,InnoDB支持。应对高并发事务,MVCC比单纯的加锁更高效。
  5. MyISAM不支持聚集索引,InnoDB支持聚集索引。

    • MyISAM引擎主键索引和其他索引区别不大,叶子节点都包含索引值和行指针。
    • innoDB引擎二级索引叶子存储的是索引值和主键值(不是行指针),这样可以减少行移动和数据页分裂时二级索引的维护工作。

    img

MVCC 实现原理?

MVCC(Multiversion concurrency control) 就是同一份数据保留多版本的一种方式,进而实现并发控制。在查询的时候,通过read view和版本链找到对应版本的数据。

作用:提升并发性能。对于高并发场景,MVCC比行级锁更有效、开销更小。

MVCC 实现原理如下:

MVCC 的实现依赖于版本链,版本链是通过表的三个隐藏字段实现。

  • DB_TRX_ID:当前事务id,通过事务id的大小判断事务的时间顺序。
  • DB_ROLL_PRT:回滚指针,指向当前行记录的上一个版本,通过这个指针将数据的多个版本连接在一起构成undo log版本链。
  • DB_ROLL_ID:主键,如果数据表没有主键,InnoDB会自动生成主键。

每条表记录大概是这样的:

img

使用事务更新行记录的时候,就会生成版本链,执行过程如下:

  1. 用排他锁锁住该行;
  2. 将该行原本的值拷贝到 undo log,作为旧版本用于回滚;
  3. 修改当前行的值,生成一个新版本,更新事务id,使回滚指针指向旧版本的记录,这样就形成一条版本链。

下面举个例子方便大家理解。

  1. 初始数据如下,其中DB_ROW_ID和DB_ROLL_PTR为空。

    img

  2. 事务A对该行数据做了修改,效果如下:

    img

  3. 之后事务B也对该行记录做了修改,效果如下:

    img

  4. 此时undo log有两行记录,并且通过回滚指针连在一起。

接下来了解下read view的概念。

read view可以理解成对数据在每个时刻的状态拍成“照片”记录下来。这样获取某时刻的数据时就还是原来的”照片“上的数据,是不会变的。

read view内部维护一个活跃事务链表,表示生成read view的时候还在活跃的事务。这个链表包含在创建read view之前还未提交的事务,不包含创建read view之后提交的事务。

不同隔离级别创建read view的时机不同。

  • read committed:每次执行select都会创建新的read_view,保证能读取到其他事务已经提交的修改。
  • repeatable read:在一个事务范围内,第一次select时更新这个read_view,以后不会再更新,后续所有的select都是复用之前的read_view。这样可以保证事务范围内每次读取的内容都一样,即可重复读。

read view的记录筛选方式

前提DATA_TRX_ID 表示每个数据行的最新的事务ID;up_limit_id表示当前快照中的最先开始的事务;low_limit_id表示当前快照中的最慢开始的事务,即最后一个事务。

img

  1. 如果DATA_TRX_ID < up_limit_id:说明在创建read view时,修改该数据行的事务已提交,该版本的记录可被当前事务读取到。
  2. 如果DATA_TRX_ID >= low_limit_id:说明当前版本的记录的事务是在创建read view之后生成的,该版本的数据行不可以被当前事务访问。此时需要通过版本链找到上一个版本,然后重新判断该版本的记录对当前事务的可见性。
  3. 如果up_limit_id <= DATA_TRX_ID < low_limit_i

    1. 需要在活跃事务链表中查找是否存在ID为DATA_TRX_ID的值的事务。
    2. 如果存在,因为在活跃事务链表中的事务是未提交的,所以该记录是不可见的。此时需要通过版本链找到上一个版本,然后重新判断该版本的可见性。
    3. 如果不存在,说明事务trx_id 已经提交了,这行记录是可见的。

总结:InnoDB 的MVCC是通过 read view 和版本链实现的,版本链保存有历史版本记录,通过read view 判断当前版本的数据是否可见,如果不可见,再从版本链中找到上一个版本,继续进行判断,直到找到一个可见的版本。

快照读和当前读

表记录有两种读取方式。

  • 快照读:读取的是快照版本。普通的SELECT就是快照读。通过MVCC来进行并发控制的,不用加锁。
  • 当前读:读取的是最新版本。UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是当前读。

快照读情况下,InnoDB通过mvcc机制避免了幻读现象。而mvcc机制无法避免当前读情况下出现的幻读现象。因为当前读每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。

下面举个例子说明下:

  1. 首先,user表只有两条记录,具体如下:

    img

  2. 事务a和事务b同时开启事务start transaction
  3. 事务a插入数据然后提交;

    insert into user`(user_name, user_password, user_mail, user_state) values('tyson', 'a', 'a', 0);`

  4. 事务b执行全表的update;

    update user set user_name = `'a'`;

  5. 事务b然后执行查询,查到了事务a中插入的数据。(下图左边是事务b,右边是事务a)

    img

以上就是当前读出现的幻读现象。

那么MySQL如何实现避免幻读?

  • 在快照读情况下,MySQL通过mvcc来避免幻读。
  • 在当前读情况下,MySQL通过next-key来避免幻读(加行锁和间隙锁来实现的)。

next-key包括两部分:行锁和间隙锁。行锁是加在索引上的锁,间隙锁是加在索引之间的。

Serializable隔离级别也可以避免幻读,会锁住整张表,并发性极低,一般不会使用。

bin log/redo log/undo log

MySQL日志主要包括查询日志、慢查询日志、事务日志、错误日志、二进制日志等。其中比较重要的是 bin log(二进制日志)和 redo log(重做日志)和 undo log(回滚日志)。

bin log

二进制日志(bin log)是MySQL数据库级别的文件,记录对MySQL数据库执行修改的所有操作,不会记录select和show语句,主要用于恢复数据库和同步数据库。

redo log

重做日志(redo log)是Innodb引擎级别,用来记录Innodb存储引擎的事务日志,不管事务是否提交都会记录下来,用于数据恢复。当数据库发生故障,InnoDB存储引擎会使用redo log恢复到发生故障前的时刻,以此来保证数据的完整性。将参数innodb_flush_log_at_tx_commit设置为1,那么在执行commit时会将redo log同步写到磁盘。

undo log

除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它保留了记录修改前的内容。通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC

bin log和redo log有什么区别?

  1. bin log会记录所有日志记录,包括innoDB、MyISAM等存储引擎的日志;redo log只记录innoDB自身的事务日志。
  2. bin log只在事务提交前写入到磁盘,一个事务只写一次;而在事务进行过程,会有redo log不断写入磁盘。
  3. binlog 是逻辑日志,记录的是SQL语句的原始逻辑;redo log 是物理日志,记录的是在某个数据页上做了什么修改。

大表数据查询,怎么优化

  1. 优化shema、sql语句+索引;
  2. 第二加缓存,memcached, redis;
  3. 主从复制,读写分离;
  4. 垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
  5. 水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;

【推荐】利用延迟关联或者子查询优化超多分页场景。

说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

正例:先快速定位需要获取的id段,然后再关联:

SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

分库分表

当单表的数据量达到1000W或100G以后,优化索引、添加从库等可能对数据库性能提升效果不明显,此时就要考虑对其进行切分了。切分的目的就在于减少数据库的负担,缩短查询的时间。

数据切分可以分为两种方式:垂直划分和水平划分。

垂直划分

垂直划分数据库是根据业务进行划分,例如购物场景,可以将库中涉及商品、订单、用户的表分别划分出成一个库,通过降低单库的大小来提高性能,但这种方式并没有解决高数据量带来的性能损耗。同样的,分表的情况就是将一个大表根据业务功能拆分成一个个子表,例如商品基本信息和商品描述,商品基本信息一般会展示在商品列表,商品描述在商品详情页,可以将商品基本信息和商品描述拆分成两张表。

img

图片来源于网络

优点:行记录变小,数据页可以存放更多记录,在查询时减少I/O次数。

缺点

  • 主键出现冗余,需要管理冗余列;
  • 会引起表连接JOIN操作,可以通过在业务服务器上进行join来减少数据库压力;
  • 依然存在单表数据量过大的问题。

水平划分

水平划分是根据一定规则,例如时间或id序列值等进行数据的拆分。比如根据年份来拆分不同的数据库。每个数据库结构一致,但是数据得以拆分,从而提升性能。

img

图片来源于网络

优点:单库(表)的数据量得以减少,提高性能;切分出的表结构相同,程序改动较少。

缺点

  • 分片事务一致性难以解决
  • 跨节点join性能差,逻辑复杂
  • 数据分片在扩容时需要迁移

大表怎么优化?某个表有近千万数据,CRUD比较慢,如何优化?分库分表了是怎么做的?分表分库了有什么问题?有用到中间件么?他们的原理知道么?

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:

  1. 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。
  2. 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
  3. 缓存: 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存;

操作一条sql的流程:

查询语句执行流程?

查询语句的执行流程如下:

  1. 权限校验
  2. 、查询缓存、
  3. 分析器、
  4. 优化器、
  5. 权限校验、
  6. 执行器、
  7. 引擎。

举个例子,查询语句如下:

select` `* ``from` `user` `where` `id > 1 ``and` `name` `= ``'大彬'``;
  1. 首先检查权限,没有权限则返回错误;
  2. MySQL以前会查询缓存,缓存命中则直接返回,没有则执行下一步;
  3. 词法分析和语法分析。提取表名、查询条件,检查语法是否有错误;
  4. 两种执行方案,先查 id > 1 还是 name = '大彬',优化器根据自己的优化算法选择执行效率最好的方案;
  5. 校验权限,有权限就调用数据库引擎接口,返回引擎的执行结果。

更新语句执行过程?

更新语句执行流程如下:分析器、权限校验、执行器、引擎、redo log(prepare 状态)、binlog、redo log(commit状态)

举个例子,更新语句如下:

update user set name = 大彬 where id = 1;
  1. 先查询到 id 为1的记录,有缓存会使用缓存。
  2. 拿到查询结果,将 name 更新为 大彬,然后调用引擎接口,写入更新数据,innodb 引擎将数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态。
  3. 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
  4. 更新完成。

为什么记录完 redo log,不直接提交,先进入prepare状态?

假设先写 redo log 直接提交,然后写 binlog,写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 binlog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。

exist和in的区别?

exists 用于对外表记录做筛选。

exists 会遍历外表,将外查询表的每一行,代入内查询进行判断。当 exists 里的条件语句能够返回记录行时,条件就为真,返回外表当前记录。反之如果exists里的条件语句不能返回记录行,条件为假,则外表当前记录被丢弃。

select` `a.* ``from` `A awhere exists(``select` `1 ``from` `B b ``where` `a.id=b.id)

in 是先把后边的语句查出来放到临时表中,然后遍历临时表,将临时表的每一行,代入外查询去查找。

select` `* ``from` `Awhere id ``in``(``select` `id ``from` `B)

子查询的表大的时候,使用exists可以有效减少总的循环次数来提升速度;当外查询的表大的时候,使用IN可以有效减少对外查询表循环遍历来提升速度。

Redis

持久化:

RDB是Redis默认的持久化方式。

按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。

通过配置文件中的save参数来定义快照的周期。

img

优点:

1、只有一个文件 dump.rdb,方便持久化。
2、容灾性好,一个文件可以保存到安全的磁盘。

3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。

使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能

4.相对于数据集大时,比 AOF 的启动效率更高。

缺点:

1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。(尚未来得及保存)

所以这种方式更适合数据要求不严谨的时候)
2、AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。

AOF:持久化

AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。

当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。

img

如何选择合适的持久化方式

当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。

有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB还可以避免AOF程序的bug。

优点:

1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次。
2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))

缺点:

1、AOF 文件比 RDB 文件大,且恢复速度慢。

2、数据集大的时候,比 rdb 启动效率低。

事务:

Redis的事务总是具有ACID中的一致性和隔离性(单线程),其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。

哨兵:

哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
哨兵的核心知识

哨兵至少需要 3 个实例,来保证自己的健壮性。
哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

缓存异常

缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
附加

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(推荐)

就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。
Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

设置热点数据永远不过期。
加互斥锁,互斥锁

缓存降级

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

如何保证缓存与数据库双写时的数据一致性?

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况

串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再删除缓存

Redis常见性能问题和解决方案?

Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。
尽量避免在压力较大的主库上增加从库
Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。

使用Redis做过异步队列吗,是如何实现的

使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

Redis如何实现延时队列

使用sortedset,使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。

Redis回收进程如何工作的?

一个客户端运行了新的命令,添加了新的数据。
Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
一个新的命令被执行,等等。
所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

Redis回收使用的是什么算法?

LRU算法

项目中:

1.整合SpringBoot+MyBatis搭建基本骨架

本文主要讲解mall整合SpringBoot+MyBatis搭建基本骨架,

以商品品牌为例实现基本的CRUD操作及通过PageHelper实现分页查询。

配置yaml

# 配置端口server:  port: 80spring:  # 配置数据源  datasource:    type: com.alibaba.druid.pool.DruidDataSource    driver-class-name: com.mysql.jdbc.Driver    url: jdbc:mysql://localhost:3306/project?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false    username: root    password: root# mybatis-plus相关配置mybatis-plus:  # 配置mapper的扫描,找到所有的mapper.xml映射文件  mapper-locations: classpath*:mapper/*Mapper.xml  # 搜索指定包名  type-aliases-package: com.drj.entity  configuration:    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl#引入swaggerswagger:  enable: true

2.mall整合Swagger-UI实现在线API文档

Swagger-UI

Swagger-UIHTML, Javascript, CSS的一个集合,可以动态地根据注解生成在线API文档。

常用注解

  • @Api:用于修饰Controller类,生成Controller相关文档信息
  • @ApiOperation:用于修饰Controller类中的方法,生成接口方法相关文档信息
  • @ApiParam:用于修饰接口中的参数,生成接口参数相关文档信息
  • @ApiModelProperty:用于修饰实体类的属性,当实体类是请求参数或返回结果时,直接生成相关文档信息
  public Docket createRestApi(){           return new Docket(DocumentationType.SWAGGER_2)                 .apiInfo(apiInfo())                                 .select()                //为当前包下controller生成API文档                       .apis(RequestHandlerSelectors.basePackage("com.macro.mall.tiny.controller")) //为有@Api注解的Controller生成API文档 //                              .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))//为有@ApiOperation注解的方法生成API文档 //                        .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))                .paths(PathSelectors.any())                       .build();     } 

3.整合SpringSecurity和JWT实现认证和授权(一)

本文主要讲解整合SpringSecurityJWT实现后台用户的登录和授权功能,同时改造Swagger-UI的配置使其可以自动记住登录令牌进行发送。

SpringSecurity

SpringSecurity是一个强大的可高度定制的认证和授权框架,对于Spring应用来说它是一套Web安全标准。SpringSecurity注重于为Java应用提供认证和授权功能,像所有的Spring项目一样,它对自定义需求具有强大的扩展性。

JWT

JWT是JSON WEB TOKEN的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。

JWT的组成

  • JWT token的格式:header.payload.signature
  • header中用于存放签名的生成算法

    {"alg": "HS512"}Copy to clipboardErrorCopied
  • payload中用于存放用户名、token的生成时间和过期时间

    {"sub":"admin","created":1489079981393,"exp":1489684781}Copy to clipboardErrorCopied
  • signature为以header和payload生成的签名,一旦header和payload被篡改,验证将失败

    //secret为加密算法的密钥String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),s

    JWT实现认证和授权的原理

    • 用户调用登录接口,登录成功后获取到JWT的token;
    • 之后用户每次调用接口都在http的header中添加一个叫Authorization的头,值为JWT的token;
    • 后台程序通过对Authorization头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。

仅需四步,整合SpringSecurity+JWT实现登录认证!

学习过我的mall项目的应该知道,mall-admin模块是使用SpringSecurity+JWT来实现登录认证的,而mall-portal模块是使用的SpringSecurity基于Session的默认机制来实现登陆认证的。很多小伙伴都找不到mall-portal的登录接口,最近我把这两个模块的登录认证给统一了,都使用SpringSecurity+JWT的形式实现。 主要是通过把登录认证的通用逻辑抽取到了mall-security模块来实现的,下面我们讲讲如何使用mall-security模块来实现登录认证,仅需四步即可。

整合步骤

这里我们以mall-portal改造为例来说说如何实现。
  • 第一步,给需要登录认证的模块添加mall-security依赖:
<dependency>    <groupId>com.macro.mall</groupId>    <artifactId>mall-security</artifactId></dependency>Copy to clipboardErrorCopied
  • 第二步,添加MallSecurityConfig配置类,继承mall-security中的SecurityConfig配置,并且配置一个UserDetailsService接口的实现类,用于获取登录用户详情:
/** * mall-security模块相关配置 * Created by macro on 2019/11/5. */@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled=true)public class MallSecurityConfig extends SecurityConfig {    @Autowired    private UmsMemberService memberService;    @Bean    public UserDetailsService userDetailsService() {        //获取登录用户信息        return username -> memberService.loadUserByUsername(username);    }}Copy to clipboardErrorCopied
  • 第三步,在application.yml中配置下不需要安全保护的资源路径:
secure:  ignored:    urls: #安全路径白名单      - /swagger-ui.html      - /swagger-resources/**      - /swagger/**      - /**/v2/api-docs      - /**/*.js      - /**/*.css      - /**/*.png      - /**/*.ico      - /webjars/springfox-swagger-ui/**      - /druid/**      - /actuator/**      - /sso/**      - /home/**Copy to clipboardErrorCopied
  • 第四步,在UmsMemberController中实现登录和刷新token的接口:
/** * 会员登录注册管理Controller * Created by macro on 2018/8/3. */@Controller@Api(tags = "UmsMemberController", description = "会员登录注册管理")@RequestMapping("/sso")public class UmsMemberController {    @Value("${jwt.tokenHeader}")    private String tokenHeader;    @Value("${jwt.tokenHead}")    private String tokenHead;    @Autowired    private UmsMemberService memberService;    @ApiOperation("会员登录")    @RequestMapping(value = "/login", method = RequestMethod.POST)    @ResponseBody    public CommonResult login(@RequestParam String username,                              @RequestParam String password) {        String token = memberService.login(username, password);        if (token == null) {            return CommonResult.validateFailed("用户名或密码错误");        }        Map<String, String> tokenMap = new HashMap<>();        tokenMap.put("token", token);        tokenMap.put("tokenHead", tokenHead);        return CommonResult.success(tokenMap);    }    @ApiOperation(value = "刷新token")    @RequestMapping(value = "/refreshToken", method = RequestMethod.GET)    @ResponseBody    public CommonResult refreshToken(HttpServletRequest request) {        String token = request.getHeader(tokenHeader);        String refreshToken = memberService.refreshToken(token);        if (refreshToken == null) {            return CommonResult.failed("token已经过期!");        }        Map<String, String> tokenMap = new HashMap<>();        tokenMap.put("token", refreshToken);        tokenMap.put("tokenHead", tokenHead);        return CommonResult.success(tokenMap);    }}Copy to clipboardErrorCopied

实现原理

将SpringSecurity+JWT的代码封装成通用模块后,就可以方便其他需要登录认证的模块来使用,下面我们来看看它是如何实现的,首先我们看下mall-security的目录结构。

目录结构

mall-security├── component|    ├── JwtAuthenticationTokenFilter -- JWT登录授权过滤器|    ├── RestAuthenticationEntryPoint -- 自定义返回结果:未登录或登录过期|    └── RestfulAccessDeniedHandler -- 自定义返回结果:没有权限访问时├── config|    ├── IgnoreUrlsConfig -- 用于配置不需要安全保护的资源路径|    └── SecurityConfig -- SpringSecurity通用配置└── util     └── JwtTokenUtil -- JWT的token处理工具类Copy to clipboardErrorCopied

做了哪些变化

其实我也就添加了两个类,一个IgnoreUrlsConfig,用于从application.yml中获取不需要安全保护的资源路径。一个SecurityConfig提取了一些SpringSecurity的通用配置。
  • IgnoreUrlsConfig中的代码:
/** * 用于配置不需要保护的资源路径 * Created by macro on 2018/11/5. */@Getter@Setter@ConfigurationProperties(prefix = "secure.ignored")public class IgnoreUrlsConfig {    private List<String> urls = new ArrayList<>();}Copy to clipboardErrorCopied
  • SecurityConfig中的代码:
/** * 对SpringSecurity的配置的扩展,支持自定义白名单资源路径和查询用户逻辑 * Created by macro on 2019/11/5. */public class SecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity httpSecurity) throws Exception {        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity                .authorizeRequests();        //不需要保护的资源路径允许访问        for (String url : ignoreUrlsConfig().getUrls()) {            registry.antMatchers(url).permitAll();        }        //允许跨域请求的OPTIONS请求        registry.antMatchers(HttpMethod.OPTIONS)                .permitAll();        // 任何请求需要身份认证        registry.and()                .authorizeRequests()                .anyRequest()                .authenticated()                // 关闭跨站请求防护及不使用session                .and()                .csrf()                .disable()                .sessionManagement()                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)                // 自定义权限拒绝处理类                .and()                .exceptionHandling()                .accessDeniedHandler(restfulAccessDeniedHandler())                .authenticationEntryPoint(restAuthenticationEntryPoint())                // 自定义权限拦截器JWT过滤器                .and()                .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);    }    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.userDetailsService(userDetailsService())                .passwordEncoder(passwordEncoder());    }    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    @Bean    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {        return new JwtAuthenticationTokenFilter();    }    @Bean    @Override    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }    @Bean    public RestfulAccessDeniedHandler restfulAccessDeniedHandler() {        return new RestfulAccessDeniedHandler();    }    @Bean    public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {        return new RestAuthenticationEntryPoint();    }    @Bean    public IgnoreUrlsConfig ignoreUrlsConfig() {        return new IgnoreUrlsConfig();    }    @Bean    public JwtTokenUtil jwtTokenUtil() {        return new JwtTokenUtil();    }}

Shiro步步为营--如何优雅地与JWT集成

img

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
SLS时序存储(MetricStore)双十一总结
随着应用架构的演进和云原生的发展,应用系统变得越来越复杂,为了保持系统稳定,对可观测性提出了更高的要求,SLS为此开发了时序存储(MetricStore),支持高可靠性、可拓展性、支持开源生态等优势,并结合SLS原有的采集、数据加工、分析、可视化、告警等能力,形成统一的解决方案。
256 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
4620 0
键盘事件keydown、keypress、keyup随笔整理总结(摘抄)
原文1:http://www.cnblogs.com/silence516/archive/2013/01/25/2876611.html 原文2:http://www.cnblogs.com/leolai/archive/2012/08/01/2618386.
1197 0
Reflection.Emit的使用场景、工具包及示例总结
最近处理一个业务需要动态的生成一些业务模型和库,使用到了Emit的处理,相关的资料整理一下供参考。 Reflection.Emit目的 使用的场景: 应用中自定义一个自己的语言 运行中动态的创建类型、模块等,同时又需要提高效率(可以动态编译一次,然后就不用再处理了) 延迟绑定对象的使用,在和Office这类的软件时会用到 动态插件系统等 … System.
929 0
Java基础-17总结,登录注册案例,Set集合,HashSet,TreeSet,LinkHashSet
你需要的是什么,直接评论留言。 获取更多资源加微信公众号“Java帮帮” (是公众号,不是微信好友哦) 还有“Java帮帮”今日头条号,技术文章与新闻,每日更新,欢迎阅读 学习交流请加Java帮帮交流QQ群553841695 分享是一种美德,分享更快乐! 1:登录注册案例(理解) 需求:用户登录注册案例。 按
1820 0
5
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载