Keep Thinking Keep Moving

随机洗牌的算法 有更新!

今天偶然看到群里的群友说道,面试被问如何将扑克牌随机洗牌输出。笔者觉得这道题挺有意思而且挺开放性,有多种不同的实现方式。然后我就随手写了一个算法,仔细一想这个算法的优化空间挺大,于是又写出三种算法。

第一种

我们通过JDK的随机算法获取一个随机下标,再通过Set集合来判断牌是否有被抽到过,如果抽到过的话,继续进行循环,直到抽到原牌数量为止。

public class ShuffleCard1 {

    public static int[] getShuffleCards(int[] cards) {
        // 获取随机数种子
        Random rand = new Random(System.currentTimeMillis());
        // 用Set集合存储已抽过的牌
        Set<Integer> isExisted = new HashSet();
        // 声明洗牌后数组
        int[] shuffleCards = new int[cards.length];
        // 已抽到的牌数量
        int drawCount = 0;
        // 当抽到的牌数量没达到原牌数组的大小时循环
        while (drawCount < cards.length) {
            // 获取一个随机下标
            int index = rand.nextInt(cards.length);
            // 判断该下标对应的牌是否已被抽过,没有的话,抽出
            if (!isExisted.contains(cards[index])) {
                shuffleCards[drawCount++] = cards[index];
                isExisted.add(cards[index]);
            }
        }
        return shuffleCards;
    }
}
2019-09-17 1 评论 422 浏览
阅读全文
Spring AOP详解 有更新!

情景案例

小明辛苦忙了一整年终于完成了包含300个接口的业务系统项目。项目圆满上线并稳定运行了一段时间了。突然有一天总监说,对于会造成数据变化的所有接口,我们必须记录用户的操作日志。然后小明就吭哧吭哧给其中150个接口,挨个加上日志代码,累得真够呛。

过了一阵子总监又说,所有变化很少的数据全部都加上缓存,缓存涉及到刷新缓存、获取缓存、删除缓存的问题。于是乎,小明就又吭哧吭哧地给其中的100个接口加上缓存相关的代码。

又过了一阵子总监说,所有涉及充值退款费用相关的接口,需要生成发票单存入数据库。这时候小明又需要吭哧吭哧给涉及到的50个接口,挨个加上发票存储操作。

小明天天加班也没在工期内完成任务,并且原本的业务代码已经变得臃肿不堪了。

原本的代码:

/**
 * 业务方法
 */
public static void method() {

    // 业务操作
    doBusiness();

}

经过硬编码添加各种非业务性代码后的业务代码:

/**
 * 业务方法
 */
public static void method() {
    // 日志操作
    doLog();
    // 业务操作
    doBusiness();
    // 缓存操作
    doLog();
    // 发票操作
    doReceipt();

}

读者应该能明显感受到在没有AOP代理的情况下的缺点

  1. 业务代码和非业务代码混杂在一起,原本清晰的业务流程淹没在与业务不相关的代码中。
  2. 增加非业务性的功能时,都需要手工硬编码去实现,费时费力。
  3. 代码变得不好维护,一是代码耦合度高,二是需要通过硬编码的方式去拓展或者修改功能。

2019-10-07 0 评论 429 浏览
阅读全文

很多文章在谈论到BIO、NIO、AIO的时候仅仅是抛出一堆定义,以及一些生动的例子。看似很好理解。但是并没有将最基础的本质原理显现出来,如果没有没有从IO的原理出发的话是很难理解这三者之间的区别的。所以本篇文章从Java是如何进行IO操作为开头进行分析。

Java中的IO原理

首先Java中的IO都是依赖操作系统内核进行的,我们程序中的IO读写其实调用的是操作系统内核中的read&write两大系统调用。

那内核是如何进行IO交互的呢?

  1. 网卡收到经过网线传来的网络数据,并将网络数据写到内存中。
  2. 当网卡把数据写入到内存后,网卡向cpu发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据。
  3. 将内存中的网络数据写入到对应socket的接收缓冲区中。
  4. 当接收缓冲区的数据写好之后,应用程序开始进行数据处理。

对应抽象到java的socket代码简单示例如下:

public class SocketServer {
  public static void main(String[] args) throws Exception {
    // 监听指定的端口
    int port = 8080;
    ServerSocket server = new ServerSocket(port);
    // server将一直等待连接的到来
    Socket socket = server.accept();
    // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
    InputStream inputStream = socket.getInputStream();
    byte[] bytes = new byte[1024];
    int len;
    while ((len = inputStream.read(bytes)) != -1) {
      //获取数据进行处理
      String message = new String(bytes, 0, len,"UTF-8");
    }
    // socket、server,流关闭操作,省略不表
  }
}

2019-10-04 0 评论 445 浏览
阅读全文

主从复制解决的问题

  • 数据分布:通过复制将数据分布到不同地理位置
  • 负载均衡:读写分离以及将读负载到多台从库
  • 备份:可作为实时备份
  • 高可用性:利用主主复制实现高可用

复制原理

复制的原理其实很简单,仅分为以下三步:

  1. 在主库上把数据更改记录到二进制日志binary log中,具体是在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中去,Mysql会按照事务提交的顺序来记录二进制日志的。日志记录好之后,主库通知存储引擎提交事务。

  2. 从库会启动一个IO线程,该线程会连接到主库。而主库上的binlog dump线程会去读取主库本地的binlog日志文件中的更新事件。发往从库,从库接收到日志之后会将其记录到本地的中继日志relay-log当中。

  3. 从库中的SQL线程读取中继日志relay-log中的事件,将其重放到从库中。(在5.6版本之前SQL线程是单线程的,使得主从之间延迟更大)

两种复制方式

日志文件中记录的到底是什么呢?
mysql支持了两种日志格式,这两种日志格式也体现了各自的复制方式

2019-09-30 0 评论 1,607 浏览
阅读全文

先准备两台Mysql数据库

  • 主数据库:192.168.0.106
  • 从数据库:192.168.0.107

如果还没创建好数据库的话可以参考我之前的mysql搭建过程。

docker安装mysql小白教程

1.在主库创建同步账户Slave

CREATE USER 'slave'@'%'  IDENTIFIED BY 'slave123456';

如果出现以下异常的话。

ERROR 3009 (HY000): Column count of mysql.user is wrong. 
Expected 45, found 43. Created with MySQL 50645, now running 50727. 
Please use mysql_upgrade to fix this error.

2019-09-28 0 评论 346 浏览
阅读全文

如果还没有安装好docker的话可以参考我之前的docker安装教程

Docker小白安装教程

1.先搜索一下mysql镜像列表

docker search mysql

出现以下mysql相关的镜像列表,列表中有Star数、Official(是否官方)等参数,默认按Star数排列。

NAME                              DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
mysql                             MySQL is a widely used, open-source relation…   8621                [OK]                
mariadb                           MariaDB is a community-developed fork of MyS…   2997                [OK]                
mysql/mysql-server                Optimized MySQL Server Docker images. Create…   637                                     [OK]
centos/mysql-57-centos7           MySQL 5.7 SQL database server                   63                                      
centurylink/mysql                 Image containing mysql. Optimized to be link…   61                                      [OK]
mysql/mysql-cluster               Experimental MySQL Cluster Docker images. Cr…   51                                      
deitch/mysql-backup               REPLACED! Please use http://hub.docker.com/r…   41                                      [OK]
tutum/mysql                       Base docker image to run a MySQL database se…   34                                      
bitnami/mysql                     Bitnami MySQL Docker Image                      33                                      [OK]
schickling/mysql-backup-s3        Backup MySQL to S3 (supports periodic backup…   28                                      [OK]
prom/mysqld-exporter                                                              23                                      [OK]

2019-09-26 0 评论 415 浏览
阅读全文

通俗解释

为什么

为什么需要docker呢?假设你想要部署一个服务,它可能依赖了JDK,又依赖了Mysql,又依赖了redis。这时候你告诉运维人员,你这个服务所需要的依赖有这些。这时候运维人员就需要去下载JDK,Mysql,Redis等等配置好一切,然后再拿到你的服务进行部署。而拥有了docker就不一样了,你只要将这个服务与JDK、Mysql、Redis一起打包成一个镜像丢给运维人员即可。运维人员直接运行这个打包好的镜像即可。

那docker为什么能做到这样呢?

原理

我们之前会在自己的windows下通过vmware模拟出linux的虚拟机。但vmware模拟出来的虚拟机是将整个计算机包括硬件和软件一起虚拟出来。而docker比vmware更轻量,它模拟出来的是最精简的一个linux内核,每个容器里面相当于一个虚拟的linux。我们可以在这个虚拟的linux中,进行我们定制化的操作和配置,然后封装成镜像。下次别人使用时就不用重复地进行配置和操作了。

开始安装

先卸载旧版本的docker

sudo yum remove docker \
              docker-client \
              docker-client-latest \
              docker-common \
              docker-latest \
              docker-latest-logrotate \
              docker-logrotate \
              docker-engine

如果之前有下载过docker的话,移除之前的版本以及相关的依赖。

2019-09-26 0 评论 394 浏览
阅读全文

CountDownLatch

解释:

CountDownLatch相当于一个门闩,门闩上挂了N把锁。只有N把锁都解开的话,门才会打开。怎么理解呢?我举一个赛跑比赛的例子,赛跑比赛中必须等待所有选手都准备好了,裁判才能开发令枪。选手才可以开始跑。CountDownLatch当中主要有两个方法,一个是await()会挂上锁阻塞当前线程,相当于裁判站在起始点等待,等待各位选手准备就绪,一个是countDown方法用于解锁,相当于选手准备好了之后调用countDown方法告诉裁判自己准备就绪,当所有人都准备好了之后裁判开发令枪。

代码:

public class TestCountDownLatch {
    public static void main(String[] args) {
        // 需要等待两个线程,所以传入参数为2
        CountDownLatch latch = new CountDownLatch(2);
        // 该线程运行1秒
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("1号选手准备就绪!用时1秒!");
                latch.countDown();
            }
        }).start();
        
        // 该线程运行3秒
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2号选手准备就绪!用时3秒!");
                latch.countDown();
            }
        }).start();
        
        try {
            System.out.println("请1号选手和2号选手各就各位!");
            // 主线程在此等待两个线程执行完毕之后继续执行
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 两个线程执行完毕后,主线程恢复运行
        System.out.println("裁判发枪,1号选手和2号选手开跑!");
    }
}

2019-09-22 3 评论 231 浏览
阅读全文

什么是同步容器?

同步容器通过synchronized关键字修饰容器保证同一时刻内只有一个线程在使用容器,从而使得容器线程安全。synchronized的意思是同步,它体现在将多线程变为串行等待执行。(但注意一点,复合操作不能保证线程安全。举例:A线程第一步获取尾节点,第二步将尾结点的值加1,但在A线程执行完第一步的时候,B线程删除了尾节点,在A线程执行第二步的时候就会报空指针)

什么是并发容器?

并发容器指的是允许多线程同时使用容器,并且保证线程安全。而为了达到尽可能提高并发,Java并发工具包中采用了多种优化方式来提高并发容器的执行效率,核心的就是:锁、CAS(无锁)、COW(读写分离)、分段锁。

2019-09-21 0 评论 318 浏览
阅读全文

简述

生产者消费者模式简而言之就是两种不同的线程分别扮演生产者和消费者,通过一个商品容器来生产商品和消费商品。生产者和消费者模式是学习多线程的好例子,下文就以四种不同实现的消费者生产者模式来理解多线程的编程。

以下的例子都共用消费者和生产者对象,而将商品容器(Stock)按照四种形式进行实现。

生产者:

生产者持有商品容器,并实现了Runnable接口,在run方法中无限循环地往商品容器stock中放入商品。

public class Producer implements Runnable{
    // 商品容器
    private Stock stock;

    public Producer(Stock stock) {
        this.stock = stock;
    }

    @Override
    public void run() {

        while (true) {
            // 随机生成商品 放入商品容器 stock中
            String product = "商品" + System.currentTimeMillis() % 100;
            System.out.println("生产了" + product);
            stock.put(product);
            // 休眠0.5秒 
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }
}

2019-09-20 0 评论 239 浏览
阅读全文

之前写了一篇文章关于四种线程池的解析。
但是对于FixedThreadPool与CachedThreadPool适用的场景其实还是比较模糊难以界定的。所以笔者今天通过设计大任务并发和小任务并发来验证FixedThreadPool与CachedThreadPool的适用场景。

首先我设计了一个任务基类,它通过计算圆周率来模拟cpu的密集计算、通过写日志到本地文件来模拟IO。
这两个方法都通过参数n来调整任务的大小规模。

2019-09-17 0 评论 259 浏览
阅读全文

首先我们先看一下获取四种线程池的代码:

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

可以发现这四种线程池都是由Executors类生成的。依次点开四个方法的内部实现发现,它们最终调用的都是同一个ThreadPoolExecutor()的构造器,而区别在于构造器的参数不同。我们来看下ThreadPoolExecutor的参数列表:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

正是由于这几个参数的不同导致了四种线程池的工作机制不同。参考源码对于参数的注释,我们列出参数的含义。

2019-09-13 0 评论 390 浏览
阅读全文

随机算法

先将服务器放进数组或者列表当中,通过JDK的随机算法,获取一个在数组有效范围内的下标,根据这个随机下标访问对应服务器。由概率统计理论可以得知,随着客户端调用服务器的次数增多,其实际效果越来越接近于平均分配请求到服务器列表中的每一台服务器。

代码:

 public String random(){
        String[] servers = {"server1", "server2", "server3"};
        // 将系统的当前时间作为种子获取一个随机器
        Random generator = new Random(System.currentTimeMillis());
        // 将服务器列表大小作为上界传入随机生成器
        int index = generator.nextInt(servers.length);
        return servers[index];
    }

2019-09-11 0 评论 326 浏览
阅读全文

思考

Spring已经占据我们Java开发框架中的半壁江山了,从一开始工作我们就在使用Spring。但是到底为什么要用Spring,可能很多人都没有去思考过这个问题?许多人可能也疲于应对需求,无暇思考这种看似理所当然的问题。那今天,我们就好好来讨论一下究竟为什么要使用Spring IOC?

逆向思考

假设在最初没有Spring IOC这种框架的时候,我们采用传统MVC的方式来开发一段常见的用户逻辑。

用户DAO

public class UserDAO {

    private String database;

    public UserDAO(String dataBase) {
        this.database = dataBase;
    }
    public void doSomething() {
        System.out.println("保存用户!");
    }

}

用户Service

public class UserService {

    private UserDAO dao;

    public UserService(UserDAO dao) {
        this.dao = dao;
    }
    public void doSomething() {
        dao.doSomething();
    }

}

用户Controller

public class Controller {

    public UserService service;

    public Controller(UserService userService) {
        this.service = userService;
    }

    public void doSomething() {
        service.doSomething();
    }

}

2019-09-11 3 评论 986 浏览
阅读全文

废话不多说,我们先做一个傻瓜版的IOC demo作为例子

自定义的Bean定义

class MyBeanDefinition{

    public String id;
    public String className;
    public String value;

    public MyBeanDefinition(String id, String className, String value) {
        this.id = id;
        this.className = className;
        this.value = value;
    }

}

2019-09-10 0 评论 334 浏览
阅读全文

适配器模式(Adapter)

定义:

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

通俗解释:

有一个中国人和一个日本人,他们都只会母语并且不想再学习其他语言(意味着不修改两个不兼容的类的代码)。但是他们之间又想进行交流,那怎么办呢?这时候如果有翻译的话就可以让中国人和日本人不用学习新的语言,即可互相交流。这里的中国人和日本人是互不兼容的两个接口,而翻译就是他们之间的适配器。

2019-08-28 0 评论 402 浏览
阅读全文

原型模式(Prototype)

定义:

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

通俗解释:

美术课上老师布置了一个素描作业。然后同学们让美术课代表画了素描作业。其他同学就可以拿着这份素描作业去复印店复印。其他同学完全不用知道怎么画这个素描。美术课代表画的素描作业就是已经创建实例。它作为原型。用这个原型复制出新素描的话,无需关心素描细节。

2019-08-28 0 评论 368 浏览
阅读全文

建造者模式(Builder)

定义:

将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。

通俗解释:

有一个包工头负责建房子。房子主要由地基,墙,屋顶构成。他设计了建房的图纸(先打地基,再砌墙,再盖屋顶)。包工头手下有工人,工人分为石材工和木头工。工人都会打造地基、墙还有屋顶。包工头想盖木房子的话,就把木材工叫过来,把建房的图纸给他。同理,想盖石头房子的话,就把石材工叫来,还是用原来那份建房的图纸给他(意味着不用修改代码即可建不同的房子)。

2019-08-28 0 评论 355 浏览
阅读全文

工厂方法

定义:

定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当 中。这符合六大原则中的依赖倒置原则。依赖抽象而不依赖实现。

通俗解释:

有一个顾客想去4S店试车,不管他去到哪一家4S店只要说我想试车。4S店就会给他一辆 车试开。这里的顾客就是用户类,说“我想试车”就是用户的调用方法。4S店就是抽象工 具体工厂有宝马4S店,奔驰4S店。车就是抽象商品,宝马车奔驰车就是具体商品。下一 顾客去沃尔沃的话,还是只需要说原来的那句话“我想试车”。不需要改变自己的话就可 以继续试其他4S店的车。

2019-08-27 0 评论 351 浏览
阅读全文

抽象工厂

定义:

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就
能得到同族的不同等级的产品的模式结构。

通俗解释:

有一个很爱吃汉堡可乐的顾客,他每次去肯德基或者麦当劳的时候,只需要跟服务员说我要吃汉堡薯条
可乐即可。这里的顾客就是访问类,快餐店就是抽象工厂,肯德基和麦当劳是工厂实现类。汉堡、可乐
是抽象产品族。肯德基生产具体产品奥尔良鸡腿堡和百事可乐。麦当劳生产具体产品麦辣鸡腿堡和可口
可乐。

2019-08-27 0 评论 481 浏览
阅读全文