Java非常规手写代码题

文章目录

1、写三种单例模式的实现方式

1、枚举

简单高效,无需加锁,线程安全,可以避免通过反射破坏枚举单例

java 复制代码
public enum Singleton {
    INSTANCE;
    public void doSomething(String str) {
    	System.out.println(str);
     }
}

2、静态内部类

  • 当外部类 Singleton 被加载的时候,并不会创建静态内部类SingletonInner 的实例对象
  • 只有当调用getInstance() 方法时, SingletonInner 才会被加载,此时才会创建单例对象 INSTANCE
  • INSTANCE 的唯⼀性、创建过程的线程安全性,都由 JVM 来保证
  • 无需加锁,线程安全,并且支持延时加载
java 复制代码
public class Singleton {
    // 私有化构造⽅法
    private Singleton() {
     }
    // 对外提供获取实例的公共⽅法
    public static Singleton getInstance() {
    	return SingletonInner.INSTANCE;
     }
    // 定义静态内部类
    private static class SingletonInner{
    	private final static Singleton INSTANCE = new Singleton();
     }
}

3、双重检验锁

java 复制代码
public class Singleton {
    private volatile static Singleton uniqueInstance;
    // 私有化构造⽅法
    private Singleton() {}
    public static Singleton getUniqueInstance() {
        //先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
        if (uniqueInstance == null) {
        //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                	uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

uniqueInstance = new Singleton(); 三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化uniqueInstance
  3. 将uniqueInstance指向分配的内存地址

2、编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号

约瑟夫环问题

  1. 要求出最后留下的那个⼈的编号
  2. 求全过程,即要算出每轮出局的人

公式: (f(n - 1, k) + k - 1) % n + 1

f(n,k) 表示 n 个人报数,每次报数报 到 k 的人出局,最终最后一个人的编号

java 复制代码
public class Josephus {
    // 定义递归函数
    public static int f(int n, int k) {
        // 如果只有⼀个⼈,则返回 1
        if (n == 1) {
        	return 1;
        }
        return (f(n - 1, k) + k - 1) % n + 1;
        }
    public static void main(String[] args) {
        int n = 10;
        int k = 3;
        System.out.println("最后留下的那个⼈的编号是:" + f(n, k));
    }
}

3、写两个线程打印 1-n,⼀个线程打印奇数,⼀个线程打印偶数

  1. 线程的等待/通知机制( wait() 和 notify() )
  2. 信号量Semaphore
java 复制代码
package com.mys.leetcode;

/**
 * @author mys
 * @date 2023/10/26 20:32
 */
public class ParityPrinter {
    private int max; // 总打印次数
    private int number = 1; // 打印次数
    private boolean odd; // 是否该打印奇数

    public ParityPrinter(int max) {
        this.max = max;
    }

    /**
     * 打印奇数
     */
    public synchronized void printOdd() {
        while (number < 100) {
            // 如果当前应该打印偶数,就等待
            while (odd) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + number);
            number ++;

            odd = true;
            notify(); // 唤醒另一个线程
        }
    }

    /**
     * 打印偶数
     */
    public synchronized void printEven() {
        while (number < 100) {
            // 如果当前应该打印偶数,就等待
            while (!odd) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + number);
            number ++;

            odd = false;
            notify(); // 唤醒另一个线程
        }
    }

    public static void main(String[] args) {
        ParityPrinter printer = new ParityPrinter(100);
        Thread t1 = new Thread(printer::printOdd);
        Thread t2 = new Thread(printer::printEven);
        t1.start();
        t2.start();
    }
}
复制代码
Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8

4、LRU 缓存实现

LRU:最近最少使用

适用场景:频繁访问(缓存、页面置换)、有局部性、数据分布均匀、缓存容量适中

使用LinkedHashMap实现LRUCache

LinkedHashMap内部维护了双链表,增加了元素的顺序信息

removeEldestEntry:是否移除老数据

java 复制代码
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private int capacity;
    public LRUCache(int capacity) {
        // LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    // 当缓存元素个数超过容5时,移除最老的元素
        return size() > capacity;
    }

    public static void main(String[] args) {
        // 创建一个容量为3的LRU缓存
        LRUCache<Integer, String> lruCache = new LRUCache<>(3);
        // 添加数据
        lruCache.put(1, "one");
        lruCache.put(2, "two");
        lruCache.put(3, "three");
        // 此时缓存为:{1=One, 2=Two, 3=Three}
        System.out.println(lruCache);

        // 访问某个元素,使其成为最近访问元素
        String value = lruCache.get(2);
        // 此时缓存为:{1=One, 3=Three, 2=Two}
        System.out.println(lruCache);

        // 添加新数据,触发淘汰
        lruCache.put(4, "Four");
        // 此时缓存为:{3=Three, 2=Two, 4=Four}
        System.out.println(lruCache);
    }
}
复制代码
{1=One, 2=Two, 3=Three}
{1=One, 3=Three, 2=Two}
{3=Three, 2=Two, 4=Four}

5、用Java 实现栈

java 复制代码
public class Stack {
    private int[] arr;
    private int top;

    public Stack(int capacity) {
        arr = new int[capacity];
        top = -1;
    }

    public void push(int element) {
        if (top == arr.length - 1) {
            throw new IllegalStateException("栈已满");
        }
        top ++;
        arr[top] = element;
    }

    public int pop() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        int element = arr[top];
        top --;
        return element;
    }

    public int top() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        return arr[top];
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public int size() {
        return top + 1;
    }
}

6、加权轮询算法的实现

加权轮询: 它通过给不同的服务器分配不同的权重来实现请求的均衡分发

  1. 为服务器分配初始权重,权重越高,可以处理更高的请求
  2. 新请求到达,负载均衡器选择权重最高的机器处理请求
  3. 处理之后,服务器权重减去一个设定好的值,减小权重
  4. 所有服务器权重为0,重新赋初值
java 复制代码
class Server {
    private String name;
    private int weight;

    public Server(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

    public int getWeight() {
        return weight;
    }
}
public class WeightedRoundRobin {
    /*
    1. 为服务器分配初始权重,权重越高,可以处理更高的请求
    2. 新请求到达,负载均衡器选择权重最高的机器处理请求
    3. 处理之后,服务器权重减去一个设定好的值,减小权重
    4. 所有服务器权重为0,重新赋初值
     */

    private List<Server> servers;
    private int currentIndex;

    public WeightedRoundRobin(List<Server> servers) {
        this.servers = servers;
        currentIndex = 0;
    }

    // 根据服务器权重,选择下一个服务器
    public Server getNextServer() {
        int totalWeight = calculateTotalWeight(); // 所有服务器总权重
        int maxWeight = findMaxWeight(); // 所有服务器中的最大权重
        int gcd = calculateGCD(); // 所有权重的最大公约数
        // 根据最大权重遍历
        while (true) {
            currentIndex = (currentIndex + 1) % servers.size();
            if (currentIndex == 0) {
                maxWeight -= gcd;
                if (maxWeight <= 0) {
                    maxWeight = findMaxWeight();
                    if (maxWeight == 0) {
                        return null; // 所有服务器的权重都为0,无法分配请求
                    }
                }
            }
            if (servers.get(currentIndex).getWeight() >= maxWeight) {
                return servers.get(currentIndex);
            }
        }
    }

    // 计算所有服务器总权重
    private int calculateTotalWeight() {
        int totalWeight = 0;
        for (Server server : servers) {
            totalWeight += server.getWeight();
        }
        return totalWeight;
    }

    // 找到所有服务器中的最大权重
    private int findMaxWeight() {
        int maxWeight = 0;
        for (Server server : servers) {
            if (server.getWeight() > maxWeight) {
                maxWeight = server.getWeight();
            }
        }
        return maxWeight;
    }

    // 计算所有权重的最大公约数
    private int calculateGCD() {
        int weightsLength = servers.size();
        int[] weights = new int[weightsLength];
        for (int i = 0; i < weightsLength; i ++) {
            weights[i] = servers.get(i).getWeight();
        }
        return findGCD(weights, weightsLength);
    }

    // 找多个权值的最大公约数
    private int findGCD(int[] weights, int n) {
        int result = weights[0];
        for (int i = 0; i < n; i ++) {
            result = gcd(result, weights[i]);
        }
        return result;
    }

    // 计算两个数的最大公约数
    private int gcd(int a, int b) {
        if (b == 0) {
            return a;
        } else {
            return gcd(b, a % b);
        }
    }

    public static void main(String[] args) {
        List<Server> servers = new ArrayList<>();
        servers.add(new Server("Server1", 3));
        servers.add(new Server("Server2", 5));
        servers.add(new Server("Server3", 1));
        WeightedRoundRobin wrr = new WeightedRoundRobin(servers);
        // 模拟请求分发
        for (int i = 0; i < 10; i++) {
            Server server = wrr.getNextServer();
            System.out.println("Request " + (i + 1) + " sent to: " + server.getName());
        }
    }
}

7、手写死锁

定义、四个必要条件、避免死锁、银行家算法、安全状态

java 复制代码
public class DeadLockDemo {
    private static Object resource1 = new Object();
    private static Object resource2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + " get resource1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " wait resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + " get resource2");
                }
            }
        }, "线程1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + " get resource2");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " wait resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + " get resource1");
                }
            }
        }, "线程2").start();
    }
}
复制代码
Thread[线程1,5,main] get resource1
Thread[线程2,5,main] get resource2
Thread[线程1,5,main] wait resource2
Thread[线程2,5,main] wait resource1

8、快速排序

  1. 确定分界点:q[l] q[(l+r)/2] q[r] 随机
  2. 调整范围:<x放左边,>x放右边
  3. 递归:处理左右两端

代码思想:两指针同时往中间走,若i指向的数<x,指针后移;若j指向的数>x,指针前移。完成之后指针指向的数swap,继续进行下一次循环

java 复制代码
public void quickSort(int[] nums, int l, int r) {
    if (l >= r) return;
    int i = l - 1, j = r + 1, x = nums[(l + r) / 2];
    while (i < j) {
        do i ++; while (nums[i] < x);
        do j --; while (nums[j] > x);
        if (i < j) {
            int tmp = nums[i];
            nums[i] = nums[j];
            nums[j] = tmp;
        }
    }
    quickSort(nums, l, j);
    quickSort(nums, j + 1, r);
}

思想2:

  1. 从后往前走,找比x更小的数,放在low位置;
  2. 从前往后走,找比x更大的数,放在high位置
  3. 当low>=high,一轮循环结束,确定了x的位置,以x为中心,将数组分为左右两边,左右两边的数组继续循环

eg: 5 1 7 3 1 6 9 4 x=5

第一趟:4 1 1 3 5 6 9 7 确定了5;x1=4, x2=6

第二趟:3 1 1 4 5 6 9 7 确定了4,5,6;x1=3, x2=9

第三趟:1 1 3 4 5 6 7 9

9、生产者消费者

问题:如何确保⽣产者不会往满了的数据缓冲区继续添加数据,而消费者也不会从空的缓冲区取数据------引入⼀个共享的缓冲区

Procuder

java 复制代码
public class Producer implements Runnable{
    private final BlockingQueue<Integer> queue;
    private final int MAX_SIZE = 5; // 缓冲区最大容量

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 10; i ++) {
                produce(i); // 生产
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void produce(int item) throws InterruptedException {
        synchronized (queue) {
            while (queue.size() == MAX_SIZE) {
                System.out.println("缓冲区已满,生产者等待");
                queue.wait(); // 冲区已满,生产者等待
            }
            queue.put(item); // 生产者放入缓冲区
            System.out.println("生产物品:" + item);
            queue.notifyAll(); // 唤醒所有等待的消费者
        }
    }
}

Comsumer

java 复制代码
public class Consumer implements Runnable{
    private final BlockingQueue<Integer> queue;
    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                consume(); // 消费物品
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void consume() throws InterruptedException {
        synchronized (queue) {
            while (queue.isEmpty()) {
                System.out.println("缓冲区空,消费者等待");
                queue.wait(); // 缓冲区空,消费者等待
            }
            int item = queue.take(); // 从缓冲区取物品
            System.out.println("消费者消费:" + item);
            queue.notifyAll(); //  唤醒所有等待的生产者
        }
    }
}

Test

java 复制代码
public class ProducerConsumerExample {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer(queue);
        Thread producerThread = new Thread(producer);
        Thread consumerThread = new Thread(consumer);
        producerThread.start();
        consumerThread.start();
    }
}
复制代码
生产物品:1
生产物品:2
生产物品:3
生产物品:4
生产物品:5
缓冲区已满,生产者等待
消费者消费:1
消费者消费:2
消费者消费:3
消费者消费:4
消费者消费:5
缓冲区空,消费者等待
相关推荐
老鼠只爱大米2 分钟前
LeetCode经典算法面试题 #108:将有序数组转换为二叉搜索树(递归分治、迭代法等多种实现方案详解)
算法·leetcode·二叉树·二叉搜索树·平衡树·分治法
踩坑记录12 分钟前
leetcode hot100 104. 二叉树的最大深度 easy 递归dfs 层序遍历bfs
leetcode·深度优先·宽度优先
怪兽源码25 分钟前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
恒悦sunsite31 分钟前
Redis之配置只读账号
java·redis·bootstrap
梦里小白龙37 分钟前
java 通过Minio上传文件
java·开发语言
人道领域38 分钟前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
sheji52611 小时前
JSP基于信息安全的读书网站79f9s--程序+源码+数据库+调试部署+开发环境
java·开发语言·数据库·算法
毕设源码-邱学长1 小时前
【开题答辩全过程】以 基于Java Web的电子商务网站的用户行为分析与个性化推荐系统为例,包含答辩的问题和答案
java·开发语言
摇滚侠1 小时前
Java项目教程《尚庭公寓》java项目从开发到部署,技术储备,MybatisPlus、MybatisX
java·开发语言
踩坑记录1 小时前
leetcode hot100 23. 合并 K 个升序链表 hard 分治 迭代
leetcode·链表