mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-26 05:06:42 +08:00
1519 lines
32 KiB
HTML
1519 lines
32 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>答疑篇:加餐篇思考题答案合集.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/00 开篇词 业务代码真的会有这么多坑?.md.html">00 开篇词 业务代码真的会有这么多坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/01 使用了并发工具类库,线程安全就高枕无忧了吗?.md.html">01 使用了并发工具类库,线程安全就高枕无忧了吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/02 代码加锁:不要让“锁”事成为烦心事.md.html">02 代码加锁:不要让“锁”事成为烦心事.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/03 线程池:业务代码最常用也最容易犯错的组件.md.html">03 线程池:业务代码最常用也最容易犯错的组件.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/04 连接池:别让连接池帮了倒忙.md.html">04 连接池:别让连接池帮了倒忙.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/05 HTTP调用:你考虑到超时、重试、并发了吗?.md.html">05 HTTP调用:你考虑到超时、重试、并发了吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/06 2成的业务代码的Spring声明式事务,可能都没处理正确.md.html">06 2成的业务代码的Spring声明式事务,可能都没处理正确.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/07 数据库索引:索引并不是万能药.md.html">07 数据库索引:索引并不是万能药.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/08 判等问题:程序里如何确定你就是你?.md.html">08 判等问题:程序里如何确定你就是你?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/09 数值计算:注意精度、舍入和溢出问题.md.html">09 数值计算:注意精度、舍入和溢出问题.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/10 集合类:坑满地的List列表操作.md.html">10 集合类:坑满地的List列表操作.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/11 空值处理:分不清楚的null和恼人的空指针.md.html">11 空值处理:分不清楚的null和恼人的空指针.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/12 异常处理:别让自己在出问题的时候变为瞎子.md.html">12 异常处理:别让自己在出问题的时候变为瞎子.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/13 日志:日志记录真没你想象的那么简单.md.html">13 日志:日志记录真没你想象的那么简单.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/14 文件IO:实现高效正确的文件读写并非易事.md.html">14 文件IO:实现高效正确的文件读写并非易事.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/15 序列化:一来一回你还是原来的你吗?.md.html">15 序列化:一来一回你还是原来的你吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/16 用好Java 8的日期时间类,少踩一些“老三样”的坑.md.html">16 用好Java 8的日期时间类,少踩一些“老三样”的坑.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/17 别以为“自动挡”就不可能出现OOM.md.html">17 别以为“自动挡”就不可能出现OOM.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/18 当反射、注解和泛型遇到OOP时,会有哪些坑?.md.html">18 当反射、注解和泛型遇到OOP时,会有哪些坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/19 Spring框架:IoC和AOP是扩展的核心.md.html">19 Spring框架:IoC和AOP是扩展的核心.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/20 Spring框架:框架帮我们做了很多工作也带来了复杂度.md.html">20 Spring框架:框架帮我们做了很多工作也带来了复杂度.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/21 代码重复:搞定代码重复的三个绝招.md.html">21 代码重复:搞定代码重复的三个绝招.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/22 接口设计:系统间对话的语言,一定要统一.md.html">22 接口设计:系统间对话的语言,一定要统一.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/23 缓存设计:缓存可以锦上添花也可以落井下石.md.html">23 缓存设计:缓存可以锦上添花也可以落井下石.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/24 业务代码写完,就意味着生产就绪了?.md.html">24 业务代码写完,就意味着生产就绪了?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/25 异步处理好用,但非常容易用错.md.html">25 异步处理好用,但非常容易用错.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/26 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?.md.html">26 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/27 数据源头:任何客户端的东西都不可信任.md.html">27 数据源头:任何客户端的东西都不可信任.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/28 安全兜底:涉及钱时,必须考虑防刷、限量和防重.md.html">28 安全兜底:涉及钱时,必须考虑防刷、限量和防重.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/29 数据和代码:数据就是数据,代码就是代码.md.html">29 数据和代码:数据就是数据,代码就是代码.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/30 如何正确保存和传输敏感数据?.md.html">30 如何正确保存和传输敏感数据?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/31 加餐1:带你吃透课程中Java 8的那些重要知识点(一).md.html">31 加餐1:带你吃透课程中Java 8的那些重要知识点(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/32 加餐2:带你吃透课程中Java 8的那些重要知识点(二).md.html">32 加餐2:带你吃透课程中Java 8的那些重要知识点(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/33 加餐3:定位应用问题,排错套路很重要.md.html">33 加餐3:定位应用问题,排错套路很重要.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/34 加餐4:分析定位Java问题,一定要用好这些工具(一).md.html">34 加餐4:分析定位Java问题,一定要用好这些工具(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/35 加餐5:分析定位Java问题,一定要用好这些工具(二).md.html">35 加餐5:分析定位Java问题,一定要用好这些工具(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/36 加餐6:这15年来,我是如何在工作中学习技术和英语的?.md.html">36 加餐6:这15年来,我是如何在工作中学习技术和英语的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/37 加餐7:程序员成长28计.md.html">37 加餐7:程序员成长28计.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/38 加餐8:Java程序从虚拟机迁移到Kubernetes的一些坑.md.html">38 加餐8:Java程序从虚拟机迁移到Kubernetes的一些坑.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(一).md.html">答疑篇:代码篇思考题集锦(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(三).md.html">答疑篇:代码篇思考题集锦(三).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(二).md.html">答疑篇:代码篇思考题集锦(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/Java 业务开发常见错误 100 例/答疑篇:加餐篇思考题答案合集.md.html">答疑篇:加餐篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:安全篇思考题答案合集.md.html">答疑篇:安全篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:设计篇思考题答案合集.md.html">答疑篇:设计篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/结束语 写代码时,如何才能尽量避免踩坑?.md.html">结束语 写代码时,如何才能尽量避免踩坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>答疑篇:加餐篇思考题答案合集</h1>
|
||
|
||
<p>你好,我是朱晔。</p>
|
||
|
||
<p>今天,我们继续一起分析这门课的“不定期加餐”篇中 5 讲的课后思考题。这些题目涉及了 Java 8 基础知识、定位和分析应用问题相关的几大知识点。</p>
|
||
|
||
<p>接下来,我们就一一具体分析吧。</p>
|
||
|
||
<h3>加餐 1 | 带你吃透课程中 Java 8 的那些重要知识点(一)</h3>
|
||
|
||
<p>问题:对于并行流部分的并行消费处理 1 到 100 的例子,如果把 forEach 替换为 forEachOrdered,你觉得会发生什么呢?</p>
|
||
|
||
<p>答:forEachOrdered 会让 parallelStream 丧失部分的并行能力,主要原因是 forEach 遍历的逻辑无法并行起来(需要按照循序遍历,无法并行)。</p>
|
||
|
||
<p>我们来比较下面的三种写法:</p>
|
||
|
||
<pre><code>//模拟消息数据需要1秒时间
|
||
|
||
|
||
|
||
private static void consume(int i) {
|
||
|
||
|
||
|
||
try {
|
||
|
||
|
||
|
||
TimeUnit.SECONDS.sleep(1);
|
||
|
||
|
||
|
||
} catch (InterruptedException e) {
|
||
|
||
|
||
|
||
e.printStackTrace();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
System.out.print(i);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//模拟过滤数据需要1秒时间
|
||
|
||
|
||
|
||
private static boolean filter(int i) {
|
||
|
||
|
||
|
||
try {
|
||
|
||
|
||
|
||
TimeUnit.SECONDS.sleep(1);
|
||
|
||
|
||
|
||
} catch (InterruptedException e) {
|
||
|
||
|
||
|
||
e.printStackTrace();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
return i % 2 == 0;
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
@Test
|
||
|
||
|
||
|
||
public void test() {
|
||
|
||
|
||
|
||
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", String.valueOf(10));
|
||
|
||
|
||
|
||
StopWatch stopWatch = new StopWatch();
|
||
|
||
|
||
|
||
stopWatch.start("stream");
|
||
|
||
|
||
|
||
stream();
|
||
|
||
|
||
|
||
stopWatch.stop();
|
||
|
||
|
||
|
||
stopWatch.start("parallelStream");
|
||
|
||
|
||
|
||
parallelStream();
|
||
|
||
|
||
|
||
stopWatch.stop();
|
||
|
||
|
||
|
||
stopWatch.start("parallelStreamForEachOrdered");
|
||
|
||
|
||
|
||
parallelStreamForEachOrdered();
|
||
|
||
|
||
|
||
stopWatch.stop();
|
||
|
||
|
||
|
||
System.out.println(stopWatch.prettyPrint());
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//filtre和forEach串行
|
||
|
||
|
||
|
||
private void stream() {
|
||
|
||
|
||
|
||
IntStream.rangeClosed(1, 10)
|
||
|
||
|
||
|
||
.filter(ForEachOrderedTest::filter)
|
||
|
||
|
||
|
||
.forEach(ForEachOrderedTest::consume);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//filter和forEach并行
|
||
|
||
|
||
|
||
private void parallelStream() {
|
||
|
||
|
||
|
||
IntStream.rangeClosed(1, 10).parallel()
|
||
|
||
|
||
|
||
.filter(ForEachOrderedTest::filter)
|
||
|
||
|
||
|
||
.forEach(ForEachOrderedTest::consume);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//filter并行而forEach串行
|
||
|
||
|
||
|
||
private void parallelStreamForEachOrdered() {
|
||
|
||
|
||
|
||
IntStream.rangeClosed(1, 10).parallel()
|
||
|
||
|
||
|
||
.filter(ForEachOrderedTest::filter)
|
||
|
||
|
||
|
||
.forEachOrdered(ForEachOrderedTest::consume);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>得到输出:</p>
|
||
|
||
<pre><code>\---------------------------------------------
|
||
|
||
|
||
|
||
ns % Task name
|
||
|
||
|
||
|
||
\---------------------------------------------
|
||
|
||
|
||
|
||
15119607359 065% stream
|
||
|
||
|
||
|
||
2011398298 009% parallelStream
|
||
|
||
|
||
|
||
6033800802 026% parallelStreamForEachOrdered
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>从输出中,我们可以看到:</p>
|
||
|
||
<p>stream 方法的过滤和遍历全部串行执行,总时间是 10 秒 +5 秒 =15 秒;</p>
|
||
|
||
<p>parallelStream 方法的过滤和遍历全部并行执行,总时间是 1 秒 +1 秒 =2 秒;</p>
|
||
|
||
<p>parallelStreamForEachOrdered 方法的过滤并行执行,遍历串行执行,总时间是 1 秒 +5 秒 =6 秒。</p>
|
||
|
||
<h3>加餐 2 | 带你吃透课程中 Java 8 的那些重要知识点(二)</h3>
|
||
|
||
<p>问题 1:使用 Stream 可以非常方便地对 List 做各种操作,那有没有什么办法可以实现在整个过程中观察数据变化呢?比如,我们进行 filter+map 操作,如何观察 filter 后 map 的原始数据呢?</p>
|
||
|
||
<p>答:要想观察使用 Stream 对 List 的各种操作的过程中的数据变化,主要有下面两个办法。</p>
|
||
|
||
<p>第一,使用 peek 方法。比如如下代码,我们对数字 1~10 进行了两次过滤,分别是找出大于 5 的数字和找出偶数,我们通过 peek 方法把两次过滤操作之前的原始数据保存了下来:</p>
|
||
|
||
<pre><code>List<Integer> firstPeek = new ArrayList<>();
|
||
|
||
|
||
|
||
List<Integer> secondPeek = new ArrayList<>();
|
||
|
||
|
||
|
||
List<Integer> result = IntStream.rangeClosed(1, 10)
|
||
|
||
|
||
|
||
.boxed()
|
||
|
||
|
||
|
||
.peek(i -> firstPeek.add(i))
|
||
|
||
|
||
|
||
.filter(i -> i > 5)
|
||
|
||
|
||
|
||
.peek(i -> secondPeek.add(i))
|
||
|
||
|
||
|
||
.filter(i -> i % 2 == 0)
|
||
|
||
|
||
|
||
.collect(Collectors.toList());
|
||
|
||
|
||
|
||
System.out.println("firstPeek:" + firstPeek);
|
||
|
||
|
||
|
||
System.out.println("secondPeek:" + secondPeek);
|
||
|
||
|
||
|
||
System.out.println("result:" + result);
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>最后得到输出,可以看到第一次过滤之前是数字 1~10,一次过滤后变为 6~10,最终输出 6、8、10 三个数字:</p>
|
||
|
||
<pre><code>firstPeek:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||
|
||
|
||
|
||
secondPeek:[6, 7, 8, 9, 10]
|
||
|
||
|
||
|
||
result:[6, 8, 10]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>第二,借助 IDEA 的 Stream 的调试功能。详见这里,效果类似下图:</p>
|
||
|
||
<p><img src="assets/3ee49c0589286bba37dd66032530d65e.png" alt="img" /></p>
|
||
|
||
<p>问题 2:Collectors 类提供了很多现成的收集器,那我们有没有办法实现自定义的收集器呢?比如,实现一个 MostPopularCollector,来得到 List 中出现次数最多的元素,满足下面两个测试用例:</p>
|
||
|
||
<pre><code>assertThat(Stream.of(1, 1, 2, 2, 2, 3, 4, 5, 5).collect(new MostPopularCollector<>()).get(), is(2));
|
||
|
||
|
||
|
||
assertThat(Stream.of('a', 'b', 'c', 'c', 'c', 'd').collect(new MostPopularCollector<>()).get(), is('c'));
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>答:我来说下我的实现思路和方式:通过一个 HashMap 来保存元素的出现次数,最后在收集的时候找出 Map 中出现次数最多的元素:</p>
|
||
|
||
<pre><code>public class MostPopularCollector<T> implements Collector<T, Map<T, Integer>, Optional<T>> {
|
||
|
||
|
||
|
||
//使用HashMap保存中间数据
|
||
|
||
|
||
|
||
@Override
|
||
|
||
|
||
|
||
public Supplier<Map<T, Integer>> supplier() {
|
||
|
||
|
||
|
||
return HashMap::new;
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//每次累积数据则累加Value
|
||
|
||
|
||
|
||
@Override
|
||
|
||
|
||
|
||
public BiConsumer<Map<T, Integer>, T> accumulator() {
|
||
|
||
|
||
|
||
return (acc, elem) -> acc.merge(elem, 1, (old, value) -> old + value);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//合并多个Map就是合并其Value
|
||
|
||
|
||
|
||
@Override
|
||
|
||
|
||
|
||
public BinaryOperator<Map<T, Integer>> combiner() {
|
||
|
||
|
||
|
||
return (a, b) -> Stream.concat(a.entrySet().stream(), b.entrySet().stream())
|
||
|
||
|
||
|
||
.collect(Collectors.groupingBy(Map.Entry::getKey, summingInt(Map.Entry::getValue)));
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//找出Map中Value最大的Key
|
||
|
||
|
||
|
||
@Override
|
||
|
||
|
||
|
||
public Function<Map<T, Integer>, Optional<T>> finisher() {
|
||
|
||
|
||
|
||
return (acc) -> acc.entrySet().stream()
|
||
|
||
|
||
|
||
.reduce(BinaryOperator.maxBy(Map.Entry.comparingByValue()))
|
||
|
||
|
||
|
||
.map(Map.Entry::getKey);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
@Override
|
||
|
||
|
||
|
||
public Set<Characteristics> characteristics() {
|
||
|
||
|
||
|
||
return Collections.emptySet();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h3>加餐 3 | 定位应用问题,排错套路很重要</h3>
|
||
|
||
<p>问题:如果你现在打开一个 App 后发现首页展示了一片空白,那这到底是客户端兼容性的问题,还是服务端的问题呢?如果是服务端的问题,又如何进一步细化定位呢?你有什么分析思路吗?</p>
|
||
|
||
<p>答:首先,我们需要区分客户端还是服务端错误。我们可以先从客户端下手,排查看看是否是服务端问题,也就是通过抓包来看服务端的返回(一般而言客户端发布之前会经过测试,而且无法随时变更,所以服务端出错的可能性会更大一点)。因为一个客户端程序可能对应几百个服务端接口,先从客户端(发出请求的根源)开始排查问题,更容易找到方向。</p>
|
||
|
||
<p>服务端没有返回正确的输出,那么就需要继续排查服务端接口或是上层的负载均衡了,排查方式为:</p>
|
||
|
||
<p>查看负载均衡(比如 Nginx)的日志;</p>
|
||
|
||
<p>查看服务端日志;</p>
|
||
|
||
<p>查看服务端监控。</p>
|
||
|
||
<p>如果服务端返回了正确的输出,那么要么是由于客户端的 Bug,要么就是外部配置等问题了,排查方式为:</p>
|
||
|
||
<p>查看客户端报错(一般而言,客户端都会对接 SAAS 的异常服务);</p>
|
||
|
||
<p>直接本地启动客户端调试。</p>
|
||
|
||
<h3>加餐 4 | 分析定位 Java 问题,一定要用好这些工具(一)</h3>
|
||
|
||
<p>问题 1:JDK 中还有一个 jmap 工具,我们会使用 jmap -dump 命令来进行堆转储。那么,这条命令和 jmap -dump:live 有什么区别呢?你能否设计一个实验,来证明下它们的区别呢?</p>
|
||
|
||
<p>答:jmap -dump 命令是转储堆中的所有对象,而 jmap -dump:live 是转储堆中所有活着的对象。因为,jmap -dump:live 会触发一次 FullGC。</p>
|
||
|
||
<p>写一个程序测试一下:</p>
|
||
|
||
<pre><code>@SpringBootApplication
|
||
|
||
|
||
|
||
@Slf4j
|
||
|
||
|
||
|
||
public class JMapApplication implements CommandLineRunner {
|
||
|
||
|
||
|
||
//-Xmx512m -Xms512m
|
||
|
||
|
||
|
||
public static void main(String[] args) {
|
||
|
||
|
||
|
||
SpringApplication.run(JMapApplication.class, args);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
@Override
|
||
|
||
|
||
|
||
public void run(String... args) throws Exception {
|
||
|
||
|
||
|
||
while (true) {
|
||
|
||
|
||
|
||
//模拟产生字符串,每次循环后这个字符串就会失去引用可以GC
|
||
|
||
|
||
|
||
String payload = IntStream.rangeClosed(1, 1000000)
|
||
|
||
|
||
|
||
.mapToObj(__ -> "a")
|
||
|
||
|
||
|
||
.collect(Collectors.joining("")) + UUID.randomUUID().toString();
|
||
|
||
|
||
|
||
log.debug(payload);
|
||
|
||
|
||
|
||
TimeUnit.MILLISECONDS.sleep(1);
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>然后,使用 jmap 不带和带 live 分别生成两个转储:</p>
|
||
|
||
<pre><code>jmap -dump:format=b,file=nolive.hprof 57323
|
||
|
||
|
||
|
||
jmap -dump:live,format=b,file=live.hprof 5732
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>可以看到,nolive 这个转储的不可到达对象包含了 164MB char[](可以认为基本是字符串):</p>
|
||
|
||
<p><img src="assets/8e4f3eea80edfe6d867cab754967589b.png" alt="img" /></p>
|
||
|
||
<p>而 live 这个转储只有 1.3MB 的 char[],说明程序循环中的这些字符串都被 GC 了:</p>
|
||
|
||
<p><img src="assets/18403a0b683c3b726700d5624f968287.png" alt="img" /></p>
|
||
|
||
<p>问题 2:你有没有想过,客户端是如何和 MySQL 进行认证的呢?你能否对照MySQL 的文档,使用 Wireshark 观察分析这一过程呢?</p>
|
||
|
||
<p>答:一般而言,认证(握手)过程分为三步。</p>
|
||
|
||
<p>首先,服务端给客户端主动发送握手消息:</p>
|
||
|
||
<p><img src="assets/29f5e4a9056b7b6aeb9d9ac2yy5e97b2.png" alt="img" /></p>
|
||
|
||
<p>Wireshark 已经把消息的字段做了解析,你可以对比官方文档的协议格式一起查看。HandshakeV10 消息体的第一个字节是消息版本 0a,见图中红色框标注的部分。前面四个字节是 MySQL 的消息头,其中前三个字节是消息体长度(16 进制 4a=74 字节),最后一个字节是消息序列号。</p>
|
||
|
||
<p>然后,客户端给服务端回复的 HandshakeResponse41 消息体,包含了登录的用户名和密码:</p>
|
||
|
||
<p><img src="assets/a0b37df4f3e92f7e8602409a7ca0f696.png" alt="img" /></p>
|
||
|
||
<p>可以看到,用户名是 string[NUL]类型的,说明字符串以 00 结尾代表字符串结束。关于 MySQL 协议中的字段类型,你可以参考这里。</p>
|
||
|
||
<p>最后,服务端回复的 OK 消息,代表握手成功:</p>
|
||
|
||
<p><img src="assets/d746f34df74cfedb4d294db1e2b771e7.png" alt="img" /></p>
|
||
|
||
<p>这样分析下来,我们可以发现使用 Wireshark 观察客户端和 MySQL 的认证过程,非常方便。而如果不借助 Wireshark 工具,我们只能一个字节一个字节地对照协议文档分析内容。</p>
|
||
|
||
<p>其实,各种 CS 系统定义的通讯协议本身并不深奥,甚至可以说对着协议文档写通讯客户端是体力活。你可以继续按照这里我说的方式,结合抓包和文档,分析一下 MySQL 的查询协议。</p>
|
||
|
||
<h3>加餐 5 | 分析定位 Java 问题,一定要用好这些工具(二)</h3>
|
||
|
||
<p>问题:Arthas 还有一个强大的热修复功能。比如,遇到高 CPU 问题时,我们定位出是管理员用户会执行很多次 MD5,消耗大量 CPU 资源。这时,我们可以直接在服务器上进行热修复,步骤是:jad 命令反编译代码 -> 使用文本编辑器(比如 Vim)直接修改代码 -> 使用 sc 命令查找代码所在类的 ClassLoader-> 使用 redefine 命令热更新代码。你可以尝试使用这个流程,直接修复程序(注释 doTask 方法中的相关代码)吗?</p>
|
||
|
||
<p>答:Arthas 的官方文档有详细的操作步骤,实现 jad->sc->redefine 的整个流程,需要注意的是:</p>
|
||
|
||
<p>redefine 命令和 jad/watch/trace/monitor/tt 等命令会冲突。执行完 redefine 之后,如果再执行上面提到的命令,则会把 redefine 的字节码重置。 原因是,JDK 本身 redefine 和 Retransform 是不同的机制,同时使用两种机制来更新字节码,只有最后的修改会生效。</p>
|
||
|
||
<p>使用 redefine 不允许新增或者删除 field/method,并且运行中的方法不会立即生效,需要等下次运行才能生效。</p>
|
||
|
||
<p>以上,就是咱们这门课里面 5 篇加餐文章的思考题答案了。至此,咱们这个课程的“答疑篇”模块也就结束了。</p>
|
||
|
||
<p>关于这些题目,以及背后涉及的知识点,如果你还有哪里感觉不清楚的,欢迎在评论区与我留言,也欢迎你把今天的内容分享给你的朋友或同事,一起交流。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(二).md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:安全篇思考题答案合集.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099707298753d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|