前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >华为 OD,婉拒了。

华为 OD,婉拒了。

作者头像
沉默王二
发布2024-03-13 15:02:20
900
发布2024-03-13 15:02:20
举报
文章被收录于专栏:沉默王二沉默王二

大家好,我是二哥呀。

之前有球友提问,说拿到了西安华为OD 的 offer,涨幅也不错,给了 15k,方向是网络维护/软件开发/运维都沾点边,问我要不要去。

他是西安那边的,在深圳工作,我是建议他去,不过后来他和原公司的 leader 交流了以后,给涨薪了 20%,于是就决定先不去华为 OD 了。

那其实对于学历 OK(双非一本是铁能进),有 gap 期,或者 debuff 比较满的,或者对外包不是特别抗拒的小伙伴,真的可以去 OD 过渡一下。目前也有球友过了华为 OD 的综测,正在纠结选部门。

华为 OD 面经

如果有小伙伴对华为 OD 感兴趣的话,我在《Java 面试指南》中也收录了一些华为 OD 的面经,可以作为参考。

这次我们就以同学 3 为例,来看看如果遇到这些面试题的话,该如何回答?

主要的答案依据来自星球嘉宾三分恶的面渣逆袭,刚好趁接下来这段时间对面渣逆袭的内容进行升级和迭代。

  • 在线版面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html
  • PDF 版面渣逆袭:https://t.zsxq.com/04FuZrRVf

先来看技术一面的题目大纲:

  • 讲一讲 Redis热key和大key
  • 讲一讲 Java 中线程的通信方式
  • 讲一讲 Spring Boot 的特性
  • 讲一讲 JVM 垃圾回收
  • 讲一讲责任链模式
  • 讲一讲 HashMap 是否安全

01、Redis热key和大key

所谓的热 key,就是指在很短时间内被频繁访问的键。

比如,热门新闻或热门商品,这类 key 通常会有大流量的访问,对存储这类信息的 Redis 来说,是不小的压力。

某天某流量明星突然爆出一个大瓜,微博突然就崩了,这就是热 key 的压力。

再比如说 Redis 是集群部署,热 key 可能会造成整体流量的不均衡(网络带宽、CPU 和内存资源),个别节点出现 OPS 过大的情况,极端情况下热点 key 甚至会超过 Redis 本身能够承受的 OPS。

OPS(Operations Per Second)是 Redis 的一个重要指标,表示 Redis 每秒钟能够处理的命令数。

通常以 Key 被请求的频率来判定,比如:

  • QPS 集中在特定的 Key:总的 QPS(每秒查询率)为 10000,其中一个 Key 的 QPS 飙到了 8000。
  • 带宽使用率集中在特定的 Key:一个拥有上千成员且总大小为 1M 的哈希 Key,每秒发送大量的 HGETALL 请求。
  • CPU 使用率集中在特定的 Key:一个拥有数万个成员的 ZSET Key,每秒发送大量的 ZRANGE 请求。

  • HGETALL 命令用于返回哈希表中,所有的字段和值。
  • ZRANGE 命令用于返回有序集中,指定区间内的成员。

大 key 指的是存储了大量数据的键,比如:

  • 单个简单的 key 存储的 value 很大,size 超过 10KB
  • hash,set,zset,list 中存储过多的元素(以万为单位)

推荐阅读:阿里:发现并处理Redis的大Key和热Key

02、Java 中线程的通信方式

Java 中线程之间的通信主要是为了解决线程之间如何协作运行的问题。Java 提供了多种线程通信的方式,使得线程可以在合适的时间和地点进行同步。

三分恶面渣逆袭:线程间通信方式

我这里就直说一下:等待/通知机制吧。

一个线程调用共享对象的 wait() 方法时,它会进入该对象的等待池,并释放已经持有的该对象的锁,进入等待状态,直到其他线程调用相同对象的 notify()notifyAll() 方法。

一个线程调用共享对象的 notify() 方法时,它会唤醒在该对象等待池中等待的一个线程,使其进入锁池,等待获取锁。

Condition 也提供了类似的方法,await() 负责等待、signal()signalAll() 负责通知。

通常与锁(特别是 ReentrantLock)一起使用,为线程提供了一种等待某个条件成真的机制,并允许其他线程在该条件变化时通知等待线程。更灵活、更强大。

那其实除了上面提到的这些,还有很多通信工具类 CountDownLatch、CyclicBarrier、Semaphore 等并发工具类。

03、Spring Boot 的特性

Spring Boot 是一个开源的、用于简化 Spring 应用初始化和开发过程的框架。提供了一套默认配置,约定优于配置,来帮助我们快速搭建 Spring 项目骨架,极大地提高了我们的生产效率,再也不用为 Spring 的繁琐配置而烦恼了。

以前的 Spring 开发需要配置大量的 xml 文件,并且需要引入大量的第三方 jar 包,还需要手动放到 classpath 下。

三分恶面渣逆袭:SpringBoot图标

Spring Boot 的优点非常多,比如说:

  1. 通过 Intellij IDEA 或者官方的 Spring Initializr 就可以快速创建新项目,只需要选择需要的依赖就可以五分钟内搭建一个项目骨架。
  2. Spring Boot 内嵌了 Tomcat、Jetty、Undertow 等容器,不需要在服务器上部署 WAR 包了,直接运行 jar 包就可以启动项目,超级方便。
  3. Spring Boot 无需再像以前一样在 web.xml、applicationContext.xml 等配置文件里配置大量的内容,大部分初始工作 Spring Boot 都帮我们做好了。例如,如果项目中添加了 spring-boot-starter-web,Spring Boot 会自动配置 Tomcat 和 Spring MVC。
  4. Spring Boot 允许我们通过 yaml 来管理应用的配置,比传统的 properties 文件更加简洁。
  5. Spring Boot 提供了一系列的 Starter,可以快速集成常用的框架,例如 Spring Data JPA、Spring Security、MyBatis 等。
  6. Spring Boot 提供了一系列的 Actuator,可以帮助我们监控和管理应用,比如健康检查、审计、统计等。
  7. 配合 Spring Cloud 可以快速构建微服务架构。

04、JVM 垃圾回收

垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存爆掉。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。

JVM 在做垃圾回收之前,需要先搞清楚什么是垃圾,什么不是垃圾,那么就需要一种垃圾判断算法,通常有引用技术算法、可达性分析算法。

  • 引用计数算法是通过在对象头中分配一个空间来保存该对象被引用的次数。
  • 可达性分析算法的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,然后向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 之间没有任何引用相连时,即从 GC Roots 到该对象节点不可达,则证明该对象是需要垃圾收集的。

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是进行垃圾回收,如何高效地进行垃圾回收呢?

①、标记清除算法,分为 2 部分,先把内存区域中的这些对象进行标记,哪些属于可回收的标记出来,然后把这些垃圾拎出来清理掉。

图片来源于小牛肉

②、复制算法,在标记清除算法上演化而来的,用于解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。

图片来源于小牛肉

标记整理算法,标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。

图片来源于小牛肉

分代收集算法,严格来说并不是一种思想或理论,而是融合上述 3 种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记清理或者标记整理算法来进行回收。

JVM 提供了多种垃圾回收器,不同的垃圾回收器适用于不同的场景和需求,包括 CMS GC、G1 GC、ZGC 等。

CMS 是第一个关注 GC 停顿时间(STW 的时间)的垃圾收集器,JDK 1.5 时引入,JDK9 被标记弃用,JDK14 被移除。

G1(Garbage-First Garbage Collector)在 JDK 1.7 时引入,在 JDK 9 时取代 CMS 成为了默认的垃圾收集器。

图片来源于有梦想的肥宅

ZGC 是 JDK11 推出的一款低延迟垃圾收集器,适用于大内存低延迟服务的内存管理和回收,SPEC jbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于 G1 和 CMS。

推荐阅读:深入理解 JVM 的垃圾收集器:CMS、G1、ZGC

05、责任链模式

推荐阅读:责任链模式

责任链模式是一种行为设计模式,它使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。

请求会沿着一条链传递,直到有一个对象处理它为止。这种模式常用于处理不同类型的请求以及在不确定具体接收者的情况下将请求传递给多个对象中的一个。

图片来源于天未

假设有一个日志系统,根据日志的严重性级别(错误、警告、信息)将日志消息发送给不同的处理器处理。

代码语言:javascript
复制
abstract class Logger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;

    protected int level;

    // 责任链中的下一个元素
    protected Logger nextLogger;

    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }

    public void logMessage(int level, String message) {
        if (this.level <= level) {
            write(message);
        }
        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }

    abstract protected void write(String message);
}

class ConsoleLogger extends Logger {
    public ConsoleLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("Standard Console::Logger: " + message);
    }
}

class ErrorLogger extends Logger {
    public ErrorLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("Error Console::Logger: " + message);
    }
}

class FileLogger extends Logger {
    public FileLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("File::Logger: " + message);
    }
}

public class ChainPatternDemo {
    private static Logger getChainOfLoggers() {
        Logger errorLogger = new ErrorLogger(Logger.ERROR);
        Logger fileLogger = new FileLogger(Logger.DEBUG);
        Logger consoleLogger = new ConsoleLogger(Logger.INFO);

        errorLogger.setNextLogger(fileLogger);
        fileLogger.setNextLogger(consoleLogger);

        return errorLogger;
    }

    public static void main(String[] args) {
        Logger loggerChain = getChainOfLoggers();

        loggerChain.logMessage(Logger.INFO, "INFO 级别");
        loggerChain.logMessage(Logger.DEBUG, " Debug 级别");
        loggerChain.logMessage(Logger.ERROR, "Error 级别");
    }
}

在这个示例中,创建了一个日志处理链。不同级别的日志将被相应级别的处理器处理。责任链模式让日志系统的扩展和维护变得更加灵活。

输出结果:

代码语言:javascript
复制
Standard Console::Logger: INFO 级别
File::Logger:  Debug 级别
Standard Console::Logger:  Debug 级别
Error Console::Logger: Error 级别
File::Logger: Error 级别
Standard Console::Logger: Error 级别

06、HashMap 是否安全

推荐阅读:HashMap详解

HashMap 不是线程安全的,如果需要线程安全,需要使用ConcurrentHashMap,HashMap 之所以不是线程安全的,主要有以下几个问题:

①、多线程下扩容会死循环。JDK1.7 中的 HashMap 使用的是头插法插入元素,在多线程的环境下,扩容的时候就有可能导致出现环形链表,造成死循环。

不过,JDK 8 时已经修复了这个问题,扩容时会保持链表原来的顺序。

②、多线程的 put 可能会导致元素的丢失。因为计算出来的位置可能会被其他线程的 put 覆盖,很好理解。本来哈希冲突是应该用链表的,但多线程时由于没有加锁,相同位置的元素可能就被干掉了。

③、put 和 get 并发时,可能导致 get 为 null。线程 1 执行 put 时,因为元素个数超出阈值而导致出现扩容,线程 2 此时执行 get,就有可能出现这个问题,因为线程 1 执行完 table = newTab 之后,线程 2 中的 table 此时也发生了变化,此时去 get 的时候当然会 get 到 null 了,因为元素还没有转移。

代码语言:javascript
复制
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        // 超过最大值就不再扩充了,就只好随你碰撞去吧
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 没超过最大值,就扩充为原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 计算新的resize上限
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 沉默王二 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 华为 OD 面经
    • 01、Redis热key和大key
      • 02、Java 中线程的通信方式
        • 03、Spring Boot 的特性
          • 04、JVM 垃圾回收
            • 05、责任链模式
              • 06、HashMap 是否安全
              相关产品与服务
              云数据库 Redis
              腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档