# 072. 基于 storm 完成 LRUMap 中 topn 热门商品列表的算法讲解与编写

# top n 简易算法

public static void main(String[] args) {
    /**
     * top n 简易算法:手写思路
     * top 3 列表: 5、3、1
     * 比如来一个 6,那么比 5 大,把 5 所在位置往后移位。最后把 6 放到 第一位,
     * 变成:6、5、3
     */

    int n = 10;
    int[] topn = new int[n];

    // 循环 n 次,模拟有这么多数据需要计算
    for (int i = 0; i < 100; i++) {
        int randomNum = RandomUtils.nextInt(100);
//            int randomNum = i;
        // 每次都从第一个开始比较
        for (int j = 0; j < topn.length; j++) {
            int target = topn[j];
            if (randomNum > target) {
                // 从当前位置往后移动一位
                System.arraycopy(topn, j, topn, j + 1, n - (j + 1));
                topn[j] = randomNum;
                break;
            }
        }
    }
    // 某一次的输出结果 [99, 99, 99, 99, 99, 96, 93, 93, 91, 91]
    System.out.println(Arrays.toString(topn));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 商品列表计算 top n

public class ProductCountBolt extends BaseRichBolt {
    private LRUMap<Long, Long> countMap = new LRUMap(100);

    @Override
    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
        // 启动一个线程,1 分钟计算一次
        new Thread(new Runnable() {
            @Override
            public void run() {
                int n = 3;

                Map.Entry<Long, Long>[] top = new Map.Entry[n];
                while (true) {
                    Arrays.fill(top, null);
                    Utils.sleep(6000);
                    for (Map.Entry<Long, Long> entry : countMap.entrySet()) {
                        long value = entry.getValue();
                        for (int i = 0; i < top.length; i++) {
                            Map.Entry<Long, Long> targetObj = top[i];
                            if (targetObj == null) {
                                top[i] = entry;
                                break;
                            }
                            long target = targetObj.getValue();
                            if (value > target) {
                                // 使用数组 + 系统原生 copy ,性能很棒
                                // 而且 top n 不大的话,更快
                                System.arraycopy(top, i, top, i + 1, n - (i + 1));
                                top[i] = entry;
                                break;
                            }
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + Arrays.toString(top));
                }
            }
        }).start();
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

启动 storm 后,来测试统计是否正确

访问:http://eshop-cache03/product?method=product&productId=11&shopId=1

这里为了能让商品 id 能落到同一个 task 上,选择了商品 id:2、5、8、11 总共 4个进行访问次数测试

统计输出如下

商品 5,次数 1
商品 2,次数 1
Thread-41:[null, null, null]
Thread-40:[null, null, null]
Thread-39:[5=1, 2=1, null]
// 后面的非 39 线程的我就删除了,为了看着清晰一点
商品 2,次数 2
Thread-39:[2=2, 5=1, null]
Thread-39:[2=2, 5=1, null]
Thread-39:[2=2, 5=1, null]
Thread-39:[2=2, 5=1, null]
Thread-39:[2=2, 5=1, null]
Thread-39:[2=2, 5=1, null]
商品 8,次数 1
Thread-39:[2=2, 5=1, 8=1]   // top 列表中 3 个都满了
Thread-39:[2=2, 5=1, 8=1]
商品 11,次数 1               // 再来一个 11 ,看看效果
Thread-39:[2=2, 5=1, 8=1]  // 结果没有看到 11,这个很正常,访问次数都不比现在的大,所以没有入围
商品 11,次数 2
Thread-39:[2=2, 11=2, 5=1] // 当访问两次后,商品 8 被挤掉了
商品 11,次数 3
Thread-39:[11=3, 2=2, 5=1]
Thread-39:[11=3, 2=2, 5=1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

从日志测试来看,这个算法是没有问题的

# 另外一种简单的 top n 算法

这种 topn 算法没有那么麻烦,思路也很清晰

public static void topn() {
    int n = 10;
    int[] topn = new int[n];

    // 循环 n 次,模拟有这么多数据需要计算
    for (int i = 0; i < 100; i++) {
        int randomNum = RandomUtils.nextInt(100);
        // int randomNum = i;
        for (int j = 0; j < topn.length; j++) {
            int target = topn[j];
            if (randomNum >= target) {
                topn[j] = randomNum;
                randomNum = target;
            }
        }
    }
    System.out.println(Arrays.toString(topn));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18