每天一套面试题Day2-华为高频

题目来自牛客

1.String、StringBuffer、Stringbuilder有什么区别?

StringBuffer。所有公共方法都有synchronized关键字修饰 由于同步机制,性能比StringBuilder稍慢。 StringBuilder的方法与StringBuffer完全一致,只是没有synchronized修饰。单线程用StringBuilder,多线程用StringBuffer String不可变,每次修改生成新对象;StringBuffer和StringBuilder可变。

// 不可变对象
String str = "Hello";
str = str + " World"; // 实际上创建了新的String对象

StringBuffer线程安全但性能较低,StringBuilder非线程安全但效率更高。单线程用StringBuilder,多线程用StringBuffer。

2.线程的创建方式

2.1 继承Thread

定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法 创建MyThread类的对象 调用线程对象的start()方法启动线程(启动后还是执行run方法的) 注意:启动线程必须是调用start方法,不是调用run方法。 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。 只有调用start方法才是启动一个新的线程执行。

public class ThreadDemo1 {
    // main方法本身是由一条主线程负责推荐执行的。
    public static void main(String[] args) {
        // 目标:认识多线程,掌握创建线程的方式一:继承Thread类来实现
        // 4、创建线程类的对象:代表线程。
        Thread t1 = new MyThread();
        // 5、调用start方法,启动线程。还是调用run方法执行的
        t1.start(); // 启动线程,让线程执行run方法

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出:" + i);
        }
    }
}

// 1、定义一个子类继承Thread类,成为一个线程类。
class MyThread extends Thread {
    // 2、重写Thread类的run方法
    @Override
    public void run() {
        // 3、在run方法中编写线程的任务代码(线程要干的活儿)
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程输出:" + i);
        }
    }
}

main也是一个线程,所以现在其实已经有了两条线程,是多线程。 出现结果:交替出现主线程和子线程的结果。

2.2实现Runnable接口喂给Thread构造器

定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法 创建MyRunnable任务对象 把MyRunnable任务对象交给Thread处理。

调用线程对象的start()方法启动线程。


public class ThreadDemo2 {
    public static void main(String[] args) {
        // 目标:掌握多线程的创建方式二:实现Runnable接口来创建。
        // 3、创建线程任务类的对象代表一个线程任务。
        Runnable r = new MyRunnable();
        // 4、把线程任务对象交给一个线程对象来处理
        Thread t1 = new Thread(r); // public Thread(Runnable r)
//        Thread t1 = new Thread(r, "1号子线程"); // public Thread(Runnable r,String name)
        // 5、启动线程
        t1.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出:" + i);
        }
    }
}

// 1、定义一个线程任务类实现Runnable接口
class MyRunnable implements Runnable {
    // 2、重写run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程输出:" + i);
        }
    }
}

简化写法用匿名内部类(把接口的那个实现类用匿名内部类的写法)

public class ThreadDemo2_2 {
    public static void main(String[] args) {
        // 目标:掌握多线程的创建方式二:使用Runnable接口的匿名内部类来创建
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("子线程1输出:" + i);
                }
            }
        };
        Thread t1 = new Thread(r); // public Thread(Runnable r)
        t1.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("子线程2输出:" + i);
                }
            }
        }).start();

        new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    System.out.println("子线程3输出:" + i);
                }
        }).start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出:" + i);
        }
    }
}

2.3 利用Callable接口,FutureTask (本质同二,多了拿结果功能)

其实本质上还是把Runnable喂给thread,只是子类而已。

public class ThreadDemo3 {
    public static void main(String[] args) {
        // 目标:掌握多线程的创建方式三:实现Callable接口,方式三的优势:可以获取线程执行完毕后的结果的。
        // 3、创建一个Callable接口的实现类对象。
        Callable<String> c1 = new MyCallable(100);
        // 4、把Callable对象封装成一个真正的线程任务对象FutureTask对象。
        /**
         * 未来任务对象的作用?
         *    a、本质是一个Runnable线程任务对象,可以交给Thread线程对象处理。
         *    b、可以获取线程执行完毕后的结果。
         */
        FutureTask<String> f1 = new FutureTask<>(c1); // public FutureTask(Callable<V> callable)
        // 5、把FutureTask对象作为参数传递给Thread线程对象。
        Thread t1 = new Thread(f1);
        // 6、启动线程。
        t1.start();

        Callable<String> c2 = new MyCallable(50);
        FutureTask<String> f2 = new FutureTask<>(c2); // public FutureTask(Callable<V> callable)
        Thread t2 = new Thread(f2);
        t2.start();

        // 获取线程执行完毕后返回的结果
        try {
            // 如果主线程发现第一个线程还没有执行完毕,会让出CPU,等第一个线程执行完毕后,才会往下执行!
            System.out.println(f1.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            // 如果主线程发现第二个线程还没有执行完毕,会让出CPU,等第一个线程执行完毕后,才会往下执行!
            System.out.println(f2.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 1、定义一个实现类实现Callable接口
class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }
    // 2、实现call方法,定义线程执行体
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "子线程计算1-" + n + "的和是:"  + sum;
    }
}

2.4 用线程池

3如何创建线程池(ExecutorService),线程池的参数

3.1 用ThreadPoolExecutor(更推荐)

alt alt

3.2 用Executors线程池的工具类调用方法返回

alt

4.HashMap底层原理和扩容机制

HashMap底层采用数组 + 链表(JDK7) → 数组 + 链表/红黑树(JDK8 引入,当链表长度 ≥8 且数组长度 ≥64 时,链表转换为红黑树),通过二次哈希,哈希算法确定元素存储位置。默认初始容量16,负载因子0.75,当元素数量超过(容量×负载因子)时触发扩容。扩容时创建双倍容量新数组,通过高位运算重新计算节点位置(JDK8优化为无需重新计算而是与原数组大小相与,因为是原来的两倍,如果为0不动,如果不为0,那么就放在原序号+原数组大小位置上),原数据通过尾插法(避免JDK7以及之前多线程的死循环)迁移到新数组。链表长度超过8且数组长度≥64时会转为红黑树,提升查询效率。

5.ArrayList和LinkedList的区别

ArrayList底层是数组实现的,数组是一组连续的内存单元,读取快(使用索引),插入删除慢 LinkedList底层基于双向链表,增删相对数组快。对首元素,尾元素(双向链表)进行增删改查的速度极快。

6.JVM内存模型

alt

7.AOP

AOP(面向切面编程)的核心思想是解耦和分离关注点(一个程序分解为不同的部分,每个部分解决一个特定的问题(即一个关注点)。 AOP的核心概念:

连接点:JoinPoint,可以被AOP控制的方法。

通知:Advice,定义了在何时执行什么共性功能的代码块 alt

切入点:PointCut,匹配连接点的条件

切面:Aspect,封装横切逻辑的模块,比如日志切面、事务切面

AOP的典型应用场景:日志记录。事务管理(@Transactional)。

Spring AOP通过动态代理实现,分为 JDK动态代理(目标类实现了接口,代理类实现与目标类相同的接口),CGLIB动态代理(目标类未实现接口, Code Generation Library,代码生成库,生成目标类的子类作为代理类。)

8.Redis的数据类型

alt

Bitmap(位图)类型非常适合二值状态统计的场景,很省空间。比如签到统计,判断用户登陆态。 HyperLogLog 提供不精确的去重计数。,在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。传统统计UV的问题:需要记录每个用户的ID,用户量巨大时内存消耗大。UV(Unique Visitor)即独立访客数,是指一段时间内访问网站的不同用户的数量

9.Java中的深拷贝,浅拷贝和引用拷贝

引用拷贝:复制对象引用地址,新旧变量指向同一对象。 浅拷贝:创建新对象,复制基本类型值,引用类型仍指向原对象。 深拷贝:完全复制对象及关联的所有子对象,新旧对象完全独立。

实现方式:浅拷贝常用clone()方法(需重写),深拷贝需递归复制或序列化实现。 核心区别:深拷贝隔离数据修改,浅拷贝和引用拷贝存在数据关联性。

变量 person   ───┐
                 │
变量 refCopy ───┼──→ { name: 'Alice', address: ● } ─→ { city: 'Beijing', ... }
                 │
变量 shallowCopy ───→ { name: 'Alice', address: ● } ─┘
                    (这是新对象,但address指向同一地址)
class Person implements Cloneable {
    String name;        // 基本类型
    Address address;    // 引用类型
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // 关键:调用父类(就是Object)clone方法
    }
}

class Address {
    String city;
}
public static void main(String[] args) throws Exception {
    // 创建原对象
    Person original = new Person();
    original.name = "张三";
    original.address = new Address();
    original.address.city = "北京";
    
    // 浅拷贝
    Person copy = (Person) original.clone();
    
    // 测试1:修改基本类型 - 独立
    copy.name = "李四";
    System.out.println(original.name); // 输出:张三(原对象不变)
    
    // 测试2:修改引用类型 - 共享
    copy.address.city = "上海";
    System.out.println(original.address.city); // 输出:上海(原对象被影响!)
}

10.TCP的三次握手

三次 = 双向确认 + 合并包 SYN同步序列号这个动作,实际上是告诉对方自己的序列化从哪里开始。为1代表这个包是一个连接建立请求。seq里面是自己的的序列化开始序号。 ACK=1 就是告诉对方:我包里的确认号ack是有效的,请按这个号来确认。

客户端 → 服务器: SYN=1, seq=x (客户端自己的初始序列号)
服务器 → 客户端: SYN=1, ACK=1, seq=y (服务器的初始序列号), ack=x+1
客户端 → 服务器: ACK=1, seq=x+1, ack=y+1

建立连接的过程其实就是确认双方都能收能发,并且知道对方的初始序列号的过程。中间的一次担当了两个功能而已,让客户端知道服务器能收也能发。

11.什么是进程和线程?进程和线程的区别?

进程是操作系统中资源分配(内存资源,I/O资源等)的基本单位,线程是CPU调度的基本单位。进程共享进程的资源,但是有自己独立的栈空间。 线程切换的开销相对于进程来说小一些。

12.SQL常见调优办法

来自牛客,MYSQL八股待系统学

SQL 调优常见方法包括索引优化、查询重构、使用缓存、减少锁定和优化数据库配置等。

13.事务隔离级别

来自牛客,MYSQL八股待系统学

MySQL 提供四种事务隔离级别:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read,MySQL 默认) 和 串行化(Serializable),它们从低到高依次增强数据一致性,但并发性能递减。

#面试真题#
全部评论

相关推荐

楼主是大三鼠鼠,没有绩点没有科研,只有一段小厂实习。所以打算自己找一段大厂实习丰富一下简历。从3.22开始刷力扣准备面试,到四月底刷了80%左右吧。截止到5.8还没有大厂正式offer。先贴一下总结:面试:四月份:腾讯pcg-应用效能技术方向&nbsp;二面挂腾讯ieg-后台采购部&nbsp;二面挂字节火山引擎-后台开发&nbsp;二面挂某小厂&nbsp;二面过&nbsp;骑驴找马中五月份:华为ict-计算产品线&nbsp;二面过&nbsp;已入池笔试:阿里机试a0.5道&nbsp;挂pdd机试a0道&nbsp;挂华为机试-ai岗&nbsp;300分题a了90%&nbsp;过(感谢牛客网的免费刷题功能哈哈哈哈)手撕:1.&nbsp;第一次面腾讯-一面:不是力扣题,是面试官从题库里抽的。有多个请求在不同的时间点到达,请使用滑动窗口的方法做流量控制。如果达到上限,返回false,否则加入队列,返回true。我当时写的其实算是滑动窗口,不过可能面试官没太理解,我也不敢反驳。所以可能算没做出来。。。2.&nbsp;第一次面腾讯-二面:力扣129:求根节点到叶子结点的数字之和深度优先搜索+回溯,当时回溯没有写明白,因此没做出来。后面想明白了,最简单的方法就是递归实现深搜,然后在递归的参数里写一个当前的状态,每次调用递归就不用手动删除,也好思考。3.&nbsp;第二次面腾讯一面:给了两个题,第一道是实现链表操作,插入删除什么的直接写就行。第二道是力扣3:最长无重复字符子串,用的是滑动窗口+哈希表解决。两个题写了40多分钟,还算简单,但是我调试输出花时间。4.&nbsp;第二次面腾讯二面:没有手撕,可能是因为一面撕了两道。5.&nbsp;字节一面:力扣200:岛屿数量。广度优先搜索,写完面试官问我是不是可以优化,每次递归的时候只搜索下、右,毕竟反正是要按顺序遍历完整个地图的。我说可以,结果他说不行,因为可能出现U型岛屿。。。被挖坑了。6.&nbsp;字节二面:力扣103:二叉树的锯齿形层序遍历。实际上就是层序遍历:维护一个队列,每层左至右取出,然后左右子节点依次入队,就可以得到层序节点数组。把每一层%2判断奇偶决定顺序即可。7.&nbsp;华为一面:力扣17:电话号码的字母组合。属于回溯知识点,用字典映射+递归可以很快做出。总结:手撕运气比较好,基本上都是力扣偏简单的中等题,难度不大。第一次面试是pcg的线下面(因为是学校招聘会投的简历),面试官拷打了我1h,问了很多简历上从没想过的细节问题,虽然让我汗流浃背,但是收获真的很大,可惜当时忘了加微信。。。(当时以为加面试官微信可能涉及私联之类的,无语了)在那以后,简历上的问题基本上我都能答的差不多了。体感上八股也并不多,不过是因为总是给我捞到开发岗,问一些消息/缓存中间件的细节,而鼠鼠平时学的算法内容比较多,答不上来,所以全都是二面挂掉了。。。感谢华子面试官,终于问我算法相关了(训练显存分布、推理优化之类的)还有就是大家一定要去学一下ai&nbsp;agent的框架和比较火热的agent的架构,openclaw/hermes,rpc/react框架什么的,面试高频以及&nbsp;有没有华为同部门/岗位的朋友&nbsp;欢迎交流呀~一起泡池子哈哈哈哈哈
我的求职总结
点赞 评论 收藏
分享
05-17 23:14
已编辑
东南大学 算法工程师
查看13道真题和解析
点赞 评论 收藏
分享
评论
2
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务