大数值可以用下划线分割
临时文件操作
@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
要点:
- 为了保证进程之间的内存隔离,引入虚拟内存的概念,它给每个进程一份独立连续的内存空间,给进程营造了一种独享主存的错觉。
- 每个进程都有一个页表(page table),维护虚拟内存和物理内存的地址映射
- 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)。- 上下文切换:用户态(上)和内核态(下)的切换
- 零拷贝,就是通过一些机制来减少CPU copy次数和上下文切换次数。(两次DMA copy是省不掉的)
- java中的NIO:Channel(全双工-双向传输)相当于内核缓冲区,Buffer(分堆内存和堆外内存)相当于用户缓冲区
- Netty零拷贝完全是基于(Java 层面)用户态的,更多偏向于数据操作优化
不同字符串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查找,如果发现有更新的目录,就认为需要强制刷新压面
正则
- 叠词分割
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)。
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
}