小语种网站案例,国外app素材网站,成都广告设计公司招聘,县城乡建设局网站今天分享一篇一位同学去字节面试的实习面经#xff0c;技术栈是java#xff0c;投了go后端岗位#xff0c;主要拷打了 redismysql网络系统java算法#xff0c;面试问题主要集中在 mysql、redis、网络这三部门#xff0c;因为面试官是搞 go 的#xff0c;java 只是随便问了…今天分享一篇一位同学去字节面试的实习面经技术栈是java投了go后端岗位主要拷打了 redismysql网络系统java算法面试问题主要集中在 mysql、redis、网络这三部门因为面试官是搞 go 的java 只是随便问了几个题目。
不同厂的面试风格都不同如果 java 同学去面阿里、美团、京东这类的 java 大厂面试的问题大概率是集中在 java 相关的问题比如 java 并发、java 集合、jvm 这三块所以大家可以根据要面试的公司可以重点去准备这家公司倾向问的问题的方向。
Redis 相关
介绍一下redis数据库
Redis 是一种基于内存的数据库对数据的读写操作都是在内存中完成因此读写速度非常快常用于缓存消息队列、分布式锁等场景。
Redis 提供了多种数据类型来支持不同的业务场景比如 String(字符串)、Hash(哈希)、 List (列表)、Set(集合)、Zset(有序集合)、Bitmaps位图、HyperLogLog基数统计、GEO地理信息、Stream流并且对数据类型的操作都是原子性的因为执行命令由单线程负责的不存在并发竞争的问题。
除此之外Redis 还支持事务 、持久化、Lua 脚本、多种集群方案主从复制模式、哨兵模式、切片机群模式、发布/订阅模式内存淘汰机制、过期删除机制等等。
redis为什么更快
官方使用基准测试的结果是单线程的 Redis 吞吐量可以达到 10W/每秒如下图所示 img
之所以 Redis 采用单线程网络 I/O 和执行命令那么快有如下几个原因 Redis 的大部分操作都在内存中完成并且采用了高效的数据结构因此 Redis 瓶颈可能是机器的内存或者网络带宽而并非 CPU既然 CPU 不是瓶颈那么自然就采用单线程的解决方案了 Redis 采用单线程模型可以避免了多线程之间的竞争省去了多线程切换带来的时间和性能上的开销而且也不会导致死锁问题。 Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求IO 多路复用机制是指一个线程处理多个 IO 流就是我们经常听到的 select/epoll 机制。简单来说在 Redis 只运行单线程的情况下该机制允许内核中同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达就会交给 Redis 线程处理这就实现了一个 Redis 线程处理多个 IO 流的效果。
redis 怎么实现持久化的
Redis 的读写操作都是在内存中所以 Redis 性能才会高但是当 Redis 重启后内存中的数据就会丢失那为了保证内存中的数据不会丢失Redis 实现了数据持久化的机制这个机制会把数据存储到磁盘这样在 Redis 重启就能够从磁盘中恢复原有的数据。
Redis 共有两种数据持久化的方式 AOF 日志每执行一条写操作命令就把该命令以追加的方式写入到一个文件里 RDB 快照将某一时刻的内存数据以二进制的方式写入磁盘
redis单线程在多核机器里使用会不会浪费机器资源
虽然 Redis 的主要工作网络 I/O 和执行命令一直是单线程模型但是在 Redis 6.0 版本之后也采用了多个 I/O 线程来处理网络请求这是因为随着网络硬件的性能提升Redis 的性能瓶颈有时会出现在网络 I/O 的处理上。
所以为了提高网络 I/O 的并行度Redis 6.0 对于网络 I/O 采用多线程来处理。但是对于命令的执行Redis 仍然使用单线程来处理。
Redis 官方表示Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上。
redis 执行命令还是单线程那如何利用多核心来提升性能
可以在系统部署多个 redis docker 容器来处理达到充分利用 cpu 多核心的效果
redis缓存穿透、缓存击穿、缓存雪崩是什么怎么解决
缓存雪崩
当大量缓存数据在同一时间过期或者 Redis 故障宕机时如果此时有大量的用户请求都无法在 Redis 中处理于是全部请求都直接访问数据库从而导致数据库的压力增加严重的会造成数据库宕机从而形成一系列连锁反应造成整个系统崩溃。
解决方法 大量数据同时过期 均匀设置过期时间避免将大量的数据设置成同一个过期时间。 互斥锁当业务线程在处理用户请求时如果发现访问的数据不在 Redis 里就加个互斥锁保证同一时间内只有一个请求来构建缓存。未能获取互斥锁的请求等待锁释放后重新读取缓存或者返回空值或者默认值。 双key策略使用两个key一个是主key设置过期时间一个是备key不会设置过期key不一样但是value值是一样。当业务线程访问不到主key的缓存数据时就直接返回备key的缓存数据然后在更新缓存的时候同时更新主key和备key的数据。 后台更新缓存业务线程不再负责更新缓存缓存也不设置有效期而是让缓存“永久有效”并将更新缓存的工作交由后台线程定时更新。 Redis故障宕机 服务熔断或请求限流机制启动服务熔断机制暂停业务应用对缓存服务的访问直接返回错误所以不用再继续访问数据库保证数据库系统的正常运行等到 Redis 恢复正常后再允许业务应用访问缓存服务。服务熔断机制是保护数据库的正常允许但是暂停了业务应用访问缓存服系统全部业务都无法正常工作。也可以启用请求限流机制只将少部分请求发送到数据库进行处理再多的请求就在入口直接拒绝服务。 构建高可靠集群通过主从节点的方式构建 Redis 缓存高可靠集群。如果 Redis 缓存的主节点故障宕机从节点可以切换成为主节点继续提供缓存服务避免了由于 Redis 故障宕机而导致的缓存雪崩问题。
缓存击穿
如果缓存中的某个热点数据过期了此时大量的请求访问了该热点数据就无法从缓存中读取直接访问数据库数据库很容易就被高并发的请求冲垮。
解决方案 互斥锁方案保证同一时间只有一个业务线程更新缓存未能获取互斥锁的请求要么等待锁释放后重新读取缓存要么就返回空值或者默认值。 不给热点数据设置过期时间由后台异步更新缓存或者在热点数据准备要过期前提前通知后台线程更新缓存以及重新设置过期时间。
缓存穿透
当用户访问的数据既不在缓存中也不在数据库中导致请求在访问缓存时发现缓存缺失再去访问数据库时发现数据库中也没有要访问的数据没办法构建缓存数据来服务后续的请求。那么当有大量这样的请求到来时数据库的压力骤增这就是缓存穿透的问题。
解决方案 非法请求的限制当有大量恶意请求访问不存在的数据的时候会发生缓存穿透可以在 API 入口处判断求请求参数是否合理请求参数是否含有非法值、请求字段是否存在如果判断出是恶意请求就直接返回错误避免进一步访问缓存和数据库。 缓存空值或者默认值当线上业务发现缓存穿透的现象时可以针对查询的数据在缓存中设置一个空值或者默认值这样后续请求就可以从缓存中读取到空值或者默认值返回给应用而不会继续查询数据库。 使用布隆过滤器快速判断数据是否存在避免通过查询数据库来判断数据是否存在可以在写入数据库数据时使用布隆过滤器做个标记然后在用户请求到来时业务线程确认缓存失效后可以通过查询布隆过滤器快速判断数据是否存在如果不存在就不用通过查询数据库来判断数据是否存在。
怎么用redis分布式锁
基于 Redis 节点实现分布式锁时对于加锁操作我们需要满足三个条件。 加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作但需要以原子操作的方式完成所以我们使用 SET 命令带上 NX 选项来实现加锁 锁变量需要设置过期时间以免客户端拿到锁后发生异常导致锁一直无法释放所以我们在 SET 命令执行时加上 EX/PX 选项设置其过期时间 锁变量的值需要能区分来自不同客户端的加锁操作以免在释放锁时出现误释放操作所以我们使用 SET 命令设置锁变量值时每个客户端设置的值是一个唯一值用于标识客户端
满足这三个条件的分布式命令如下
SET lock_key unique_value NX PX 10000 lock_key 就是 key 键 unique_value 是客户端生成的唯一的标识区分来自不同客户端的锁操作 NX 代表只在 lock_key 不存在时才对 lock_key 进行设置操作 PX 10000 表示设置 lock_key 的过期时间为 10s这是为了避免客户端发生异常而无法释放锁。
而解锁的过程就是将 lock_key 键删除del lock_key但不能乱删要保证执行操作的客户端就是加锁的客户端。所以解锁的时候我们要先判断锁的 unique_value 是否为加锁客户端是的话才将 lock_key 键删除。
可以看到解锁是有两个操作这时就需要 Lua 脚本来保证解锁的原子性因为 Redis 在执行 Lua 脚本时可以以原子性的方式执行保证了锁释放操作的原子性。
// 释放锁时先比较 unique_value 是否相等避免锁的误释放
if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end这样一来就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。
MySQL 相关
mysql事务特性是什么
**原子性(atomicity)**一个事务必须视为一个不可分割的最小工作单元整个事务中的所有操作要么全部提交成功要么全部失败回滚对于一个事务来说不可能只执行其中的一部分操作这就是事务的原子性。
**一致性(consistency)**数据库总是从一个一致性的状态转换到另一个一致性的状态。
**隔离性(isolation)**一个事务所做的修改在最终提交以前对其他事务是不可见的。
**持久性(durability)**一旦事务提交则其所做的修改就会永久保存到数据库中。此时即使系统崩溃修改的数据也不会丢失。
实现 持久性通过 redo log来保证的 原子性通过 undo log来保证的 隔离性通过 MVCC 或锁机制来保证的 一致性通过持久性原子性隔离性来保证
MySQL的行级锁有哪些
主要有三中记录锁、间隙锁、临建锁。 记录锁锁住的是一条记录记录锁分为排他锁和共享锁。 间隙锁只存在于可重复读隔离级别目的是为了解决可重复读隔离级别下幻读的现象。 临键锁是 Record Lock Gap Lock 的组合锁定一个范围并且锁定记录本身。next-key lock 即能保护该记录又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。
mysql 有哪些索引
可以按照四个角度来分类索引。 按「数据结构」分类Btree索引、Hash索引、Full-text索引。 按「物理存储」分类聚簇索引主键索引、二级索引辅助索引。 按「字段特性」分类主键索引、唯一索引、普通索引、前缀索引。 按「字段个数」分类单列索引、联合索引。
mysql为什么用 b树索引
BTree 是一种多叉树叶子节点才存放数据非叶子节点只存放索引每个节点里的数据是按主键顺序存放的。在叶子节点中包括了所有的索引值信息并且每一个叶子节点都指向下一个叶子节点形成一个链表。BTree 存储千万级的数据只需要 3-4 层高度就可以满足千万级的表查询目标数据最多需要 3-4 次磁盘 I/O。
B树和B树相比 B树所有关键码都存放在叶节点中上层的非叶节点的关键码是其子树中最小关键码的复写 B树叶节点包含了全部关键码及指向相应数据记录存放地址的指针且叶节点本身按关键码从小到大顺序连接 B树在搜索过程中如果查询和内部节点的关键字一致那么搜索过程不停止而是继续向下搜索这个分支
优势 单点查询B 树进行单个索引查询时最快可以在 O(1) 的时间代价内就查到。从平均时间代价来看会比 B 树稍快一些。但是 B 树的查询波动会比较大因为每个节点即存索引又存记录所以有时候访问到了非叶子节点就可以找到索引而有时需要访问到叶子节点才能找到索引。B 树的非叶子节点不存放实际的记录数据仅存放索引数据量相同的情况下B树的非叶子节点可以存放更多的索引查询底层节点的磁盘 I/O次数会更少。 插入和删除效率B 树有大量的冗余节点删除一个节点的时候可以直接从叶子节点中删除甚至可以不动非叶子节点删除非常快。B 树的插入也是一样有冗余节点插入可能存在节点的分裂如果节点饱和但是最多只涉及树的一条路径。B 树没有冗余节点删除节点的时候非常复杂可能涉及复杂的树的变形。 范围查询B 树所有叶子节点间有一个链表进行连接而 B 树没有将所有叶子节点用链表串联起来的结构因此只能通过树的遍历来完成范围查询范围查询效率不如 B 树。B 树的插入和删除效率更高。存在大量范围检索的场景适合使用 B树比如数据库。而对于大量的单个索引查询的场景可以考虑 B 树比如nosql的MongoDB。
为什么索引数据结构不用hash
虽然哈希可以在O1 时间复杂度查询到数据但是哈希表的元素都是无须存放的没办法进行范围查询。
组合索引是什么优点
通过将多个字段组合成一个索引该索引就被称为联合索引。
比如将商品表中的 product_no 和 name 字段组合成联合索引(product_no, name)创建联合索引的方式如下
CREATE INDEX index_product_no_name ON product(product_no, name);联合索引(product_no, name) 的 BTree 示意图如下图中叶子节点之间我画了单向链表但是实际上是双向链表原图我找不到了修改不了偷个懒我不重画了大家脑补成双向链表就行。 联合索引
可以看到联合索引的非叶子节点用两个字段的值作为 BTree 的 key 值。当在联合索引查询数据时先按 product_no 字段比较在 product_no 相同的情况下再按 name 字段比较。
也就是说联合索引查询的 BTree 是先按 product_no 进行排序然后再 product_no 相同的情况再按 name 字段排序。
因此使用联合索引时存在最左匹配原则也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候如果不遵循「最左匹配原则」联合索引会失效这样就无法利用到索引快速查询的特性了。
当查询的数据是能在二级索引的 BTree 的叶子节点里查询到这时就不用再查主键索引查比如下面这条查询语句
select id from product where product_no 0002;这种在二级索引的 BTree 就能查询到结果的过程就叫作「覆盖索引」也就是只需要查一个 BTree 就能找到数据。
网络相关
介绍一些osi七层模型
分为应用层、表示层、会话层、运输层、网络层、链路层、物理层。 应用层(数据)确定进程之间通信的性质以及满足用户需要以及提供网络和用户应用为应用程序提供服务DNSHTTPHTTPSDHCPFTPPOP3(Post Office Protocol)、SMTP(Simple Mail Transfer Protocol)都是这层的协议。 表示层(数据)主要解决用户信息的语法表示问题表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别如数据转换压缩和加密解密。 会话层(数据)会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。比如服务器验证用户登录就是在会话层。 传输层(段)实现网络不同主机上的用户进程之间的数据通信可靠与不可靠的传输传输层的错误检测流量控制拥塞控制。TCP UDP就这层。 网络层(包)本层通过IP寻址来建立两个节点之间的连接为源端的运输层送来的分组选择合适的路由和交换节点正确无误地按照地址传送给目的端的运输层。IP就是这层。 数据链路层(帧)将上层数据封装成帧用MAC地址访问媒介并由错误检测和修正 物理层(比特流)设备之间比特流的传输物理接口电气特性。
tcp和udp哪层
属于传输层
TCP 和 UDP 的区别、TCP 是如何保证可靠传输的
区别 连接性TCP是面向连接的协议通过三次握手建立连接然后进行数据传输传输完成后通过四次挥手关闭连接。而UDP是无连接的协议发送数据之前不需要建立连接也没有连接的关闭过程。 可靠性TCP提供可靠的传输通过序号、确认和重传机制来确保数据的可靠性。它使用滑动窗口和累计确认来保证数据的按序到达并通过超时重传机制来处理丢失的数据包。而UDP不提供可靠性保证它只是简单地将数据包发送出去不保证数据的可靠性和按序到达。 传输方式TCP提供面向字节流的传输将数据划分为多个TCP报文段进行传输保证数据的完整性和顺序性。而UDP提供面向报文的传输每个UDP数据包都是独立的不保证数据的完整性和顺序性。
TCP如何保证可靠传输 序号和确认TCP使用序号和确认机制来保证数据的按序到达。发送方给每个数据包分配一个序号接收方通过确认序号告知发送方已成功接收到数据包。 滑动窗口TCP使用滑动窗口机制来控制发送方发送数据的速率和接收方接收数据的速率。滑动窗口大小可以动态调整以适应网络状况。 超时重传TCP使用超时重传机制来处理丢失的数据包。发送方在发送数据后启动一个定时器如果在超时时间内未收到确认就认为数据包丢失进行重传。 流量控制TCP使用流量控制机制来控制发送方发送数据的速率以避免接收方被淹没。接收方通过窗口大小告知发送方自己的接收能力发送方根据接收方的窗口大小来控制发送速率。 拥塞控制TCP使用拥塞控制机制来避免网络拥塞。通过动态调整发送方的发送速率根据网络的拥塞程度进行拥塞窗口的调整以保持网络的稳定性和公平性。
数据链路层有哪些协议
主要有 arp 协议ARP 是借助 ARP 请求与 ARP 响应两种类型的包确定 MAC 地址的。 ARP 广播 主机会通过广播发送 ARP 请求这个包中包含了想要知道的 MAC 地址的主机 IP 地址。 当同个链路中的所有设备收到 ARP 请求时会去拆开 ARP 请求包里的内容如果 ARP 请求包中的目标 IP 地址与自己的 IP 地址一致那么这个设备就将自己的 MAC 地址塞入 ARP 响应包返回给主机。
http和https有什么区别 HTTP 明文传输数据都是未加密的安全性较差HTTPS(SSLHTTP数据传输过程是加密的安全性较好。 使用 HTTPS 协议需要到 CA 申请证书。 HTTP 页面响应速度比 HTTPS 快主要是因为 HTTP 使用 TCP 三次握手建立连接而 HTTPS除了 TCP 的三个包还要加上SSL握手的消耗。 用的端口也不一样前者是 80后者是 443。 HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议所以要比较 HTTPS 比 HTTP 要更耗费服务器资源。
网络代理正向和反向区别
正向代理的主动方是用户主要用来解决跨域问题还有隐藏用户访问记录的作用。 正向代理 客户端向代理服务器发送请求代理服务器代表客户端向目标服务器请求资源。 客户端需要明确指定代理服务器请求的目标服务器对客户端是不可见的。 代理服务器可以缓存请求的资源提高访问速度。 常见的应用场景是绕过网络限制访问被封锁的网站保护客户端的隐私等。
反向代理的主动方是服务器主要是提供负载均衡、安全防护等作用。 反向代理 客户端向反向代理服务器发送请求反向代理服务器根据请求的内容和规则将请求转发给后端的目标服务器。 客户端不需要明确指定代理服务器请求的目标服务器对客户端是透明的。 反向代理服务器可以根据负载均衡算法将请求分发给多个后端服务器提高系统的性能和可靠性。 常见的应用场景是负载均衡、高可用性、安全过滤、SSL加密等。
操作系统相关
进程开辟虚拟空间有哪些段都用什么用
用户空间分布的情况以 32 位系统为例我画了一张图来表示它们的关系 虚拟内存空间划分
通过这张图你可以看到用户空间内存从低到高分别是 6 种不同的内存段 代码段包括二进制可执行代码 数据段包括已初始化的静态常量和全局变量 BSS 段包括未初始化的静态变量和全局变量 堆段包括动态分配的内存从低地址开始向上增长 文件映射段包括动态库、共享内存等 栈段包括局部变量和函数调用的上下文等。栈的大小是固定的一般是 8 MB。当然系统也提供了参数以便我们自定义大小
上图中的内存布局可以看到代码段下面还有一段内存空间的灰色部分这一块区域是「保留区」之所以要有保留区这是因为在大多数的系统里我们认为比较小数值的地址不是一个合法地址例如我们通常在 C 的代码里会将无效的指针赋值为 NULL。因此这里会出现一段不可访问的内存保留区防止程序因为出现 bug导致读或写了一些小内存地址的数据而使得程序跑飞。
在这 7 个内存段中堆和文件映射段的内存是动态分配的。比如说使用 C 标准库的 malloc() 或者 mmap() 就可以分别在堆和文件映射段动态分配内存。
栈里面放什么信息
主要存放函数的局部变量函数返回后局部变量会自动销毁。
进程上下文切换是什么
进程是由内核管理和调度的所以进程的切换只能发生在内核态。
所以进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源还包括了内核堆栈、寄存器等内核空间的资源。
通常会把交换的信息保存在进程的 PCB当要运行另外一个进程的时候我们需要从这个进程的 PCB 取出上下文然后恢复到 CPU 中这使得这个进程可以继续执行如下图所示 进程上下文切换
对于线程上下文切换的话因为虚拟内存是共享的所以在切换时虚拟内存这些资源就保持不动只需要切换线程的私有数据、寄存器等不共享的数据。所以线程的上下文切换相比进程开销要小很多。
java相关
java和go有什么区别 语法Java是一种面向对象的编程语言使用类和对象来组织代码。它使用强类型的静态类型检查并且有丰富的面向对象特性。而Go是一种并发编程语言它采用了简洁的语法和结构更注重代码的可读性和简洁性。 并发性Go在语言级别提供了并发编程的支持通过goroutine和channel来实现轻量级的并发和通信。而Java需要使用线程和锁等机制来实现并发编程相对来说更加复杂。 性能Go在性能方面表现出色它具有高效的垃圾回收机制和协程调度器适用于高并发和高性能的应用场景。Java也具有良好的性能但相对于Go来说在某些场景下可能会有一些性能损失。 生态系统Java拥有庞大的生态系统和丰富的第三方库和框架支持广泛应用于企业级应用开发。而Go的生态系统相对较小但在一些领域如网络编程和云原生应用有着独特的优势。 开发体验Go注重简洁性和可读性语法简单明了对于开发者来说比较友好。Java的语法相对较复杂需要更多的代码量和工具支持。
volatile关键字作用具体怎么做到可见性
volatile关键字是Java中用来修饰变量的关键字它的作用是保证变量的可见性和禁止指令重排序。
可见性是指当一个线程修改了共享变量的值后其他线程能够立即看到最新的值。在多线程环境下由于线程的工作内存和主内存之间存在缓存不一致的情况普通的变量在一个线程中的修改可能对其他线程是不可见的。
使用volatile关键字修饰的变量当一个线程修改了该变量的值后会立即将最新的值刷新到主内存中并且当其他线程读取该变量时会从主内存中重新获取最新的值而不是使用线程自己的工作内存中的旧值。
volatile关键字通过使用内存屏障的机制来实现可见性。内存屏障会强制刷新缓存并保证读写操作的顺序性从而保证变量的可见性。
具体来说当一个线程对volatile变量进行写操作时会在写操作之后插入写屏障将最新的值刷新到主内存中。当其他线程对该变量进行读操作时会在读操作之前插入读屏障从主内存中获取最新的值。
垃圾回收算法有哪些
垃圾回收算法有四种分别是标记清除法、标记整理法、复制算法、分代收集算法。
标记清除算法
首先利用可达性去遍历内存把存活对象和垃圾对象进行标记。标记结束后统一将所有标记的对象回收掉。这种垃圾回收算法效率较低并且会产生大量不连续的空间碎片。
复制清除算法
半区复制用于新生代垃圾回收。将内存分为大小相同的两块每次使用其中的一块。当这一块的内存使用完后就将还存活的对象复制到另一块去然后再把使用的空间一次清理掉。
特点实现简单运行高效但可用内存缩小为了原来的一半浪费空间。
标记整理算法
根据老年代的特点提出的一种标记算法标记过程仍然与标记-清除算法一样但后续步骤不是直接对可回收对象进行清理而是让所有存活的对象都向一端移动然后直接清理掉边界以外的内存。
分类收集算法
根据各个年代的特点采用最适当的收集算法。
一般将堆分为新生代和老年代。 新生代使用复制算法 老年代使用标记清除算法或者标记整理算法
在新生代中每次垃圾收集时都有大批对象死去只有少量存活使用复制算法比较合适只需要付出少量存活对象的复制成本就可以完成收集。老年代对象存活率高适合使用标记-清理或者标记-整理算法进行垃圾回收。
算法 验证对称二叉树 岛屿数量
面试感受
主要拷打数据库这方面了虽然问题不算难但是疏忽复习有些问题当时没有想出来后面还得加强巩固一下数据库的内容算法做了两题只做出来一道另外一题leetcode也做过但是太紧张没想出来算法还是得多练练。