java

大数值可以用下划线分割

临时文件操作

@RequestParam和@RequestBody区别

https://blog.csdn.net/bachbrahms/article/details/85010338

@RequestParam可以接收Url上的键值对参数(不区分get/post),也可以接收body -> form-data / x-www-form-urlencoded中的键值对
@RequestBody要求参数必须通过http body传递,一般后端通过自定义对象接收一组参数。如果只需要传递一个参数,如下所示,最好不要写成{"content":"xxx"}的格式,否则content={"content":"xxx"},需要额外做json解析。

零拷贝(Zero-copy)

https://mp.weixin.qq.com/s/mZujKx1bKl1T6gEI1s400Q

要点:

  1. 为了保证进程之间的内存隔离,引入虚拟内存的概念,它给每个进程一份独立连续的内存空间,给进程营造了一种独享主存的错觉。
  2. 每个进程都有一个页表(page table),维护虚拟内存和物理内存的地址映射
  3. Linux I/O 读写方式:Linux 提供了轮询、I/O 中断以及 DMA 传输这3种磁盘与主存之间的数据传输机制。
    a. 轮询方式是基于死循环对 I/O 端口进行不断检测。
    b. I/O 中断方式是指当数据到达时,磁盘主动向CPU发起中断请求,由CPU自身负责数据的传输过程
    c. DMA 传输则在 I/O 中断的基础上引入了 DMA 磁盘控制器,由DMA磁盘控制器负责数据的传输,降低了 I/O 中断操作对 CPU 资源的大量消耗(在数据copy上解放CPU)。
  4. 上下文切换:用户态(上)和内核态(下)的切换
  5. 零拷贝,就是通过一些机制来减少CPU copy次数和上下文切换次数。(两次DMA copy是省不掉的)
  6. java中的NIO:Channel(全双工-双向传输)相当于内核缓冲区,Buffer(分堆内存和堆外内存)相当于用户缓冲区
  7. Netty零拷贝完全是基于(Java 层面)用户态的,更多偏向于数据操作优化
传统I/O操作
Linux零拷贝对比
RocketMQ 和 Kafka 对比

不同字符串MD5值会冲撞吗

确实存在多个字符串对应一个MD5的情况,这种情况叫做哈希冲撞。毕竟MD5只是摘要,不论多长的字符串都能散列到一个固定的长度,原字符串的长度是不限的,如果不出现冲撞,那就不符合客观规律了。
那再看回来,既然为什么MD5会出现重复,为什么还会选择这个来进行密码的加密呢?
首先,一般的密码长度都会有长度的限制,而且基本都会小于MD5散列结果的长度,这就让MD5唯一有了条件。而MD摘要经过了几代的演进发展到MD5,其主要优化的,就是散列的分散性,这个分散性的体现,就是避免冲撞的发生。所以使用在密码加密上还是有可靠保障的。

代码技巧

Map<String, Long> map = list.stream().collect(Collectors.groupingBy(Student::getGroupId, Collectors.counting()));

ArrayList<String> list = Lists.newArrayList("1", "1", "3", "5", "5");
Map<String, Long> map = list.stream().collect(Collectors.groupingBy((e) -> e, Collectors.counting()));

判断奇偶

i % 2 ==1    //错误,负数%2的余数是-1。eg:-3 %2 = -1
i % 2 == 0   //正确

计算过程中精度溢出

Long 1 = 24 * 60 * 60 * 60 * 1000;  //错误

三元运算符碰上不同类型版变量

int a = 0;
char b = 'b';
System.out.println(true ? b : a);

秒杀

方案1:直接扣库存。set stock = stock - n where stock - n > 0
方案2:乐观锁,先查库存再扣库存。select stock;set stock = stock - n where version = #{version}
方案3:在前两种方案基础上,前置redis限流,减轻数据库压力
方案4:在方案3基础上,使用MQ异步处理扣库存和下订单等操作,借助其他入口通知用户处理结果
注:库存查询的SQL并发度很高,可以将查/改redis,然后定时将redis数据同步到数据库

redis限流--令牌桶机制

借助redis的list结构,leftpop获取令牌,key=redis_limit,value=UUID。
另起定时任务,每几秒往令牌桶rightPush几个令牌(要设置最大令牌数)。
或者延时更新,获取令牌时先更新令牌

maven

貌似:snapshot版本,同一个坐标版本可以安装多次,虽然仓库中左边一样,但内部的jar、pom等名称的时间戳等发生了变化。对于修复bug来说,没必要每次都对坐标版本进行升级。
release版本,同一个坐标版本不能多次安装,必须每次都进行升级。
maven -U xxx会强制更新maven依赖,但是貌似有时不好使

String和intern()

编码

peek()

主要作用是调试,是一个中间操作,后边必须有种植操作,否则peek()不生效。
List<A> list = Lists.newArrayList(new A("1 "), new A(" 2 "), new A("3"));
list.stream().peek(e -> e.setName(e.getName().trim()));    -- peek()不生效
System.out.println(JsonUtil.toJsonStr(list));

List<A> list = Lists.newArrayList(new A("1 "), new A(" 2 "), new A("3"));
list.stream().peek(e -> e.setName(e.getName().trim())).collect(Collectors.toList());    -- peek()生效
System.out.println(JsonUtil.toJsonStr(list));

前端缓存问题解决

前后端分离的场景:前端项目每次打包上传oss时,在oss上都根据时间戳创建新的资源目录。
如果前端页面跳转或者页面内接口调用失败时,就去oss查找,如果发现有更新的目录,就认为需要强制刷新压面

正则

  1. 叠词分割
 String s = "张三@@@李四YYY王五*****王尼玛";
 String[] split = s.split("(.)\\1+");    //注:\1或$1表示分组的后向引用,表示前边的分组重复1次,\1+ 表示前边的分组重复多次
 s.replaceAll("(.)\\1+", "$1");  //叠词浓缩为单词

WebSocket

WebSocket是应用层协议(跟Http同级),是TCP/IP协议的子集,通过HTTP/1.1协议的101状态码进行握手。也就是说,WebSocket协议的建立需要先借助HTTP协议,在服务器返回101状态码之后,就可以进行websocket全双工双向通信了,就没有HTTP协议什么事情了。
WebSocket 是一个独立的基于 TCP 的协议,它与 HTTP 之间的唯一关系就是它的握手请求可以作为一个升级请求(Upgrade request)经由 HTTP 服务器解释
http请求-应答模式是“半双工”模式,WebSocket是“全双工”模式。
WebSocket 协议是由 HTML5 规范定义的,原本是为了浏览器而设计的,可以避免同源的限制,浏览器可以与任意服务端通信,现代浏览器基本上都已经支持 WebSocket。
虽然 WebSocket 原本是被定义在 HTML5 中,但它也适用于移动端,尽管移动端也可以直接通过 Socket 与服务端通信,但借助 WebSocket,可以利用 80(HTTP) 或 443(HTTPS)端口通信,有效的避免一些防火墙的拦截。

微信和支付宝

微信:公众号+小程序。openid在不同公众号下唯一。卡包一次只能加一张,每次都需要授权,授权后会回调给自己的服务端
支付宝:生活号+小程序。userId在所有生活号唯一。卡包可以同步将卡券信息发送给支付宝,无需授权,无需回调交互。

base64原理

用64个可以打印的字符来表示文字,图片,二进制文件等。
26*2=52个英文字母,10个数字,还有"+"和"/"。
编码后,数据长度大约增加33%,因为要用8位(bit)表示之前的6位(bit)。
eg:abc => 3个字节共24位(bit)长度,要拆成4段,每段6位(bit),然后前边补两位0扩展成8位。所以编码后变为"YWJj"

非空判断

java.util.Objects.requireNonNull(node, "参数不能为空");

Stream

参见://www.greatytc.com/p/dd5fb725331b
Stream需要一个源 + 0或多个中间处理(stage) + 1个终端处理
不存储数据,不是一个数据结构。
中间处理:map、filter、reduce、sort等。分有状态(有上下元素的依赖关系,需要遍历所有元素:sort、filter等)和无状态(map等)两种。每一次中间处理,如果返回Stream,则都是创建了一个新的Stream(继承AbstractPipeline类,实现Stream接口)。

惰性处理思想(终端操作会触发Sink回调,内部有短路操作)

注:所有元素都走完filter这个StatefulOp有状态操作后,才会进入下一个pipeline(map) StatelessOp操作,终端操作的代码中执行Sink回调。

StatelessOp:无状态操作;StatefulOp:有状态操作

函数式接口和lambda表达式

只有一个抽象方法的接口,被视为函数式接口,比如Runnable接口只有run()方法。函数可以作为一个对象存在。
函数式接口使用匿名内部类实现时不太优雅,可以使用lambda的方式简化。
jdk8预定义了几个常用的函数式接口:Consumer,Supplier,Function,Predicate
jdk7中新增的invokedynamic虚拟机指令就是为了支持lambda。
lambda运行时会创建匿名内部类对象,命名规则如下:

lambda表达式的使用,最终都能追溯到一个函数式接口。

eg1:
Runnable r = () -> { ... };     => 追溯到Runnable
eg2:
list.forEach((a) -> System.out.println(a));    => 追溯到Consumer
eg3:
list.stream().sorted((a, b) -> a.length() - b.length()).collect(toList());=>追溯到Comparator

方法引用

用来简化lambda的

错误码枚举类

好处:长链路快速追踪。eg:用户模块错误码003xxx

定义model转换包统一存放model和DTO的装换

使用MapStruct:https://www.bbsmax.com/A/8Bz8PVq6zx/

ThreadLocal在tomcat等容器中是有残留的

可以用postman发100次请求验证

hsf使用

对接EDAS(企业级分布式应用服务)时其官方提供私服仓库,可以下载spring-cloud-starter-hsf和spring-cloud-starter-pandora
服务方:@HSFProvider(serviceInterface = HelloService.class, serviceVersion = "1.0.0")
销方方:@HSFConsumer(clientTimeout = 3000, serviceVersion = "1.0.0")

状态机

根据状态推进业务逻辑的流转

select、poll和epoll区别

epoll是select和poll的升级。
select:维护阻塞队列,每次socket收到消息需要唤醒进程,进程都要去遍历socket列表,且select维护等待队列和阻塞是绑定操作
epoll:额外有个就绪队列,被唤醒的socket会自动维护到就绪队列,这线程唤醒后就不需要遍历大的socket列表;且它维护等待队列和阻塞是两步独立操作。既然epoll将“维护监视队列”和“进程阻塞”分离,也意味着需要有个数据结构来保存监视的socket。至少要方便的添加和移除,还要便于搜索,以避免重复添加。epoll使用了红黑树作为索引结构

发票前端项目

前端域名绑定到CDN上,CDN配置oss的回源地址。每次访问CDN时,如果没有缓存数据则去oss上访问index.html文件

状态机作用

幂等性处理
状态驱动事件流转

ConcurrentHashMap(1.7)

Segment数组套HashEntry数组:两层数组结构,HashEntry本身是链表节点[key/value/hash/next],需要两次hash定位才能找到HashEntry链表头结点。
注意:不论hashmap还是ConcurrentHashMap,都是单向链表(AQS中的condition队列也是单向链表)

put()

concurrentHashMap.put()实际上是segment.put(),而Segment类继承了ReentrantLock类,可以拿到ReentrantLock类的成员变量Sync(继承AbstractQueuedSynchronizer)对象进行lock()和release()。
put()先计算hash(hashcode经过一系列位运算得到)进一步拿到Segment;
HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); => segment.tryLock()失败最多自旋64次(单核CPU自旋1次),如果能获取锁失败则进行lock()加锁阻塞。
(tab.length - 1) & hash => HashEntry[]的index => HashEntry链表头结点 => new HashEntry挂到尾部

size()

不加锁去尝试多次计算ConcurrentHashMap的size(每个Segment的count变量求和),最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的;否则给每个Segment加锁,然后计算ConcurrentHashMap的size返回

ConcurrentHashMap(1.8)

这个结构和 JDK1.8 版本中的 Hashmap 的实现结构基本一致,但是为了保证线程安全性,
ConcurrentHashMap 的实现会稍微复杂一下。锁粒度减小,但是并发度不变(默认都是16)

put()

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))   // no lock when adding to empty bin
                    break;                  
            }
            else if ((fh = f.hash) == MOVED)  //头结点hash=-1表示正在扩容
                tab = helpTransfer(tab, f);    //帮助并发扩容
            else {
                V oldVal = null;
                synchronized (f) {  //锁的是头节点
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {  //头结点hash>0表示链表
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key, value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {  //红黑树
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)  //链表长度>8则尝试转为红黑树:内部判断Node[].length > 64才会转,否则2倍扩容
                        treeifyBin(tab, i);  
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

并发扩容

每个线程最少处理16个bucket,即Node[]上连续16段长度(边界划分),扩容负载因子0.75
高低位迁移:HighNode hn和LowNode ln(很巧妙的"(n-1)&hash"的算法)

//nextTab:扩容后的新Node[];tab:扩容前的旧Node[]
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
setTabAt(nextTab, i, ln);    //迁移时,旧数组的低位链表迁移到新数组相同角标位置
setTabAt(nextTab, i + n, hn);   //迁移时,旧数组的高位链表迁移到新数组(i+n)角标位置
setTabAt(tab, i, fwd);   //老数组当前角标位置放一个转发节点ForwardingNode(hash=-1),concurrentHashMap.get(key)时做转发

size()

1.7在size()方法调用时才统计
1.8在put()方法最后就会调用addCount(1)进行统计,调用size()时:size = baseCount + 计数表(CounterCell[]初始长度为2) value的求和。计数表(CounterCell[ ]) 是为了解决数量并发问题引入的,如果没有并发则基于baseCount自增,否则基于CounterCell的value值

ArrayBlockingQueue

底层数组,通过putIndex和takeIndex的循环实现FIFO,通过notEmpty和notFull的await()/signal()实现take()和put()的阻塞

静态和动态网站的区别

有没有用数据库

pipeline在项目中的应用

tomcat、netty、jenkins都有对pipeline的应用

java实现LRU算法

双向链表,维护总容量。增加元素时,如果超过总容量则删除尾结点,并将元素放到头结点;查询和修改元素时,将元素提到头结点。

定时器--jdk自带的Timer和定时任务线程池

Timer:单线程,如果同时有多个任务执行,则前边任务的阻塞会导致后边的任务不能准时执行。内部维护一个优先级Queue,将最近要执行的任务放在头部;循环获取头部任务,如果没有,则队列阻塞;如果有任务,但不到执行时间,则wait(long time)等待线程醒来。如果Timer执行异常,则所有任务都失败。时间计算依赖本地时钟,所以如果系统时间被调整,可能导致任务无法执行或延期执行。
定时任务线程池:可以调整线程池大小,实现多个定时任务同时执行。不依赖系统时钟,而是nanoTime--并不是系统时间的纳秒级计数,而是表示系统已过去的时间,所以不依赖系统时钟。

nanoTime和currentTimeMillis

测试发现:把系统时间调快1小时零5分,nanoTime只增加了300秒,而currentTimeMillis增加了1小时零5分

ArrayList扩容机制

1.5倍扩容,Arrays.copyOf();

堆外内存

netty,nio,kafka,RocketMQ,ehcache都有使用。
减少GC压力和数据复制(物理内存和Heap内存的复制),只有Full GC才会

ThreadLocal和InheritableThreadLocal

public static void main(String[] args) {
        //InheritableThreadLocal会自动从父线程继承
        InheritableThreadLocal<Object> local1 = new InheritableThreadLocal<>();
        local1.set("abc");
        new Thread(()->{
            System.out.println(local1.get());   //abc
        }).start();

        ThreadLocal<Object> local2 = new ThreadLocal<>();  // => null
        local2.set("cba");
        new Thread(()->{
            System.out.println(local2.get());   //null
        }).start();
    }

注:如果是线程池的方式,需要使用阿里的TransmittableThreadLocal代替ThreadLocal

json反序列化碰上枚举

数组copy

int[] Arrays.copyOf(int[] original, int newLength)
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

HashMap线程不安全

1.7采用头插法,在扩容时可能形成死环;1.8采用尾插法,解决了该问题,但是仍遗留了大量问题:

图中:
modCount和size都是成员变量,如果多线程并发操作,不安全。
如果两个线程同时做完了if==null的判断,new Node()之后会发生覆盖。

CAS缺点

ABA问题 => AtomicStampedReference
锁竞争可能非常激烈 => 自适应自旋
引用数据类型只能针对引用值做更改,具体值不行

处理大文本文件

设置jvm参数:-Xms68m -Xms128m => 生成6G的txt文件
write:

public static void createBigFile() throws Exception {
        File file = new File("/Users/xuanxushuai/logs/big_file.txt");
        FileWriter fileWriter = new FileWriter(file);
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
        long start = System.currentTimeMillis();
        String str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
        for (int i = 0; i < 10000000; i++) {
            bufferedWriter.write(str + i);  //这里会频繁垃圾回收
            bufferedWriter.newLine();
        }
        bufferedWriter.flush();
        bufferedWriter.close();
        long end = System.currentTimeMillis();
        System.out.println("执行结束,耗时:" + (end - start) + "ms");  //18000ms
}

read:

public static void readBigFile() throws Exception {
        File file = new File("/Users/xuanxushuai/logs/big_file.txt");
        BufferedReader reader = new BufferedReader(new FileReader(file));
        int num = 0;
        reader.lines().filter(e -> e.endsWith("9900")).forEach(System.out::println);    //BufferedReader的lines()方法返回Stream对象,方便代码书写;或者使用传统readLine()方法一样不会OOM,都是一行一行的处理
        System.out.println("包含9900的字符串,共有行数:" + num);
}

Stream操作

skip

越过N个元素,配合limit可以做分页

        List<User> list = new ArrayList();
        list.add(new User("tom", 23));
        list.add(new User("aci", 20));
        list.add(new User("fruke", 33));
        list.add(new User("jack", 15));

        list.stream().skip(1).limit(2).forEach(System.out::println);

flatMap

        List<String> list = Arrays.asList("1,2,3", "c,b,a");
        list.stream().map(e -> e.split(",")).forEach(System.out::println);  //得到两个String[]
        list.stream().flatMap(e -> Arrays.stream(e.split(","))).forEach(System.out::println);   //先得到两个子Stream,然后合并成一个Stream。得到6个元素:123456

reduce

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        System.out.println(list.stream().reduce((a, b) -> a + b).orElse(0));    //可以转为Integer::sum
        System.out.println(list.stream().reduce(Integer::sum).orElse(0));   //reduce的返回值是Optional<T>,不要直接get(),可能抛异常NoSuchElementException
        System.out.println(list.stream().reduce(0, Integer::sum));  //两参的reduce方法不需要orElse()或get(),第一个参数既指定泛型,又参与orElse

        List<BigDecimal> list2 = Arrays.asList(new BigDecimal("10"), new BigDecimal("5"), new BigDecimal("15"));
        System.out.println(list2.stream().reduce((a, b) -> a.add(b)).orElse(BigDecimal.ZERO));  //可以转为BigDecimal::add
        System.out.println(list2.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
        System.out.println(list2.stream().reduce(BigDecimal.ZERO, BigDecimal::add));

collect

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 3, 2);
        //System.out.println(list.stream().collect(Collectors.toMap(e -> e, e -> e + "_001")));//=> {1=1_001, 2=2_001, 3=3_001, 4=4_001, 5=5_001}
        System.out.println(list.stream().collect(Collectors.summingInt(e -> e)));
        System.out.println(list.stream().mapToInt(e -> e).sum());   //stream => IntStream后,多了sum(),average()等
        System.out.println(list.stream().map(Object::toString).collect(Collectors.joining(",")));   //joining要求是字符串的join
        System.out.println(list.stream().map(Object::toString).collect(Collectors.joining(",", "[", "]")));   //[1,2,3,4,5,6,7,8,9,10]
        Map<Integer, List<Integer>> map = list.stream().collect(Collectors.groupingBy(e -> e.intValue()));  //自定义分组条件
        System.out.println(map);    //{1=[1], 2=[2, 2], 3=[3, 3], 4=[4], 5=[5]}
        Map<Boolean, List<Integer>> map2 = list.stream().collect(Collectors.partitioningBy(e -> e > 3));    //分成2部分
        System.out.println(map2);   //{false=[1, 2, 3, 3, 2], true=[4, 5]}

测试环境远程调试

如果Host写的是SLB的转发域名,需要SLB额外开启Port(本例8000)端口;如果Host直接是ECS的公网ip则不需要SLB配置。
项目启动时,额外添加-agentlib命令,会额外占用一个Port(本例8000)。

image.png

Stream.flatmap

https://blog.csdn.net/liyantianmin/article/details/96178586

BigDecimal值比较

public static void main(String[] args) {
        System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1.00")));   //false
        System.out.println(new BigDecimal("1.0").compareTo(new BigDecimal("1.00")) == 0);   //true
}

多线程碰到的坑




原因:@Async是springAOP搞的,比直接开线程中间多了一步,所以pool.submit(() -> testX(ContextUtil.getCityId())); 是正确的

List分割

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        List<List<Integer>> partition = com.google.common.collect.Lists.partition(list, 5);
        System.out.println(partition);  //[[1, 2, 3, 4, 5], [6, 7]]
}

集合根据对象的某个属性去重

Collection<ShopStock> distinct = list.stream().collect(Collectors.toMap(ShopStock::getGoodsId, v -> v, (k1, k2) -> k1)).values();

对象排序

list.stream().sorted(Comparator.comparing(GoodsProductionTimeDTO::getSkuCode).thenComparing(GoodsProductionTimeDTO::getWarehouseCode)).collect(toList());

复杂排序

要求:先按照skuCode排序(转Long类型),再按照cityid排序,再按照warehouseCode排序
public static void main(String[] args) {
        GoodsProductionTimeDTO dto1 = new GoodsProductionTimeDTO();
        dto1.setSkuCode("400");
        dto1.setCityId(17);
        dto1.setWarehouseCode("124");

        GoodsProductionTimeDTO dto2 = new GoodsProductionTimeDTO();
        dto2.setSkuCode("204");
        dto2.setCityId(17);
        dto2.setWarehouseCode("123");

        GoodsProductionTimeDTO dto3 = new GoodsProductionTimeDTO();
        dto3.setSkuCode("204");
        dto3.setCityId(18);
        dto3.setWarehouseCode("123");

        GoodsProductionTimeDTO dto4 = new GoodsProductionTimeDTO();
        dto4.setSkuCode("400");
        dto4.setCityId(18);
        dto4.setWarehouseCode("023");

        GoodsProductionTimeDTO dto5 = new GoodsProductionTimeDTO();
        dto5.setSkuCode("204");
        dto5.setCityId(18);
        dto5.setWarehouseCode("023");

        List<GoodsProductionTimeDTO> list = Lists.newArrayList(dto1, dto2, dto3, dto4, dto5);

        list = list.stream().sorted(Comparator.comparing(GoodsProductionTimeDTO::getSkuCode, (a, b) -> Long.valueOf(a).compareTo(Long.valueOf(b)))
                .thenComparing(GoodsProductionTimeDTO::getCityId)
                .thenComparing(GoodsProductionTimeDTO::getWarehouseCode))
                .collect(toList());

        list.forEach(System.out::println);
    }

    //等价于
    list = list.stream().sorted(Comparator.comparing(GoodsProductionTimeDTO::getSkuCode, Comparator.comparing(Long::valueOf))
                .thenComparing(GoodsProductionTimeDTO::getCityId)
                .thenComparing(GoodsProductionTimeDTO::getWarehouseCode))
                .collect(toList());

//注意:不要自己sort返回0和-1,因为会影响第二步排序准确性(没弄明白)

异常堆栈丢失异常细节

Java层层上抛异常过程中,会丢失原始报错的具体细节,不能在最外层catch,需要在内部catch
eg:内部层层throws,外部统一catch

[ERROR][2021-08-03T22:36:40.831+0800][com.cxyx.iscm.base.interceptor.ErrorLogProviderFilter:21] _undef||_msg=com.cxyx.iscm.base.constants.exception.BusinessException: 系统异常:null||traceid=0aa02d82610954788101b2ed00006886||spanid=0820258211c582b2||exception=java.lang.RuntimeException: com.cxyx.iscm.base.constants.exception.BusinessException: 系统异常:null
BusinessException(errcode=10500, errmsg=系统异常:null, formatArgs=null)
    at com.cxyx.iscm.goods.provider.GoodsSkuProviderImpl.submitSku(GoodsSkuProviderImpl.java:835)  //这里是最外层方法的位置,具体报错位置在方法内部
    at org.apache.dubbo.common.bytecode.Wrapper12.invokeMethod(Wrapper12.java)
    at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
    at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:84)
    at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:56)

内部catch

[ERROR][2021-08-03T23:16:39.006+0800][com.cxyx.iscm.goods.service.impl.GoodsSkuServiceImpl:1856] _undef||_msg=抛出了异常||traceid=0aa02d8261095dd67b7e698c00006c86||spanid=08800d00bcf45f18||exception=com.cxyx.iscm.base.constants.exception.BusinessException: sku已存在[11033148267180],请不要重复提交
    at com.cxyx.iscm.goods.service.impl.GoodsSkuServiceImpl.submitSku(GoodsSkuServiceImpl.java:1824) //这里是真正异常的地方
    at com.cxyx.iscm.goods.service.impl.GoodsSkuServiceImpl$$FastClassBySpringCGLIB$$b8ebfa1e.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

key保持index顺序的Map

    public static void main(String[] args) {
        String str = "ccacb";
        Map<Character, Integer> map = new LinkedHashMap<>();
        char[] chars = str.toCharArray();
        for (char key : chars) {
            Integer value = map.get(key);
            map.put(key, value == null ? 1 : value + 1);
        }
        System.out.println(map);   // => {c=3, a=1, b=1}
    }

String转集合

    public static void main(String[] args) {
        String str = "ccacb";
        Set<Character> list = str.chars().mapToObj(e -> (char) e).collect(Collectors.toSet());
        list.forEach(System.out::print);    //  =>  abc
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352