记录下来
还没写完,慢慢更1.多线程交替打印:打印内容为ABC循环或者交替打印一段话import java.util.concurrent.Semaphore;public class ThreadExample {    public static Semaphore semaphore1 = new Semaphore(1);    public static Semaphore semaphore2 = new Semaphore(0);    public static Semaphore semaphore3 = new Semaphore(0);    public static void main(String[] args) {        Thread threadA = new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    try {                        semaphore1.acquire();                        System.out.println("A");                        semaphore2.release();                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                }            }        });        Thread threadB = new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    try {                        semaphore2.acquire();                        System.out.println("B");                        semaphore3.release();                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                }            }        });        Thread threadC = new Thread(new Runnable() {            @Override            public void run() {                while (true) {                    try {                        semaphore3.acquire();                        System.out.println("C");                        semaphore1.release();                    } catch (InterruptedException e) {                        throw new RuntimeException(e);                    }                }            }        });        threadA.start();        threadB.start();        threadC.start();    }}2. 多线程场景题:有5个人,在那赛跑,请你设计一个多线程的裁判程序给出他们赛跑的结果顺序,5个人的速度随机处理package org.example.countdownlatch;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.CountDownLatch;import java.util.concurrent.CyclicBarrier;/** * 5个线程模拟赛马,所有马就绪后才能开跑,所有马到达终点后裁判宣布赛马成绩 * CountDownLatch和CyclicBarrier */public class HorseRace {    // 用于存储赛马成绩    private static List<String> results = Collections.synchronizedList(new ArrayList<>());    // 用于控制所有马同时开跑    private static CountDownLatch startLatch = new CountDownLatch(1);    // 用于确保所有马到达终点后再宣布成绩    private static CyclicBarrier finishBarrier = new CyclicBarrier(5, new Runnable() {        @Override        public void run() {            // 裁判宣布成绩            System.out.println("Race finished! Announcing results:");            for (String result : results) {                System.out.println(result);            }        }    });    public static void main(String[] args) {        for (int i = 1; i <= 5; i++) {            new Thread(new Horse("Horse " + i)).start();        }        // 所有马就绪后开跑        try {            System.out.println("All horses ready. Race starts now!");            startLatch.countDown(); // 马开跑        } catch (Exception e) {            e.printStackTrace();        }    }    static class Horse implements Runnable {        private String name;        public Horse(String name) {            this.name = name;        }        @Override        public void run() {            try {                // 马就绪,等待开跑信号                startLatch.await();                // 马开始跑                long raceTime = (long) (Math.random() * 10000); // 模拟跑的时间                Thread.sleep(raceTime);                // 马到达终点                results.add(name + " finished in " + raceTime + " ms");                // 等待其他马到达终点                finishBarrier.await();            } catch (Exception e) {                e.printStackTrace();            }        }    }}3. 手写线程池(实现一个简易线程池)实现简易线程池,首先定义接口,主要包括,线程池基本功能,拒绝策略,线程池工厂,等待的任务队列。以及自定义异常,然后实现线程池的基本功能。线程池基本功能接口public interface ThreadPool {    //提交任务到线程池    void execute(Runnable runnable);    //关闭    void shutdown();    //获取线程池初始化时的线程大小    int getInitSize();    //获取线程池最大线程数    int getMaxSize();    //获取线程池核心线程数量    int getCoreSize();    //获取活跃线程数量    int getActiveCount();    //获取线程池缓存队列大小    int getQueueSize();    //查看线程是否被销毁    boolean isShutdown();}拒绝策略接口 @FunctionalInterface//这个类定义了当缓存队列达到上限的时候,将通过什么方式来通知提交者,实现了默认的三种方法public interface DenyPolicy {    void reject(Runnable runnable, ThreadPool threadPool);}线程池工厂接口@FunctionalInterface//创建线程的工厂public interface ThreadFactory {    Thread creatThread(Runnable runnable);}任务队列接口//缓存提交到线程池的队列任务public interface RunnableQueue {    //新线程进来时,提交任务到缓存队列    void offer(Runnable runnable);    //取出线程    Runnable take();    //获取队列中线程的数量    int size();}自定义异常//自定义异常public class RunnableDenyException extends RuntimeException {    public RunnableDenyException(String msg) {        super(msg);    }}拒绝策略实现。(三个拒绝策略)    //直接丢弃线程,什么都不做,不通知    class DiscardDenyPolicy implements DenyPolicy {        @Override        public void reject(Runnable runnable, ThreadPool threadPool) {        }    }    //抛出异常通知    class AbortDenyPolicy implements DenyPolicy {        @Override        public void reject(Runnable runnable, ThreadPool threadPool) {            throw new RunnableDenyException("这个线程:" + runnable + " 将会被丢弃");        }    }    //使线程在提交者所在的线程中运行    class RunnerDenyPolicy implements DenyPolicy {        @Override        public void reject(Runnable runnable, ThreadPool threadPool) {            if (!threadPool.isShutdown()) {                runnable.run();            }        }    }任务队列实现import java.util.LinkedList;public class LinkedRunnableQueue implements RunnableQueue {    //任务队列的最大容量    private final int limit;    //容量最大时,需要使用的拒绝策略    private final DenyPolicy denyPolicy;    //存放任务的队列    private final LinkedList<Runnable> runnableLinkedList;    private final ThreadPool threadPool;    public LinkedRunnableQueue(int limit, DenyPolicy denyPolicy, ThreadPool threadPool) {        this.limit = limit;        this.denyPolicy = denyPolicy;        this.threadPool = threadPool;        runnableLinkedList = new LinkedList<>();    }    @Override    public void offer(Runnable runnable) {        synchronized (runnableLinkedList) {            //如果缓存数量超过最大值,则使用拒绝策略            if (runnableLinkedList.size() >= limit) {                denyPolicy.reject(runnable, threadPool);            } else {                //成功加入list的末尾,并唤醒阻塞中的线程                runnableLinkedList.addLast(runnable);                runnableLinkedList.notifyAll();            }        }    }    @Override    public Runnable take() {        synchronized (runnableLinkedList) {            //如果缓存队列为空,则挂起,等待新的任务进来唤醒            while (runnableLinkedList.isEmpty()) {                try {                    runnableLinkedList.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }        return runnableLinkedList.removeFirst();    }    @Override    public int size() {        synchronized (runnableLinkedList) {            //返回list中的个数            return runnableLinkedList.size();        }    }}线程工厂实现import java.util.concurrent.atomic.AtomicInteger;class DefaultThreadFactory implements ThreadFactory {    private static final AtomicInteger GROUP_COUNTER = new AtomicInteger(1);    private static final ThreadGroup group = new ThreadGroup("我的线程-" +            GROUP_COUNTER.getAndDecrement());    private static final AtomicInteger COUNTER = new AtomicInteger(0);    @Override    public Thread creatThread(Runnable runnable) {        return new Thread(group, runnable, "线程池-" + COUNTER.getAndDecrement());    }}线程池实现import java.util.ArrayDeque;import java.util.Queue;import java.util.concurrent.TimeUnit;import java.util.stream.IntStream;public class BasicThreadPool extends Thread implements ThreadPool {    //初始化线程池的数量    private final int initSize;    //线程池最大线程数    private final int maxSize;    //线程池核心线程数    private final int coreSize;    //当前活跃线程的数量    private int activeCount;    //创建线程的工厂    private final ThreadFactory threadFactory;    //任务队列    private final RunnableQueue runnableQueue;    //线程是否被摧毁    private volatile boolean isShutdown = false;    //工作队列    private final Queue<ThreadTask> internalTasks = new ArrayDeque<>();    //拒绝策略    private final static DenyPolicy DEFAULT_DENY_POLICY = new DiscardDenyPolicy();    //看下面,自定义线程工厂    private final static ThreadFactory DEFAULT_THREAD_FACTORY =            new DefaultThreadFactory();    private final long keepAliveTime;    private final TimeUnit timeUnit;    //构造默认线程池时需要传入的参数:初始线程池的数量,最大线程的数量,核心线程数量,任务队列的最大数    public BasicThreadPool(int initSize, int maxSize, int coreSize,                           int queueSize) {        this(initSize, maxSize, coreSize, DEFAULT_THREAD_FACTORY,                queueSize, DEFAULT_DENY_POLICY, 2,                TimeUnit.SECONDS);    }    public BasicThreadPool(int initSize, int maxSize, int coreSize, ThreadFactory threadFactory, int queueSize,                           DenyPolicy denyPolicy, long keepAliveTime, TimeUnit timeUnit) {        this.initSize = initSize;        this.maxSize = maxSize;        this.coreSize = coreSize;        this.threadFactory = threadFactory;        this.runnableQueue = new LinkedRunnableQueue(queueSize, denyPolicy, this);        this.keepAliveTime = keepAliveTime;        this.timeUnit = timeUnit;        this.init();    }    //初始化线程池并创建initSize个线程    private void init() {        //继承了Thread类,初始化时先启动自己        start();        IntStream.range(0, initSize).forEach(i -> newThread());    }    //创建新的任务线程并启动    private void newThread() {        InternalTask internalTask = new InternalTask(runnableQueue);        Thread thread = this.threadFactory.creatThread(internalTask);        ThreadTask threadTask = new ThreadTask(thread, internalTask);        internalTasks.offer(threadTask);        this.activeCount++;        thread.start();    }    private void removeThread() {        ThreadTask threadTask = internalTasks.remove();        threadTask.internalTask.stop();        this.activeCount--;    }    @Override    public void execute(Runnable runnable) {        if (this.isShutdown) {            throw new IllegalStateException("这个线程池已经被销毁了");        }        this.runnableQueue.offer(runnable);    }    @Override    public void run() {        //自动维护线程池        while (!isShutdown && !isInterrupted()) {            try {                timeUnit.sleep(keepAliveTime);            } catch (InterruptedException e) {                e.printStackTrace();                isShutdown = true;                break;            }            synchronized (this) {                if (isShutdown) {                    break;                }                //当任务队列大于0,活跃线程小于核心线程的时候,扩容线程                if (runnableQueue.size() > 0 && activeCount < coreSize) {                    IntStream.range(initSize, coreSize).forEach(i -> newThread());                    continue;                }                if (runnableQueue.size() > 0 && activeCount < maxSize) {                    IntStream.range(coreSize, maxSize).forEach(i -> newThread());                }                if (runnableQueue.size() == 0 && activeCount > coreSize) {                    IntStream.range(coreSize, activeCount).forEach(i -> removeThread());                }            }        }    }    @Override    public void shutdown() {    }    //这一段方法不是特别重要,就有读者自己写    @Override    public int getInitSize() {        return 0;    }    @Override    public int getMaxSize() {        return 0;    }    @Override    public int getCoreSize() {        return 0;    }    @Override    public int getActiveCount() {        return 0;    }    @Override    public int getQueueSize() {        return 0;    }    @Override    public boolean isShutdown() {        return this.isShutdown;    }    //把线程和internalTask一个组合    private static class ThreadTask {        public ThreadTask(Thread thread, InternalTask internalTask) {            this.thread = thread;            this.internalTask = internalTask;        }        Thread thread;        InternalTask internalTask;    }}线程池内部使用//实现Runnable,用于线程池内部,该类会用到RunnableQueue,会不断的从队列中拿出线程并运行public class InternalTask implements Runnable {    private final RunnableQueue runnableQueue;    private volatile boolean running = true;    public InternalTask(RunnableQueue runnableQueue) {        this.runnableQueue = runnableQueue;    }    @Override    public void run() {        //如果当前线程在运行中切没有被中断,则不断从缓存队列中拿出线程运行        while (running && !Thread.currentThread().isInterrupted()) {            try {                Runnable task = runnableQueue.take();                task.run();            } catch (Exception e) {                running = false;                break;            }        }    }    //停止当前任务,会在shutdown中使用    public void stop() {        this.running = false;    }}测试类import java.util.concurrent.TimeUnit;public class Main {    public static void main(String[] args) {        final ThreadPool threadPool = new BasicThreadPool(2, 6, 4, 100);        for (int i = 0; i <= 20; i++) {            threadPool.execute(() -> {                try {                    TimeUnit.SECONDS.sleep(1);                    System.out.println(Thread.currentThread().getName() + "开始了");                } catch (InterruptedException e) {                    e.printStackTrace();                }            });        }    }}4. 生产者-消费者模型:例如一个厨子4s生产一个,一个客人10s消费一个import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class NonBlockingProducerConsumer {    // 定义一个非阻塞队列作为共享缓冲区    private static final Queue<String> queue = new ConcurrentLinkedQueue<>();    private static final int MAX_CAPACITY = 5; // 队列的最大容量    // 定义锁和条件变量    private static final Lock lock = new ReentrantLock();    private static final Condition notFull = lock.newCondition(); // 队列未满    private static final Condition notEmpty = lock.newCondition(); // 队列非空    public static void main(String[] args) {        // 创建厨子线程(生产者)        Thread chefThread = new Thread(() -> {            try {                while (true) {                    lock.lock(); // 获取锁                    try {                        // 如果队列已满,等待                        while (queue.size() == MAX_CAPACITY) {                            System.out.println("Queue is full, Chef is waiting...");                            notFull.await();                        }                        // 生产菜品                        String dish = "Dish-" + System.currentTimeMillis();                        queue.add(dish);                        System.out.println("Chef produced: " + dish);                        // 唤醒消费者                        notEmpty.signalAll();                    } finally {                        lock.unlock(); // 释放锁                    }                    // 模拟厨子生产时间                    Thread.sleep(4000);                }            } catch (InterruptedException e) {                Thread.currentThread().interrupt();            }        });        // 创建客人线程(消费者)        Thread customerThread = new Thread(() -> {            try {                while (true) {                    lock.lock(); // 获取锁                    try {                        // 如果队列为空,等待                        while (queue.isEmpty()) {                            System.out.println("Queue is empty, Customer is waiting...");                            notEmpty.await();                        }                        // 消费菜品                        String dish = queue.poll();                        System.out.println("Customer consumed: " + dish);                        // 唤醒生产者                        notFull.signalAll();                    } finally {                        lock.unlock(); // 释放锁                    }                    // 模拟客人消费时间                    Thread.sleep(10000);                }            } catch (InterruptedException e) {                Thread.currentThread().interrupt();            }        });        // 启动线程        chefThread.start();        customerThread.start();    }}5. 单例模式:懒汉,饿汉,双重校验锁懒汉public class Singleton {    //私有构造方法    private Singleton() {}    //在成员位置创建该类的对象    private static Singleton instance;    //对外提供静态方法获取该对象    public static Singleton getInstance() {        if(instance == null) {            instance = new Singleton();        }        return instance;    }}恶汉public class Singleton {    //私有构造方法    private Singleton() {}    //在成员位置创建该类的对象    private static Singleton instance = new Singleton();    //对外提供静态方法获取该对象    public static Singleton getInstance() {        return instance;    }}双重校验锁public class Singleton {    //私有构造方法    private Singleton() {}    private static volatile Singleton instance;   //对外提供静态方法获取该对象    public static Singleton getInstance() {        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际        if(instance == null) {            synchronized (Singleton.class) {                //抢到锁之后再次判断是否为空                if(instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}6. 动态代理jdk动态代理//卖票接口public interface SellTickets {    void sell();}//火车站  火车站具有卖票功能,所以需要实现SellTickets接口public class TrainStation implements SellTickets {    public void sell() {        System.out.println("火车站卖票");    }}//代理工厂,用来创建代理对象public class ProxyFactory {    private TrainStation station = new TrainStation();    public SellTickets getProxyObject() {        //使用Proxy获取代理对象        /*            newProxyInstance()方法参数说明:                ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可                Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口                InvocationHandler h : 代理对象的调用处理程序         */        SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),                station.getClass().getInterfaces(),                new InvocationHandler() {                    /*                        InvocationHandler中invoke方法参数说明:                            proxy : 代理对象                            method : 对应于在代理对象上调用的接口方法的 Method 实例                            args : 代理对象调用接口方法时传递的实际参数                     */                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        System.out.println("代理点收取一些服务费用(JDK动态代理方式)");                        //执行真实对象                        Object result = method.invoke(station, args);                        return result;                    }                });        return sellTickets;    }}//测试类public class Client {    public static void main(String[] args) {        //获取代理对象        ProxyFactory factory = new ProxyFactory();                SellTickets proxyObject = factory.getProxyObject();        proxyObject.sell();    }}7. 手写一个HashMap,HashSetHashMapimport java.util.LinkedList;public class MyHashMap<K, V> {    private static final int DEFAULT_CAPACITY = 16; // 默认容量    private static final double LOAD_FACTOR = 0.75; // 负载因子    private LinkedList<Node<K, V>>[] buckets;    private int size; // 当前元素数量    public MyHashMap() {        this(DEFAULT_CAPACITY);    }    @SuppressWarnings("unchecked")    public MyHashMap(int capacity) {        buckets = new LinkedList[capacity];        for (int i = 0; i < capacity; i++) {            buckets[i] = new LinkedList<>();        }        size = 0;    }    // 存储键值对    public void put(K key, V value) {        int index = getIndex(key); // 计算哈希值对应的桶索引        LinkedList<Node<K, V>> bucket = buckets[index];        // 检查是否已经存在该键        for (Node<K, V> node : bucket) {            if (node.key.equals(key)) {                node.value = value; // 更新值                return;            }        }        // 如果不存在该键,则新增节点        bucket.add(new Node<>(key, value));        size++;        // 检查是否需要扩容        if ((double) size / buckets.length > LOAD_FACTOR) {            resize();        }    }    // 获取值    public V get(K key) {        int index = getIndex(key);        LinkedList<Node<K, V>> bucket = buckets[index];        for (Node<K, V> node : bucket) {            if (node.key.equals(key)) {                return node.value;            }        }        return null; // 如果没有找到键,返回 null    }    // 删除键值对    public void remove(K key) {        int index = getIndex(key);        LinkedList<Node<K, V>> bucket = buckets[index];        for (Node<K, V> node : bucket) {            if (node.key.equals(key)) {                bucket.remove(node);                size--;                return;            }        }    }    // 获取当前元素数量    public int size() {        return size;    }    // 扩容方法    @SuppressWarnings("unchecked")    private void resize() {        LinkedList<Node<K, V>>[] oldBuckets = buckets;        buckets = new LinkedList[oldBuckets.length * 2];        for (int i = 0; i < buckets.length; i++) {            buckets[i] = new LinkedList<>();        }        size = 0;        // 将旧桶中的所有元素重新插入新桶        for (LinkedList<Node<K, V>> bucket : oldBuckets) {            for (Node<K, V> node : bucket) {                put(node.key, node.value);            }        }    }    // 计算桶索引    private int getIndex(K key) {        return Math.abs(key.hashCode()) % buckets.length;    }    // 内部类:存储键值对的节点    private static class Node<K, V> {        K key;        V value;        Node(K key, V value) {            this.key = key;            this.value = value;        }    }}HashSet(在HashMap基础上实现)public class MyHashSet<E> {    private static final Object PRESENT = new Object(); // 占位对象    private final MyHashMap<E, Object> map;    public MyHashSet() {        map = new MyHashMap<>();    }    // 添加元素    public boolean add(E e) {        return map.put(e, PRESENT) == null; // 如果之前不存在该键,返回 true    }    // 删除元素    public boolean remove(E e) {        return map.get(e) != null && (map.remove(e) != null);    }    // 判断是否包含某个元素    public boolean contains(E e) {        return map.get(e) != null;    }    // 获取集合大小    public int size() {        return map.size();    }    // 清空集合    public void clear() {        map = new MyHashMap<>();    }}8. 有一个0-4的随机器rand4,如何实现0-6的随机器rand6,概率相同。拓展:rand X = func(rand Y),实现func函数rand4实现rand6public class Rand6FromRand4 {    // 假设 rand4 是一个已有的函数,返回 [0, 4] 的随机数    public static int rand4() {        return (int) (Math.random() * 5); // 模拟 rand4    }    // 实现 rand6,返回 [0, 6] 的随机数    public static int rand6() {        while (true) {            // 调用两次 rand4,生成一个范围为 [0, 24] 的随机数            int num = rand4() * 5 + rand4(); // num ∈ [0, 24]            // 只使用前 21 个数(21 是 7 的倍数,可以均匀分配到 [0, 6])            if (num < 21) {                return num % 7; // 将 [0, 20] 映射到 [0, 6]            }            // 如果 num ≥ 21,丢弃并重新生成        }    }}randx实现randypublic class RandXFromRandY {    // 假设 randY 是一个已有的函数,返回 [0, Y-1] 的随机数    public static int randY(int Y) {        return (int) (Math.random() * Y); // 模拟 randY    }    // 实现 randX,返回 [0, X-1] 的随机数    public static int randX(int X, int Y) {        int n = 1;        int range = Y;        // 找到最小的 n,使得 Y^n >= X        while (range < X) {            range *= Y;            n++;        }        while (true) {            // 调用 n 次 randY,生成一个范围为 [0, Y^n - 1] 的随机数            int num = 0;            int factor = 1;            for (int i = 0; i < n; i++) {                num += randY(Y) * factor;                factor *= Y;            }            // 只使用前 k 个数,k 是 X 的倍数            if (num < range / X * X) {                return num % X; // 将 [0, k-1] 映射到 [0, X-1]            }            // 如果超出范围,丢弃并重新生成        }    }}9. 及其逆天的一个阿里手撕,来自于@byebyeneu:写三个Spring接口,调用第一个接口的时候返回这个接口的累计调用次数,调用第二个接口的时候返回调用这个接口的累计p99,调用第三个接口的时候,如果这个接口这时的qps<10,返回success,如果这个接口这时qps>10,返回err10.判断今天星期几import java.time.LocalDate;import java.time.DayOfWeek;public class DayOfWeekExample {    public static void main(String[] args) {        // 获取当前日期        LocalDate today = LocalDate.now();        // 获取今天是星期几        DayOfWeek dayOfWeek = today.getDayOfWeek();        // 输出星期几(英文)        System.out.println("Today is: " + dayOfWeek);        // 如果需要输出中文的星期几        String[] chineseWeekdays = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};        int dayValue = dayOfWeek.getValue(); // 1 表示星期一,7 表示星期日        System.out.println("今天是: " + chineseWeekdays[dayValue - 1]);    }}11.求YYYY-MM-DD的上一天public static String getPreviousDay(String dateStr) throws Exception {        // 定义日期格式        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");        // 将字符串解析为 Date 对象        Date date = sdf.parse(dateStr);        // 使用 Calendar 计算上一天        Calendar calendar = Calendar.getInstance();        calendar.setTime(date);        calendar.add(Calendar.DAY_OF_MONTH, -1);        // 将结果格式化为字符串并返回        return sdf.format(calendar.getTime());    }12.两个线程和队列模拟TCP三次握手
点赞 74
评论 17
全部评论

相关推荐

05-22 09:23
门头沟学院 Java
点赞 评论 收藏
分享
06-15 02:05
已编辑
南昌航空大学 数据分析师
Eason三木:你如果想干技术岗,那几个发公众号合唱比赛的经历就去掉,优秀团员去掉,求职没用。然后CET4这种不是奖项,是技能,放到下面的专业技能里或者单独列一个英语能力。 另外好好改改你的排版,首行缩进完全没有必要,行间距好好调调,别让字和标题背景黏在一起,你下面说能做高质量PPT你得展现出来啊,你这简历排版我用PPT做的都能比你做的好。 然后自我评价,你如果要干数据工程师,抗压能力强最起码得有吧。
简历中的项目经历要怎么写
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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