Java手写系列

  • 前言
  • 生产者消费者问题

  • 交替打印A、B
  • 死锁问题
  • 快速排序
  • 堆排序
  • 归并排序
  • LRU算法
  • 单例模式


java 手机上签字实现_java 手机上签字实现

前言

这篇文章主要是以JAVA代码展示一些基础及面试要求手写的代码段,想到啥就写啥,也将持续更新。

java 手机上签字实现_java 手机上签字实现_02

生产者消费者问题

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。 ------百度百科

生产者消费者有多种解法,下面就展示Synchronized的解法。

public class Test {
    static class Stock{
        //最大容量
        private final int MAX_SIZE = 30;
        private int cur_SIZE;
        Object object = new Object();
        public void produce() throws InterruptedException {
            while(true){
                synchronized (object){
                    while(cur_SIZE >= MAX_SIZE){
                        System.out.println(Thread.currentThread().getName() + "发现仓库已满");
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    ++cur_SIZE;
                    System.out.println(Thread.currentThread().getName() + "生产了产品" + cur_SIZE);
                    object.notifyAll();
                }
                Thread.sleep(100);
            }
        }
        public void consume() throws InterruptedException {
            while(true){
                synchronized (object){
                    while(cur_SIZE <= 0){
                        System.out.println(Thread.currentThread().getName() + "发现仓库为空");
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    --cur_SIZE;
                    System.out.println(Thread.currentThread().getName() + "消费了产品" + cur_SIZE);
                    object.notifyAll();
                }
                Thread.sleep(200);
            }
        }
    }
    public static void main(String[] args) {
        Stock stock = new Stock();
        new Thread(() -> {
            try {
                stock.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"生产者1001").start();
        new Thread(() -> {
            try {
                stock.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"生产者1002").start();
        new Thread(() -> {
            try {
                stock.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者2001").start();
        new Thread(() -> {
            try {
                stock.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者2002").start();
    }
}
生产者1001生产了产品1
生产者1002生产了产品2
消费者2001消费了产品1
消费者2002消费了产品0
生产者1002生产了产品1
生产者1001生产了产品2
....
生产者1002生产了产品30
生产者1001发现仓库已满
消费者2002消费了产品29
生产者1001生产了产品30

交替打印A、B

使用了ReentrantLock的newCondition()方法

public class Test {
    ReentrantLock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    private void printA(){
        while(true){
            lock.lock();
            System.out.println("--A--");
            try {
                condition2.signal();
                condition1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private void printB(){
        while(true){
            lock.lock();
            System.out.println("--B--");
            try {
                condition1.signal();
                condition2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        new Thread(() -> {
            test.printA();
        }).start();
        new Thread(() -> {
            test.printB();
        }).start();
    }
}

运行结果

--A--
--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--
--B--
--A--

死锁问题

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。-------百度百科

public class Test {
    Object lockA = new Object();
    Object lockB = new Object();
    public void lockA2lockB(){
        synchronized (lockA){
            System.out.println("拿到A锁");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println("能进来这里吗?");
            }
        }
    }
    public void lockB2lockA(){
        synchronized (lockB){
            System.out.println("拿到B锁");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockA){
                System.out.println("能进来这里吗?");
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        new Thread(() -> {
            test.lockA2lockB();
        },"线程A").start();
        new Thread(() -> {
            test.lockB2lockA();
        },"线程B").start();
    }
}
拿到A锁
拿到B锁
(可以看到没有输出“能进来这里吗?”)

快速排序

算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(target);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
public void quickSort(int[] arr,int left,int right){
      if(left >= right || left < 0 || right >= arr.length){
          return;
      }
      int index = partition(arr,left,right);
      if(index == left){
          quickSort(arr,index + 1,right);
      }else if(index == right){
          quickSort(arr,left,right - 1);
      }else{
          quickSort(arr,left,index - 1);
          quickSort(arr,index + 1,right);
      }
  }
  public int partition(int[] arr,int left,int right){
      int target = arr[left];
      int index = left;
      for(int i = left + 1;i <= right;i++){
          if(arr[i] < target){
              index++;
              swap(arr,index,i);
          }
      }
      swap(arr,index,left);
      return index;
  }
  public static void swap(int[] arr,int i ,int j){
      int temp = arr[i];
      arr[i] = arr[j];
      arr[j] = temp;
  }

  public static void main(String[] args) {
      int[] nums = new int[]{2,5,73,13,34,90,24,64};
      Test test = new Test();
      test.quickSort(nums,0,nums.length - 1);
      for (int num : nums){
          System.out.println(num);
      }
  }

运行截图

2
5
13
24
34
64
73
90

算法分析

最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)

堆排序

算法描述

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
public class HeapSort {
    public static void sort(int[] arr) {
        int n = arr.length;

        // 创建最大堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 从最后一个节点开始依次取出并调整堆
        for (int i = n - 1; i > 0; i--) {
            // 将最大值移动到数组末尾
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;

            // 调整堆
            heapify(arr, i, 0);
        }
    }

    // 调整以i为根节点的堆,使其满足最大堆的性质
    private static void heapify(int[] arr, int n, int i) {
        int largest = i; // 假设当前节点值最大
        int left = 2 * i + 1; // 左子节点索引
        int right = 2 * i + 2; // 右子节点索引

        // 如果左子节点值比当前节点值大,更新最大值索引
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        // 如果右子节点值比当前节点值大,更新最大值索引
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        // 如果最大值索引变化,调整堆
        if (largest != i) {
            int temp = arr[i];
            arr[i] = arr[largest];
            arr[largest] = temp;
            heapify(arr, n, largest);
        }
    }

    public static void main(String[] args) {
        int[] arr = { 64, 34, 25, 12, 22, 11, 90 };
        HeapSort.sort(arr);
        System.out.println(Arrays.toString(arr)); // [11, 12, 22, 25, 34, 64, 90]
    }
}

算法分析

最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)

归并排序

算法描述

把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。

public static int[] MergeSort(int[] array) {
        if (array.length < 2) return array;
        int mid = array.length / 2;
        int[] left = Arrays.copyOfRange(array, 0, mid);
        int[] right = Arrays.copyOfRange(array, mid, array.length);
        return merge(MergeSort(left), MergeSort(right));
    }
    public static int[] merge(int[] left, int[] right) {
        int[] result = new int[left.length + right.length];
        for (int index = 0, i = 0, j = 0; index < result.length; index++) {
            if (i >= left.length)
                result[index] = right[j++];
            else if (j >= right.length)
                result[index] = left[i++];
            else if (left[i] > right[j])
                result[index] = right[j++];
            else
                result[index] = left[i++];
        }
        return result;
    }

    public static void main(String[] args){
        int[] arr = new int[]{1,4,3,7,2,9};
        int[] sortArr = MergeSort(arr);
        for (int i : sortArr) {
            System.out.println(i + " ");
        }
    }

最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)

LRU算法

LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。------百度百科

作为面试必备的一种算法,必须要会!

public class LRU {
    class LRUNode{
        int key;
        int value;
        LRUNode next;
        LRUNode pre;
        public LRUNode(){

        }
        public LRUNode(int key,int value){
            this.key = key;
            this.value = value;
        }
    }
    LRUNode head;
    LRUNode tail;
    int size;
    int capacity;
    HashMap<Integer,LRUNode> map = new HashMap<>();
    public LRU(int capacity){
        head = new LRUNode();
        tail = new LRUNode();
        head.next = tail;
        tail.pre = head;
        size = 0;
        this.capacity = capacity;
    }
    public void put(int key,int value){
        LRUNode node = map.get(key);
        if(node == null){
            LRUNode newNode = new LRUNode(key, value);
            map.put(key,newNode);
            addToHead(newNode);
            size++;
            if (size > capacity){
                LRUNode removedNode = removeTail();
                map.remove(removedNode.key);
                size--;
            }
        }else {
            node.value = value;
            moveToHead(node);
        }
    }
    public int get(int key){
        LRUNode node = map.get(key);
        if(node == null){
            return -1;
        }
        moveToHead(node);
        return node.value;
    }
    private void removeNode(LRUNode node){
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }
    private LRUNode removeTail(){
        LRUNode node = tail.pre;
        removeNode(node);
        return node;
    }
    private void addToHead(LRUNode node){
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
        node.pre = head;
    }
    private void moveToHead(LRUNode node){
        removeNode(node);
        addToHead(node);
    }
}
class Test{
    public static void main(String[] args) {
        LRU lru = new LRU(3); //容量为3
        lru.put(1,10);
        lru.put(4,40);
        lru.put(7,70);
        lru.get(4);   //get了之后把4放到7前面,所以满3个之后是7没了,4还在
        lru.put(2,20);
        lru.put(6,60);
        System.out.println("7还在吗?" + lru.get(7));
        System.out.println(lru.map.keySet());
    }
}

运行截图

7还在吗?-1
[2, 4, 6]

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

class Test1 {
    private static TestClass instance;
    private Test1(){

    }
    public static TestClass getInstance(){
        if(instance == null){
            synchronized (TestClass.class){
                if(instance == null){   //因为可能有多个线程在等synchronized的释放,所以要再判断一次
                    instance = new TestClass();
                }
            }
        }
        return instance;
    }
}
class Test2{
    private static Test2 instance = new Test2();
    private Test2(){

    }
    public static Test2 getInstance(){
        return instance;
    }
}
public class Test{
    public static void main(String[] args) {
        //懒汉式
        TestClass instance1 = Test1.getInstance();
        TestClass instance2 = Test1.getInstance();
        System.out.println(instance1 == instance2);
        //饿汉式
        Test2 instance3 = Test2.getInstance();
        Test2 instance4 = Test2.getInstance();
        System.out.println(instance3 == instance4);
    }
}