diff --git "a/Java\345\237\272\347\241\200\345\255\246\344\271\240/Java\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232JDK 5-15\351\203\275\346\234\211\345\223\252\344\272\233\347\273\217\345\205\270\346\226\260\347\211\271\346\200\247.md" "b/Java\345\237\272\347\241\200\345\255\246\344\271\240/Java\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232JDK 5-15\351\203\275\346\234\211\345\223\252\344\272\233\347\273\217\345\205\270\346\226\260\347\211\271\346\200\247.md" new file mode 100644 index 0000000..85cc926 --- /dev/null +++ "b/Java\345\237\272\347\241\200\345\255\246\344\271\240/Java\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232JDK 5-15\351\203\275\346\234\211\345\223\252\344\272\233\347\273\217\345\205\270\346\226\260\347\211\271\346\200\247.md" @@ -0,0 +1,1380 @@ +### 前言 +JDK 15发布啦~ 我们一起回顾JDK 5-15 的新特性吧,大家一起学习哈~ + +本文已经收录到github +> https://github.com/whx123/JavaHome + +**公众号:捡田螺的小男孩** + +### Java 5 新特性 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/03f5d66e89bb4cd49e85fbfd795f204d~tplv-k3u1fbpfcp-zoom-1.image) +#### 1. 泛型 +泛型本质是参数化类型,解决不确定具体对象类型的问题。 +``` + List strList=new ArrayList(); +``` +#### 2. 增强循环(for-each) +for-each循环简化了集合的遍历。 +``` +String [] str = {"关注","公众号","捡田螺的小男孩"}; +for (String temp:str) { + System.out.println(temp); +} +``` +#### 3. 自动封箱拆箱 +- 自动装箱: 就是将基本数据类型自动转换成对应的包装类。 +- 自动拆箱:就是将包装类自动转换成对应的基本数据类型。 + +包装类型有:Integer,Double,Float,Long,Short,Character和Boolean +``` +Integer i =666; //自动装箱 +int a= i; //自动拆箱 +``` + +#### 4. 枚举 +关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这就是枚举类型。 +``` +enum SeasonEnum { + SPRING,SUMMER,FALL,WINTER; +} +``` +#### 5. 可变参数 +我们在定义方法参数的时候不确定定义多少个,就可以定义为**可变参数**,它本质上是一个**数组**。 +``` +public static void main(String[] args) throws Exception { + String [] str = {"关注","公众号","捡田螺的小男孩"}; + testVarargs(str); + String str1 = "关注公众号,捡田螺的小男孩"; + testVarargs(str1); +} +//可变参数String... args +private static void testVarargs(String... args) { + for (String arg : args) { + System.out.println(arg); + } +} +``` +#### 6. 注解 +可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。 +``` +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface Override { +} +``` + +#### 7.静态导入 +通过import static类,就可以使用类里的静态变量或方法。看一下例子哈~ +``` +import static java.lang.System.out; //静态导入System类的静态变量out +public class Test { + public static void main(String[] args) throws Exception { + String str1 = "关注公众号,捡田螺的小男孩"; + System.out.println(str1); //常规写法 + out.println(str1); //静态导入,可以直接使用out输出 + } +} +``` +#### 8. 线程并发库(JUC) +JDK5 丰富了线程处理功能,java.util.concurrent包提供了以下的类、接口: + +> - 线程池:ExecutorService接口 +> - 线程护斥:Lock 类 +> - 线程通信:Condition接口 +> - 同步队列:ArrayBlockingQueue类 +> - 同步集合:ConcurrentHashMap类 + + +### Java 6 新特性 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a91df99e8eb948e7a208d7222eb162cf~tplv-k3u1fbpfcp-zoom-1.image) +#### 1.Desktop类和SystemTray类 +JDK 6在java.awt包下,新增了两个类:Desktop类和SystemTray类 +> - **Desktop类**: 用来打开系统默认浏览器浏览指定的URL,打开系统默认邮件客户端发邮件等 +> - **SystemTray类**:用来在系统托盘区创建一个托盘程序,如果在微软的Windows上,它被称为“任务栏”状态区域。 + +``` +//获取Desktop实例 +Desktop desktop = Desktop.getDesktop(); +desktop.browse(URI.create("https://www.baidu.com")); +``` + +#### 2. 使用JAXB2来实现对象与XML之间的映射 + JAXB,即Java Architecture for XML Binding,可以实现对象与XML之间的映射,常用注解如下: +> - @XmlRootElement:注解在类上面,对应xml的跟元素,使用name属性定义根节点的名称。 +> - @XmlElement:指定一个字段或get/set方法映射到xml的节点,使用name属性定义这个根节点的名称。 +> - @XmlAttribute:将JavaBean对象的属性映射为xml的属性,使用name属性为生成的xml属性指定别名。 +> - @XmlAccessorType:定义映射这个类中的何种类型都需要映射到xml。 +> - @XmlSchema: 将包映射到XML名称空间 + +**看个例子吧~** +``` +public class JAXB2XmlTest { + + public static void main(String[] args) throws JAXBException, IOException { + + List list = new ArrayList<>(); + list.add(new Singer("jay", 8)); + list.add(new Singer("eason", 10)); + + SingerList singerList = new SingerList(); + singerList.setSingers(list); + + String str = JAXB2XmlTest.beanToXml(singerList, SingerList.class); + String path = "C:\\jay.txt"; + BufferedWriter bfw = new BufferedWriter(new FileWriter(new File(path))); + bfw.write(str); + bfw.close(); + + } + + private static String beanToXml(Object obj, Class load) throws JAXBException { + JAXBContext context = JAXBContext.newInstance(load); + Marshaller marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "GBK"); + StringWriter writer = new StringWriter(); + marshaller.marshal(obj,writer); + return writer.toString(); + } +} +public class Singer { + + private String name; + private int age; + public Singer(String name, int age) { + this.name = name; + this.age = age; + } + @XmlAttribute(name="name") + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + @XmlAttribute(name="age") + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } +} +@XmlRootElement(name="list") +public class SingerList { + + private List singers; + + @XmlElement(name="singer") + public List getSingers() { + return singers; + } + + public void setSingers(List singers) { + this.singers = singers; + } +} +``` + +**运行效果:** +``` + + + + + +``` + +#### 3.轻量级 Http Server API +JDK 6中提供了简单的Http Server API,可以构建嵌入式Http服务器,同时支持Http和Https协议。HttpServer会调用HttpHandler实现类的回调方法来处理客户端请求,这里用户只需实现HttpHandler接口就可以了。 + +``` +/** + * 根据Java提供的API实现Http服务器 + */ +public class MyHttpServer { + + /** + * @param args + * @throws IOException + */ + public static void main(String[] args) throws IOException { + //创建HttpServer服务器 + HttpServer httpServer = HttpServer.create(new InetSocketAddress(8080), 10); + //将 /jay请求交给MyHandler处理器处理 + httpServer.createContext("/", new MyHandler()); + httpServer.start(); + } +} + +public class MyHandler implements HttpHandler { + + public void handle(HttpExchange httpExchange) throws IOException { + //请求头 + Headers headers = httpExchange.getRequestHeaders(); + Set>> entries = headers.entrySet(); + + StringBuffer response = new StringBuffer(); + for (Map.Entry> entry : entries){ + response.append(entry.toString() + "\n"); + } + //设置响应头属性及响应信息的长度 + httpExchange.sendResponseHeaders(200, response.length()); + //获得输出流 + OutputStream os = httpExchange.getResponseBody(); + os.write(response.toString().getBytes()); + os.close(); + } +} +``` +#### 4. 插入式注解处理API + +> JDK 6提供了插入式注解处理API,可以让我们定义的注解在编译期而不是运行期生效,从而可以在编译期修改字节码。lombok框架就是使用该特性来实现的,Lombok通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString等方法,大大简化了代码的开发。 + +#### 5. STAX +STAX,是JDK6中一种处理XML文档的API。 +``` +public class STAXTest { + + public static void main(String[] args) throws Exception { + + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + XMLEventReader xmlEventReader = xmlInputFactory.createXMLEventReader(new FileInputStream("C:\\jay.xml")); + XMLEvent event = null; + StringBuffer stringBuffer = new StringBuffer(); + while (xmlEventReader.hasNext()) { + event = xmlEventReader.nextEvent(); + stringBuffer.append(event.toString()); + } + System.out.println("xml文档解析结果:"); + System.out.println(stringBuffer); + } +} +``` +**运行结果:** +``` +xml文档解析结果: + + + +ENDDOCUMENT +``` + +#### 6. Common Annotations +> Common annotations原本是Java EE 5.0(JSR 244)规范的一部分,现在SUN把它的一部分放到了Java SE 6.0中。随着Annotation元数据功能加入到Java SE 5.0里面,很多Java 技术都会用Annotation部分代替XML文件来配置运行参数。 + + 以下列举Common Annotations 1.0里面的几个Annotations: +- @Generated:用于标注生成的源代码 +- @Resource: 用于标注所依赖的资源,容器据此注入外部资源依赖,有基于字段的注入和基于setter方法的注入两种方式 。 +- @Resources:同时标注多个外部依赖,容器会把所有这些外部依赖注入 +- @PostConstruct:标注当容器注入所有依赖之后运行的方法,用来进行依赖注入后的初始化工作,只有一个方法可以标注为PostConstruct 。 +- @PreDestroy:当对象实例将要被从容器当中删掉之前,要执行的回调方法要标注为PreDestroy + +#### 7. Compiler API +javac编译器可以把.java的源文件编译为.class文件,JDK 6的新特性Compiler API(JSR 199)也可以动态编译Java源文件。 +``` +public class CompilerApiTest { + public static void main(String[] args) throws Exception { + JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null,null,null); + Iterable javaFileObjects = standardJavaFileManager.getJavaFileObjects("C:\\Singer.java"); + javaCompiler.getTask(null, standardJavaFileManager, null, null, null, javaFileObjects).call(); + standardJavaFileManager.close(); + } +} + +``` +运行结果:会在C目录生成Singer.class文件 + +#### 8. 对脚本语言的支持(如: ruby, groovy, javascript) +JDK6增加了对脚本语言的支持(JSR 223),原理是将脚本语言编译成字节码,这样脚本语言也能享用Java平台的诸多优势,包括可移植性,安全等。JDK6实现包含了一个基于Mozilla Rhino的 脚本语言引擎,因此可以支持javascript,当然JDK也支持ruby等其他语言 + +``` +public class JavaScriptTest { + + public static void main(String[] args) throws Exception { + ScriptEngineManager factory = new ScriptEngineManager(); + ScriptEngine engine = factory.getEngineByName("JavaScript"); + String script; + try { + script = "print('Hello')"; + engine.eval(script);// 执行脚本 + }catch (Exception e) { + e.printStackTrace(); + } + } +} +//output +Hello +``` +### Java 7 新特性 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c52833f0793f4c0fadf77f172b016912~tplv-k3u1fbpfcp-zoom-1.image) +#### 1.switch 支持String字符串类型。 +``` +String singer = "jay"; +switch (singer) { + case "jay" : + System.out.println("周杰伦"); + break; + case "eason" : + System.out.println("陈奕迅"); + break ; + default : + System.out.println("其他"); + break ; + } +``` + +#### 2.try-with-resources,资源自动关闭 +JDK 7 之前: +``` +BufferedReader br = new BufferedReader(new FileReader("d:七里香.txt")); +try { + return br.readLine(); +} finally { + br.close(); +} +``` +JDK 7 之后: +``` +/* + * 声明在try括号中的对象称为资源,在方法执行完毕后会被自动关闭 + */ +try (BufferedReader br = new BufferedReader(new FileReader("d:七里香.txt")) { + return br.readLine(); +} +``` + +#### 3. 整数类型如(byte,short,int,long)能够用二进制来表示 +``` +//0b或者0B表示二进制 +int a = 0b010; +int b = 0B010; +``` +#### 4. 数字常量支持下划线 +``` +int a = 11_11;//a的值为1111,下划线不影响实际值,提升可读性 +``` + +#### 5. 泛型实例化类型自动推断,即”<>” +JDK 7 之前: +``` +Map> map = new HashMap>(); +``` +JDK 7之后: +``` +//不须声明类型,自动根据前面<>推断其类型 +Map> map = new HashMap<>(); +``` + +#### 6.一个catch中捕获多个异常类型,用(|)分隔开 +JDK 7之前 +``` +try{ + //do something +} catch (FirstException e) { + logger.error(e); +} catch (SecondException e) { + logger.error(ex); +} +``` +JDk 7之后 +``` +try{ + //do something +} catch (FirstException | SecondException e) { + logger.error(e); +} +``` +#### 7. 增强的文件系统 + +Java7 提供了全新的NIO2.0 API,方便文件管理的编码。如,可以在java.nio.file包下使用Path、Paths、Files、WatchService等常用类型。 + +``` +Path path = Paths.get("C:\\jay\\七里香.txt"); //创建Path对象 +byte[] bytes= Files.readAllBytes(path); //读取文件 +System.out.println(path.getFileName()); //获取当前文件名称 +System.out.println(path.toAbsolutePath()); // 获取文件绝对路径 +System.out.println(new String(bytes, "utf-8")); +``` + +#### 8. Fork/join 框架 +Java7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/823a548cf6d64cedb1dd507c5234788f~tplv-k3u1fbpfcp-zoom-1.image) + +Fork/join计算1-1000累加值: +``` +public class ForkJoinPoolTest { + + private static final Integer DURATION_VALUE = 100; + + static class ForkJoinSubTask extends RecursiveTask{ + + // 子任务开始计算的值 + private Integer startValue; + // 子任务结束计算的值 + private Integer endValue; + + private ForkJoinSubTask(Integer startValue , Integer endValue) { + this.startValue = startValue; + this.endValue = endValue; + } + + @Override + protected Integer compute() { + //小于一定值DURATION,才开始计算 + if(endValue - startValue < DURATION_VALUE) { + System.out.println("执行子任务计算:开始值 = " + startValue + ";结束值 = " + endValue); + Integer totalValue = 0; + for (int index = this.startValue; index <= this.endValue; index++) { + totalValue += index; + } + return totalValue; + } else { + // 将任务拆分,拆分成两个任务 + ForkJoinSubTask subTask1 = new ForkJoinSubTask(startValue, (startValue + endValue) / 2); + subTask1.fork(); + ForkJoinSubTask subTask2 = new ForkJoinSubTask((startValue + endValue) / 2 + 1 , endValue); + subTask2.fork(); + return subTask1.join() + subTask2.join(); + } + } + } + + public static void main(String[] args) throws ExecutionException, InterruptedException { + // Fork/Join框架的线程池 + ForkJoinPool pool = new ForkJoinPool(); + ForkJoinTask taskFuture = pool.submit(new ForkJoinSubTask(1,1000)); + + Integer result = taskFuture.get(); + System.out.println("累加结果是:" + result); + + } +} +``` +运行结果: +``` +... +执行子任务计算:开始值 = 189;结束值 = 250 +执行子任务计算:开始值 = 251;结束值 = 313 +执行子任务计算:开始值 = 314;结束值 = 375 +执行子任务计算:开始值 = 376;结束值 = 438 +执行子任务计算:开始值 = 439;结束值 = 500 +执行子任务计算:开始值 = 501;结束值 = 563 +执行子任务计算:开始值 = 564;结束值 = 625 +执行子任务计算:开始值 = 626;结束值 = 688 +执行子任务计算:开始值 = 689;结束值 = 750 +执行子任务计算:开始值 = 751;结束值 = 813 +执行子任务计算:开始值 = 814;结束值 = 875 +执行子任务计算:开始值 = 876;结束值 = 938 +执行子任务计算:开始值 = 939;结束值 = 1000 +累加结果是:500500 +``` + +### Java 8 新特性 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5730671f3ef344bd89163eb4a3f9f710~tplv-k3u1fbpfcp-zoom-1.image) + +#### 1.lambada表达式 +Lambda 允许把函数作为一个方法的参数,传递到方法中 + +语法格式: +``` +(parameters) -> expression 或 (parameters) ->{ statements; } +``` +代码示例: +``` +Arrays.asList("jay", "Eason", "SHE").forEach( + ( String singer ) -> System.out.print( singer + ",") ); +``` + +#### 2. 函数式接口 +Lambda的设计者为了让现有的功能与Lambda表达式很好兼容,设计出函数式接口。 +- 函数式接口是指只有一个函数的接口,可以隐式转换为lambada表达式。 +- Java 8 提供了注解@FunctionalInterface,显示声明一个函数式接口。 +- java.lang.Runnable和java.util.concurrent.Callable是函数式接口的例子~ + +``` +@FunctionalInterface +public interface Runnable { + public abstract void run(); +} +``` + +#### 3. 方法引用 +方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。它与Lambda表达式配合使用,可以减少冗余代码,使代码更加简洁。 +``` +//利用函数式接口Consumer的accept方法实现打印,Lambda表达式如下 +Consumer consumer = x -> System.out.println(x); +consumer.accept("jay"); +//引用PrintStream类(也就是System.out的类型)的println方法,这就是方法引用 +consumer = System.out::println; +consumer.accept("关注公众号捡田螺的小男孩"); +``` + +#### 4. 默认方法 + + 默认方法就是一个在接口里面有了一个实现的方法。它允许将新方法添加到接口,但不强制实现了该接口的类必须实现新的方法。 + +``` +public interface ISingerService { + // 默认方法 + default void sing(){ + System.out.println("唱歌"); + } + void writeSong(); +} + +//JaySingerServiceImpl 不用强制实现ISingerService的默认sing()方法 +public class JaySingerServiceImpl implements ISingerService { + @Override + public void writeSong() { + System.out.println("写了一首七里香"); + } +} +``` + +#### 5.Stream API +Stream API,支持对元素流进行函数式操作,它集成在Collections API 中,可以对集合进行批量操作。常用API: +- filter 筛选 +- map流映射 +- reduce 将流中的元素组合起来 +- collect 返回集合 +- sorted 排序 +- flatMap 流转换 +- limit返回指定流个数 +- distinct去除重复元素 +``` +public class Singer { + + private String name; + private Integer songNum; + private Integer age; + ... +} + +List singerList = new ArrayList(); +singerList.add(new Singer("jay", 11, 36)); +singerList.add(new Singer("eason", 8, 31)); +singerList.add(new Singer("JJ", 6, 29)); + +List singerNameList = singerList.stream() + .filter(singer -> singer.getAge() > 30) //筛选年龄大于30 + .sorted(Comparator.comparing(Singer::getSongNum)) //根据歌曲数量排序 + .map(Singer::getName) //提取歌手名字 + .collect(Collectors.toList()); //转换为List +``` + +#### 6. Optional + Java 8引入Optional类,用来解决NullPointerException。Optional代替if...else解决空指针问题,使代码更加简洁。 + +if...else 判空 +``` +Singer singer = getSingerById("666"); +if (singer != null) { + String name = singer.getName(); + System.out.println(name); +} +``` +Optional的判空 +``` +Optional singer = Optional.ofNullable(getSingerById("666")); +singer.ifPresent(s -> System.out.println(s.getName())); +``` + +#### 7. Date Time API +JDK 8之前的日期API处理存在非线程安全、时区处理麻烦等问题。Java 8 在 java.time包下提供了新的日期API,简化了日期的处理~ + +``` +LocalDate today = LocalDate.now(); +int year = today.getYear(); +System.out.println("今年是" + year); +//是否闰年 +System.out.println("今年是不是闰年:" + today.isLeapYear()); + +LocalDateTime todayTime = LocalDateTime.now(); +System.out.println("当前时间" + todayTime); +//时区指定 +System.out.println("美国时间:" + ZonedDateTime.of(todayTime,ZoneId.of("America/Los_Angeles"))); + +LocalDate specailDate = LocalDate.of(2020, 6, 20); +LocalDate expectDate = specailDate.plus(100, ChronoUnit.DAYS); +System.out.println("比较特别的一天" + specailDate); +System.out.println("特殊日期的100天" + expectDate); +``` + +#### 8. 重复注解 +重复注解,即一个注解可以在一个类、属性或者方法上同时使用多次;用@Repeatable定义重复注解 +``` +@Repeatable(ScheduleTimes.class) +public @interface ScheduleTime { + String value(); +} + +public @interface ScheduleTimes { + ScheduleTime[] value(); +} + +public class ScheduleTimeTask { + @ScheduleTime("10") + @ScheduleTime("12") + public void doSomething() { } +} +``` +#### 9. Base64 +Java 8把Base64编码的支持加入到官方库中~ +``` +String str = "公众号:捡田螺的小男孩"; +String encoded = Base64.getEncoder().encodeToString(str.getBytes( StandardCharsets.UTF_8)); +String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8); +``` + +#### 10. JVM的新特性 +使用元空间Metaspace代替持久代(PermGen space),JVM参数使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize设置大小。 + +### Java 9 新特性 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8b0457ff711b412094b2900ba4b4e974~tplv-k3u1fbpfcp-zoom-1.image) +#### 1. java模块系统 + +什么是模块化? +> 一个大型系统,比如一个商城网站,它会包含很多模块的,如:订单模块,用户信息模块,商品信息模块,广告位模块等等。各个模块之间会相互调用。如果每个模块单独运行都会带动其他所有模块,性能非常低效。但是,如果某一模块运行时,只会启动它所依赖的模块,性能大大提升。这就是JDK 9模块化的思想。 + +什么是JDK 9模块化? +> Java 平台模块系统,即Project Jigsaw,把模块化开发实践引入到了Java平台中。在引入了模块系统之后,JDK 被重新组织成94个模块。Java 应用可以通过新增的jlink 工具,创建出只包含所依赖的JDK模块的自定义运行时镜像。这样可以极大的减少Java运行时环境的大小。 + +Java 9 模块的重要特征: +> - 在其工件(artifact)的根目录中包含了一个描述模块的 module-info.class 文 件。 +> - 工件的格式可以是传统的 JAR 文件或是 Java 9 新增的 JMOD 文件。 +> - 这个文件由根目录中的源代码文件 module-info.java 编译而来。 +> - 该模块声明文件可以描述模块的不同特征。 + +在 module-info.java 文件中,我们可以用新的关键词module来声明一个模块,如下所示。下面给出了一个模块com.mycompany.mymodule的最基本的模块声明 + +``` +module com.jay.sample { //关键词module来声明一个模块 + exports com.jay.sample; //使用 exports可以声明模块对其他模块所导出的包。 + requires com.jay.common; //使用requires可以声明模块对其他模块的依赖关系。 +} +``` + +#### 2. 不可变集合工厂方法 + +为了创建不可变集合,JDK9之前酱紫的: +``` +List stringList = new ArrayList<>(); +stringList.add("关注公众号:"); +stringList.add("捡田螺的小男孩"); +List unmodifiableList = Collections.unmodifiableList(stringList); +``` +JDK 9 提供了List.of()、Set.of()、Map.of()和Map.ofEntries()等工厂方法来创建不可变集合: +``` +List unmodifiableList = List.of("关注公众号:","捡田螺的小男孩"); +``` + +#### 3. 接口支持私有方法 +JDK 8支持在接口实现默认方法和静态方法,但是不能在接口中创建私有方法,为了避免了代码冗余和提高阅读性,JDK 9在接口中支持私有方法。 +``` +public interface IPrivateInterfaceTest { + + //JDK 7 之前 + String a = "jay"; + void method7(); + + //JDK 8 + default void methodDefault8(){ + System.out.println("JDK 8新特性默认方法"); + } + static void methodStatic8() { + System.out.println("JDk 8新特性静态方法"); + } + + //Java 9 接口支持私有方法 + private void method9(){} +} +``` + +#### 4. 钻石操作符升级 +- 钻石操作符是在 java 7 中引入的,可以让代码更易读,但它不能用于匿名的内部类。 +- 在 java 9 中, 它可以与匿名的内部类一起使用,从而提高代码的可读性。 + +``` +//JDK 5,6 +Map map56 = new HashMap(); +//JDk 7,8 +Map map78 = new HashMap<>(); +//JDK 9 结合匿名内部类的实现 +Map map9 = new HashMap<>(){}; +``` + +#### 5. Optional 类改进 + java 9 中,java.util.Optional 添加了很多新的有用方法,如: +- stream() +- ifPresentOrElse() +- or() + +ifPresentOrElse 方法的改进就是有了 else,接受两个参数 Consumer 和 Runnable。 +``` +import java.util.Optional; + +public class OptionalTest { + public static void main(String[] args) { + Optional optional = Optional.of(1); + + optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() -> + System.out.println("Not Present.")); + + optional = Optional.empty(); + + optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() -> + System.out.println("Not Present.")); + } +} +``` +#### 6. 多版本兼容Jar包 +> 很多公司使用的JDK都是老版本的,JDK6、JDk5 ,甚至JDk4的,不是他们不想升级JDk版本,而是担心兼容性问题。JDK 9的一个新特性,多版本兼容Jar包解决了这个问题。举个例子:假设你一直用的是小米8,已经非常习惯它的运行流程了,突然出来小米9,即使小米9很多新功能引人入胜,但是有些人不会轻易买小米9,因为已经已经习惯小米8的流程。同理,为什么很多公司不升级JDK,就是在此。但是呢,JDK 9的这个功能很强大,它可以让你的版本升级到JDK 9,但是还是老版本的运行流程,即在老的运行流程继承新的功能~ + + + +#### 7. JShell工具 +jShell工具相当于cmd工具,然后呢,你可以像在cmd工具操作一样,直接在上面运行Java方法,Java语句等~ +``` +jshell> System.out.println("关注公众号:捡田螺的小男孩"); +关注公众号:捡田螺的小男孩 +``` + +#### 8. try-with-resources的改进 +JDK 9对try-with-resources异常处理机制进行了升级~ +``` +//JDK 7,8 +try (BufferedReader br = new BufferedReader(new FileReader("d:七里香.txt")) { + br.readLine(); +}catch(IOException e){ + log.error("IO 异常,e:{}",e); +} +//JDk 9 +BufferedReader br = new BufferedReader(new FileReader("d:七里香.txt") +try(br){ + br.readLine(); +}catch(IOException e){ + log.error("IO 异常,e:{}",e); +} +``` + +#### 9. Stream API的改进 +JDK 9 为Stream API引入以下这些方法,丰富了流处理操作: +- takeWhile() +- dropWhile() +- iterate +- ofNullable + +**takeWhile** + +使用一个断言(Predicate 接口)作为参数,返回给定Stream的子集直到断言语句第一次返回 false +``` +// 语法格式 +default Stream takeWhile(Predicate predicate) +//代码示例 +Stream.of(1,2,3).takeWhile(s-> x<2) + .forEach(System.out::println); + //输出 + 1 +``` +**dropWhile** + +与 takeWhile()作用相反,使用一个断言(Predicate 接口)作为参数,直到断言语句第一次返回true,返回给定Stream的子集 +``` +//语法 +default Stream dropWhile(Predicate predicate) +//代码示例 +Stream.of(1,2,3).dropWhile(s-> x<2) + .forEach(System.out::println); +//输出 +2 +3 +``` +**iterate** + +iterate() 方法能够返回以seed(第一个参数)开头,匹配 Predicate(第二个参数)直到返回false,并使用第三个参数生成下一个元素的元素流。 +``` +//语法 +static Stream iterate(T seed, Predicate hasNext, UnaryOperator next) +//代码示例 +IntStream.iterate(2, x -> x < 10, x -> x*x).forEach(System.out::println); +//输出 +2 +4 +``` +**ofNullable** + +如果指定元素为非null,则获取一个元素并生成单个元素流,元素为null则返回一个空Stream。 +``` +//语法 +static Stream ofNullable(T t) +//代码示例 +Stream s1= Stream.ofNullable(100); +s1.forEach(System.out::println) +Stream s2 = Stream.ofNullable(null); +s2.forEach(System.out::println) +//输出 +100 + +``` + +#### 10.其他 +> - HTTP 2客户端 (支持 WebSocket和 HTTP2 流以及服务器推送) +> - 进程API(控制和管理操作系统进程) +> - String底层存储结构更改(char[]替换为byte[]) +> - 标识符添加限制( String _ ="hello"不能用) +> - 响应式流 API (支持Java 9中的响应式编程) + +### Java 10 新特性 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1641de35ea274dbb9d7d34c78ea40a0f~tplv-k3u1fbpfcp-zoom-1.image) +#### 1.局部变量类型推断 + +JDK 10增加了局部变量类型推断(Local-Variable Type Inference)功能,让 Java 可以像Js里的var一样可以自动推断数据类型。Java中的var是一个保留类型名称,而不是关键字。 + +JDK 10之前 +``` +List list = new ArrayList(); +Stream stream = Stream.of(1, 2, 3); +``` +JDK 10 之后 +``` +var list = new ArrayList(); // ArrayList +var stream = Stream.of(1, 2, 3); +``` +var 变量类型推断的使用也有局限性,仅**局限**于以下场景: +- 具有初始化器的局部变量 +- 增强型for循环中的索引变量 +- 传统for循环中声明的局部变量 + +而**不能用于** +- 推断方法的参数类型 +- 构造函数参数类型推断 +- 推断方法返回类型 +- 字段类型推断 +- 捕获表达式 + + +#### 2. 不可变集合的改进 +JDK 10中,List,Set,Map 提供了一个新的静态方法copyOf(Collection coll),它返回Collection集合一个不可修改的副本。 + +JDK 源码: +``` +static List copyOf(Collection coll) { + return ImmutableCollections.listCopy(coll); +} +``` + +使用实例: + +``` +var oldList = new ArrayList(); +oldList.add("欢迎关注公众号:"); +oldList.add("捡田螺的小男孩"); + +var copyList = List.copyOf(oldList); +oldList.add("在看、转载、点赞三连"); +copyList.add("双击666"); //UnsupportedOperationException异常 +``` + +#### 3. 并行全垃圾回收器 G1 + +> JDK 9引入 G1 作为默认垃圾收集器,执行GC 时采用的是基于单线程标记扫描压缩算法(mark-sweep-compact)。为了最大限度地减少 Full GC 造成的应用停顿的影响,Java 10 中将为 G1 引入多线程并行 GC,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。 + + +#### 4. 线程本地握手 + +Java 10 中线程管控引入JVM安全点的概念,将允许在不运行全局JVM安全点的情况下实现线程回调,由线程本身或者JVM线程来执行,同时保持线程处于阻塞状态,这将会很方便使得停止单个线程或不停止线程成为可能。 + +#### 5. Optional新增orElseThrow()方法 +Optional、OptionalDouble等类新增一个方法orElseThrow(),在没有值时抛出异常 + +#### 6. 其他新特性 +- 基于 Java 的 实验性 JIT 编译器 +- 类数据共享 +- Unicode 语言标签扩展 +- 根证书 +- 基于时间(Time-Based)的版本控制模型 + +### Java 11 新特性 + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4041b93cd08c4865aaceec2bafbd214f~tplv-k3u1fbpfcp-zoom-1.image) + +#### 1.字符串操作 +String类是Java最常用的类,JDK 11增加了一系列好用的字符串处理方法 +- isBlank() 判空。 +- strip() 去除首尾空格 +- stripLeading() 去除字符串首部空格 +- stripTrailing() 去除字符串尾部空格 +- lines() 分割获取字符串流。 +- repeat() 复制字符串 + +``` +// 判断字符串是否为空白 +" ".isBlank(); // true + +// 去除首尾空格 +" jay ".strip(); // "jay" + +// 去除首部空格 +" jay ".stripLeading(); // "jay " + +去除字符串尾部空格 +" jay ".stripLeading(); // " jay" + +// 行数统计 +"a\nb\nc".lines().count(); // 3 + +// 复制字符串 +"jay".repeat(3); // "jayjayjay" + +``` + +#### 2.用于 Lambda 参数的局部变量语法 + +局部变量类型推断是Java 10引入的新特性,但是不能在Lambda 表达式中使用。Java 11再次创新,它允许开发者在 Lambda 表达式中使用 var 进行参数声明。 +``` +var map = new HashMap(); +map.put("公众号", "捡田螺的小男孩"); +map.forEach((var k, var v) -> { + System.out.println(k + ": " + v); +}); +``` + +#### 3.标准化HTTP Client +Java 9 引入Http Client API,Java 10对它更新,Java 11 对它进行标准化。这几个版本后,Http Client几乎被完全重写,支持HTTP/1.1和HTTP/2 ,也支持 websockets。 + +``` +HttpClient client = HttpClient.newHttpClient(); +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://github.com/whx123/JavaHome")) + .GET() + .build(); + +// 同步 +HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); +System.out.println(response.body()); + +// 异步 +client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .thenAccept(System.out::println); +``` + +#### 4. 单个命令编译运行源代码 +Java 11增强了Java 启动器,使之能够运行单一文件的Java 源代码。 + +- Java 11之前,要运行一个 Java 源代码必须先编译,再运行 +``` +// 编译 +javac Jay.java +// 运行 +java Jay +``` +- Java 11之后,只要一个java命令就搞定 +``` +java Jay.java +``` + +#### 5. ZGC:可伸缩低延迟垃圾收集器 + +ZGC ,即 Z Garbage Collector(垃圾收集器或垃圾回收器)。它是一个可伸缩的、低延迟的垃圾收集器。 +ZGC 主要为了满足如下目标进行设计: +- GC 停顿时间不超过 10ms +- 既能处理几百 MB 的小堆,也能处理几个 TB 的大堆 +- 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比) +- 方便在此基础上引入新的 GC 特性和利用 colord +- 针以及 Load barriers 优化奠定基础 +- 当前只支持 Linux/x64 位平台 + +#### 6.其他一些特性 +- 添加 Epsilon 垃圾收集器。 +- 支持 TLS 1.3 协议 +- 飞行记录器分析工具 +- 动态类文件常量 +- 低开销的 Heap Profiling + +### Java 12 新特性 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a9700faac24f45508d6f7071e779d1b8~tplv-k3u1fbpfcp-zoom-1.image) +#### 1. Switch 表达式扩展(预览功能) +传统的switch语句,容易漏写break而出错,同时写法并不简洁优雅。 + +Java 12之前 +``` +switch (day) { + case MONDAY: + case FRIDAY: + case SUNDAY: + System.out.println(6); + break; + case TUESDAY: + System.out.println(7); + break; + case THURSDAY: + case SATURDAY: + System.out.println(8); + break; + case WEDNESDAY: + System.out.println(9); + break; +} + +``` +JDk 12 之后,Switch表达式得到增强,能接受语句和表达式。 +``` +switch (day) { + case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); + case TUESDAY -> System.out.println(7); + case THURSDAY, SATURDAY -> System.out.println(8); + case WEDNESDAY -> System.out.println(9); +} +``` + +#### 2. 紧凑的数据格式 +JDK 12 新增了NumberFormat对复杂数字的格式化 +``` +NumberFormat numberFormat = NumberFormat.getCompactNumberInstance(Locale.CHINA, NumberFormat.Style.SHORT); +System.out.println(numberFormat.format(100000)); +//output +10万 +``` + +#### 3. 字符串支持transform、indent操作 +- transform 字符串转换,可以配合函数式接口Function一起使用 +``` +List list1 = List.of("jay", " 捡田螺的小男孩"); +List list2 = new ArrayList<>(); +list1.forEach(element -> + list2.add(element.transform(String::strip) + .transform((e) -> "Hello," + e)) + ); +list2.forEach(System.out::println); +//输出 +Hello,jay +Hello,捡田螺的小男孩 +``` +- indent 缩进,每行开头增加空格space和移除空格 +``` +String result = "Java\n Python\nC".indent(3); +System.out.println(result); +//输出 + Java + Python + C +``` + +#### 4. Files.mismatch(Path, Path) +Java 12 新增了mismatch方法,此方法返回第一个不匹配的位置,如果没有不匹配,则返回 -1L。 +``` +public static long mismatch(Path path, Path path2) throws IOException; +``` +代码示例: +``` +Path file1 = Paths.get("c:\\jay.txt"); +Path file2 = Paths.get("c:\\捡田螺的小男孩.txt"); + +try { + long fileMismatch = Files.mismatch(file1, file2); + System.out.println(fileMismatch); +} catch (IOException e) { + e.printStackTrace(); +} +``` +#### 5. Teeing Collector + +Teeing Collector 是 Streams API 中引入的新的收集器实用程序,它的作用是 merge 两个 collector 的结果,API格式如下: +``` +public static + Collector teeing(Collector downstream1, + Collector downstream2, + BiFunction merger) +``` +直接看代码例子吧,如下为求学生的平均分和总分的例子 +``` + List studentList= Arrays.asList( + new Student("jay", 90), + new Student("捡田螺的小男孩", 100), + new Student("捡表情的小男孩", 80) + ); + String teeingResult=studentList.stream().collect( + Collectors.teeing( + Collectors.averagingInt(Student::getScore), + Collectors.summingInt(Student::getScore), + (s1,s2)-> s1+ ":"+ s2 + ) + ); + System.out.println(teeingResult); //90:270 +``` +#### 6.其他特性 +- 支持unicode 11(684个新字符、11个新blocks、7个新脚本) +- JVM 常量 API (主要在新的java.lang.invoke.constant包中定义了一系列基于值的符号引用类型,能够描述每种可加载常量。) +- Shenandoah GC(低暂停时间垃圾收集器) +- G1 收集器提升 (可中止的混合收集集合、及时返回未使用的已分配内存) +- 默认CDS档案 +- JMH 基准测试 + +### Java 13 新特性 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/404668ad77d248ec97d482622182eb70~tplv-k3u1fbpfcp-zoom-1.image) + +#### Switch 表达式扩展(引入 yield 关键字) +传统的switch: +``` +private static String getText(int number) { + String result = ""; + switch (number) { + case 1, 2: + result = "one or two"; + break; + case 3: + result = "three"; + break; + case 4, 5, 6: + result = "four or five or six"; + break; + default: + result = "unknown"; + break; +``` +Java 13之后,value break 语句不再被编译,而是用 yield 来进行值返回 +``` +private static String getText(int number) { + return switch (number) { + case 1, 2: + yield "one or two"; + case 3: + yield "three"; + case 4, 5, 6: + yield "four or five or six"; + default: + yield "unknown"; + }; +} +``` +#### 2.文本块升级 +Java 13之前,字符串不能够多行使用,需要通过换行转义或者换行连接符等等,反正就是好麻烦、好难维护。 +``` +String html = "\n" + + " \n" + + "

Hello, 捡田螺的小男孩

\n" + + " \n" + + "\n"; +``` +Java 13之后,清爽多了~ +``` +String html = """ + + +

Hello, 捡田螺的小男孩

+ + + """; +``` +#### 3. SocketAPI 重构 +- 传统的Java Socket API(java.net.ServerSocket 和 java.net.Socket)依赖于SocketImpl 的内部实现 +- 在 Java 13之前,通过使用 PlainSocketImpl 作为 SocketImpl 的具体实现。 +- Java 13 中的新底层实现,引入 NioSocketImpl 的实现用以替换 SocketImpl 的 PlainSocketImpl 实现,此实现与 NIO(新 I/O)实现共享相同的内部基础结构,并且与现有的缓冲区高速缓存机制集成在一起。 + +一个Socket简单例子: +``` +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +public class SocketAPITest { + public static void main(String[] args) { + try (ServerSocket serverSocket = new ServerSocket(8080)){ + boolean runFlag = true; + while(runFlag){ + Socket clientSocket = serverSocket.accept(); + //搞事情 + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} +``` +运行以上的实例,看下是否有以下关键词输出~ +``` +[class,load] sun.nio.ch.NioSocketImpl +``` + +#### 4.FileSystems.newFileSystem新方法 +FileSystems 类中添加了以下三种新方法,以便更容易地使用将文件内容视为文件系统的文件系统提供程序: +- 1、newFileSystem(Path) +- 2、newFileSystem(Path, Map) +- 3、newFileSystem(Path, Map, ClassLoader) + +#### 5. 增强 ZGC 释放未使用内存 +- ZGC 是Java 11 中引入的最为瞩目的垃圾回收特性,是一种可伸缩、低延迟的垃圾收集器。但是实际使用中,它不能够主动将未使用的内存释放给操作系统。 +- Java 13 中对 ZGC 的改进,包括释放未使用内存给操作系统、支持最大堆大小为 16TB、JVM参数-XX:SoftMaxHeapSize 来软限制堆大小 + +#### 6.其他特性 +- 动态 CDS 存档, 扩展了 Java 10 中引入的类数据共享功能, 使用CDS 存档变得更容易。 +- 文本块的字符串类新方法,如formatted(Object…args),stripIndent()等。 + + + +### Java 14 新特性 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c386249741174a38b6d29f6900829a66~tplv-k3u1fbpfcp-zoom-1.image) +#### 1. instanceof模式匹配 +instanceof 传统使用方式: +``` +if (person instanceof Singer) { + Singer singer = (Singer) person; + singer.sing(); +} else if (person instanceof Writer) { + Writer writer = (Writer) person; + writer.write(); +} +``` +Java 14 对 instanceof 进行模式匹配改进之后 +``` +if (person instanceof Singer singer) { + singer.sing(); +} else if (person instanceof Writer writer) { + writer.write(); +} +``` + + +#### 2.Record 类型(预览功能) +Java 14将Record 类型作为预览特性而引入,有点类似于Lombok 的@Data注解,看个例子吧: +``` +public record Person(String name, int age) { + public static String address; + + public String getName() { + return name; + } +} +``` +反编译结果: +``` +public final class Person extends java.lang.Record { + private final java.lang.String name; + private final java.lang.String age; + + public Person(java.lang.String name, java.lang.String age) { /* compiled code */ } + + public java.lang.String getName() { /* compiled code */ } + + public java.lang.String toString() { /* compiled code */ } + + public final int hashCode() { /* compiled code */ } + + public final boolean equals(java.lang.Object o) { /* compiled code */ } + + public java.lang.String name() { /* compiled code */ } + + public java.lang.String age() { /* compiled code */ } +} +``` +可以发现,当用 Record 来声明一个类时,该类将自动拥有下面特征: +- 构造方法 +- hashCode() 方法 +- euqals() 方法 +- toString() 方法 +- 类对象被final 关键字修饰,不能被继承。 + + +#### 3. Switch 表达式-标准化 +switch 表达式在之前的 Java 12 和 Java 13 中都是处于预览阶段,终于在 Java 14 标准化,成为稳定版本。 +- Java 12 为switch 表达式引入Lambda 语法 +- Java 13 使用yield代替 break 关键字来返回表达式的返回值。 +``` +String result = switch (day) { + case "M", "W", "F" -> "MWF"; + case "T", "TH", "S" -> "TTS"; + default -> { + if (day.isEmpty()) { + yield "Please insert a valid day."; + } else { + yield "Looks like a Sunday."; + } + } +}; +System.out.println(result); +``` + +#### 4. 改进 NullPointerExceptions提示信息 +Java 14 之前: +``` +String name = song.getSinger().getSingerName() + +//堆栈信息 +Exception in thread "main" java.lang.NullPointerException + at NullPointerExample.main(NullPointerTest.java:6) +``` +Java 14,通过引入JVM 参数-XX:+ShowCodeDetailsInExceptionMessages,可以在空指针异常中获取更为详细的调用信息。 +``` +Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Singer.getSingerName()" +because the return value of "rainRow.getSinger()" is null + at NullPointerExample.main(NullPointerTest.java:6) +``` +#### 5. 其他特性 +- G1 的 NUMA 可识别内存分配 +- 删除 CMS 垃圾回收器 +- GC 支持 MacOS 和 Windows 系统 + +### Java 15 新特性 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/691c5bdd4de64d41bc5f50599b9ba078~tplv-k3u1fbpfcp-zoom-1.image) +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96a3928212f1483ca669f6cbc7893c21~tplv-k3u1fbpfcp-zoom-1.image) +#### 1.EdDSA 数字签名算法 +- 使用 Edwards-Curve 数字签名算法(EdDSA)实现加密签名。 +- 与其它签名方案相比,EdDSA 具有更高的安全性和性能。 +- 得到许多其它加密库(如 OpenSSL、BoringSSL)的支持。 + +#### 2.Sealed Classes(封闭类,预览) +封闭类,可以是封闭类、封闭接口,防止其他类或接口扩展或实现它们。 +``` +public abstract sealed class Singer + permits Jay, Eason{ + ... +} +``` +类Singer被sealed 修饰,是封闭类,只能被2个指定子类(Jay, Eason)继承。 + +#### 3. Hidden Classes(隐藏类) +- 隐藏类天生为框架设计的。 +- 隐藏类只能通过反射访问,不能直接被其他类的字节码。 + +#### 4. Remove the Nashorn JavaScript Engine +- Nashorn太难维护了,移除 Nashorn JavaScript引擎成为一种必然 +- 其实早在JDK 11 中就已经被标记为 deprecated 了。 + +#### 5.Reimplement the Legacy DatagramSocket API(重新实现DatagramSocket API) +- 重新实现老的DatagramSocket API +- 更改java.net.DatagramSocket 和 java.net.MulticastSocket 为更加简单、现代化的底层实现。 + +#### 6.其他 +- Disable and Deprecate Biased Locking(准备禁用偏向锁) +- instanceof 自动匹配模式(预览) +- ZGC,一个可伸缩、低延迟的垃圾回收器。(转正) +- Text Blocks,文本功能转正(JDK 13和14预览,14终于转正) +- Remove the Solaris and SPARC Ports(删除 Solaris 和 SPARC 端口) +- 外部存储器访问 API(允许Java 应用程序安全有效地访问 Java 堆之外的外部内存。) +- Record类型二次预览(在Java 14就预览过啦) + + +### 参考与感谢 +- [JDK6 新特性](https://blog.csdn.net/weixin_40926603/article/details/84970283) +- [Java 7的新功能](https://stackoverflow.com/questions/213958/new-features-in-java-7) +- [Java 9 新特性概述](https://developer.ibm.com/zh/articles/the-new-features-of-Java-9/) +- [Java 9 新特性](https://www.runoob.com/java/java9-new-features.html) +- [Java 10 新特性介绍](https://developer.ibm.com/zh/technologies/java/articles/the-new-features-of-java-10/) +- [Java 11 新特性介绍](https://developer.ibm.com/zh/technologies/java/articles/the-new-features-of-java-11/) +- [Java 13 新特性概述](https://developer.ibm.com/zh/technologies/java/articles/the-new-features-of-java-13/) +- [Java 14 新特性概述](https://developer.ibm.com/zh/technologies/java/articles/the-new-features-of-java-14/) +- [JDK/Java 15发布](https://mp.weixin.qq.com/s/bNbNzo-Jy_SskRAupUBbNQ) +- [Java 15 正式发布, 14 个新特性,刷新你的认知!! +](https://www.cnblogs.com/javastack/p/13683220.html) +### 个人公众号 + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2845e9f737f14311a119f45751683b3c~tplv-k3u1fbpfcp-zoom-1.image) + + + + + diff --git "a/Java\345\237\272\347\241\200\345\255\246\344\271\240/README.MD" "b/Java\345\237\272\347\241\200\345\255\246\344\271\240/README.MD" new file mode 100644 index 0000000..a038f88 --- /dev/null +++ "b/Java\345\237\272\347\241\200\345\255\246\344\271\240/README.MD" @@ -0,0 +1,22 @@ +## Java基础篇(持续更新中) +​ +公众号:捡田螺的小男孩 +​ +- [一文读懂线程池的工作原理(故事白话文)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488078&idx=1&sn=0a7cef472002f6582fd2354fba83706a&chksm=cf21cd67f85644716263c3a80cead9b7bb36d9677f6f8b06d0602077ece70fcafa9d20c1cffb&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Synchronized解析——如果你愿意一层一层剥开我的心](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487942&idx=1&sn=bbc68d1b9da23bf6474378e310b1ef1b&chksm=cf21ceeff85647f9ad7a08226849fcba3f9481387d13b17a5787fb94027647de81c349f9e390&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [异步编程利器:CompletableFuture详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490456&idx=1&sn=95836324db57673a4d7aea4fb233c0d2&chksm=cf21c4b1f8564da72dc7b39279362bcf965b1374540f3b339413d138599f7de59a5f977e3b0e&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java日常开发的21个坑,你踩过几个?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488115&idx=1&sn=bdd4a4ca36bc7ea902106d058e8537fb&chksm=cf21cd5af856444cb36af600705615454b0aaa2b289b97ddb52d594556ac07a1915b73ecce19&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [一份Java程序员的珍藏书单,请您注意查收](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488066&idx=1&sn=44b5a90be1b69d7938dbcf516d85f041&chksm=cf21cd6bf856447d869278386250f59a926881375df848e54f86a21682bdab50f9e09ca56fbd&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础:JDK 5-15都有哪些经典新特性](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488058&idx=1&sn=aab4d0dc9020cb62710086474ca90baf&chksm=cf21cd13f8564405040593daa45c62aec218e13f5ff42d679c59f768dd4fcc53ddcf34e0a454&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备基础:Git 命令全方位学习](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488013&idx=1&sn=7011a51a347e3da2cf8f8540b4d9a5d6&chksm=cf21cd24f8564432d74bc13551ebdeae71a71ea31e339c7a8f1f42f181078b5192475d598626&token=162724582&ang=zh_CN&scene=21#wechat_redirect) +- [给你的Java程序拍个片子吧:jstack命令解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487990&idx=1&sn=b5b3c565392f39e5ac517696603b2ed9&chksm=cf21cedff85647c960407dce77fe04d08e51f8c7332310ccacd925be5567c187aa761dd1d1c8&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础结构图](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487985&idx=1&sn=ead28c6c8d81b98e59603b848d250b30&chksm=cf21ced8f85647ce336f19016c7ff1936b21c81066815c8f28b830098716111548edb9767b21&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备:序列化全方位解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487980&idx=1&sn=2a9ce519f87a1ffe1511022e6724208e&chksm=cf21cec5f85647d357c79860171fc1799ef3c44a2bdd0716e8437e31708a17d9000b4224bd36&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [优化if-else代码的八种方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487970&idx=1&sn=c296bb03419adf93955c6d0f27e56b29&chksm=cf21cecbf85647dd0ef5160559bc0d524a4be004a28bc5d2770a43409e3b090123c0930cf047&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础:泛型解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487964&idx=1&sn=24d7228cc10afc98c52dbf35da61a7b9&chksm=cf21cef5f85647e3d2b3f1e126cdc46d9e889d2e30c09716e0aea016beee3ca6d4c321cf60ae&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础:内部类解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487963&idx=1&sn=a0b49cd49a3dd51b6736c9ffa0a5997a&chksm=cf21cef2f85647e49a4bdb43f27583f03fb9ec4719767512dc084edd05675599c3bec44251fb&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [老是遇到乱码问题:它是如何产生的,又如何解决呢?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487962&idx=1&sn=7424e843c80b228283fc08d4d24cc4bb&chksm=cf21cef3f85647e5a9c92d280624ad2564e885561a8b64cbf6722459f7c13da7421765321aa9&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [谈谈Java反射:从入门到实践,再到原理](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487959&idx=1&sn=778114d611f18b0f307a3f3ab6cd9117&chksm=cf21cefef85647e84b77c0e46620e91cf5ff079785b58a7dc66e5ed7419e21e0da9180699617&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备的一些流程图](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487946&idx=1&sn=57a86e1d2fe1a9ecf00594a0bb6baf5f&chksm=cf21cee3f85647f5cf2ba728cc0838923140130a18ad117e248cf9843460614fc855d556968a&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备:查看日志常用的linux命令](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487954&idx=1&sn=6c04ff4edfcfea52a82795bcb9ed8efd&chksm=cf21cefbf85647ed8df72a23307315be5d1b3d4974c128f111bfdaa84da37cf7b49ff65c1112&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [ThreadLocal的八个关键知识点](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247500784&idx=1&sn=6519d0e092be4ed9d6f4da8d90deef2c&chksm=cf221cd9f85595cf9123043241e92a19ca9c212aa8527cfb2aeb9a2472c6bdab9045cf40f22f&token=349136600&lang=zh_CN#rd) \ No newline at end of file diff --git "a/Java\345\237\272\347\241\200\345\255\246\344\271\240/README.MD.bak" "b/Java\345\237\272\347\241\200\345\255\246\344\271\240/README.MD.bak" new file mode 100644 index 0000000..4ba4024 --- /dev/null +++ "b/Java\345\237\272\347\241\200\345\255\246\344\271\240/README.MD.bak" @@ -0,0 +1,21 @@ +## Java基础篇(持续更新中) +​ +公众号:捡田螺的小男孩 +​ +- [一文读懂线程池的工作原理(故事白话文)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488078&idx=1&sn=0a7cef472002f6582fd2354fba83706a&chksm=cf21cd67f85644716263c3a80cead9b7bb36d9677f6f8b06d0602077ece70fcafa9d20c1cffb&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Synchronized解析——如果你愿意一层一层剥开我的心](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487942&idx=1&sn=bbc68d1b9da23bf6474378e310b1ef1b&chksm=cf21ceeff85647f9ad7a08226849fcba3f9481387d13b17a5787fb94027647de81c349f9e390&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [异步编程利器:CompletableFuture详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490456&idx=1&sn=95836324db57673a4d7aea4fb233c0d2&chksm=cf21c4b1f8564da72dc7b39279362bcf965b1374540f3b339413d138599f7de59a5f977e3b0e&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java日常开发的21个坑,你踩过几个?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488115&idx=1&sn=bdd4a4ca36bc7ea902106d058e8537fb&chksm=cf21cd5af856444cb36af600705615454b0aaa2b289b97ddb52d594556ac07a1915b73ecce19&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [一份Java程序员的珍藏书单,请您注意查收](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488066&idx=1&sn=44b5a90be1b69d7938dbcf516d85f041&chksm=cf21cd6bf856447d869278386250f59a926881375df848e54f86a21682bdab50f9e09ca56fbd&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础:JDK 5-15都有哪些经典新特性](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488058&idx=1&sn=aab4d0dc9020cb62710086474ca90baf&chksm=cf21cd13f8564405040593daa45c62aec218e13f5ff42d679c59f768dd4fcc53ddcf34e0a454&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备基础:Git 命令全方位学习](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488013&idx=1&sn=7011a51a347e3da2cf8f8540b4d9a5d6&chksm=cf21cd24f8564432d74bc13551ebdeae71a71ea31e339c7a8f1f42f181078b5192475d598626&token=162724582&ang=zh_CN&scene=21#wechat_redirect) +- [给你的Java程序拍个片子吧:jstack命令解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487990&idx=1&sn=b5b3c565392f39e5ac517696603b2ed9&chksm=cf21cedff85647c960407dce77fe04d08e51f8c7332310ccacd925be5567c187aa761dd1d1c8&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础结构图](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487985&idx=1&sn=ead28c6c8d81b98e59603b848d250b30&chksm=cf21ced8f85647ce336f19016c7ff1936b21c81066815c8f28b830098716111548edb9767b21&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备:序列化全方位解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487980&idx=1&sn=2a9ce519f87a1ffe1511022e6724208e&chksm=cf21cec5f85647d357c79860171fc1799ef3c44a2bdd0716e8437e31708a17d9000b4224bd36&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [优化if-else代码的八种方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487970&idx=1&sn=c296bb03419adf93955c6d0f27e56b29&chksm=cf21cecbf85647dd0ef5160559bc0d524a4be004a28bc5d2770a43409e3b090123c0930cf047&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础:泛型解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487964&idx=1&sn=24d7228cc10afc98c52dbf35da61a7b9&chksm=cf21cef5f85647e3d2b3f1e126cdc46d9e889d2e30c09716e0aea016beee3ca6d4c321cf60ae&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备基础:内部类解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487963&idx=1&sn=a0b49cd49a3dd51b6736c9ffa0a5997a&chksm=cf21cef2f85647e49a4bdb43f27583f03fb9ec4719767512dc084edd05675599c3bec44251fb&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [老是遇到乱码问题:它是如何产生的,又如何解决呢?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487962&idx=1&sn=7424e843c80b228283fc08d4d24cc4bb&chksm=cf21cef3f85647e5a9c92d280624ad2564e885561a8b64cbf6722459f7c13da7421765321aa9&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [谈谈Java反射:从入门到实践,再到原理](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487959&idx=1&sn=778114d611f18b0f307a3f3ab6cd9117&chksm=cf21cefef85647e84b77c0e46620e91cf5ff079785b58a7dc66e5ed7419e21e0da9180699617&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备的一些流程图](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487946&idx=1&sn=57a86e1d2fe1a9ecf00594a0bb6baf5f&chksm=cf21cee3f85647f5cf2ba728cc0838923140130a18ad117e248cf9843460614fc855d556968a&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java程序员必备:查看日志常用的linux命令](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487954&idx=1&sn=6c04ff4edfcfea52a82795bcb9ed8efd&chksm=cf21cefbf85647ed8df72a23307315be5d1b3d4974c128f111bfdaa84da37cf7b49ff65c1112&token=162724582&lang=zh_CN&scene=21#wechat_redirect) \ No newline at end of file diff --git "a/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246.md" "b/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246.md" new file mode 100644 index 0000000..12a0435 --- /dev/null +++ "b/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246.md" @@ -0,0 +1,535 @@ +### 前言 +不要因为迷茫,而停止了脚下的路。给大家推荐一份Java程序员必看的书单,豆瓣评分都挺不错的,每一本都值得去读,都值得去收藏,加油呀 + +本文已经收录到github + httpsgithub.comwhx123JavaHome + +- 专题一:Java 基础篇书单 +- 专题二:代码优化篇书单 +- 专题三:计算机网络篇书单 +- 专题四:操作系统 && 计算机底层书单 +- 专题五:数据结构与算法篇书单 +- 专题六:缓存篇书单 +- 专题七:数据库书单 +- 专题八:微服务 && 分布式篇书单 +- 专题九:消息中间件书单 +- 专题十:容器书单 +- 专题十一:面试相关书单 +- 专题十二:软件开发&&程序人生书单 + +公众号:捡田螺的小男孩 + +### 专题一:Java 基础篇书单 +- 《Java编程思想》 +- 《深入理解Java虚拟机:JVM高级特性与最佳实践》 +- 《Head First 设计模式》 +- 《Effective java》 +- 《Java核心技术》 +- 《Java8 实战》 +- 《Java并发编程实战》 +- 《Spring揭秘》 +- 《MyBatis技术内幕》 + + +#### Java编程思想 +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcpadaee100b9544590bbee4c54a605d6e8~tplv-k3u1fbpfcp-zoom-1.image) + + 本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,作者都会用小而直接的编程示例讲解明白。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 + +- 综合评分:9.1 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- Java界的神书,如果你是一名Java开发程序员,或者准备进入Java开发领域,都非常推荐你去读一读~ + +#### 深入理解Java虚拟机:JVM高级特性与最佳实践 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp524a027174b542db9a4164ae72ae072a~tplv-k3u1fbpfcp-zoom-1.image) + + 这是一本从工作原理和工程实践两个维度深入剖析JVM的著作,是计算机领域公认的经典。 + +- 综合评分:9.6 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 不会JVM知识的Java程序员,是没有灵魂的。这本书真的超级适合学习JVM方面的知识。 + +#### Head First 设计模式 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcpe839c06e8da74f65b4d5ac9fed60a921~tplv-k3u1fbpfcp-zoom-1.image) + + 本书涵盖了23个设计模式,例子简单易懂,抛砖引玉,读起来很有意思的。真的是一本非常赞的设计模式入门书籍 + +- 综合评分:9.2 +- 个人推荐指数:五星 +- 适合对象:初级、中级 +- 对代码有追求的伙伴,想好好学习设计模式的小伙伴,都去看看吧~用设计模式写出优雅代码~ + + +#### Effective java +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcp2a8667e690b24195a0b3e7f2a16b3eed~tplv-k3u1fbpfcp-zoom-1.image) + + 本书一共包含90个条目,每个条目讨论Java程序设计中的一条规则。这些规则反映了最有经验的优秀程序员在实践中常用的一些有益的做法。 + +- 综合评分:9.6 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 学习Java必读的一本书籍,我们写代码从来不是图谁写得多,或者比谁写得多,而是应该思考如何编写出清晰、正确、健壮、易维护的程序代码。 + + +#### Java核心技术 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcp8df7b5aec8774aa48a8117659f5d131e~tplv-k3u1fbpfcp-zoom-1.image) + 这本书是以为拥有20多年教学与研究经验的资深Java技术专家撰写,是程序员的优选Java指南。 + +- 综合评分:9.3 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- Java入门的工具书,基础知识讲的很细,如果你是入门Java的话,可以去看一看呀~ + +#### Java8 实战 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcpd9fcf57481d542d081eaae89274f7b25~tplv-k3u1fbpfcp-zoom-1.image) + 本书全面介绍了Java 8 这个里程碑版本的新特性,包括Lambdas、流和函数式编程。函数式编程,让代码更简洁~ + +- 综合评分:9.3 +- 个人推荐指数:五星 +- 适合对象:初级、中级 +- 江湖流传Java版本任你发,我用Java 8,可以知道java 8的地位了。这本书全面系统地讲述了Java8给Java语言带来的改变,例子也很生动,值得一读~ + +#### Java并发编程实战 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcpb847255548164d05ac402ddcc1188307~tplv-k3u1fbpfcp-zoom-1.image) + 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。 + +- 综合评分:9.0 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 讲并发的一本书,个人觉得不能算得上Java神书,但是呢,茶余饭后还是值得去读一读的。 + +#### Spring揭秘 +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcp217e1bf0c9f0479f817b6f9b5a696068~tplv-k3u1fbpfcp-zoom-1.image) + 本书内容全面,论述深刻入理,必将成为每个Java专业开发人员必备的Spring图书。 + +- 综合评分:9.1 +- 个人推荐指数:五星 +- 适合对象:初级、中级 +- 可以当做spring的入门书籍,想用甜而不腻来形容这本书。 + +####MyBatis技术内幕 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp176ac64ea5a44d979b9587e33dcb0dfd~tplv-k3u1fbpfcp-zoom-1.image) + 《MyBatis技术内幕》旨在为读者理解MyBatis的设计原理、阅读MyBatis源码、扩展MyBatis功能提供帮助和指导,让读者更加深入地了解MyBatis的运行原理、设计理念。希望《MyBatis技术内幕》能够帮助读者全面提升自身的技术能力,让读者在设计业务系统时,可以参考MyBatis的优秀设计,更好地应用MyBatis。 + +- 综合评分:8.6 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 还不错,讲的还挺清晰。但是呢一上来就开始讲源码,有些读者可能有点抗拒。 + + +### 专题二:代码优化篇书单 +- 《重构:改善既有代码的设计》 +- 《代码整洁之道》 +- 《代码整洁之道 程序员的职业素养》 + +#### 重构:改善既有代码的设计 +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcpc3fb1b0c51d64258994ecdace65ae999~tplv-k3u1fbpfcp-zoom-1.image) + 本书凝聚了软件开发社区专家多年摸索而获得的宝贵经验,拥有不因时光流逝而磨灭的价值。代码重构,因为这本书,成为了众多普通程序员日常开发工作中不可或缺的一部分。 + +- 综合评分:9.0 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 花了三周,把这本书看了两遍,真得觉得很有帮助。比如代码的坏味道,讲到重复代码、过长的函数、过长的类、过多的参数等等,都是我们司空见惯的不好代码,我们工作中应该注意、积累、思考,写出优雅、健壮的好代码。 + + +#### 代码整洁之道 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcp6a5f6e0f2b044fe09afe0d236b47f401~tplv-k3u1fbpfcp-zoom-1.image) + + 本书作者给出了一系列行之有效的整洁代码操作实践,些实践在本书中体现为一条条规则,并辅以来自现实项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。 + +- 综合评分:9.1 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 个人更加推荐的是英文版,虽然我看的也是中文版,哈哈~ + + +#### 代码整洁之道 程序员的职业素养 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcpeccaed0661f24dfe9a8ec41e28633acc~tplv-k3u1fbpfcp-zoom-1.image) + 这本书汇聚编程大师40余年编程生涯的心得体会. 阐释软件工艺中的原理、技术、工具和实践. 助力专业软件开发人员具备令人敬佩的职业素养。 + +- 综合评分:8.7 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 这本书值得多读几遍,坚持以正确的方式做对的事情。生活是这样,写代码也是~ + + +### 专题三:计算机网络篇书单 +- 《计算机网络 自顶向下方法》 +- 《TCPIP详解》 +- 《图解 HTTP》 +- 《深入剖析Tomcat》 +- 《深入理解Nginx》 + +#### 计算机网络 自顶向下方法 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp26eb718b0e8644188c8804115a630992~tplv-k3u1fbpfcp-zoom-1.image) + 本书是经典的计算机网络教材,采用作者独创的自顶向下方法来讲授计算机网络的原理及其协议,自第1版出版以来已经被数百所大学和学院选作教材,被译为14种语言。 + +- 综合评分:9.2 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 大学时候的教材,有空可以回过头复习一下的,挺不错的一本书。 + +#### TCPIP详解 +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcp440d8b0fca5441ae8f2909a5b5838ccd~tplv-k3u1fbpfcp-zoom-1.image) + 《TCPIP详解卷1:协议》是一本完整而详细的TCPIP协议指南。描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。适合作为计算机专业学生学习网络的教材和教师参考书,也适用于研究网络的技术人员。 + +- 综合评分:9.2 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 有些朋友可能觉得读起来会有点枯燥,但是有时间的话,坚持把它看完吧。作为一名程序员,还是需要对计算机网路相关知识了解的多点才好。 + +#### 图解HTTP +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcp64969deabfb648d7becdfd032cec4d9b~tplv-k3u1fbpfcp-zoom-1.image) + 本书对互联网基盘——HTTP协议进行了全面系统的介绍。HTTP协议的发展历史娓娓道来,严谨细致地剖析了HTTP协议的结构,列举诸多常见通信场景及实战案例,最后延伸到Web安全、最新技术动向等方面。 + +- 综合评分:8.1 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 这本挺还不错的,涵盖了HTTP的主要知识,并且言简意赅,挺适合入门的。 + +#### 深入剖析Tomcat +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcpcd1c1bcaf8bd444ab4de44f7613da22c~tplv-k3u1fbpfcp-zoom-1.image) + 本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 + +- 综合评分:8.4 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 对Tomcat感兴趣的朋友可以去读一读~ + +#### 深入理解Nginx +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcp979962fa403242b1aeeb74f02e64025c~tplv-k3u1fbpfcp-zoom-1.image) + 本书致力于说明开发Nginx模块的必备知识,深受广大读者的喜爱。 + +- 综合评分:8.7 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 好书是好书,但是有费咖啡,除了讲解nginx,还深入详解其相关知识。 + +### 专题四:操作系统 && 计算机底层 +- 《现代操作系统》 +- 《鸟哥的Linux私房菜基础学习篇》 +- 《鸟哥的Linux私房菜 (服务器架设篇)》 +- 《Linux内核设计与实现》 +- 《编码隐匿在计算机软硬件背后的语言》 +- 《程序是怎么跑起来的》 + +#### 现代操作系统 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcp30d980eb30bd4152b11c1baacaddfe04~tplv-k3u1fbpfcp-zoom-1.image) +本书是操作系统领域的经典之作.书中集中讨论了操作系统的基本原理,包括进程、线程、存储管理、文件系统、输入输出、死锁等,同时还包含了有关计算机安全、多媒体操作系统、掌上计算机操作系统、微内核、多核处理机上的虚拟机以及操作系统设计等方面的内容。 + +- 综合评分:8.9 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 大学的教材,有时间可以复习一下~ + +#### 鸟哥的Linux私房菜基础学习篇 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcpfe0649b65c254b8d8c5828975810d06d~tplv-k3u1fbpfcp-zoom-1.image) + 本书内容丰富全面,基本概念的讲解非常细致,深入浅出。各种功能和命令的介绍,都配以大量的实例操作和详尽的解析。 + +- 综合评分:9.1 +- 个人推荐指数:五星 +- 适合对象:初级、中级 +- linux的入门书籍,非常推荐去读~ + +#### 鸟哥的Linux私房菜服务器架设篇 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcp293fae6443694c6b8e7e01e134bd2c98~tplv-k3u1fbpfcp-zoom-1.image) + 本书针对服务器的维护与管理,以及发生问题时的应对策略都加以说明。作者先从系统基础以及网络基础讲起,再谈到网络攻击与防火墙防护主机后,才进入服务器的架设。 + +- 综合评分:8.8 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 总体来说,非常不错,可以作为服务器架设的入门书籍。 + +#### Linux内核设计与实现 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcpc8c670cb4156469ead42f75ccc33a738~tplv-k3u1fbpfcp-zoom-1.image) +《Linux内核设计与实现(原书第3版)》详细描述了Linux内核的主要子系统和特点,包括Linux内核的设计、实现和接口。从理论到实践涵盖了Linux内核的方方面面,可以满足读者的各种兴趣和需求。 + +- 综合评分:8.6 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 没有纠缠于内核代码本身,而是把握Linux内核设计的思想,详略得当,特点鲜明,很不错。 + +#### 编码隐匿在计算机软硬件背后的语言 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcpe9f390087b834e628be74201951cf5f1~tplv-k3u1fbpfcp-zoom-1.image) + 一本讲述计算机工作原理的书,作者用丰富的想象和清晰的笔墨将看似繁杂的理论阐述得通俗易懂,你丝毫不会感到枯燥和生硬。 + +- 综合评分:9.3 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 大学的时候就值得读的一本书 + +#### 程序是怎么跑起来的 +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcpd4782186490e42bd8c338923685beb4b~tplv-k3u1fbpfcp-zoom-1.image) + 本书从计算机的内部结构开始讲起,以图配文的形式详细讲解了二进制、内存、数据压缩、源文件和可执行文件、操作系统和应用程序的关系、汇编语言、硬件控制方法等内容,目的是让读者了解从用户双击程序图标到程序开始运行之间到底发生了什么。同时专设了“如果是你,你会怎样介绍?”专栏,以小学生、老奶奶为对象讲解程序的运行原理,颇为有趣。本书图文并茂,通俗易懂,非常适合计算机爱好者及相关从业人员阅读。 + +- 综合评分:8.0 +- 个人推荐指数:三星 +- 适合对象:初级、中级 +- 有空的话可以去看看,通俗易懂、图文并茂~ + +### 专题五:数据结构与算法 +- 《数据结构与算法分析:Java语言描述》 +- 《算法导论》 +- 《编程珠玑》 +- 《算法》 + +#### 数据结构与算法分析:Java语言描述 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcpb298b1f156914f78911b9d013d9fee56~tplv-k3u1fbpfcp-zoom-1.image) + 本书是国外数据结构与算法分析方面的经典教材,使用卓越的Java编程语言作为实现工具讨论了数据结构(组织大量数据的方法)和算法分析(对算法运行时间的估计)。 + +- 综合评分:8.3 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 学习数据结构与算法,做个有想法的程序员。其实源码底层,就有数据结构与算法的体现,如Hashmap底层就是链表、红黑树~ + + +#### 算法导论 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp2927cdf2a760463ca16942b6038d9cc5~tplv-k3u1fbpfcp-zoom-1.image) + 这本书深入浅出,全面地介绍了计算机算法。对每一个算法的分析既易于理解又十分有趣,并保持了数学严谨性。本书的设计目标全面,适用于多种用途。 + +- 综合评分:9.3 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 读书时代,搞ACM的伙伴必备的一本书,我们大学班主任也非常推荐这本书。这是本经典好书,值得多看几遍。 + +#### 编程珠玑 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp6cbe12c3d2f1430585af583e478f222f~tplv-k3u1fbpfcp-zoom-1.image) + 本书的特色是通过一些精心设计的有趣而又颇具指导意义的程序,对实用程序设计技巧及基本设计原则进行了透彻而睿智的描述,为复杂的编程问题提供了清晰而完备的解决思路。本书对各个层次的程序员都具有很高的阅读价值。 + +- 综合评分:9.1 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 算法导论是经典好书,而这本书的作者是算法导论作者的老师~ + + +#### 算法 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcp1668e40f45024aa3a8af1c35616886d3~tplv-k3u1fbpfcp-zoom-1.image) + + 本书全面讲述算法和数据结构的必备知识,具有以下几大特色算法领域的经典参考书、 内容全面、全新修订的代码、与实际应用相结合、与实际应用相结合等 + +- 综合评分:9.4 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 非常清晰的算法书,什么叫态度?什么叫好书?这本书就是最好的范例。 + +### 专题六:缓存编程书单 +- 《Redis设计与实现》 +- 《Redis开发与运维》 + +#### Redis设计与实现 +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcp6c0b0eb7ad2c4477b324bb96687ed526~tplv-k3u1fbpfcp-zoom-1.image) + + 系统而全面地描述了 Redis 内部运行机制。图示丰富,描述清晰,并给出大量参考信息,是NoSQL数据库开发人员案头必备。包括大部分Redis单机特征,以及所有多机特性。 + +- 综合评分:8.6 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 本书通俗易懂,可以先快速读一遍,了解下redis 机制。 + + +#### Redis开发与运维 +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcpc5df5188a5434042a914f251c4c11e47~tplv-k3u1fbpfcp-zoom-1.image) + + 本书全面讲解Redis基本功能及其应用,并结合线上开发与运维监控中的实际使用案例,深入分析并总结了实际开发运维中遇到的“陷阱”,以及背后的原因, 包含大规模集群开发与管理的场景、应用案例与开发技巧,为高效开发运维提供了大量实际经验和建议。 + +- 综合评分:8.9 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 该书逻辑清晰,深度恰当,尤其适合运维人员和初级开发人员。 + +### 专题七:数据库书单 +- 《sql必知必会》 +- 《高性能Mysql》 +- 《MySQL技术内幕:innodb存储引擎》 + +#### sql必知必会 +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcpd56471ad5efb475881cf37012a1d5a73~tplv-k3u1fbpfcp-zoom-1.image) + 本书是深受世界各地读者欢迎的SQL经典畅销书,内容丰富,文字简洁明快,针对Oracle、SQL Server、MySQL、DB2、PostgreSQL、SQLite等各种主流数据库提供了大量简明的实例。 + +- 综合评分:8.5 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 本书内容比较简单,适合新手,非常精炼。 + +#### 高性能Mysql +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcp15363beaf148451595f4a6b481def4aa~tplv-k3u1fbpfcp-zoom-1.image) + 《高性能mysql(第3版)》不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 + +- 综合评分:9.3 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- DBA必读,开发也推荐阅读,全面的一本有关mysql应用的书籍。 + +#### MySQL技术内幕:innodb存储引擎 +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcp1f72706478e14b2cb7ff9835dbdac550~tplv-k3u1fbpfcp-zoom-1.image) + 《MySQL技术内幕InnoDB存储引擎(第2版)》从源代码的角度深度解析了InnoDB的体系结构、实现原理、工作机制,并给出了大量最佳实践,能帮助你系统而深入地掌握InnoDB,更重要的是,它能为你设计管理高性能、高可用的数据库系统提供绝佳的指导。 + +- 综合评分:8.5 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 简单易懂,讲解比较透彻,值得一读。尤其对InnoDB的事务、锁等方面讲得比较详细。 + + +### 专题八:微服务 && 分布式 +- 《微服务架构设计模式》 +- 《从Paxos到Zookeeper分布式一致性原理与实践》 + +#### 微服务架构设计模式 +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcpa8577a8cca4a4ef98622366c927dd4e4~tplv-k3u1fbpfcp-zoom-1.image) + 本书将教会你如何开发和部署生产级别的微服务架构应用。这套宝贵的架构设计模式建立在数十年的分布式系统经验之上,Chris 还为开发服务添加了新的模式,并将它们组合成可在真实条件下可靠地扩展和执行的系统。本书不仅仅是一个模式目录,还提供了经验驱动的建议,以帮助你设计、实现、测试和部署基于微服务的应用程序。 + +- 综合评分:9.1 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 44 个架构设计模式,理论联系实际开发经验,并且涉及涉及接口设计、数据库设计、架构设计等各个方面,很不错的一本书~ + +#### 从Paxos到Zookeeper分布式一致性原理与实践 +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcpb2565b26f5de48af94ab530d68d4ec2e~tplv-k3u1fbpfcp-zoom-1.image) + 《Paxos到Zookeeper:分布式一致性原理与实践》从分布式一致性的理论出发,向读者简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 + +- 综合评分:7.7 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 可以当做zookeeper的入门书籍,偏理论,整体看一下是值得的。 + +### 专题九:消息中间件书单 +- 《Kafka权威指南》 +- 《RabbitMQ实战指南》 + +#### 《Kafka权威指南》 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcpb2a6370b0a294d77a4f2dcac5d179342~tplv-k3u1fbpfcp-zoom-1.image) + 本书详细介绍了如何部署Kafka集群、开发可靠的基于事件驱动的微服务,以及基于Kafka平台构建可伸缩的流式应用程序。通过详尽示例,你将会了解到Kafka的设计原则、可靠性保证、关键API,以及复制协议、控制器和存储层等架构细节。 + +- 综合评分:8.7 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 学习Kafka入门一本不错的书籍 + +#### RabbitMQ实战指南 +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcp3a900389498b433c807c8b013eac1234~tplv-k3u1fbpfcp-zoom-1.image) + 《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。 + +- 综合评分:8.7 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 学习RabbitMQ 一本很不错的书籍 + +### 专题十:容器书单 +- 《Docker——容器与容器云》 + +#### Docker——容器与容器云(第2版) +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcpd854fc3f26b3450599c970efcbaba10f~tplv-k3u1fbpfcp-zoom-1.image) + 从实践者的角度出发,以Docker和Kubernetes为重点,沿着“基本用法介绍”到“核心原理解读”到“高级实践技巧”的思路,一本书讲透当前主流的容器和容器云技术,有助于读者在实际场景中利用Docker容器和容器云解决问题并启发新的思考。 + +- 综合评分:8.4 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 市面上Docker的书不多,学习Docker的话,就推荐这本吧 + + +### 专题十一:面试相关书单 +- 《剑指offer》 +- 《程序员代码面试指南:IT名企算法与数据结构题目最优解》 +- 《编程之美微软技术面试心得》 +- leetCode + +#### 剑指offer +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcpf615a0abb0cb47319042581152b6feab~tplv-k3u1fbpfcp-zoom-1.image) + 本书系统整理基础知识、代码质量、解题思路、优化效率和综合能力这5个面试要点,适合即将走向工作岗位的大学生阅读,也适合作为正在应聘软件行业的相关就业人员和计算机爱好者的参考书。 + +- 综合评分:9.2 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 非常棒的书,把解题技巧、面试需要的能力等等都讲解到了。 + +#### 程序员代码面试指南:IT名企算法与数据结构题目最优解 +![](httpsp9-juejin.byteimg.comtos-cn-i-k3u1fbpfcpc1297d84543540339f79d7982ff057f8~tplv-k3u1fbpfcp-zoom-1.image) + 这是一本程序员面试宝典!书中对IT名企代码面试各类题目的最优解进行了总结,并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一痛点,本书选取将近200道真实出现过的经典代码面试题,帮助广大程序员的面试准备做到万无一失。 + +- 综合评分:8.9 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 书中题目挺经典的,讲解也很详细。 + + +#### 编程之美微软技术面试心得 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp531d18c72c8a4cd594a383a0386b1be2~tplv-k3u1fbpfcp-zoom-1.image) + 这本书收集了约60道算法和程序设计题目,这些题目大部分在近年的笔试、面试中出现过,或者是被微软员工热烈讨论过。作者试图从书中各种有趣的问题出发,引导读者发现问题,分析问题,解决问题,寻找更优的解法。 + +- 综合评分:8.4 +- 个人推荐指数:四星 +- 适合对象:初级、中级 +- 以前大四我们班同学找工作的时候,手上都有这基本书《剑指offer》、《编程之美》 + +#### letecode +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcp4b4d3470fa6344f8b7f282ca92f02bd8~tplv-k3u1fbpfcp-zoom-1.image) +- 综合评分:9.6 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- leetCode不是一本书,而是一个编程题网站,如果你面试找工作,尤其是面大厂,一定记得先去leetCode刷题哈。 + + +### 专题十二:软件开发&&程序人生相关书单 +- 《领域驱动设计 软件核心复杂性应对之道》 +- 《人月神话》 +- 《程序员修炼之道》 +- 《软技能:代码之外的生存指南》 +- 《程序员思维修炼》 +- 《代码大全(第2版)》 + +#### 领域驱动设计 软件核心复杂性应对之道 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp863375dcf1434ac4a4e55783de47e0e4~tplv-k3u1fbpfcp-zoom-1.image) + 《领域驱动设计软件核心复杂性应对之道》是领域驱动设计方面的经典之作。全书围绕着设计和开发实践,结合若干真实的项目案例,向读者阐述如何在真实的软件开发中应用领域驱动设计。 + +- 综合评分:9.2 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 多读几遍,尤其做过几个比较大规模的系统后,再回来看这本书,会有不少收获的。 + + +#### 人月神话 +![](httpsp6-juejin.byteimg.comtos-cn-i-k3u1fbpfcp98efbe0ba94b43309684d31ae5e5afd2~tplv-k3u1fbpfcp-zoom-1.image) + 在软件领域,很少能有像《人月神话》一样具有深远影响力和畅销不衰的著作。Brooks博士为人们管理复杂项目提供了最具洞察力的见解,既有很多发人深省的观点,又有大量软件工程的实践。 + +- 综合评分:8.6 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 软件工程的经典著作,值得细细品味~ + +#### 程序员修炼之道(第2版) +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcp06d3d31de9ae4345b37884c72e8a66d5~tplv-k3u1fbpfcp-zoom-1.image) + 《程序员修炼之道》之所以在全球范围内广泛传播,被一代代开发者奉为圭臬,盖因它可以创造出真正的价值:或编写出更好的软件,或探究出编程的本质,而所有收获均不依赖于特定语言、框架和方法。 + +- 综合评分:9.1 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 有朋友说,这本书改变了他的一生。 + +#### 软技能:代码之外的生存指南 +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcpaa59bc55f1ec441f91b7a9b5d06591f2~tplv-k3u1fbpfcp-zoom-1.image) + 这是一本为软件开发者量身定做的职业发展指南,处于任何发展阶段的软件开发者,都将从本书中获益。 + +- 综合评分:9.1 +- 个人推荐指数:四星 +- 适合对象:初级、中级、高级 +- 我们从事软件开发这个行业,真的不仅仅有编码呢,你的职业发展是怎样的?如果迷茫就去看书,可以先看看这本~ + +#### 代码大全(第2版) +![](httpsp1-juejin.byteimg.comtos-cn-i-k3u1fbpfcp381471f5e33e46c6af7afc2a89a85fd6~tplv-k3u1fbpfcp-zoom-1.image) + 这是一本完整的软件构建手册,涵盖了软件构建过程中的所有细节。它从软件质量和编程思想等方面论述了软件构建的各个问题,并详细论述了紧跟潮流的新技术、高屋建瓴的观点、通用的概念,还含有丰富而典型的程序示例。 + +- 综合评分:9.1 +- 个人推荐指数:五星 +- 适合对象:初级、中级、高级 +- 作为一名程序员,感觉这本书必读 + + +### 个人公众号 +![](httpsp3-juejin.byteimg.comtos-cn-i-k3u1fbpfcpe6b23c704fc94ca09207b779f953cce6~tplv-k3u1fbpfcp-zoom-1.image) +- 更多干货,关注公众号 + + + + diff --git "a/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246/README.MD" "b/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246/README.MD" new file mode 100644 index 0000000..5ee6e33 --- /dev/null +++ "b/Java\347\250\213\345\272\217\345\221\230\351\234\200\350\246\201\347\234\213\345\223\252\344\272\233\344\271\246/README.MD" @@ -0,0 +1,5 @@ +## Java程序员书单(持续更新中) +​ +公众号:捡田螺的小男孩 +​ +- [一份Java程序员的珍藏书单,请您注意查收](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488066&idx=1&sn=44b5a90be1b69d7938dbcf516d85f041&chksm=cf21cd6bf856447d869278386250f59a926881375df848e54f86a21682bdab50f9e09ca56fbd&token=162724582&lang=zh_CN&scene=21#wechat_redirect) diff --git "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/README.MD" "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/README.MD" new file mode 100644 index 0000000..7397d39 --- /dev/null +++ "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/README.MD" @@ -0,0 +1,32 @@ +## 1. 面试真题 + +关注公众号:捡田螺的小男孩 +​ +- [oppo后端16连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498750&idx=1&sn=19fe8b4fff28fe81db14e733053bbc74&chksm=cf2224d7f855adc1d0984980a4e3de31fe33329164a472ca8d8255a8a80b69b2e23850811323&token=2001057130&lang=zh_CN#rd) +- [小厂后端十连问(附答案)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498316&idx=1&sn=7749b78293b7b2af51eda99844e08a56&chksm=cf222565f855ac7324232e2af459f8b6e6eb5fd5b272c2b29bda08cc579421b6704a0de94b2e&token=2001057130&lang=zh_CN#rd) +- [腾讯云后端15连问!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498171&idx=1&sn=f5a7ec25a569822be0f73fbcd413e8ba&chksm=cf222692f855af84fba419166fcd4235c0e78af3a2e1ec4c723a4efb1bd1ad6f8a5b9404c599&token=2001057130&lang=zh_CN#rd) +- [社招后端21连问(三年工作经验一面)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498084&idx=1&sn=96c8148cfeeeb16668ed9e03fa9131cc&chksm=cf22264df855af5b6e81b93738cca28989226a53ec702fcfaa0cc5004dded4208c5ee5ea844a&token=2001057130&lang=zh_CN#rd) +- [一份热乎乎的字节面试真题](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497742&idx=1&sn=18765e7356f446a7f2521f45b467d5d3&chksm=cf222727f855ae31dd2029e3219814211336c41d9228d271a583d3691ddadca586529aca9302&token=2001057130&lang=zh_CN#rd) +- [面试必备:虾皮服务端15连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497256&idx=1&sn=3b799c2d514aa25e85a6faa60d639a0b&chksm=cf222901f855a017b73356b99b830b8800a7a9172fab891c5759d8dd69a270872ea9480c0b7c&token=2001057130&lang=zh_CN#rd) +- [宇宙条一面:十道经典面试题解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495342&idx=1&sn=54e1c0c16a6467001524c34818025331&chksm=cf223187f855b89140db5ca429e6efc19d0111abf7f36b78a0ecd73b00fded1ff1e7ba32a6f1&token=2001057130&lang=zh_CN#rd) +- [蚂蚁金服一面:十道经典面试题解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247493270&idx=1&sn=1c78a81d6e1bd0f0fd947fe8c3a33e32&chksm=cf2239bff855b0a9627855f20a17799e0506eb7548a409bfa0ee0450328d7519ec70f7b962cc&token=2001057130&lang=zh_CN#rd) +​ +## 2. 必考经典面试题 +​ +- [Redis主从、哨兵、 Cluster集群一锅端!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498254&idx=1&sn=6489360c3b226df9811e66cb40ec7656&chksm=cf222527f855ac3112628bcec7730064fee3fdbe869fbd0a7410c22766a0c036a7e5c1a69fa0&token=2001057130&lang=zh_CN#rd) +- [我们为什么要分库分表?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498625&idx=1&sn=0d7bd9d1b46eeff4c715a6761355e9b0&chksm=cf2224a8f855adbea8931c8e011711f6c70cffeef8ddf8b87729c710eacef11b46eef80fda36&token=2001057130&lang=zh_CN#rd) +- [面试必备:聊聊MySQL的主从](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497982&idx=1&sn=bb589329cceb5462fc41f66ec63dbf56&chksm=cf2227d7f855aec16dd4d3b3425c0401850eeaf2c9cdc82e82722d38a00c24ee9ccfa3353774&token=2001057130&lang=zh_CN#rd) +- [消息队列经典十连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497847&idx=1&sn=29a32672b712e7dfadfa36c9902b2ec7&chksm=cf22275ef855ae484fb3f51a5726e9a4bc45222e8fbbd33631d177dc4b5619c36889ea178463&token=2001057130&lang=zh_CN#rd) +- [八种幂等设计](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497427&idx=1&sn=2ed160c9917ad989eee1ac60d6122855&chksm=cf2229faf855a0ecf5eb34c7335acdf6420426490ee99fc2b602d54ff4ffcecfdab24eeab0a3&token=2001057130&lang=zh_CN#rd) +- [看一遍就理解:零拷贝详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247496788&idx=1&sn=f65ddd10d16d8376efa0037762153932&chksm=cf222b7df855a26bad76249e7b77e28da3097b226f9165d79f5031516d9c345827fca901559c&token=2001057130&lang=zh_CN#rd) +- [看一遍就理解:IO模型详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247496448&idx=1&sn=cd502f850290a25949dd4a11ac55a039&chksm=cf222c29f855a53f094bde2868900fa252b07385e73564e9ee9f0510cb4e74387d9d23ab67e6&token=2001057130&lang=zh_CN#rd) +- [看一遍就理解:MVCC原理详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495277&idx=1&sn=a1812febb4246f824ce54d778f672025&chksm=cf223144f855b8528ad6cce707dc3a1b4d387817bd751dfab4f79dda90c6640f9763d25f3f33&token=2001057130&lang=zh_CN#rd) +- [2W字!详解20道Redis经典面试题!(珍藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494124&idx=1&sn=c185f7d999d5f006608d05707a8a7eea&chksm=cf2236c5f855bfd329c6e2ee27f23f8131ebcd312960190a10f1a819d67f07a21a08ad17f263&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [七种方案!探讨Redis分布式锁的正确使用姿势](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488142&idx=1&sn=79a304efae7a814b6f71bbbc53810c0c&chksm=cf21cda7f85644b11ff80323defb90193bc1780b45c1c6081f00da85d665fd9eb32cc934b5cf&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [面试必备!TCP协议经典十五连问!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490868&idx=1&sn=96889bfe6a97f9200fa2d682cf2f5d89&chksm=cf21c21df8564b0b0757df584560a69340b1775fe1c70b867439565969ec3aed19c442ff4eeb&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [2W字!梳理50道经典计算机网络面试题(收藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247492832&idx=1&sn=601fa1c340a313bc0f74bb75cdb6a95a&chksm=cf223bc9f855b2dfb8d0e74f3360e2edfe25c3a728fe17e9e80b6022340994fd9d9e1ca83ca8&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [ZooKeeper的十二连问,你顶得了嘛?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488047&idx=1&sn=4913c7e1c3b8835f7512d8dc6b845727&chksm=cf21cd06f8564410cce6121230256facb1ab3b5a9ed35579896f428d84bdea7b86836109d575&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [50道Java集合经典面试题(收藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488007&idx=1&sn=c5c16c8ec916c791e776216f3177c7e2&chksm=cf21cd2ef85644382a985e9fed1956d6ee60c86ce69e65f31f775318435fdb86bf368e26edf2&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [100道MySQL数据库经典面试题解析(收藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488000&idx=1&sn=1c38db7fd110bbcc1ffb2d72a56aaf25&chksm=cf21cd29f856443f25a3fe98ae8e888faceef9bee45df045969b2cffb105363dcc2a4480bb74&token=1495321435&lang=zh_CN&scene=21#wechat_redirect) +- [Spring 面试63问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497672&idx=1&sn=6ff0350e23d014b29a47bdec79af9ef5&chksm=cf2228e1f855a1f70fa78d9bd85c53dfbe154c1325aa1e203e4c918132c430d51bb68e961eda&token=2001057130&lang=zh_CN#rd) +- [多线程50连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247501446&idx=1&sn=3d83f3c1035c963c1fda3f77ab750e71&chksm=cf2219aff85590b9ba054dc33956a5cafe1beaa77b231dc4dc0cf891be3e16ef367f6b2ac4ed&token=245109219&lang=zh_CN#rd) \ No newline at end of file diff --git "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/README.MD.bak" "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/README.MD.bak" new file mode 100644 index 0000000..cfb8d9d --- /dev/null +++ "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/README.MD.bak" @@ -0,0 +1,31 @@ +## 1. 面试真题 + +关注公众号:捡田螺的小男孩 +​ +- [oppo后端16连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498750&idx=1&sn=19fe8b4fff28fe81db14e733053bbc74&chksm=cf2224d7f855adc1d0984980a4e3de31fe33329164a472ca8d8255a8a80b69b2e23850811323&token=2001057130&lang=zh_CN#rd) +- [小厂后端十连问(附答案)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498316&idx=1&sn=7749b78293b7b2af51eda99844e08a56&chksm=cf222565f855ac7324232e2af459f8b6e6eb5fd5b272c2b29bda08cc579421b6704a0de94b2e&token=2001057130&lang=zh_CN#rd) +- [腾讯云后端15连问!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498171&idx=1&sn=f5a7ec25a569822be0f73fbcd413e8ba&chksm=cf222692f855af84fba419166fcd4235c0e78af3a2e1ec4c723a4efb1bd1ad6f8a5b9404c599&token=2001057130&lang=zh_CN#rd) +- [社招后端21连问(三年工作经验一面)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498084&idx=1&sn=96c8148cfeeeb16668ed9e03fa9131cc&chksm=cf22264df855af5b6e81b93738cca28989226a53ec702fcfaa0cc5004dded4208c5ee5ea844a&token=2001057130&lang=zh_CN#rd) +- [一份热乎乎的字节面试真题](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497742&idx=1&sn=18765e7356f446a7f2521f45b467d5d3&chksm=cf222727f855ae31dd2029e3219814211336c41d9228d271a583d3691ddadca586529aca9302&token=2001057130&lang=zh_CN#rd) +- [面试必备:虾皮服务端15连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497256&idx=1&sn=3b799c2d514aa25e85a6faa60d639a0b&chksm=cf222901f855a017b73356b99b830b8800a7a9172fab891c5759d8dd69a270872ea9480c0b7c&token=2001057130&lang=zh_CN#rd) +- [宇宙条一面:十道经典面试题解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495342&idx=1&sn=54e1c0c16a6467001524c34818025331&chksm=cf223187f855b89140db5ca429e6efc19d0111abf7f36b78a0ecd73b00fded1ff1e7ba32a6f1&token=2001057130&lang=zh_CN#rd) +- [蚂蚁金服一面:十道经典面试题解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247493270&idx=1&sn=1c78a81d6e1bd0f0fd947fe8c3a33e32&chksm=cf2239bff855b0a9627855f20a17799e0506eb7548a409bfa0ee0450328d7519ec70f7b962cc&token=2001057130&lang=zh_CN#rd) +​ +## 2. 必考经典面试题 +​ +- [Redis主从、哨兵、 Cluster集群一锅端!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498254&idx=1&sn=6489360c3b226df9811e66cb40ec7656&chksm=cf222527f855ac3112628bcec7730064fee3fdbe869fbd0a7410c22766a0c036a7e5c1a69fa0&token=2001057130&lang=zh_CN#rd) +- [我们为什么要分库分表?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498625&idx=1&sn=0d7bd9d1b46eeff4c715a6761355e9b0&chksm=cf2224a8f855adbea8931c8e011711f6c70cffeef8ddf8b87729c710eacef11b46eef80fda36&token=2001057130&lang=zh_CN#rd) +- [面试必备:聊聊MySQL的主从](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497982&idx=1&sn=bb589329cceb5462fc41f66ec63dbf56&chksm=cf2227d7f855aec16dd4d3b3425c0401850eeaf2c9cdc82e82722d38a00c24ee9ccfa3353774&token=2001057130&lang=zh_CN#rd) +- [消息队列经典十连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497847&idx=1&sn=29a32672b712e7dfadfa36c9902b2ec7&chksm=cf22275ef855ae484fb3f51a5726e9a4bc45222e8fbbd33631d177dc4b5619c36889ea178463&token=2001057130&lang=zh_CN#rd) +- [八种幂等设计](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497427&idx=1&sn=2ed160c9917ad989eee1ac60d6122855&chksm=cf2229faf855a0ecf5eb34c7335acdf6420426490ee99fc2b602d54ff4ffcecfdab24eeab0a3&token=2001057130&lang=zh_CN#rd) +- [看一遍就理解:零拷贝详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247496788&idx=1&sn=f65ddd10d16d8376efa0037762153932&chksm=cf222b7df855a26bad76249e7b77e28da3097b226f9165d79f5031516d9c345827fca901559c&token=2001057130&lang=zh_CN#rd) +- [看一遍就理解:IO模型详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247496448&idx=1&sn=cd502f850290a25949dd4a11ac55a039&chksm=cf222c29f855a53f094bde2868900fa252b07385e73564e9ee9f0510cb4e74387d9d23ab67e6&token=2001057130&lang=zh_CN#rd) +- [看一遍就理解:MVCC原理详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495277&idx=1&sn=a1812febb4246f824ce54d778f672025&chksm=cf223144f855b8528ad6cce707dc3a1b4d387817bd751dfab4f79dda90c6640f9763d25f3f33&token=2001057130&lang=zh_CN#rd) +- [2W字!详解20道Redis经典面试题!(珍藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494124&idx=1&sn=c185f7d999d5f006608d05707a8a7eea&chksm=cf2236c5f855bfd329c6e2ee27f23f8131ebcd312960190a10f1a819d67f07a21a08ad17f263&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [七种方案!探讨Redis分布式锁的正确使用姿势](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488142&idx=1&sn=79a304efae7a814b6f71bbbc53810c0c&chksm=cf21cda7f85644b11ff80323defb90193bc1780b45c1c6081f00da85d665fd9eb32cc934b5cf&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [面试必备!TCP协议经典十五连问!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490868&idx=1&sn=96889bfe6a97f9200fa2d682cf2f5d89&chksm=cf21c21df8564b0b0757df584560a69340b1775fe1c70b867439565969ec3aed19c442ff4eeb&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [2W字!梳理50道经典计算机网络面试题(收藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247492832&idx=1&sn=601fa1c340a313bc0f74bb75cdb6a95a&chksm=cf223bc9f855b2dfb8d0e74f3360e2edfe25c3a728fe17e9e80b6022340994fd9d9e1ca83ca8&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [ZooKeeper的十二连问,你顶得了嘛?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488047&idx=1&sn=4913c7e1c3b8835f7512d8dc6b845727&chksm=cf21cd06f8564410cce6121230256facb1ab3b5a9ed35579896f428d84bdea7b86836109d575&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [50道Java集合经典面试题(收藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488007&idx=1&sn=c5c16c8ec916c791e776216f3177c7e2&chksm=cf21cd2ef85644382a985e9fed1956d6ee60c86ce69e65f31f775318435fdb86bf368e26edf2&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [100道MySQL数据库经典面试题解析(收藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488000&idx=1&sn=1c38db7fd110bbcc1ffb2d72a56aaf25&chksm=cf21cd29f856443f25a3fe98ae8e888faceef9bee45df045969b2cffb105363dcc2a4480bb74&token=1495321435&lang=zh_CN&scene=21#wechat_redirect) +- [Spring 面试63问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497672&idx=1&sn=6ff0350e23d014b29a47bdec79af9ef5&chksm=cf2228e1f855a1f70fa78d9bd85c53dfbe154c1325aa1e203e4c918132c430d51bb68e961eda&token=2001057130&lang=zh_CN#rd) \ No newline at end of file diff --git "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/\347\274\223\345\255\230,Redis/Redis\344\270\272\344\273\200\344\271\210\350\277\231\344\271\210\345\277\253.md" "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/\347\274\223\345\255\230,Redis/Redis\344\270\272\344\273\200\344\271\210\350\277\231\344\271\210\345\277\253.md" new file mode 100644 index 0000000..a3f6b30 --- /dev/null +++ "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/\347\274\223\345\255\230,Redis/Redis\344\270\272\344\273\200\344\271\210\350\277\231\344\271\210\345\277\253.md" @@ -0,0 +1,175 @@ + +## 前言 + +大家好呀,我是捡田螺的小男孩。我们都知道Redis很快,它QPS可达10万(每秒请求数)。**Redis为什么这么快呢**,本文将跟大家一起学习。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3880491879ed4a228d8f4213d987f6a3~tplv-k3u1fbpfcp-zoom-1.image) + + +- 公众号:**捡田螺的小男孩** +- [github地址](https://github.com/whx123/JavaHome),感谢每一颗star + +## 基于内存实现 + +我们都知道内存读写是比磁盘读写快很多的。Redis是基于内存存储实现的数据库,相对于数据存在磁盘的数据库,就省去磁盘磁盘I/O的消耗。MySQL等磁盘数据库,需要建立索引来加快查询效率,而Redis数据存放在内存,直接操作内存,所以就很快。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0d7be13173814a43a60960fe59a48c61~tplv-k3u1fbpfcp-zoom-1.image) + +## 高效的数据结构 + +我们知道,MySQL索引为了提高效率,选择了B+树的数据结构。其实合理的数据结构,就是可以让你的应用/程序更快。先看下Redis的数据结构&内部编码图: + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ddd7723cc0e4953b746b13db7a5cea3~tplv-k3u1fbpfcp-zoom-1.image) + + +### SDS简单动态字符串 + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e68a18c50f149dda136fc9b1aa73ab8~tplv-k3u1fbpfcp-zoom-1.image) + +``` +struct sdshdr { //SDS简单动态字符串 + int len; //记录buf中已使用的空间 + int free; // buf中空闲空间长度 + char buf[]; //存储的实际内容 +} +``` + + +#### 字符串长度处理 + +在C语言中,要获取```捡田螺的小男孩```这个字符串的长度,需要从头开始遍历,复杂度为O(n); +在Redis中, 已经有一个**len**字段记录当前字符串的长度啦,直接获取即可,时间复杂度为O(1)。 + +#### 减少内存重新分配的次数 + +在C语言中,修改一个字符串,需要重新分配内存,修改越频繁,内存分配就越频繁,而分配内存是会**消耗性能**的。而在Redis中,SDS提供了两种优化策略:空间预分配和惰性空间释放。 + +**空间预分配** + +当SDS简单动态字符串修改和空间扩充时,除了分配必需的内存空间,还会额外分配未使用的空间。分配规则是酱紫的: + +> - SDS修改后,len的长度小于1M,那么将额外分配与len相同长度的未使用空间。比如len=100,重新分配后,buf 的实际长度会变为100(已使用空间)+100(额外空间)+1(空字符)=201。 +> - SDS修改后, len长度大于1M,那么程序将分配1M的未使用空间。 + +**惰性空间释放** + +当SDS缩短时,不是回收多余的内存空间,而是用free记录下多余的空间。后续再有修改操作,直接使用free中的空间,减少内存分配。 + +#### 哈希 + +Redis 作为一个K-V的内存数据库,它使用用一张全局的哈希来保存所有的键值对。这张哈希表,有多个哈希桶组成,哈希桶中的entry元素保存了```*key```和```*value```指针,其中```*key```指向了实际的键,```*value```指向了实际的值。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/13924387cd9e4a2d96d873dba1cc3ca9~tplv-k3u1fbpfcp-zoom-1.image) + +哈希表查找速率很快的,有点类似于Java中的**HashMap**,它让我们在**O(1)** 的时间复杂度快速找到键值对。首先通过key计算哈希值,找到对应的哈希桶位置,然后定位到entry,在entry找到对应的数据。 + +有些小伙伴可能会有疑问:你往哈希表中写入大量数据时,不是会遇到**哈希冲突**问题嘛,那效率就会降下来啦。 +> **哈希冲突:** 通过不同的key,计算出一样的哈希值,导致落在同一个哈希桶中。 + +Redis为了解决哈希冲突,采用了**链式哈希**。链式哈希是指同一个哈希桶中,多个元素用一个链表来保存,它们之间依次用指针连接。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/131951cd80354c24b62584d71b8fe9f9~tplv-k3u1fbpfcp-zoom-1.image) + +有些小伙伴可能还会有疑问:哈希冲突链上的元素只能通过指针逐一查找再操作。当往哈希表插入数据很多,冲突也会越多,冲突链表就会越长,那查询效率就会降低了。 + +为了保持高效,Redis 会对哈希表做**rehash操作**,也就是增加哈希桶,减少冲突。为了rehash更高效,Redis还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表,一个用于扩容,称为备用哈希表。 + +#### 跳跃表 + +跳跃表是Redis特有的数据结构,它其实就是在**链表的基础上,增加多级索引**,以提高查找效率。跳跃表的简单原理图如下: + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b62d59ffbd945e18f6dfcbf650a6eed~tplv-k3u1fbpfcp-watermark.image) + +- 每一层都有一条有序的链表,最底层的链表包含了所有的元素。 +- 跳跃表支持平均 O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。 + + +#### 压缩列表ziplist + +压缩列表ziplist是列表键和字典键的的底层实现之一。它是由一系列特殊编码的内存块构成的列表, 一个ziplist可以包含多个entry, 每个entry可以保存一个长度受限的字符数组或者整数,如下: + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ce3da7cddbe4e6e94a775151664ed93~tplv-k3u1fbpfcp-zoom-1.image) + +- zlbytes :记录整个压缩列表占用的内存字节数 +- zltail: 尾节点至起始节点的偏移量 +- zllen : 记录整个压缩列表包含的节点数量 +- entryX: 压缩列表包含的各个节点 +- zlend : 特殊值0xFF(十进制255),用于标记压缩列表末端 + +由于内存是**连续分配**的,所以遍历速度很快。。 + + +## 合理的数据编码 + +Redis支持多种数据基本类型,每种基本类型对应不同的数据结构,每种数据结构对应不一样的编码。为了提高性能,Redis设计者总结出,数据结构最适合的编码搭配。 + +Redis是使用对象(redisObject)来表示数据库中的键值,当我们在 Redis 中创建一个键值对时,至少创建两个对象,一个对象是用做键值对的键对象,另一个是键值对的值对象。 +``` +//关注公众号:捡田螺的小男孩 +typedef struct redisObject{ + //类型 + unsigned type:4; + //编码 + unsigned encoding:4; + //指向底层数据结构的指针 + void *ptr; + //... + }robj; +``` + +redisObject中,**type** 对应的是对象类型,包含String对象、List对象、Hash对象、Set对象、zset对象。**encoding** 对应的是编码。 + +- String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。 +- List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码 +- Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。 +- Set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码。 +- Zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码 + +## 合理的线程模型 + + +### 单线程模型:避免了上下文切换 + +Redis是单线程的,其实是指**Redis的网络IO和键值对读写**是由一个线程来完成的。但Redis的其他功能,比如持久化、异步删除、集群数据同步等等,实际是由额外的线程执行的。 + +Redis的单线程模型,避免了**CPU不必要的上下文切换**和**竞争锁的消耗**。也正因为是单线程,如果某个命令执行过长(如hgetall命令),会造成阻塞。Redis是面向快速执行场景的内存数据库,所以要慎用如lrange和smembers、hgetall等命令。 + +什么是**上下文切换**?举个粟子: + +> - 比如你在看一本英文小说,你看到某一页,发现有个单词不会读,你加了个书签,然后去查字典。查完字典后,你回来从书签那里继续开始读,这个流程就很舒畅。 +> - 如果你一个人读这本书,肯定没啥问题。但是如果你去查字典的时候,别的小伙伴翻了一下你的书,然后溜了。你再回来看的时候,发现书不是你看的那一页了,你得花时间找到你的那一页。 +> - 一本书,你一个人怎么看怎么打标签都没事,但是人多了翻来翻去,这本书各种标记就很乱了。可能这个解释很粗糙,但是道理应该是一样的。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/053215e73f844b8da8c2880205947fc1~tplv-k3u1fbpfcp-zoom-1.image) + +### I/O 多路复用 + +什么是I/O多路复用? +- I/O :网络 I/O +- 多路 :多个网络连接 +- 复用:复用同一个线程。 +- IO多路复用其实就是一种同步IO模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出cpu。 + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07838d0a48ef4b38acccb7b52e5435e1~tplv-k3u1fbpfcp-zoom-1.image) + + +> 多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epoll作为I/O多路复用技术的实现。并且Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。 + +## 虚拟内存机制 + +Redis直接自己构建了VM机制 ,不会像一般的系统会调用系统函数处理,会浪费一定的时间去移动和请求。 + +**Redis的虚拟内存机制是啥呢?** +> 虚拟内存机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据)。通过VM功能可以实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。 + +### 参考与感谢 + +- [Redis之VM机制](https://www.codenong.com/cs106843764/) +- [一文揭秘单线程的Redis为什么这么快?](https://zhuanlan.zhihu.com/p/57089960) +- [洞察|Redis是单线程的,但Redis为什么这么快?](https://zhuanlan.zhihu.com/p/42272979) + + diff --git "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/TCP\345\215\217\350\256\25615\350\277\236\351\227\256.md" "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/TCP\345\215\217\350\256\25615\350\277\236\351\227\256.md" new file mode 100644 index 0000000..eae7eec --- /dev/null +++ "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/TCP\345\215\217\350\256\25615\350\277\236\351\227\256.md" @@ -0,0 +1,395 @@ +### 前言 + +TCP协议是大厂面试必问的知识点。整理了15道非常经典的TCP面试题,希望大家都找到理想的offer呀 + + +![](https://files.mdnice.com/user/3535/47429a24-7e0b-4bf6-8200-7e2ec1baad63.png) + + +- 公众号:**捡田螺的小男孩** + +### 1. 讲下TCP三次握手流程 + +![](https://files.mdnice.com/user/3535/43f4b02a-ad18-45e5-8d6e-52349cc371d6.png) + +开始客户端和服务器都处于CLOSED状态,然后服务端开始监听某个端口,进入LISTEN状态 + +- 第一次握手(SYN=1, seq=x),发送完毕后,客户端进入 SYN_SEND 状态 +- 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器端进入 SYN_RCVD 状态。 +- 第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输。 + +### 2.TCP握手为什么是三次,不能是两次?不能是四次? + +TCP握手为什么是三次呢?为了方便理解,我们以谈恋爱为例子:两个人能走到一起,最重要的事情就是相爱,就是**我爱你,并且我知道,你也爱我**,接下来我们以此来模拟三次握手的过程: + + +![](https://files.mdnice.com/user/3535/374acab7-d609-4c7b-9db6-6a40dbe42926.png) + + +**为什么握手不能是两次呢?** + +如果只有两次握手,女孩子可能就不知道,她的那句**我也爱你**,男孩子是否**收到**,恋爱关系就不能愉快展开。 + +**为什么握手不能是四次呢?** + +因为握手不能是四次呢?因为三次已经够了,三次已经能让双方都知道:你爱我,我也爱你。而四次就多余了。 + +### 3. 讲下TCP四次挥手过程 + +![](https://files.mdnice.com/user/3535/439e735c-c443-4b2f-96b1-b750004d8d05.png) + +1. 第一次挥手(FIN=1,seq=u),发送完毕后,客户端进入FIN_WAIT_1 状态 +2. 第二次挥手(ACK=1,ack=u+1,seq =v),发送完毕后,服务器端进入CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态 +3. 第三次挥手(FIN=1,ACK1,seq=w,ack=u+1),发送完毕后,服务器端进入LAST_ACK 状态,等待来自客户端的最后一个ACK。 +4. 第四次挥手(ACK=1,seq=u+1,ack=w+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,**等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后**,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。 + +### 4. TCP挥手为什么需要四次呢? + +举个例子吧! + +> 小明和小红打电话聊天,通话差不多要结束时,小红说“我没啥要说的了”,小明回答“我知道了”。但是小明可能还会有要说的话,小红不能要求小明跟着自己的节奏结束通话,于是小明可能又叽叽歪歪说了一通,最后小明说“我说完了”,小红回答“知道了”,这样通话才算结束。 + +![](https://files.mdnice.com/user/3535/966cef0c-1477-4eca-aadf-059219da0198.png) + +### 5. TIME-WAIT 状态为什么需要等待 2MSL + +![](https://files.mdnice.com/user/3535/f56000c8-da62-4370-9559-71bf312f6214.png) + +2MSL,2 Maximum Segment Lifetime,即两个最大段生命周期 + +> - 1个 MSL 保证四次挥手中主动关闭方最后的 ACK 报文能最终到达对端 +> - 1个 MSL 保证对端没有收到 ACK 那么进行重传的 FIN 报文能够到达 + +### 6.TCP 和 UDP 的区别 + +1. TCP面向连接((如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。 +2. TCP要求安全性,提供可靠的服务,通过TCP连接传送的数据,不丢失、不重复、安全可靠。而UDP尽最大努力交付,即不保证可靠交付。 +3. TCP是点对点连接的,UDP一对一,一对多,多对多都可以 +4. TCP传输效率相对较低,而UDP传输效率高,它适用于对高速传输和实时性有较高的通信或广播通信。 +5. TCP适合用于网页,邮件等;UDP适合用于视频,语音广播等 +6. TCP面向字节流,UDP面向报文 + +### 7. TCP报文首部有哪些字段,说说其作用 + + +![](https://files.mdnice.com/user/3535/f9f25411-09d7-4441-bf64-3f0881c4fc01.png) + +- **16位端口号**:源端口号,主机该报文段是来自哪里;目标端口号,要传给哪个上层协议或应用程序 +- **32位序号**:一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。 +- **32位确认号**:用作对另一方发送的tcp报文段的响应。其值是收到的TCP报文段的序号值加1。 +- **4位头部长度**:表示tcp头部有多少个32bit字(4字节)。因为4位最大能标识15,所以TCP头部最长是60字节。 +- **6位标志位**:URG(紧急指针是否有效),ACk(表示确认号是否有效),PSH(缓冲区尚未填满),RST(表示要求对方重新建立连接),SYN(建立连接消息标志接),FIN(表示告知对方本端要关闭连接了) +- **16位窗口大小**:是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。 +- **16位校验和**:由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。 +- **16位紧急指针**:一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。 + +### 8. TCP 是如何保证可靠性的 + + +![](https://files.mdnice.com/user/3535/fce5ee83-8664-4f82-a453-fe25bc35dd88.png) + + +- 首先,TCP的连接是基于**三次握手**,而断开则是**四次挥手**。确保连接和断开的可靠性。 +- 其次,TCP的可靠性,还体现在**有状态**;TCP会记录哪些数据发送了,哪些数据被接受了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。 +- 再次,TCP的可靠性,还体现在**可控制**。它有报文校验、ACK应答、**超时重传(发送方)**、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。 + +### 9. TCP 重传机制 + +#### 超时重传 +TCP 为了实现可靠传输,实现了重传机制。最基本的重传机制,就是**超时重传**,即在发送数据报文时,设定一个定时器,每间隔一段时间,没有收到对方的ACK确认应答报文,就会重发该报文。 + +这个间隔时间,一般设置为多少呢?我们先来看下什么叫**RTT(Round-Trip Time,往返时间)**。 + +![](https://files.mdnice.com/user/3535/05b6c061-2515-4367-aa58-0526db10f6b6.png) + +RTT就是,一个数据包从发出去到回来的时间,即**数据包的一次往返时间**。超时重传时间,就是Retransmission Timeout ,简称**RTO**。 + +**RTO设置多久呢?** +- 如果RTO比较小,那很可能数据都没有丢失,就重发了,这会导致网络阻塞,会导致更多的超时出现。 +- 如果RTO比较大,等到花儿都谢了还是没有重发,那效果就不好了。 + +一般情况下,RTO略大于RTT,效果是最好的。一些小伙伴会问,超时时间有没有计算公式呢?有的!有个标准方法算RTO的公式,也叫**Jacobson / Karels 算法**。我们一起来看下计算RTO的公式 + +**1. 先计算SRTT(计算平滑的RTT)** + +``` +SRTT = (1 - α) * SRTT + α * RTT //求 SRTT 的加权平均 +``` + +**2. 再计算RTTVAR (round-trip time variation)** + + +``` +RTTVAR = (1 - β) * RTTVAR + β * (|RTT - SRTT|) //计算 SRTT 与真实值的差距 +``` + +**3. 最终的RTO** + +``` +RTO = µ * SRTT + ∂ * RTTVAR = SRTT + 4·RTTVAR +``` + +其中,```α = 0.125,β = 0.25, μ = 1,∂ = 4```,这些参数都是大量结果得出的最优参数。 + +但是,超时重传会有这些缺点: +> - 当一个报文段丢失时,会等待一定的超时周期然后才重传分组,增加了端到端的时延。 +> - 当一个报文段丢失时,在其等待超时的过程中,可能会出现这种情况:其后的报文段已经被接收端接收但却迟迟得不到确认,发送端会认为也丢失了,从而引起不必要的重传,既浪费资源也浪费时间。 + +并且,TCP有个策略,就是超时时间间隔会加倍。超时重传需要**等待很长时间**。因此,还可以使用**快速重传**机制。 + +#### 快速重传 + +**快速重传**机制,它不以时间驱动,而是以数据驱动。它基于接收端的反馈信息来引发重传。 + +一起来看下快速重传流程: + +![快速重传流程](https://files.mdnice.com/user/3535/2c172057-bb6c-40e1-8d64-b6a15818f596.png) + +发送端发送了 1,2,3,4,5,6 份数据: + +- 第一份 Seq=1 先送到了,于是就 Ack 回 2; +- 第二份 Seq=2 也送到了,假设也正常,于是ACK 回 3; +- 第三份 Seq=3 由于网络等其他原因,没送到; +- 第四份 Seq=4 也送到了,但是因为Seq3没收到。所以ACK回3; +- 后面的 Seq=4,5的也送到了,但是ACK还是回复3,因为Seq=3没收到。 +- 发送端连着收到三个重复冗余ACK=3的确认(实际上是4个,但是前面一个是正常的ACK,后面三个才是重复冗余的),便知道哪个报文段在传输过程中丢失了,于是在定时器过期之前,重传该报文段。 +- 最后,接收到收到了 Seq3,此时因为 Seq=4,5,6都收到了,于是ACK回7. + +但**快速重传**还可能会有个问题:ACK只向发送端告知最大的有序报文段,到底是哪个报文丢失了呢?**并不确定**!那到底该重传多少个包呢? +> 是重传 Seq3 呢?还是重传 Seq3、Seq4、Seq5、Seq6 呢?因为发送端并不清楚这三个连续的 ACK3 是谁传回来的。 + +#### 带选择确认的重传(SACK) + +为了解决快速重传的问题:**应该重传多少个包**? TCP提供了**SACK方法**(带选择确认的重传,Selective Acknowledgment)。 + +**SACK机制**就是,在快速重传的基础上,接收端返回最近收到的报文段的序列号范围,这样发送端就知道接收端哪些数据包没收到,酱紫就很清楚该重传哪些数据包啦。SACK标记是加在TCP头部**选项**字段里面的。 + +![SACK机制](https://files.mdnice.com/user/3535/9475e768-9d4d-46dd-97a5-ec2298c433bd.png) + +如上图中,发送端收到了三次同样的ACK=30的确认报文,于是就会触发快速重发机制,通过SACK信息发现只有```30~39```这段数据丢失,于是重发时就只选择了这个```30~39```的TCP报文段进行重发。 + +#### D-SACK + + D-SACK,即Duplicate SACK(重复SACK),在SACK的基础上做了一些扩展,,主要用来告诉发送方,有哪些数据包自己重复接受了。DSACK的目的是帮助发送方判断,是否发生了包失序、ACK丢失、包重复或伪重传。让TCP可以更好的做网络流控。来看个图吧: + +![D-SACK简要流程](https://files.mdnice.com/user/3535/d3647f5d-ce7c-4998-953a-04e8c8a9e71c.png) + +### 10. 聊聊TCP的滑动窗口 + +TCP 发送一个数据,需要收到确认应答,才会发送下一个数据。这样有个缺点,就是效率会比较低。 +> 这就好像我们面对面聊天,你说完一句,我应答后,你才会说下一句。那么,如果我在忙其他事情,没有能够及时回复你。你说完一句后,要等到我忙完回复你,你才说下句,这显然很不现实。 + +为了解决这个问题,TCP引入了**窗口**,它是操作系统开辟的一个缓存空间。窗口大小值表示无需等待确认应答,而可以继续发送数据的最大值。 + +TCP头部有个字段叫win,也即那个**16位的窗口大小**,它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度,从而达到**流量控制**的目的。 +> 通俗点讲,就是接受方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的缓存区还有多少空余空间,缓冲区的空余空间,我们就称之为接受窗口大小。这就是win。 + +TCP 滑动窗口分为两种: 发送窗口和接收窗口。**发送端的滑动窗口**包含四大部分,如下: +- 已发送且已收到ACK确认 +- 已发送但未收到ACK确认 +- 未发送但可以发送 +- 未发送也不可以发送 + +![](https://files.mdnice.com/user/3535/8f7d9784-f6e6-47d8-82bb-deb398431025.png) + +- 虚线矩形框,就是发送窗口。 +- SND.WND: 表示发送窗口的大小,上图虚线框的格子数就是14个。 +- SND.UNA: 一个绝对指针,它指向的是已发送但未确认的第一个字节的序列号。 +- SND.NXT:下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号。 + +接收方的滑动窗口包含三大部分,如下: +- 已成功接收并确认 +- 未收到数据但可以接收 +- 未收到数据并不可以接收的数据 + +![](https://files.mdnice.com/user/3535/40b906fe-aa60-42f3-b7bf-b4fcaa9a0588.png) + +- 虚线矩形框,就是接收窗口。 +- REV.WND: 表示接收窗口的大小,上图虚线框的格子就是9个。 +- REV.NXT:下一个接收的位置,它指向未收到但可以接收的第一个字节的序列号。 + +### 11. 聊聊TCP的流量控制 + +TCP三次握手,发送端和接收端进入到ESTABLISHED状态,它们即可以愉快地传输数据啦。 + +但是发送端不能疯狂地向接收端发送数据,因为接收端接收不过来的话,接收方只能把处理不过来的数据存在缓存区里。如果缓存区都满了,发送方还在疯狂发送数据的话,接收方只能把收到的数据包丢掉,这就浪费了网络资源啦。 + +> TCP 提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量,这就是**流量控制**。 + +TCP通过滑动窗口来控制流量,我们看下流量控制的**简要流程**吧: + +首先双方三次握手,初始化各自的窗口大小,均为 400 个字节。 + + +![TCP的流量控制](https://files.mdnice.com/user/3535/e233b594-72b5-4f9a-bc9c-23bb5c065bfe.png) + +1. 假如当前发送方给接收方发送了200个字节,那么,发送方的```SND.NXT```会右移200个字节,也就是说当前的可用窗口减少了200 个字节。 +2. 接受方收到后,放到缓冲队列里面,REV.WND =400-200=200字节,所以win=200字节返回给发送方。接收方会在 ACK 的报文首部带上缩小后的滑动窗口200字节 +3. 发送方又发送200字节过来,200字节到达,继续放到缓冲队列。不过这时候,由于大量负载的原因,接受方处理不了这么多字节,只能处理100字节,剩余的100字节继续放到缓冲队列。这时候,REV.WND = 400-200-100=100字节,即win=100返回发送方。 +4. 发送方继续干活,发送100字节过来,这时候,接受窗口win变为0。 +5. 发送方停止发送,开启一个定时任务,每隔一段时间,就去询问接受方,直到win大于0,才继续开始发送。 + +### 12. TCP的拥塞控制 + +拥塞控制是**作用于网络的,防止过多的数据包注入到网络中,避免出现网络负载过大的情况**。它的目标主要是最大化利用网络上瓶颈链路的带宽。它跟**流量控制**又有什么区别呢?流量控制是作用于接收者的,根据**接收端的实际接收能力控制发送速度**,防止分组丢失的。 + +我们可以把网络链路比喻成一根水管,如果我们想最大化利用网络来传输数据,那就是尽快让水管达到最佳充满状态。 + +![](https://files.mdnice.com/user/3535/f4b5b102-75db-47cc-8c1a-83fa01941dcc.png) + +发送方维护一个**拥塞窗口cwnd(congestion window)** 的变量,用来估算在一段时间内这条链路(水管)可以承载和运输的数据(水)的数量。它大小代表着网络的拥塞程度,并且是动态变化的,但是为了达到最大的传输效率,我们该如何知道这条水管的运送效率是多少呢? + +一个比较简单的方法就是不断增加传输的水量,直到水管快要爆裂为止(对应到网络上就是发生丢包),用 TCP 的描述就是: +> 只要网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,以便把更多的数据包发送出去,但只要网络出现拥塞,拥塞窗口的值就应该减小一些,以减少注入到网络中的数据包数。 + +实际上,拥塞控制主要有这几种常用算法 +- 慢启动 +- 拥塞避免 +- 拥塞发生 +- 快速恢复 + +#### 慢启动算法 + +慢启动算法,表面意思就是,别急慢慢来。它表示TCP建立连接完成后,一开始不要发送大量的数据,而是先探测一下网络的拥塞程度。由小到大逐渐增加拥塞窗口的大小,如果没有出现丢包,**每收到一个ACK,就将拥塞窗口cwnd大小就加1(单位是MSS)**。**每轮次**发送窗口增加一倍,呈指数增长,如果出现丢包,拥塞窗口就减半,进入拥塞避免阶段。 + +- TCP连接完成,初始化cwnd = 1,表明可以传一个MSS单位大小的数据。 +- 每当收到一个ACK,cwnd就加一; +- 每当过了一个RTT,cwnd就增加一倍; 呈指数让升 + +![](https://files.mdnice.com/user/3535/c9edd5d1-0302-45b7-bee7-22d1e505b085.png) + +为了防止cwnd增长过大引起网络拥塞,还需设置一个**慢启动阀值ssthresh**(slow start threshold)状态变量。当```cwnd```到达该阀值后,就好像水管被关小了水龙头一样,减少拥塞状态。即当**cwnd >ssthresh**时,进入了**拥塞避免**算法。 + + +#### 拥塞避免算法 + +一般来说,慢启动阀值ssthresh是65535字节,```cwnd```到达**慢启动阀值**后 +- 每收到一个ACK时,cwnd = cwnd + 1/cwnd +- 当每过一个RTT时,cwnd = cwnd + 1 + +显然这是一个线性上升的算法,避免过快导致网络拥塞问题。 + +![](https://files.mdnice.com/user/3535/600ed914-5f98-4c01-9f1d-d7dcc12244b8.png) + +#### 拥塞发生 + +当网络拥塞发生**丢包**时,会有两种情况: + +- RTO超时重传 +- 快速重传 + +如果是发生了**RTO超时重传**,就会使用拥塞发生算法 + +- 慢启动阀值sshthresh = cwnd /2 +- cwnd 重置为 1 +- 进入新的慢启动过程 + + +![](https://files.mdnice.com/user/3535/9e54bfb4-ed83-42a9-aeb0-f98b44563067.png) + +这真的是**辛辛苦苦几十年,一朝回到解放前**。其实还有更好的处理方式,就是**快速重传**。发送方收到3个连续重复的ACK时,就会快速地重传,不必等待**RTO超时**再重传。 + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5f5ca98465c40b0936ed83aba2ffc15~tplv-k3u1fbpfcp-watermark.image) + +慢启动阀值ssthresh 和 cwnd 变化如下: + +- 拥塞窗口大小 cwnd = cwnd/2 +- 慢启动阀值 ssthresh = cwnd +- 进入快速恢复算法 + +#### 快速恢复 + +快速重传和快速恢复算法一般同时使用。快速恢复算法认为,还有3个重复ACK收到,说明网络也没那么糟糕,所以没有必要像RTO超时那么强烈。 + +正如前面所说,进入快速恢复之前,cwnd 和 sshthresh已被更新: +``` +- cwnd = cwnd /2 +- sshthresh = cwnd +``` + +然后,真正的快速算法如下: + +- cwnd = sshthresh + 3 +- 重传重复的那几个ACK(即丢失的那几个数据包) +- 如果再收到重复的 ACK,那么 cwnd = cwnd +1 +- 如果收到新数据的 ACK 后, cwnd = sshthresh。因为收到新数据的 ACK,表明恢复过程已经结束,可以再次进入了拥塞避免的算法了。 + +![](https://files.mdnice.com/user/3535/1cb2de35-db67-4efa-8b64-1ea00d57c116.png) + +### 13. 半连接队列和 SYN Flood 攻击的关系 + +TCP进入三次握手前,服务端会从**CLOSED**状态变为**LISTEN**状态,同时在内部创建了两个队列:半连接队列(SYN队列)和全连接队列(ACCEPT队列)。 + +什么是**半连接队列(SYN队列)** 呢? 什么是**全连接队列(ACCEPT队列)** 呢?回忆下TCP三次握手的图: + +![三次握手](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/67e2444df1934f549e7509fb5ce4b561~tplv-k3u1fbpfcp-watermark.image) + +- TCP三次握手时,客户端发送SYN到服务端,服务端收到之后,便回复**ACK和SYN**,状态由**LISTEN变为SYN_RCVD**,此时这个连接就被推入了**SYN队列**,即半连接队列。 +- 当客户端回复ACK, 服务端接收后,三次握手就完成了。这时连接会等待被具体的应用取走,在被取走之前,它被推入ACCEPT队列,即全连接队列。 + +SYN Flood是一种典型的DoS (Denial of Service,拒绝服务) 攻击,它在短时间内,伪造**不存在的IP地址**,向服务器大量发起SYN报文。当服务器回复SYN+ACK报文后,不会收到ACK回应报文,导致服务器上建立大量的半连接半连接队列满了,这就无法处理正常的TCP请求啦。 + +主要有 **syn cookie**和**SYN Proxy防火墙**等方案应对。 + +- **syn cookie**:在收到SYN包后,服务器根据一定的方法,以数据包的源地址、端口等信息为参数计算出一个cookie值作为自己的SYNACK包的序列号,回复SYN+ACK后,服务器并不立即分配资源进行处理,等收到发送方的ACK包后,重新根据数据包的源地址、端口计算该包中的确认序列号是否正确,如果正确则建立连接,否则丢弃该包。 + +- **SYN Proxy防火墙**:服务器防火墙会对收到的每一个SYN报文进行代理和回应,并保持半连接。等发送方将ACK包返回后,再重新构造SYN包发到服务器,建立真正的TCP连接。 + +### 14. Nagle 算法与延迟确认 + +#### Nagle算法 + +如果发送端疯狂地向接收端发送很小的包,比如就1个字节,那么亲爱的小伙伴,你们觉得会有什么问题呢? + +> TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。**Nagle算法**就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。 + +Nagle算法的基本定义是:**任意时刻,最多只能有一个未被确认的小段**。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。 + +Nagle算法的实现规则: + +- 如果包长度达到MSS,则允许发送; +- 如果该包含有FIN,则允许发送; +- 设置了TCP_NODELAY选项,则允许发送; +- 未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送; +- 上述条件都未满足,但发生了超时(一般为200ms),则立即发送。 + +#### 延迟确认 + +如果接受方刚接收到发送方的数据包,在很短很短的时间内,又接收到第二个包。那么请问接收方是一个一个地回复好点,还是合并一起回复好呢? + +> 接收方收到数据包后,如果暂时没有数据要发给对端,它可以等一段时再确认(Linux上默认是40ms)。如果这段时间刚好有数据要传给对端,ACK就随着数据传输,而不需要单独发送一次ACK。如果超过时间还没有数据要发送,也发送ACK,避免对端以为丢包。 + +但是有些场景不能延迟确认,比如发现了**乱序包**、**接收到了大于一个 frame 的报文,且需要调整窗口大小**等。 + +一般情况下,**Nagle算法和延迟确认**不能一起使用,Nagle算法意味着延迟发,**延迟确认**意味着延迟接收,酱紫就会造成更大的延迟,会产生性能问题。 + +### 15. TCP的粘包和拆包 + +TCP是面向流,没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一**个完整的包可能会被TCP拆分成多个包进行发送**,**也有可能把多个小的包封装成一个大的数据包发送**,这就是所谓的TCP粘包和拆包问题。 + +![TCP的粘包和拆包](https://files.mdnice.com/user/3535/cf617d8f-70c3-4687-bb20-1bc3518bed11.png) + + +**为什么会产生粘包和拆包呢?** + +- 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包; +- 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包; +- 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包; +- 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。 + +**解决方案:** + +- 发送端将每个数据包封装为固定长度 +- 在数据尾部增加特殊字符进行分割 +- 将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。 + +### 参考与感谢 +- [TCP 的那些事儿(下)](https://coolshell.cn/articles/11609.html "TCP 的那些事儿(下)") +- [面试头条你需要懂的 TCP 拥塞控制原理](https://zhuanlan.zhihu.com/p/76023663 "面试头条你需要懂的 TCP 拥塞控制原理") +- [30张图解: TCP 重传、滑动窗口、流量控制、拥塞控制发愁](https://zhuanlan.zhihu.com/p/133307545 "30张图解: TCP 重传、滑动窗口、流量控制、拥塞控制发愁") +- [TCP协议灵魂之问,巩固你的网路底层基础](https://juejin.cn/post/6844904070889603085 "TCP协议灵魂之问,巩固你的网路底层基础") +- [TCP粘包和拆包](https://blog.csdn.net/ailunlee/article/details/95944377 "TCP粘包和拆包") +- 百度百科 + + + diff --git "a/Mysql\345\237\272\347\241\200\345\255\246\344\271\240/README.md" "b/Mysql\345\237\272\347\241\200\345\255\246\344\271\240/README.md" new file mode 100644 index 0000000..f335927 --- /dev/null +++ "b/Mysql\345\237\272\347\241\200\345\255\246\344\271\240/README.md" @@ -0,0 +1,2 @@ +- [聊聊select for update到底加了什么锁](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247506728&idx=1&sn=5526ee3984e971d4c3b251c2ad76d658&chksm=c1e026a4f697afb28224d5ce0ecca7432879b357cd6433834c66d94c72a1935ba13e2e3e274e&token=337310304&lang=zh_CN#rd) +- [数据库死锁排查思路分享](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247507770&idx=1&sn=b84b20aca057b34d511a501ff91941b5&chksm=c1e022b6f697aba05248128cb82f93aed341b1cc80e6d568c7150a4ffa6775692c7c9fa423a3&token=1822874069&lang=zh_CN#rd) diff --git "a/Mysql\345\237\272\347\241\200\345\255\246\344\271\240/order by\350\257\246\350\247\243.md" "b/Mysql\345\237\272\347\241\200\345\255\246\344\271\240/order by\350\257\246\350\247\243.md" new file mode 100644 index 0000000..3aeda16 --- /dev/null +++ "b/Mysql\345\237\272\347\241\200\345\255\246\344\271\240/order by\350\257\246\350\247\243.md" @@ -0,0 +1,348 @@ +## 前言 + +日常开发中,我们经常会使用到order by,亲爱的小伙伴,你是否知道order by 的工作原理呢?order by的优化思路是怎样的呢?使用order by有哪些注意的问题呢?本文将跟大家一起来学习,攻克order by~ + + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b1eda4fbc6c458c9182545d93cb6f15~tplv-k3u1fbpfcp-watermark.image) + +- 微信公众号:**捡田螺的小男孩** +- [github地址,感谢每一颗star](https://github.com/whx123/JavaHome) +- 如果觉得有收获,帮忙点赞,转发下哈,感谢感谢 + + +## 一个使用order by 的简单例子 + +假设用一张员工表,表结构如下: + +``` +CREATE TABLE `staff` ( +`id` BIGINT ( 11 ) AUTO_INCREMENT COMMENT '主键id', +`id_card` VARCHAR ( 20 ) NOT NULL COMMENT '身份证号码', +`name` VARCHAR ( 64 ) NOT NULL COMMENT '姓名', +`age` INT ( 4 ) NOT NULL COMMENT '年龄', +`city` VARCHAR ( 64 ) NOT NULL COMMENT '城市', +PRIMARY KEY ( `id`), +INDEX idx_city ( `city` ) +) ENGINE = INNODB COMMENT '员工表'; + +``` + +表数据如下: + + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aadfe321a1b74141a418f4a4c2f75e82~tplv-k3u1fbpfcp-watermark.image) + +我们现在有这么一个需求:**查询前10个,来自深圳员工的姓名、年龄、城市,并且按照年龄小到大排序**。对应的 SQL 语句就可以这么写: + +``` +select name,age,city from staff where city = '深圳' order by age limit 10; +``` + +这条语句的逻辑很清楚,但是它的**底层执行流程**是怎样的呢? + +## order by 工作原理 + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/582926f8d02e44d4b94e23a3fafc0ec8~tplv-k3u1fbpfcp-watermark.image) + +### explain 执行计划 + +我们先用**Explain**关键字查看一下执行计划 + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2da79360c7204f5d8fada7f8ec2d21ba~tplv-k3u1fbpfcp-watermark.image) + +- 执行计划的**key**这个字段,表示使用到索引idx_city +- Extra 这个字段的 **Using index condition** 表示索引条件 +- Extra 这个字段的 **Using filesort**表示用到排序 + +我们可以发现,这条SQL使用到了索引,并且也用到排序。那么它是**怎么排序**的呢? + +### 全字段排序 + +MySQL 会给每个查询线程分配一块小**内存**,用于**排序**的,称为 **sort_buffer**。什么时候把字段放进去排序呢,其实是通过```idx_city```索引找到对应的数据,才把数据放进去啦。 + +我们回顾下索引是怎么找到匹配的数据的,现在先把索引树画出来吧,**idx_city**索引树如下: + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/773e727c45254b7a858a780fdeff8a7c~tplv-k3u1fbpfcp-watermark.image) + +idx_city索引树,叶子节点存储的是**主键id**。 还有一棵id主键聚族索引树,我们再画出聚族索引树图吧: + + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f12ea50032b6428c899ee143663aa3de~tplv-k3u1fbpfcp-watermark.image) + + +**我们的查询语句是怎么找到匹配数据的呢**?先通过**idx_city**索引树,找到对应的主键id,然后再通过拿到的主键id,搜索**id主键索引树**,找到对应的行数据。 + +加上**order by**之后,整体的执行流程就是: + +1. MySQL 为对应的线程初始化**sort_buffer**,放入需要查询的name、age、city字段; +2. 从**索引树idx_city**, 找到第一个满足 city='深圳’条件的主键 id,也就是图中的id=9; +3. 到**主键 id 索引树**拿到id=9的这一行数据, 取name、age、city三个字段的值,存到sort_buffer; +4. 从**索引树idx_city** 拿到下一个记录的主键 id,即图中的id=13; +5. 重复步骤 3、4 直到**city的值不等于深圳**为止; +6. 前面5步已经查找到了所有**city为深圳**的数据,在 sort_buffer中,将所有数据根据age进行排序; +7. 按照排序结果取前10行返回给客户端。 + +执行示意图如下: + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6d8760fdf99741b492ab032cd3d0d87c~tplv-k3u1fbpfcp-watermark.image) + +将查询所需的字段全部读取到sort_buffer中,就是**全字段排序**。这里面,有些小伙伴可能会有个疑问,把查询的所有字段都放到sort_buffer,而sort_buffer是一块内存来的,如果数据量太大,sort_buffer放不下怎么办呢? + +### 磁盘临时文件辅助排序 + +实际上,sort_buffer的大小是由一个参数控制的:**sort_buffer_size**。如果要排序的数据小于sort_buffer_size,排序在**sort_buffer** 内存中完成,如果要排序的数据大于sort_buffer_size,则**借助磁盘文件来进行排序** + +如何确定是否使用了磁盘文件来进行排序呢? 可以使用以下这几个命令 + +``` +## 打开optimizer_trace,开启统计 +set optimizer_trace = "enabled=on"; +## 执行SQL语句 +select name,age,city from staff where city = '深圳' order by age limit 10; +## 查询输出的统计信息 +select * from information_schema.optimizer_trace +``` + +可以从 **number_of_tmp_files** 中看出,是否使用了临时文件。 + + + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c876d88537ce4b67bcf8f991df17aa94~tplv-k3u1fbpfcp-watermark.image) + +**number_of_tmp_files** 表示使用来排序的磁盘临时文件数。如果number_of_tmp_files>0,则表示使用了磁盘文件来进行排序。 + +使用了磁盘临时文件,整个排序过程又是怎样的呢? + +1. 从**主键Id索引树**,拿到需要的数据,并放到**sort_buffer内存**块中。当sort_buffer快要满时,就对sort_buffer中的数据排序,排完后,把数据临时放到磁盘一个小文件中。 +2. 继续回到主键 id 索引树取数据,继续放到sort_buffer内存中,排序后,也把这些数据写入到磁盘临时小文件中。 +3. 继续循环,直到取出所有满足条件的数据。最后把磁盘的临时排好序的小文件,合并成一个有序的大文件。 + + +**TPS:** 借助磁盘临时小文件排序,实际上使用的是**归并排序**算法。 + +小伙伴们可能会有个疑问,既然**sort_buffer**放不下,就需要用到临时磁盘文件,这会影响排序效率。那为什么还要把排序不相关的字段(name,city)放到sort_buffer中呢?只放排序相关的age字段,它**不香**吗? 可以了解下**rowid 排序**。 + + +### rowid 排序 + +rowid 排序就是,只把查询SQL**需要用于排序的字段和主键id**,放到sort_buffer中。那怎么确定走的是全字段排序还是rowid 排序排序呢? + +实际上有个参数控制的。这个参数就是**max_length_for_sort_data**,它表示MySQL用于排序行数据的长度的一个参数,如果单行的长度超过这个值,MySQL 就认为单行太大,就换rowid 排序。我们可以通过命令看下这个参数取值。 + + +``` +show variables like 'max_length_for_sort_data'; +``` + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ce8fc30884e74dd5b09c0028640ce7ea~tplv-k3u1fbpfcp-watermark.image) + +**max_length_for_sort_data** 默认值是1024。因为本文示例中name,age,city长度=64+4+64 =132 < 1024, 所以走的是全字段排序。我们来改下这个参数,改小一点, + +``` +## 修改排序数据最大单行长度为32 +set max_length_for_sort_data = 32; +## 执行查询SQL +select name,age,city from staff where city = '深圳' order by age limit 10; +``` + +使用rowid 排序的话,整个SQL执行流程又是怎样的呢? + +1. MySQL 为对应的线程初始化**sort_buffer**,放入需要排序的age字段,以及主键id; +2. 从**索引树idx_city**, 找到第一个满足 city='深圳’条件的主键 id,也就是图中的id=9; +3. 到**主键 id 索引树**拿到id=9的这一行数据, 取age和主键id的值,存到sort_buffer; +4. 从**索引树idx_city** 拿到下一个记录的主键 id,即图中的id=13; +5. 重复步骤 3、4 直到**city的值不等于深圳**为止; +6. 前面5步已经查找到了所有city为深圳的数据,在 **sort_buffer**中,将所有数据根据age进行排序; +7. 遍历排序结果,取前10行,并按照 id 的值**回到原表**中,取出city、name 和 age 三个字段返回给客户端。 + + +执行示意图如下: + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2addcf67082741e19637dae2e69ee515~tplv-k3u1fbpfcp-watermark.image) + + +对比一下**全字段排序**的流程,rowid 排序多了一次**回表**。 + +> 什么是回表?拿到主键再回到主键索引查询的过程,就叫做回表 + + +我们通过**optimizer_trace**,可以看到是否使用了rowid排序的: + + +``` +## 打开optimizer_trace,开启统计 +set optimizer_trace = "enabled=on"; +## 执行SQL语句 +select name,age,city from staff where city = '深圳' order by age limit 10; +## 查询输出的统计信息 +select * from information_schema.optimizer_trace + +``` + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/446258ee4bfa40e6ad80596202c8d5a0~tplv-k3u1fbpfcp-watermark.image) + + +### 全字段排序与rowid排序对比 + + +- 全字段排序: sort_buffer内存不够的话,就需要用到磁盘临时文件,造成**磁盘访问**。 +- rowid排序: sort_buffer可以放更多数据,但是需要再回到原表去取数据,比全字段排序多一次**回表**。 + +一般情况下,对于InnoDB存储引擎,会优先使**用全字段**排序。可以发现 **max_length_for_sort_data** 参数设置为1024,这个数比较大的。一般情况下,排序字段不会超过这个值,也就是都会走**全字段**排序。 + + +## order by的一些优化思路 + +我们如何优化order by语句呢? + + +- 因为数据是无序的,所以就需要排序。如果数据本身是有序的,那就不用排了。而索引数据本身是有序的,我们通过建立**联合索引**,优化order by 语句。 +- 我们还可以通过调整**max_length_for_sort_data**等参数优化; + + +### 联合索引优化 + +再回顾下示例SQL的查询计划 + +``` +explain select name,age,city from staff where city = '深圳' order by age limit 10; +``` + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7fce894de9024dd5913e98116dca832f~tplv-k3u1fbpfcp-watermark.image) + +我们给查询条件```city```和排序字段```age```,加个联合索引**idx_city_age**。再去查看执行计划 + +``` +alter table staff add index idx_city_age(city,age); +explain select name,age,city from staff where city = '深圳' order by age limit 10; +``` + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33621e91730b44e5bb88c27b74614b89~tplv-k3u1fbpfcp-watermark.image) + +可以发现,加上**idx_city_age**联合索引,就不需要**Using filesort**排序了。为什么呢?因为**索引本身是有序的**,我们可以看下**idx_city_age**联合索引示意图,如下: + + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19b368a039a34678aeea8e297eee05e8~tplv-k3u1fbpfcp-watermark.image) + +整个SQL执行流程变成酱紫: +1. 从索引idx_city_age找到满足**city='深圳’** 的主键 id +2. 到**主键 id索引**取出整行,拿到 name、city、age 三个字段的值,作为结果集的一部分直接返回 +3. 从索引**idx_city_age**取下一个记录主键id +4. 重复步骤 2、3,直到查到**第10条**记录,或者是**不满足city='深圳’** 条件时循环结束。 + +流程示意图如下: + +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/95f6a85904934d9688598d436fbcc1ee~tplv-k3u1fbpfcp-watermark.image) + + +从示意图看来,还是有一次回表操作。针对本次示例,有没有更高效的方案呢?有的,可以使用**覆盖索引**: + +> 覆盖索引:在查询的数据列里面,不需要回表去查,直接从索引列就能取到想要的结果。换句话说,你SQL用到的索引列数据,覆盖了查询结果的列,就算上覆盖索引了。 + +我们给city,name,age 组成一个联合索引,即可用到了覆盖索引,这时候SQL执行时,连回表操作都可以省去啦。 + +### 调整参数优化 + +我们还可以通过调整参数,去优化order by的执行。比如可以调整sort_buffer_size的值。因为sort_buffer值太小,数据量大的话,会借助磁盘临时文件排序。如果MySQL服务器配置高的话,可以使用稍微调整大点。 + +我们还可以调整max_length_for_sort_data的值,这个值太小的话,order by会走rowid排序,会回表,降低查询性能。所以max_length_for_sort_data可以适当大一点。 + +当然,很多时候,这些MySQL参数值,我们直接采用默认值就可以了。 + +## 使用order by 的一些注意点 + +### 没有where条件,order by字段需要加索引吗 + +日常开发过程中,我们可能会遇到没有where条件的order by,那么,这时候order by后面的字段是否需要加索引呢。如有这么一个SQL,create_time是否需要加索引: + +``` +select * from A order by create_time; +``` + +无条件查询的话,即使create_time上有索引,也不会使用到。因为MySQL优化器认为走普通二级索引,再去回表成本比全表扫描排序更高。所以选择走全表扫描,然后根据全字段排序或者rowid排序来进行。 + +如果查询SQL修改一下: + +``` +select * from A order by create_time limit m; +``` +- 无条件查询,如果m值较小,是可以走索引的.因为MySQL优化器认为,根据索引有序性去回表查数据,然后得到m条数据,就可以终止循环,那么成本比全表扫描小,则选择走二级索引。 + + +### 分页limit过大时,会导致大量排序怎么办? + +假设SQL如下: +``` +select * from A order by a limit 100000,10 +``` + +- 可以记录上一页最后的id,下一页查询时,查询条件带上id,如: where id > 上一页最后id limit 10。 +- 也可以在业务允许的情况下,限制页数。 + + +### 索引存储顺序与order by不一致,如何优化? + +假设有联合索引 idx_age_name, 我们需求修改为这样:**查询前10个员工的姓名、年龄,并且按照年龄小到大排序,如果年龄相同,则按姓名降序排**。对应的 SQL 语句就可以这么写: + +``` +select name,age from staff order by age ,name desc limit 10; +``` +我们看下执行计划,发现使用到**Using filesort**。 + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bdc45d41d0744567bc753892661789b7~tplv-k3u1fbpfcp-watermark.image) + +这是因为,idx_age_name索引树中,age从小到大排序,如果**age相同,再按name从小到大排序**。而order by 中,是按age从小到大排序,如果**age相同,再按name从大到小排序**。也就是说,索引存储顺序与order by不一致。 + +我们怎么优化呢?如果MySQL是8.0版本,支持**Descending Indexes**,可以这样修改索引: + +``` +CREATE TABLE `staff` ( + `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', + `id_card` varchar(20) NOT NULL COMMENT '身份证号码', + `name` varchar(64) NOT NULL COMMENT '姓名', + `age` int(4) NOT NULL COMMENT '年龄', + `city` varchar(64) NOT NULL COMMENT '城市', + PRIMARY KEY (`id`), + KEY `idx_age_name` (`age`,`name` desc) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 COMMENT='员工表'; +``` + + +### 使用了in条件多个属性时,SQL执行是否有排序过程 + +如果我们有**联合索引idx_city_name**,执行这个SQL的话,是不会走排序过程的,如下: + +``` +select * from staff where city in ('深圳') order by age limit 10; +``` + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0d5c0099035f48529075b6bc9d396cca~tplv-k3u1fbpfcp-watermark.image) + + + +但是,如果使用in条件,并且有多个条件时,就会有排序过程。 + +``` + explain select * from staff where city in ('深圳','上海') order by age limit 10; +``` + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1813404917da4dc9898ab7a14afb811f~tplv-k3u1fbpfcp-watermark.image) + +这是因为:in有两个条件,在满足深圳时,age是排好序的,但是把满足上海的age也加进来,就不能保证满足所有的age都是排好序的。因此需要Using filesort。 + +## 最后 + +- 如果觉得有收获,帮忙点赞,转发下哈,感谢感谢 +- 微信搜索公众号:**捡田螺的小男孩**,加个好友,进技术交流群 + + +### 参考与感谢 + +- MySQL实战45讲 + + + diff --git a/README.md b/README.md index 6018cc3..7018310 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,46 @@ -整理一份超级详细的Java面试题+后端基础+日常工作总结,做最暖心的男孩子,后面会慢慢把答案完善,希望大家找到理想offer +## 前言 -## 个人公众号 +整理一份超级详细的Java面试题+后端基础+日常工作总结,做最暖心的男孩子,后面会慢慢完善,希望大家找到理想offer + +⭐ 点右上角给一个 Star,鼓励技术人输出更多干货,感谢感谢,爱了! ! + +作者捡田螺的小男孩,浪迹过几家大厂,**掘金优秀创作者**,CSDN博主,知乎博主。以下内容全部出自公众号:**捡田螺的小男孩**,欢迎关注。 + +- [田螺原创精品100篇](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497536&idx=1&sn=3ac9934f607d79e51457fd01f4c8a4ef&chksm=cf222869f855a17fc30c744e5b7ccdeca407f3b7ddcca46bae1c93b1436ffc6fe417ccb8aef4&token=1990771297&lang=zh_CN#rd) + +## 工作总结 -![image](https://user-gold-cdn.xitu.io/2019/7/28/16c381c89b127bbb?w=344&h=344&f=jpeg&s=8943) +- [盘点数据库主从延迟的9个原因以及解决方案](https://mp.weixin.qq.com/s/aT7YjsTrM_dhDbddr8TSjg?token=528541177&lang=zh_CN) +- [实战项目,是如何保证缓存跟数据库数据一致性的?](https://mp.weixin.qq.com/s/UVHMeFDO4NYTnSwHZc9f1A?token=528541177&lang=zh_CN) +- [工作总结!日志打印的15个建议](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494838&idx=1&sn=cdb15fd346bddf3f8c1c99f0efbd67d8&chksm=cf22339ff855ba891616c79d4f4855e228e34a9fb45088d7acbe421ad511b8d090a90f5b019f&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [25种代码坏味道总结+优化示例](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490148&idx=1&sn=00a181bf74313f751b3ea15ebc303545&chksm=cf21c54df8564c5bc5b4600fce46619f175f7ae557956f449629c470a08e20580feef4ea8d53&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [聊聊日常开发中,如何减少bug呢?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490662&idx=1&sn=d38a090611af7f64ee3c6a31331d5228&chksm=cf21c34ff8564a59e505e6edf3065a0fc506c6d2c96f492c8d8873cd46dedbe0704e43cb9c2e&token=1990771297&lang=zh_CN#rd) +- [工作四年,分享50个让你代码更好的小建议](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488708&idx=1&sn=6e2e0a740f5d42a59641487a0bf1e3bf&chksm=cf21cbedf85642fbb485fa1c7bf9af21923d8503f2542b6f8283ce79ddc683f7d9e45da83100&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [写代码有这16个好习惯,可以减少80%非业务的bug](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488097&idx=1&sn=eaca1f92ca3ccd9de00dbc4ef3e4029a&chksm=cf21cd48f856445e4cc24c1f8bcf18d1479bad0a37a87a2fb70717d8a4e65dcf7b4d5f83d24f&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java日常开发的21个坑,你踩过几个?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488115&idx=1&sn=bdd4a4ca36bc7ea902106d058e8537fb&chksm=cf21cd5af856444cb36af600705615454b0aaa2b289b97ddb52d594556ac07a1915b73ecce19&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [CAS乐观锁解决并发问题的一次实践](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487937&idx=1&sn=206a37bf6d6a7aa1d05674c479ed7a72&chksm=cf21cee8f85647fe7a082049a41c0f640f54976d2cdf4302b24c5517ca42b854eb84b13ece10&token=1990771297&lang=zh_CN#rd) +- [写代码有这些想法,同事才不会认为你是复制粘贴程序员](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487961&idx=1&sn=e646231067968d9f58e6665914293f9a&chksm=cf21cef0f85647e6f3ff2feece004ac3bd979e37fe45103c88d0f299dfe632a5cf6dd547c1d9&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备:Java日期处理的十个坑](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487973&idx=1&sn=0f713413098fb579e5f200b829f71e89&chksm=cf21ceccf85647da450765d79bf5943da551c3be950447063b9f8c77c21bf2a39b99387a949b&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [内存泄漏问题的分析和解决方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487986&idx=1&sn=d681a585ac489703788e3baa48eb9aa3&chksm=cf21cedbf85647cd23bbab9dfec63e6877f83c34efb19bd16075d5d90fea91d3f4a20fc77921&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备基础:加签验签](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488022&idx=1&sn=70484a48173d36006c8db1dfb74ab64d&chksm=cf21cd3ff8564429a1205f6c1d78757faae543111c8461d16c71aaee092fe3e0fed870cc5e0e&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [记一次接口性能优化实践总结:优化接口性能的八个建议](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488004&idx=1&sn=00840efd9c0bd0a7f172b59eb2ca130f&chksm=cf21cd2df856443bf21d8e09cfe5c8452ecaf82e3c2210fca3b28829ded04defddcf63c0a59b&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备基础:如何安全传输存储用户密码?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488117&idx=1&sn=5d3d0eda0ed45f3f576e211de31ca3a9&chksm=cf21cd5cf856444af1407a94a2abf445265ca7c5f5855cfa1c223cb209e99040c7889621f231&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [一次代码优化实践,用了模板方法+策略+工厂方法模式](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488061&idx=1&sn=1d9ab7954b03521ab81ecf033c0e5e50&chksm=cf21cd14f8564402b213f0ef908bbdb0e12fed4b281c5803b8e539cacb1551654194becfb7d6&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [保证接口数据安全的10种方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247500285&idx=1&sn=7d0723f25d46e858859cfd79acb6fb9d&chksm=cf221ed4f85597c2093f81baa5fdedc65817bf2d23a7951236836b0f54c2335695cbed61cd13&token=1990771297&lang=zh_CN#rd) +- [实战总结!18种接口优化方案的总结](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247506674&idx=1&sn=8b2914d9aafa334029495b029b69d0b6&chksm=c1e0277ef697ae68e8c2bffe4bd7d9849be3165ef1a20286538f6a7569a6ba0879d517d55b87&token=337310304&lang=zh_CN#rd) +- [聊聊工作中常用的Lambda表达式](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247506654&idx=1&sn=4835e9f486e643765d4ad3b3fc93e079&chksm=c1e02752f697ae442f62fc122d7604f4b01979f6d1665df414bb499fd8ba211335ebc503c368&token=337310304&lang=zh_CN#rd) +- [21个MySQL表设计的经验准则](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247506621&idx=1&sn=afca898cb461827054d706a92f9b9250&chksm=c1e02731f697ae27a83e5637ee2184d1e26e5090caeaa58121d3cf5afab7d4d5832cac6d171a&token=337310304&lang=zh_CN#rd) +- [程序员必备基础:如何安全传输存储用户密码?](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247506023&idx=1&sn=b96dde436c1c9fe4bda745ca5ca1b170&source=41#wechat_redirect) + +## 福利 500+页原创面试题 + +- [田螺原创500+页面试题](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499943&idx=1&sn=fe869c0a97a306e42830336fe74e17a6&chksm=cf221f8ef8559698781709bfbccbb85087286e48434905fb18bec3a3ec0af7329c2a1632c230&token=1990771297&lang=zh_CN#rd) + + +## 个人公众号 -- 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论哈~~ +微信搜公众号:**捡田螺的小男孩** -## 一分也是爱,谢谢大家的支持哈~ -![](https://user-gold-cdn.xitu.io/2020/7/15/1735311bf66cecd8?w=430&h=580&f=jpeg&s=35456) +- 小伙伴可以关注我的公众号(扫描下面二维码,还有**很多很多干货文章**),一起学习讨论哈~~ +![扫一扫](https://user-images.githubusercontent.com/20244922/179399354-8a9fd2a8-42ba-4303-9ce5-04891e899e6d.png) diff --git "a/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/README.MD" "b/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/README.MD" new file mode 100644 index 0000000..921f58b --- /dev/null +++ "b/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/README.MD" @@ -0,0 +1,13 @@ +## leetcode(持续更新中) + +关注公众号:捡田螺的小男孩 + +- [看一遍就理解:动态规划详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247489016&idx=1&sn=bba3fb1a7a864b6ccefeb9f633751811&chksm=cf21cad1f85643c716c8c9396d3a6711f7722f8f81c8f40f5a91c525c98f73f5c476b7d49dd4&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备的基本算法:递归详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488073&idx=1&sn=ec81b4a1f8b11ea59264b55e571fed91&chksm=cf21cd60f8564476952c5abb8ffa93fc38fde354a61ca5596e1875d35760383f3a92b2879e30&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [看一遍就理解,图解单链表反转](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487967&idx=1&sn=e75373dcb0507081c242ba018b42ca82&chksm=cf21cef6f85647e0cbf0b2072eb1264a44abcaa9f4a0621ef8954a1b1d6719560f7f4cbbce60&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [面试必备:回溯算法详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497665&idx=1&sn=39011296fa99eda839ab2bbe83a42cdf&chksm=cf2228e8f855a1fe8f059130dc0b3d9ad34431a27bbe7e16f508b7e9340c24e2e4dfd8b414c2&token=1990771297&lang=zh_CN#rd) +- [leetcode必备算法:聊聊滑动窗口](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247496003&idx=1&sn=8c40eb3e611514f3bafb8d6873c03fda&chksm=cf222e6af855a77ce2fc36d4e4fc02945286300206f43975e30bc23b65c9ca67b6a1ac9806d1&token=1990771297&lang=zh_CN#rd) +- [五分钟搞定贪心算法,从此不惧大厂面试](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490681&idx=1&sn=0388da1492fe0fdfa3ed6b1a43511328&chksm=cf21c350f8564a466d89578f73886eb462c6dd485f42e7953f126be5f9af49b3fb0be3457d52&token=1990771297&lang=zh_CN#rd) +- [双指针+归并排序!图解排序链表!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247496038&idx=1&sn=96a1a665e43ee9e3337e3d941db49f1e&chksm=cf222e4ff855a75919f0be68e78472199c44d0e9d94de6d5bf621a892ba211738d6f4dbd53ac&token=1990771297&lang=zh_CN#rd) +- [双指针技巧](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488116&idx=1&sn=aeec0553e2317bef76d158d2b0e0b5a5&chksm=cf21cd5df856444b8963efc2745bce6801df4bc547b679ae8366fa8c3cd293f1f7c60c18e4f6&token=1990771297&lang=zh_CN#rd) +- [字符串匹配算法详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494506&idx=1&sn=1f13b0cc1f03af464e1063be8ef1cb57&chksm=cf223443f855bd5597898126d12c6039f64da47b8a95714018203ee5e453950c802ebecfabe1&token=1990771297&lang=zh_CN#rd) diff --git "a/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/\345\217\215\350\275\254\351\223\276\350\241\250\347\234\213\344\270\200\351\201\215\345\260\261\346\207\202.md" "b/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/\345\217\215\350\275\254\351\223\276\350\241\250\347\234\213\344\270\200\351\201\215\345\260\261\346\207\202.md" new file mode 100644 index 0000000..457a209 --- /dev/null +++ "b/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/\345\217\215\350\275\254\351\223\276\350\241\250\347\234\213\344\270\200\351\201\215\345\260\261\346\207\202.md" @@ -0,0 +1,182 @@ +## 前言 +反转链表是程序员必备的基本素养,经常在面试、笔试的过程中出现。一直觉得反转链表实现代码不是很好理解,决定搬leetcode那道经典反转链表题出来,用十多张图去解析它,希望加深大家对链表反转的理解,谢谢阅读。 + +### leetcode的反转链表原题&答案 + +**题目描述:** 反转一个单链表。 +``` +输入: 1->2->3->4->5->NULL +输出: 5->4->3->2->1->NULL +``` + +**分析:** + +假设存在链表 1 → 2 → 3 → Ø,我们想要把它改成 Ø ← 1 ← 2 ← 3。 + +在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点。不要忘记在最后返回新的头引用! + +**代码实现:** +``` +public ListNode reverseList(ListNode head) { + ListNode prev = null; + ListNode curr = head; + while (curr != null) { + ListNode nextTemp = curr.next; + curr.next = prev; + prev = curr; + curr = nextTemp; + } + return prev; +} +``` + +### 图解链表反转代码的实现 +接下来,我们图解以上代码实现,先对以上实现代码加上行号,如下: +``` +public ListNode reverseList(ListNode head) { //1 + ListNode prev = null; // 2 + ListNode curr = head; // 3 + while (curr != null) { //4 + ListNode nextTemp = curr.next; //5 + curr.next = prev; // 6 + prev = curr; //7 + curr = nextTemp; //8 + } + return prev; //9 +} +``` + +#### 第一行代码图解 + +``` +public ListNode reverseList(ListNode head) { //1 +``` +我们顺着题目描述意思,假设链表就有1、2、3个元素吧,后面还跟着一个null,又因为输入是ListNode head,所以这个即将要反转的链表如下: + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fbe941179b51?w=909&h=245&f=png&s=21126) + +#### 第二行代码图解 + +``` +ListNode prev = null; // 2 +``` +将null赋值给prev,即prev指向null,可得图如下: +![](https://user-gold-cdn.xitu.io/2020/2/7/1701f9da5c94b500?w=216&h=210&f=png&s=7193) + +#### 第三行代码图解 + +``` +ListNode curr = head; +``` +将链表head赋值给curr,即curr指向head链表,可得图如下: + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fbfcaab4dd99?w=870&h=243&f=png&s=20089) + + +#### 循环部分代码图解 + +``` + while (curr != null) { //4 + ListNode nextTemp = curr.next; //5 + curr.next = prev; // 6 + prev = curr; //7 + curr = nextTemp; //8 + } +``` +循环部分是**链表反转的核心**部分,我们先走一遍循环,图解分析一波。 + +因为**curr指向了head**,**head不为null**,所以进入循环。**先来看第5行:** +``` +ListNode nextTemp = curr.next; //5 +``` +把curr.next 赋值给nextTemp变量,即nextTemp 指向curr的下一节点(即节点2),可得图如下: + + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701ff3466fffa10?w=833&h=240&f=png&s=21780) + +再执行到第6行: +``` +curr.next = prev; // 6 +``` +把prev赋值给curr.next,因为prev初始化化指向null,即curr(节点1)指向了null,链表图解成这样了: + + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fbd124bde1f2?w=1068&h=263&f=png&s=27349) + +然后我们看执行到第7行 + +``` + prev = curr; //7 +``` +把curr赋值给prev,prev指向curr,图解如下: + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fc2fc9a7ba5c?w=1091&h=347&f=png&s=35650) + +接着,我们执行到第8行: + +``` +curr = nextTemp; //8 +``` +把nextTemp赋值给curr,即curr指向nextTemp,图解如下: + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fc5ceadd3e2f?w=1042&h=364&f=png&s=35340) + +至此,第一遍循环执行结束啦,回到循环条件,**curr依旧不为null**,我们继续图解完它。 + +5-8行代码又执行一遍,依次可得图: + +``` + ListNode nextTemp = curr.next; //5 + curr.next = prev; // 6 + prev = curr; //7 + curr = nextTemp; //8 +``` + +执行完```ListNode nextTemp = curr.next; ```后: +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fceea65ac66e?w=1011&h=360&f=png&s=34547) + +执行完```curr.next = prev; ```后: +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fd391ef75c17?w=1108&h=339&f=png&s=35342) + +执行完```prev = curr; ```后: +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fd5ad58277e1?w=1091&h=359&f=png&s=36280) + +执行完```curr = nextTemp;```后: +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fd73f53f1a2b?w=1103&h=346&f=png&s=35454) + +来到这里,发现curr还是不为null,再回到while循环,再执行一遍: + +``` + ListNode nextTemp = curr.next; //5 + curr.next = prev; // 6 + prev = curr; //7 + curr = nextTemp; //8 +``` +依次可得图: + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fdf5b6532ed5?w=1130&h=340&f=png&s=35491) + + +![](https://user-gold-cdn.xitu.io/2020/2/7/1702007c264eddd4?w=1077&h=320&f=png&s=32820) + + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fe5ac9b9d1d8?w=1135&h=330&f=png&s=34388) + +![](https://user-gold-cdn.xitu.io/2020/2/7/1701fe66d7971447?w=1137&h=330&f=png&s=34740) + +来到这里,我们发现curr已经为null了,可以跳出循环了。这时候prev指向的就是链表的反转呀,所以第9行执行完,反转链表功能实现: + +``` + return prev; //9 +``` + +### 参考与感谢 +- [LeetCode 官网](https://leetcode-cn.com/problems/reverse-linked-list/solution/) + + +### 个人公众号 + +![](https://user-gold-cdn.xitu.io/2019/7/28/16c381c89b127bbb?w=344&h=344&f=jpeg&s=8943) + +- 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论。 +- 如果你觉得本文有哪些不正确的地方,可以评论,也可以关注我公众号,私聊我,大家一起学习进步哈。 \ No newline at end of file diff --git "a/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/\351\200\222\345\275\222\347\256\227\346\263\225\350\257\246\350\247\243.md" "b/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/\351\200\222\345\275\222\347\256\227\346\263\225\350\257\246\350\247\243.md" new file mode 100644 index 0000000..2e9cb40 --- /dev/null +++ "b/letecode\350\247\243\351\242\230\347\256\227\346\263\225\344\273\213\347\273\215/\351\200\222\345\275\222\347\256\227\346\263\225\350\257\246\350\247\243.md" @@ -0,0 +1,382 @@ +### 前言 +递归是一种非常重要的算法思想,无论你是前端开发,还是后端开发,都需要掌握它。在日常工作中,统计文件夹大小,解析xml文件等等,都需要用到递归算法。它太基础太重要了,这也是为什么面试的时候,面试官经常让我们手写递归算法。本文呢,将跟大家一起学习递归算法~ + +- 什么是递归? +- 递归的特点 +- 递归与栈的关系 +- 递归应用场景 +- 递归解题思路 +- leetcode案例分析 +- 递归可能存在的问题以及解决方案 + +github地址,感谢每一颗Star +> https://github.com/whx123/JavaHome + +**公众号:捡田螺的小男孩** + +### 什么是递归? + +递归,在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。简单来说,递归表现为函数调用函数本身。在知乎看到一个比喻递归的例子,个人觉得非常形象,大家看一下: + +> 递归最恰当的比喻,就是查词典。我们使用的词典,本身就是递归,为了解释一个词,需要使用更多的词。当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词,可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。 + +来试试水,看一个递归的代码例子吧,如下: +``` +public int sum(int n) { + if (n <= 1) { + return 1; + } + return sum(n - 1) + n; +} +``` + +### 递归的特点 + +实际上,递归有两个显著的特征,终止条件和自身调用: +- 自身调用:原问题可以分解为子问题,子问题和原问题的求解方法是一致的,即都是调用自身的同一个函数。 +- 终止条件:递归必须有一个终止的条件,即不能无限循环地调用本身。 + +结合以上demo代码例子,看下递归的特点: + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/44a74aecf0cc406aa8641b69a531a72c~tplv-k3u1fbpfcp-zoom-1.image) + + +### 递归与栈的关系 +其实,递归的过程,可以理解为出入栈的过程的,这个比喻呢,只是为了方便读者朋友更好理解递归哈。以上代码例子计算sum(n=3)的出入栈图如下: +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ce18f416a01a4900bd75250e781f799a~tplv-k3u1fbpfcp-zoom-1.image) + + +为了更容易理解一些,我们来看一下 函数sum(n=5)的递归执行过程,如下: +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f587c37fea484be192fec7710634ec47~tplv-k3u1fbpfcp-zoom-1.image) + +- 计算sum(5)时,先sum(5)入栈,然后原问题sum(5)拆分为子问题sum(4),再入栈,直到终止条件sum(n=1)=1,就开始出栈。 +- sum(1)出栈后,sum(2)开始出栈,接着sum(3)。 +- 最后呢,sum(1)就是后进先出,sum(5)是先进后出,因此递归过程可以理解为栈出入过程啦~ + + +### 递归的经典应用场景 +哪些问题我们可以考虑使用递归来解决呢?即递归的应用场景一般有哪些呢? +- 阶乘问题 +- 二叉树深度 +- 汉诺塔问题 +- 斐波那契数列 +- 快速排序、归并排序(分治算法也使用递归实现) +- 遍历文件,解析xml文件 + +### 递归解题思路 +解决递归问题一般就三步曲,分别是: +- 第一步,定义函数功能 +- 第二步,寻找递归终止条件 +- 第二步,递推函数的等价关系式 + +这个递归解题三板斧理解起来有点抽象,我们拿阶乘递归例子来喵喵吧~ + +#### 1.定义函数功能 +定义函数功能,就是说,你这个函数是干嘛的,做什么事情,换句话说,你要知道递归原问题是什么呀?比如你需要解决阶乘问题,定义的函数功能就是n的阶乘,如下: +``` +//n的阶乘(n为大于0的自然数) +int factorial (int n){ + +} +``` + +#### 2.寻找递归终止条件 +递归的一个典型特征就是必须有一个终止的条件,即不能无限循环地调用本身。所以,用递归思路去解决问题的时候,就需要寻找递归终止条件是什么。比如阶乘问题,当n=1的时候,不用再往下递归了,可以跳出循环啦,n=1就可以作为递归的终止条件,如下: +``` +//n的阶乘(n为大于0的自然数) +int factorial (int n){ + if(n==1){ + return 1; + } +} +``` + +#### 3.递推函数的等价关系式 +递归的**本义**,就是原问题可以拆为同类且更容易解决的子问题,即**原问题和子问题都可以用同一个函数关系表示。递推函数的等价关系式,这个步骤就等价于寻找原问题与子问题的关系,如何用一个公式把这个函数表达清楚**。阶乘的公式就可以表示为 f(n) = n * f(n-1), 因此,阶乘的递归程序代码就可以写成这样,如下: +``` +int factorial (int n){ + if(n==1){ + return 1; + } + return n * factorial(n-1); +} +``` +**注意啦**,不是所有递推函数的等价关系都像阶乘这么简单,一下子就能推导出来。需要我们多接触,多积累,多思考,多练习递归题目滴~ + +### leetcode案例分析 + +来分析一道leetcode递归的经典题目吧~ +> 原题链接在这里哈:https://leetcode-cn.com/problems/invert-binary-tree/ + +**题目:** 翻转一棵二叉树。 + +输入: +``` + 4 + / \ + 2 7 + / \ / \ +1 3 6 9 +``` +输出: +``` + 4 + / \ + 7 2 + / \ / \ +9 6 3 1 +``` + +我们按照以上递归解题的三板斧来: + +**1. 定义函数功能** + +函数功能(即这个递归原问题是),给出一颗树,然后翻转它,所以,函数可以定义为: +``` +//翻转一颗二叉树 +public TreeNode invertTree(TreeNode root) { +} + +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +``` + +**2.寻找递归终止条件** + +这棵树什么时候不用翻转呢?当然是当前节点为null或者当前节点为叶子节点的时候啦。因此,加上终止条件就是: +``` +//翻转一颗二叉树 +public TreeNode invertTree(TreeNode root) { + if(root==null || (root.left ==null && root.right ==null)){ + return root; + } +} +``` + +**3. 递推函数的等价关系式** + +原问题之你要翻转一颗树,是不是可以拆分为子问题,分别翻转它的左子树和右子树?子问题之翻转它的左子树,是不是又可以拆分为,翻转它左子树的左子树以及它左子树的右子树?然后一直翻转到叶子节点为止。嗯,看图理解一下咯~ +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/989921d15f3d4986be085a5739c0ef62~tplv-k3u1fbpfcp-zoom-1.image) + + +首先,你要翻转根节点为4的树,就需要**翻转它的左子树(根节点为2)和右子树(根节点为7)**。这就是递归的**递**的过程啦 +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9221f4ce5eeb4db6bde09eddb2029007~tplv-k3u1fbpfcp-zoom-1.image) + + +然后呢,根节点为2的树,不是叶子节点,你需要继续**翻转它的左子树(根节点为1)和右子树(根节点为3)**。因为节点1和3都是**叶子节点**了,所以就返回啦。这也是递归的**递**的过程~ + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/005947098fcb4af4a1ac206c6c2ac004~tplv-k3u1fbpfcp-zoom-1.image) + +同理,根节点为7的树,也不是叶子节点,你需要翻转**它的左子树(根节点为6)和右子树(根节点为9)**。因为节点6和9都是叶子节点了,所以也返回啦。 + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6d0a17262234235aca86628beccb10d~tplv-k3u1fbpfcp-zoom-1.image) + + + +左子树(根节点为2)和右子树(根节点为7)都被翻转完后,这几个步骤就**归来**,即递归的归过程,翻转树的任务就完成了~ + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/afada1b801734cdc899e896b0816b63f~tplv-k3u1fbpfcp-watermark.webp) + +显然,**递推关系式**就是: +``` +invertTree(root)= invertTree(root.left) + invertTree(root.right); +``` + +于是,很容易可以得出以下代码: +``` +//翻转一颗二叉树 +public TreeNode invertTree(TreeNode root) { + if(root==null || (root.left ==null && root.right ==null){ + return root; + } + //翻转左子树 + TreeNode left = invertTree(root.left); + //翻转右子树 + TreeNode right= invertTree(root.right); +} +``` +这里代码有个地方需要注意,翻转完一棵树的左右子树,还要交换它左右子树的引用位置。 +``` + root.left = right; + root.right = left; +``` + +因此,leetcode这个递归经典题目的**终极解决代码**如下: +``` +class Solution { + public TreeNode invertTree(TreeNode root) { + if(root==null || (root.left ==null && root.right ==null)){ + return root; + } + //翻转左子树 + TreeNode left = invertTree(root.left); + //翻转右子树 + TreeNode right= invertTree(root.right); + //左右子树交换位置~ + root.left = right; + root.right = left; + return root; + } +} +``` +拿终极解决代码去leetcode提交一下,通过啦~ + + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8cf1ccb571d44f45bfe9252bb12a79bb~tplv-k3u1fbpfcp-zoom-1.image) + + +### 递归存在的问题 +- 递归调用层级太多,导致栈溢出问题 +- 递归重复计算,导致效率低下 + +#### 栈溢出问题 +- 每一次函数调用在内存栈中分配空间,而每个进程的栈容量是有限的。 +- 当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。 +- 其实,我们在前面小节也讨论了,递归过程类似于出栈入栈,如果递归次数过多,栈的深度就需要越深,最后栈容量真的不够咯 + +**代码例子如下:** +``` +/** + * 递归栈溢出测试 + */ +public class RecursionTest { + + public static void main(String[] args) { + sum(50000); + } + private static int sum(int n) { + if (n <= 1) { + return 1; + } + return sum(n - 1) + n; + } +} +``` +**运行结果:** +``` +Exception in thread "main" java.lang.StackOverflowError + at recursion.RecursionTest.sum(RecursionTest.java:13) +``` + +怎么解决这个栈溢出问题?首先需要**优化一下你的递归**,真的需要递归调用这么多次嘛?如果真的需要,先稍微**调大JVM的栈空间内存**,如果还是不行,那就需要弃用递归,**优化为其他方案**咯~ + +#### 重复计算,导致程序效率低下 +我们再来看一道经典的青蛙跳阶问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 + +绝大多数读者朋友,很容易就想到以下递归代码去解决: +``` +class Solution { + public int numWays(int n) { + if (n == 0){ + return 1; + } + if(n <= 2){ + return n; + } + return numWays(n-1) + numWays(n-2); + } +} +``` + +但是呢,去leetcode提交一下,就有问题啦,超出时间限制了 + + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ccdfe1b9cc4a43aa917c9c84d81e2341~tplv-k3u1fbpfcp-zoom-1.image) + + +为什么超时了呢?递归耗时在哪里呢?先画出**递归树**看看: + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/960f370eb35842eb9323538f7f6695ff~tplv-k3u1fbpfcp-zoom-1.image) + + +- 要计算原问题 f(10),就需要先计算出子问题 f(9) 和 f(8) +- 然后要计算 f(9),又要先算出子问题 f(8) 和 f(7),以此类推。 +- 一直到 f(2) 和 f(1),递归树才终止。 + +我们先来看看这个递归的时间复杂度吧,**递归时间复杂度 = 解决一个子问题时间*子问题个数** +- 一个子问题时间 = f(n-1)+f(n-2),也就是一个加法的操作,所以复杂度是 **O(1)**; +- 问题个数 = 递归树节点的总数,递归树的总结点 = 2^n-1,所以是复杂度**O(2^n)**。 + +因此,青蛙跳阶,递归解法的时间复杂度 = O(1) * O(2^n) = O(2^n),就是指数级别的,爆炸增长的,**如果n比较大的话,超时很正常的了**。 + +回过头来,你仔细观察这颗递归树,你会发现存在**大量重复计算**,比如f(8)被计算了两次,f(7)被重复计算了3次...所以这个递归算法低效的原因,就是存在大量的重复计算! + +**那么,怎么解决这个问题呢?** + +既然存在大量重复计算,那么我们可以先把计算好的答案存下来,即造一个备忘录,等到下次需要的话,先去**备忘录**查一下,如果有,就直接取就好了,备忘录没有才再计算,那就可以省去重新重复计算的耗时啦!这就是**带备忘录的解法** + +我们来看一下**带备忘录的递归解法**吧~ + +一般使用一个数组或者一个哈希map充当这个**备忘录**。 + +假设f(10)求解加上**备忘录**,我们再来画一下递归树: + +**第一步**,f(10)= f(9) + f(8),f(9) 和f(8)都需要计算出来,然后再加到备忘录中,如下: + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/71ae2665e01e49c8bc88f8bf01f0d2b2~tplv-k3u1fbpfcp-zoom-1.image) + +**第二步,** f(9) = f(8)+ f(7),f(8)= f(7)+ f(6), 因为 f(8) 已经在备忘录中啦,所以可以省掉,f(7),f(6)都需要计算出来,加到备忘录中~ + + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/13d385fb005a4784918f4fb810033a51~tplv-k3u1fbpfcp-zoom-1.image) + + + +**第三步,** f(8) = f(7)+ f(6),发现f(8),f(7),f(6)全部都在备忘录上了,所以都可以剪掉。 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec535ab8a0d3401eae5cb041dabac221~tplv-k3u1fbpfcp-watermark.image) + +所以呢,用了备忘录递归算法,递归树变成光秃秃的树干咯,如下: +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5eb16b3032dc493389529e3821b22ba8~tplv-k3u1fbpfcp-zoom-1.image) + + +带「备忘录」的递归算法,子问题个数=树节点数=n,解决一个子问题还是O(1),所以**带「备忘录」的递归算法的时间复杂度是O(n)**。接下来呢,我们用带「备忘录」的递归算法去撸代码,解决这个青蛙跳阶问题的超时问题咯~,代码如下: + +``` +public class Solution { + //使用哈希map,充当备忘录的作用 + Map tempMap = new HashMap(); + public int numWays(int n) { + // n = 0 也算1种 + if (n == 0) { + return 1; + } + if (n <= 2) { + return n; + } + //先判断有没计算过,即看看备忘录有没有 + if (tempMap.containsKey(n)) { + //备忘录有,即计算过,直接返回 + return tempMap.get(n); + } else { + // 备忘录没有,即没有计算过,执行递归计算,并且把结果保存到备忘录map中,对1000000007取余(这个是leetcode题目规定的) + tempMap.put(n, (numWays(n - 1) + numWays(n - 2)) % 1000000007); + return tempMap.get(n); + } + } +} +``` + +去leetcode提交一下,如图,稳了: + +![](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1322a96e4eae44229fdea9f78e0d440b~tplv-k3u1fbpfcp-zoom-1.image) + +还有没有其他方案解决这个问题呢?只有**带备忘录的递归解法**?其实吧,还可以用**动态规划**去解决 + +动态规划算法思想怎么解题?我们下期继续~ 谢谢阅读~ + +### 参考与感谢 +- [一文学会递归解题] (https://mp.weixin.qq.com/s/Hew44D8rdXb3pf8mZGk67w) +- [动态规划详解] (https://mp.weixin.qq.com/s/1V3aHVonWBEXlNUvK3S28w) + +### 更多干货 +**公众号:捡田螺的小男孩** +- 更多干货,关注公众号 +- 回复pdf,获取学习电子书 + diff --git "a/\344\270\255\351\227\264\344\273\266/README.MD" "b/\344\270\255\351\227\264\344\273\266/README.MD" new file mode 100644 index 0000000..74d80b2 --- /dev/null +++ "b/\344\270\255\351\227\264\344\273\266/README.MD" @@ -0,0 +1,8 @@ +## 中间件 + +- [一文快速入门分库分表中间件 Sharding-JDBC (必修课)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499781&idx=1&sn=74bbb25c9347408f1edf7f8c9c82d7cf&chksm=cf221f2cf855963a6549069deeabe93bb6d6e889bcd086668bf6f0e23327fa1ddb31adc6d10c&token=1990771297&lang=zh_CN#rd) +- [全方位对比Zookeeper、Eureka、Nacos、Consul和Etcd](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498268&idx=1&sn=7b24b8625fb4ff88d50c9bd55335f478&chksm=cf222535f855ac230dfca629127f93efec606641d7338c29a8d41e7d2016a7f0b6ec28a432a0&token=1990771297&lang=zh_CN#rd) +- [消息队列经典十连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497847&idx=1&sn=29a32672b712e7dfadfa36c9902b2ec7&chksm=cf22275ef855ae484fb3f51a5726e9a4bc45222e8fbbd33631d177dc4b5619c36889ea178463&token=1990771297&lang=zh_CN#rd) +- [Kafka性能篇:为何Kafka这么"快"?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488717&idx=1&sn=006c65f9a9a5796961c42f3cafc37cb4&chksm=cf21cbe4f85642f2e8ff948f8de8a69508783cee6dafd22512d6a06cd03f7065001bd1d8d87b&token=1990771297&lang=zh_CN#rd) +- [后端程序员必备:RocketMQ相关流程图/原理图](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487949&idx=1&sn=888e0917884b2918a94053e5cd560e00&chksm=cf21cee4f85647f24877791d574f5ef3f979fc9c4c84ca3fd1ea1aa08ab30c1041ad3aaa5650&token=1990771297&lang=zh_CN#rd) +- [ZooKeeper的十二连问,你顶得了嘛?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488047&idx=1&sn=4913c7e1c3b8835f7512d8dc6b845727&chksm=cf21cd06f8564410cce6121230256facb1ab3b5a9ed35579896f428d84bdea7b86836109d575&token=162724582&lang=zh_CN&scene=21#wechat_redirect) \ No newline at end of file diff --git "a/\345\210\206\345\270\203\345\274\217/README.MD" "b/\345\210\206\345\270\203\345\274\217/README.MD" new file mode 100644 index 0000000..1b5f507 --- /dev/null +++ "b/\345\210\206\345\270\203\345\274\217/README.MD" @@ -0,0 +1,12 @@ +## 分布式 + +关注公众号:捡田螺的小男孩 + +- [面试必备:聊聊分布式锁的多种实现!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498595&idx=1&sn=4e5308930e151a609baa2df820e48a89&chksm=cf22244af855ad5c71822cb33e828ce652c6f34202096a9344922b86dcbc08076d7922acde5f&token=1990771297&lang=zh_CN#rd) +- [我们为什么要分库分表?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498625&idx=1&sn=0d7bd9d1b46eeff4c715a6761355e9b0&chksm=cf2224a8f855adbea8931c8e011711f6c70cffeef8ddf8b87729c710eacef11b46eef80fda36&token=1990771297&lang=zh_CN#rd) +- [聊聊高可用的 11 个关键技巧](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498784&idx=1&sn=aad1c00d6eafb0c1f08959612c69959a&chksm=cf222309f855aa1f71ef9cf470bfa72ac73365c401ec7c7d0c3b241a9116c3112f83760793e8&token=1990771297&lang=zh_CN#rd) +- [看一遍就理解:分布式事务详解](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498358&idx=1&sn=aa6c7ceb61b73267d68d1b4fb7ccc2ed&chksm=cf22255ff855ac495861d57df276517e89779006267fa8413fe925cc15b0c3e0b0f1b1a5675e&token=1990771297&lang=zh_CN#rd) +- [几种主流的分布式定时任务,你知道哪些?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498121&idx=1&sn=e3d7e4f5297c7b2390b412a9bafc3385&chksm=cf2226a0f855afb669cde8d7f400fb334bd4c75a8c672d1208667387d03d2dfd24884e60b825&token=1990771297&lang=zh_CN#rd) +- [redis分布式锁的8大坑,记得拿小本本记下来啦](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495390&idx=1&sn=87cc1567c709cfa67b43dd8d273bb426&chksm=cf2231f7f855b8e17919f7763469d87c47d9b4c4ad25aba7e6ff60fa33b048bc47a4afd287fc&token=1990771297&lang=zh_CN#rd) +- [框架篇:分布式一致性解决方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490468&idx=2&sn=91b8e5dd2ce3db218708b5c736fce700&chksm=cf21c48df8564d9b30164e1dbf9b5ebcc1847a9450d08ee146c98eb53107af475149ad12a748&token=1990771297&lang=zh_CN#rd) +- [这三年被分布式坑惨了,曝光十大坑](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488553&idx=2&sn=fa13e9698e59f5a5485d3d3d4b8ef2b1&chksm=cf21cb00f8564216277806780c64e13c48fe32009f588349b3365afa8de97bd8ef192507bd50&token=1990771297&lang=zh_CN#rd) \ No newline at end of file diff --git "a/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/README.md" "b/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/README.md" new file mode 100644 index 0000000..7f6092a --- /dev/null +++ "b/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/README.md" @@ -0,0 +1,10 @@ +## 后端思维篇(持续更新中) + +公众号:捡田螺的小男孩 + +- [后端思想篇:设计好接口的36个锦囊!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499388&idx=1&sn=49a22120a3238e13ad7c3d3b73d9e453&chksm=cf222155f855a8434026b2c460d963c406186578c2527ca8f2bb829bbe849d87a2392a525a9b&scene=178&cur_album_id=2396778860463161350#rd) +- [后端思维篇:手把手教你写一个并行调用模板](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499504&idx=1&sn=bb62226e6cffeb1859efb0100c796050&chksm=cf2221d9f855a8cf23f75cb51c1a407578fb0f279e96ddae74b5b8c84f2f5dc71762425b17cb&scene=178&cur_album_id=2396778860463161350#rd) +- [后端思维篇:如何应用设计模式优化代码](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499524&idx=1&sn=cb4cc48a3e8d9a54b0ebc4c7ad517f14&chksm=cf22202df855a93b37327856ee88b0bf5f6ed7da67964438fc2cf747666260d5026dd62d4a17&scene=178&cur_album_id=2396778860463161350#rd) +- [后端思维篇:统一参数校验、异常处理、结果返回](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499708&idx=1&sn=808979c495acd9344732d147c0ad40d3&chksm=cf222095f855a983f31d5f6abf401fa3b5967f8839c6775d35cefc5cc6244fb4135563ff1090&scene=178&cur_album_id=2396778860463161350#rd) +- [后端思维篇:如何抽一个观察者模板](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247500159&idx=1&sn=a5328372e580b22c939a5b3084aef164&chksm=cf221e56f85597401e8c99b8dd1bc1af97fcf69207ceaa04c5c26e028ac47d1658b79ae32291&scene=178&cur_album_id=2396778860463161350#rd) +- [后端思维篇:后端思维专栏:通过层层代码去重,我又搞了一个通用模板](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247506942&idx=1&sn=ae14ed5cc179f73ea0b2f37c73ad8da4&chksm=c1e02672f697af645943ea8ee53b7cef6257ebbc21d2b77058994e98bdb1e107ad313e29e8c3&token=134957671&lang=zh_CN#rd) diff --git "a/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/\345\220\216\347\253\257\346\200\235\347\273\264\344\270\200\357\274\232\350\256\276\350\256\241\346\216\245\345\217\243\347\232\20436\344\270\252\351\224\246\345\233\212.md" "b/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/\345\220\216\347\253\257\346\200\235\347\273\264\344\270\200\357\274\232\350\256\276\350\256\241\346\216\245\345\217\243\347\232\20436\344\270\252\351\224\246\345\233\212.md" new file mode 100644 index 0000000..f999d6b --- /dev/null +++ "b/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/\345\220\216\347\253\257\346\200\235\347\273\264\344\270\200\357\274\232\350\256\276\350\256\241\346\216\245\345\217\243\347\232\20436\344\270\252\351\224\246\345\233\212.md" @@ -0,0 +1,540 @@ +## ǰ + +ҺãǼݵСкΪ˿ʲôԣ```Java``````Go``````C++```䱳ĺ˼붼Ƶġһ˼ļרҪ˵һЩơߺ˹淶صģϣԴճа + +˿ʦҪǣ**ΰһӿƺ**ԣ͸ҽܣƺýӿڵ36ҡľǺ˼רĵһƪ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1bfc8123cee34de2ad82b736121165d2~tplv-k3u1fbpfcp-zoom-1.image) + +- ںţݵСк + + +## 1. ӿڲУ + +γУÿԱرĻƵĽӿڣУǷΪգγǷԤڳȡҪϰ߹ճУܶͼbugDzУµġ + +> ݿֶΪ```varchar(16)```,Էһ32λַ㲻У**ݿֱ쳣** + +Ҳǣ㶨ĽӿڱģDzΪյģĽӿڷزûУ飬ΪijЩԭֱرһ```null```ֵ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bfd3392f3ce6408daa1940cc185f0d5f~tplv-k3u1fbpfcp-zoom-1.image) + +## 2. ޸Ͻӿʱעӿڵļ + +ܶbugΪ޸˶ɽӿڣȴ****µġؼDZȽصģֱӵϵͳʧܵġֳԱ׷Ŷ~ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/701ac23b5dd04149b277c4001721fb87~tplv-k3u1fbpfcp-zoom-1.image) + +ԣԭӿ޸ģӿǶṩĻһҪǽӿڼݡٸӰɣdubboӿڣԭֻABһCͿԿ + +``` +//Ͻӿ +void oldService(A,B){ + //½ӿڣnullC + newService(A,B,null); +} + +//½ӿڣʱɾϽӿڣҪݡ +void newService(A,B,C){ + ... +} +``` + +## 3. ƽӿʱֿǽӿڵĿչ + +Ҫʵҵ񳡾ƽӿڣֿǽӿڵĿչԡ + +ӵһûӻ޸ԱʱҪˢǷṩһԱύˢϢӿڣ˼ύˢDzͨأת˻һҪˢĻǷҪʵһӿأǵǰҵͻģ飬ӿھͺãӿڵĿչԡ + +ģ黮ֵĻδһֽˢĻٸһµĽӿڣֻҪö٣Ȼˢͨ̽ӿڣʵһˢIJ컯ɡ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cd3ee52ecaa34de384bb529cbb358889~tplv-k3u1fbpfcp-zoom-1.image) + + +## 4.ӿڿǷҪش + +ǰظ߼δDzǿǽӿȥش + +ȻDzѯʵ÷ءǸ޸ĻתģҪظˡ򵥵㣬ʹRedisظͬ󷽣һʱڵͬ󣬿ǷˡȻתӿڣߵĻ**Ƽʹݿر****ΨһˮΪΨһ** + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/059928f565ba4d27a17c54f451b0235d~tplv-k3u1fbpfcp-zoom-1.image) + + +## 5. صӿڣ̳߳ظ롣 + +һЩ½ת˽סµҪӿڣ̳߳ظҵ񶼹һ̳߳أЩҵbug̳߳ĻǾͱˣ**ҵӰ**˽̳߳ظ룬Ҫҵһḷ́߳͸ñҪҵ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d30804afc044026b4eb7bad23689c42~tplv-k3u1fbpfcp-zoom-1.image) + + +## 6. õӿҪ쳣ͳʱ + +õӿڣ߷ֲʽԶ̷ĵĻҪǣ + +- 쳣 + +> 磬˵Ľӿڣ쳣ˣôԻǵʧܻǸ澯 + +- ӿڳʱ + +> ûԤԷӿһ÷أһøʱϿʱ䣬ԱĽӿڡ**֮ǰһ**httpòóʱʱ䣬Ӧ̼һֱռ̲߳ͷţϿ̳߳ء + +- Դ +> Ľӿڵʧܣ費ҪԣԼΣҪվҵϽǶ˼ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/25ec61c10c324ada9252745fa4017ad6~tplv-k3u1fbpfcp-zoom-1.image) + + +## 7. ӿʵֿ۶Ϻͽ + +ǰϵͳһ㶼Ƿֲʽġֲʽϵͳоij񲻿ãյϵͳõ, 󱻳Ϊ**ѩЧӦ** + +ֲʽ·```A->B->C....```ͼʾ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65240791c94c44b6aab143178eeb790c~tplv-k3u1fbpfcp-zoom-1.image) + +> C⣬**ΪSQLµû**ǽBҲӳ٣ӶAҲӳ١סAռϵͳ̡߳IOԴ AķԽԽ࣬ռüԴҲԽԽ࣬ջᵼϵͳƿ֣ͬãҵϵͳ + +ΪӦԷѩ, **۶Ϻͽ**ǼӿؿƣϵͳʱؽٵϵͳѡÿԴ```Hystrix``` + +## 8. ־ӡãӿڵĹؼ룬Ҫ־ݻ + +ؼҵεأӦ㹻־ݻ +磺ʵתҵתȻתʧˣſͻͶߣȻ㻹ûдӡ־ˮȵ£ȴް취 + +ôתҵҪЩ־Ϣأ٣ǰҪӡҪɣӿڵúҪһ쳣ɣͬʱӡ쳣־ɣ£ +``` +public void transfer(TransferDTO transferDTO){ + log.info("invoke tranfer begin"); + //ӡ + log.info("invoke tranfer,paramters:{}",transferDTO); + try { + res= transferService.transfer(transferDTO); + }catch(Exception e){ + log.error("transfer fail,account{}", + transferDTO.getAccount) + log.error("transfer fail,exception:{}",e); + } + log.info("invoke tranfer end"); + } +``` + +֮ǰдһƪӡ־15飬ҿԿ[ܽᣡ־ӡ15](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494838&idx=1&sn=cdb15fd346bddf3f8c1c99f0efbd67d8&chksm=cf22339ff855ba891616c79d4f4855e228e34a9fb45088d7acbe421ad511b8d090a90f5b019f&token=162724582&lang=zh_CN&scene=21#wechat_redirect) + +## 9. ӿڵĹܶҪ߱һ + +һָӿȽϵһרһһ½ӿڣֻУ˻룬Ȼ󷵻ص½ɹԼ```userId```ɡ**Ϊ˼ٽӿڽһЩעᡢһЩòѯȫŵ½ӿڣͲ̫ס** + +ʵҲ΢һЩ˼룬ӿڵĹܵһȷ綩񡢻֡ƷϢصĽӿڶǻֿġ΢ĻDzǾͱȽϼ + + +## 10.ӿЩʹ첽 + +ٸ򵥵ӣʵһûעĽӿڡûעɹʱʼ߶ȥ֪ͨûʼ߷ţ͸ʺ첽Ϊܲһ֪ͨʧܣעʧܰɡ + +첽ķʽ򵥵ľ**̳߳**ʹϢУûעɹ߲һעɹϢעɹϢͷ֪ͨ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/585b098a67b349d495e6e8579ea85e4c~tplv-k3u1fbpfcp-zoom-1.image) + + +еĽӿڶʺΪͬӿڡҪһת˵Ĺܣǵʵתˣǿ԰ѽӿͬûתʱͻھȴת˽ͺáתˣһһǧʣһʵģ԰ѽӿΪ첽ûתʱ־ûɹȷɹȻûʮӻʮӵת˽ͺáֻߣת˳ɹٻصϵͳ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1ae74868492344c4bcbab9b480904c47~tplv-k3u1fbpfcp-zoom-1.image) + + +## 11. ŻӿںʱԶ̴пǸIJе + +һAPPҳĽӿڣҪûϢҪbannerϢҪ鵯ϢȵȡһһӿڴеDzеأ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d118e2b09e1f4fc6a1003fd44a43e4c7~tplv-k3u1fbpfcp-zoom-1.image) + +Ǵһһ飬ûϢ200msbannerϢ100ms鵯Ϣ50msһͺʱ```350ms```ˣϢǺʱ͸ˡֳǿԸΪеõġҲ˵ûϢbannerϢ鵯Ϣͬʱ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/83561366219b48a2a85a6bb0419f82a3~tplv-k3u1fbpfcp-zoom-1.image) + +Javaи첽```CompletableFuture```ͿԺܺʵܡȤСԿ֮ǰ¹[CompletableFuture](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490456&idx=1&sn=95836324db57673a4d7aea4fb233c0d2&chksm=cf21c4b1f8564da72dc7b39279362bcf965b1374540f3b339413d138599f7de59a5f977e3b0e&token=1260947715&lang=zh_CN#rd) + +## 12. ӿںϲ˵˼ + +ݿԶ̵ʱͲҪforѭá +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/855cd5cf57d047be909dbc41ddacc021~tplv-k3u1fbpfcp-zoom-1.image) + +һӣƽʱһбϸݲݿʱҪforѭһһ룬һμ롣ͬԶ̵Ҳ뷨ѯӪǩǷУһǩһǩȥ飬Ҳǩȥ飬УЧʾ͸ + +``` +// +for(int i=0;i һЩƽʱ䶯С˵ƷϢԷŵ棬ʱȲѯ棬ûٲݿ⣬Ұݿݸµ档ǣʹûҪЩ㣺ݿһα֤Ⱥѩ洩͸⡣ + +- ֤ݿͻһԣ**ʱ˫ɾɾԻơȡbiglog첽ɾ** +- +- ѩRedisȺ߿áùʱ +- 洩͸ӿڲУ顢ѯΪøĬϿֵǡ¡ + +һ```Redis```ֲʽ棬ȻЩʱҲԿʹñػ棬```Guava CacheCaffeine```ȡʹñػЩȱ㣬޷дݴ洢Ӧý̵ʧЧ + +## 14. ӿڿȵݸ + +˲ʱĸ߲ܻϵͳһЩȵݵĸ롣**ҵ롢ϵͳ롢û롢ݸ**ȡ + +- ҵԣ12306ķʱƱȵݷɢϵͳѹ +- ϵͳ룺ϵͳֳûƷ顣ֱʹòͬݿ⣬ӽ㵽Ӧòٵݲȫ롣 +- û룺صûøõĻ +- ݸ룺ʹõĻ漯Ⱥݿȵݡ + +## 15. ɱûƤл + +Ʒ˸ʥڵʱ򣬺ƤΪʥصģڵʱΪںƤȡ + +ڴдƣ´룺 +``` +if(duringChristmas){ + img = redPacketChristmasSkin; +}else if(duringSpringFestival){ + img = redSpringFestivalSkin; +} +``` +ԪڵʱӪСͻȻ뷨ƤɵصģʱDzҪȥ޸Ĵˣ·ˣ + +һʼӿʱʵ**һźƤñ**ƤûأƤֻ޸һ±ݾͺˡ + +ȻһЩʺһЩûIJһҳơijʱЩԸ㵽û档**Ҳչ˼һ֡** + +## 16.ӿڿݵ + +ӿҪݵԵģתЩҪӿڡֱ۵ҵ񳡾**ûŵ**Ľӿû**holdס**Ϣгظѵҵ߼ôƣ + +£**ʲôݵȣ** + +> ѧУݵȱʾһκͶijһԴӦþͬĸã˵ӰһִеӰЧͬ + +ұ**غݵʵ**ҪΪ˱ظݣظɡݵƳѾ󣬻Ҫÿͬ󶼷һЧأܶʱǵĴ̡ƵĹ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f8c3d0d5a653455198ba3259ef221387~tplv-k3u1fbpfcp-zoom-1.image) + + +ӿݵʵַҪ8֣ + +- select+insert+/Ψһͻ +- ֱinsert + /Ψһͻ +- ״̬ݵ +- ȡر +- token +- +- ֹ +- ֲʽ + +ҿԿƪ¹[ݵ](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497427&idx=1&sn=2ed160c9917ad989eee1ac60d6122855&chksm=cf2229faf855a0ecf5eb34c7335acdf6420426490ee99fc2b602d54ff4ffcecfdab24eeab0a3&token=1260947715&lang=zh_CN#rd) + +## 17. д룬ȿǶӿ⣬עӳ + +ǵݿⶼǼȺģҲдӿ⣬ǰһ㶼Ƕдġдݣ϶д⣬ǶڶȡʵʱҪ󲻸ߵݣȿǶӿ⣬ΪԷֵѹ + +ȡӿĻҪӳٵ⡣ + +## 18.ӿעⷵصҪҳ + +һӿڷرģӦðӣѹҲdzʵDZȽϴ󣬿ԷҳأǹܲصıģӦÿǽӿڲ֡ + +## 19. õĽӿʵ֣벻SQLŻ + +˵ģдһӿڣ벻SQLŻ + +SQLŻ⼸ά˼ + +- explain SQLѯƻصעtypeextrafilteredֶΣ +- show profile˽SQLִе̵߳״̬Լĵʱ +- Ż ǰ׺ԭʽתorder byԼgroup byŻjoinŻ +- ҳŻӳٹ¼һҳID +- ̫**ֱֿ**ͬesesѯ + +## 20.ȿƺ + +ʲôǼأ + +> ʵǾҪסķΧǶ󡣱ڼ䣬ֻҪסͿ˰ɣҪҶü˽Űɣļȡ + +дʱ漰ԴûбҪסġͺ䣬ðҶססžͿˡ + +磬ҵУһArrayListΪ漰̲߳ҪպһαȽϺʱIJе```slowNotShare```漰̰߳ȫ⣬μأ + + +``` +//漰Դ +private void slowNotShare() { + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + } +} + +//ļ +public int wrong() { + long beginTime = System.currentTimeMillis(); + IntStream.rangeClosed(1, 10000).parallel().forEach(i -> { + //̫ˣslowNotShareʵ漰Դ + synchronized (this) { + slowNotShare(); + data.add(i); + } + }); + log.info("cosume time:{}", System.currentTimeMillis() - beginTime); + return data.size(); +} +``` + + +``` +public int right() { + long beginTime = System.currentTimeMillis(); + IntStream.rangeClosed(1, 10000).parallel().forEach(i -> { + slowNotShare();//Բ + //ֻListⲿּ + synchronized (data) { + data.add(i); + } + }); + log.info("cosume time:{}", System.currentTimeMillis() - beginTime); + return data.size(); +} +``` + +## 21.ӿ״̬ʹҪͳһȷ + +ṩҪĽӿڵ״̬Ϣһת˽ӿڵdzɹʧܡлɹȣҪȷ߿ͻˡӿʧܣôʧܵԭʲôЩҪϢҪ߸ͻˣҪȷĴͶӦͬʱԱϢװһ£ҪѺ˵쳣Ϣȫ׳ͻˡ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/20a1080126274c04aa31802178c01bb0~tplv-k3u1fbpfcp-zoom-1.image) + + +## 22.ӿҪ쳣 + +ʵһõĽӿڣ벻ŵ쳣쳣ʮСɣ + +- Ҫʹ```e.printStackTrace()```,ʹ```log```ӡΪ```e.printStackTrace()```ܻᵼڴռ +- ```catch```ס쳣ʱӡ```exception```ڸöλ +- Ҫһ```Exception```׽пܵ쳣 +- ǵʹ```finally```رԴֱʹ```try-with-resource``` +- 쳣׳쳣ȫƥ䣬߲쳣쳣ĸ +- 񵽵쳣ܺٴ־ +- ע쳣ĴνṹȾ +- Զװ쳣Ҫԭʼ쳣Ϣ```Throwable cause``` +- ʱ쳣```RuntimeException``` Ӧͨ```catch```ķʽԤ飬磺```NullPointerException``` +- ע쳣ƥ˳Ȳ쳣 + +СȤԿ֮ǰдƪ¹[Java 쳣ʮ](https://mp.weixin.qq.com/s/3mqY77c8iXWvJFzkVQi9Og) + +## 23. Ż߼ + +Ż߼黹ͦҪģҲ˵ʵֵҵ룬**DZȽϸӵĻעд**У߼뾡Ч + +> 磬ҪʹûϢԣsessionѾȡ```userId```ˣȻͰûϢݿѯʹ󣬺ҪõûϢԣЩСû̫࣬־Ͱ```userId```ٴȥٲһݿ⡣ĿУִ롣ֱӰû + +α룺 + +``` +public Response test(Session session){ + UserInfo user = UserDao.queryByUserId(session.getUserId()); + + if(user==null){ + reutrn new Response(); + } + + return do(session.getUserId()); +} + +public Response do(String UserId){ + //һݿ + UserInfo user = UserDao.queryByUserId(session.getUserId()); + ...... + return new Response(); +} + +``` + + + +``` +public Response test(Session session){ + UserInfo user = UserDao.queryByUserId(session.getUserId()); + + if(user==null){ + reutrn new Response(); + } + + return do(session.getUserId()); +} + +//ֱӴUserInfoɣٶһݿ +public Response do(UserInfo user){ + ...... + return new Response(); +} +``` + +ȻֻһЩСһӣкܶƵӣҪҿУ˼Ĺ + + +## 24. ӿʵֹ̻Уעļ񡢴 + +- ȡļʱҪ```Files.readAllBytes```ֱӶȡڴ棬OOMģʹ```BufferedReader```һһ +- ܵعʱ䳤ӳٵ⣬о +- עһЩʹãΪֱӽģᴥfullGC + +## 25. ĽӿڣҪ + +ϵͳÿ뿸ס1000һʮأǶȾ˵߲ʱˣϵͳijôأ + +ȡʩеϵͳCPUڴ桢Load쮵ĺܸߣе޷Ӧ + +ֳǿԲΪ˱ϵͳֱӶ + +壺 +> ڼУǿӿڷͻʣɷֹDoSWeb档Ҳơָϵͳٸ߲ߴ£µϵͳķʣӶ֤ϵͳȶԡ + +ʹGuava```RateLimiter```Ҳʹ```Redis```ֲʽʹð↑Դ```sentinel``` + +ҿԿ֮ǰƪ¹[4־㷨](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490393&idx=1&sn=98189caa486406f8fa94d84ba0667604&chksm=cf21c470f8564d665ce04ccb9dc7502633246da87a0541b07ba4ac99423b28ce544cdd6c036b&token=162724582&lang=zh_CN&scene=21#wechat_redirect) + + +## 26.ʵʱעʱ쳣ָ롢±Խȣ + +ճУҪȡʩ**ָ߽**ʱƴȽϳ +``` +String name = list.get(1).getName(); //listԽ磬Ϊһ2Ԫع +``` + +ӦòȡʩԤһ߽£ +``` +if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){ + String name = list.get(1).getName(); +} +``` +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/10199365140845ea8f7b29a07fbaf3cc~tplv-k3u1fbpfcp-zoom-1.image) + + +## 27.֤ӿڰȫ + +APIӿǶṩģҪ֤ӿڵİȫԡ֤ӿڵİȫ**tokenƺͽӿǩ** + +**token֤**Ƚϼ򵥵ģ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b468f89cdaf4040b84e432182903fd9~tplv-k3u1fbpfcp-zoom-1.image) + +1. ͻ˷ȡtoken +2. ȫΨһtoken浽redisУһһʱ䣩Ȼ󷵻ظͻˡ +3. ͻ˴token +4. ȥredisȷtokenǷڣһ redis.del(token)ķʽڻɾɹҵ߼ɾʧܲҵ߼ֱӷؽ + +**ӿǩ**ķʽǰѽӿϢģʱ汾šappidȣͻ˽ԿǩȻùԿǩ֤ͨΪǺϷġûб۸Ĺ + +йڼǩǩģҿԿƪ¹[Աرǩǩ](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488022&idx=1&sn=70484a48173d36006c8db1dfb74ab64d&chksm=cf21cd3ff8564429a1205f6c1d78757faae543111c8461d16c71aaee092fe3e0fed870cc5e0e&token=162724582&lang=zh_CN&scene=21#wechat_redirect) + +**ǩǩtokenƣӿڱһҪܵ**ȻhttpsЭǻԱļܵġǷĻμӽأ +> ԲοHTTPSԭǷ˰ѹԿͻˣȻͻɶԳԿſͻ÷˵ĹԿܶԳԿٷˣԼ˽Կܣõͻ˵ĶԳԿʱͿ촫䱨ͻ**ԳԿ****öӦĶԳԿܱ** + +ʱ򣬽ӿڵİȫԣ**ֻš֤Ϣ**˵**û˽ݣ㱩¶** + +## 28.ֲʽα֤ + +> ֲʽ񣺾ָIJߡ֧ķԴԼֱλڲͬķֲʽϵͳIJͬڵ֮ϡ˵ֲʽָľǷֲʽϵͳеĴھΪ˱֤ͬݿڵһԡ + +ֲʽļֽ +- 2PC(׶ύ)3PC +- TCCTryConfirmCancel +- Ϣ +- Ŭ֪ͨ +- seata + +ҿԿƪ¹[һ⣺ֲʽ](https://mp.weixin.qq.com/s/3r9MfIz2RAtdFhYzwwZxjA) + +## 29. ʧЧһЩ䳡 + +ǵĽӿڿУҪʹõҪܿʧЧһЩ䳡 + +- ķȨޱpublicprivateȨޣʧЧ +- finalģᵼʧЧ +- ͬһеķֱڲãᵼʧЧ +- һûspringͲspring +- ̵߳ãͬһ߳УȡݿӲһġ +- Ĵ洢治֧ +- Լtry...catch쳣ʧЧ +- Ĵ + +Ƽҿƪ£[springʧЧ12ֳ̫](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494570&idx=2&sn=17357bcd328b2d1d83f4a72c47daac1b&chksm=cf223483f855bd95351a778d5f48ddd37917ce2790ebbbcd1d6ee4f27f7f4b147f0d41101dcc&token=2044040586&lang=zh_CN&scene=21#wechat_redirect) + + +## 30. ճõģʽ + +ѴдãҪõģʽģʽģʽģ巽ģʽ۲ģʽȵȡģʽǴƾܽᡣʹģʽԿô롢ôױ⡢֤ɿԡ + +֮ǰдһƪܽṤгģʽ£дͦģҿԿ£[ʵսгõЩģʽ](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495616&idx=1&sn=e74c733d26351eab22646e44ea74d233&chksm=cf2230e9f855b9ffe1ddb9fe15f72a273d5de02ed91cc97f3066d4162af027299718e2bf748e&token=1260947715&lang=zh_CN#rd) + +## 31. дʱ԰ȫ + +**߲**£```HashMap```ܻѭΪǷ԰ȫģԿʹ```ConcurrentHashMap```ҲϰߣҪ־һ```new HashMap()```; + +> - HashmapArraylistLinkedListTreeMapȶԲȫģ +> - VectorHashtableConcurrentHashMapȶ԰ȫ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1ba0cab945874264a8d8e87b7d7c4a1b~tplv-k3u1fbpfcp-zoom-1.image) + + +## 32.ӿڶ׶淶 + +д룬ΪʵֵǰĹܣҲҪںά˵ά벻дԼģҲǸ˿ġԽӿڶҪ׶淶 + +## 33. ӿڵİ汾 + +ӿҪð汾ơ˵ģӦð```version```ӿڰ汾ֶΣδӿڼݡʵҲӿչԵһֵɡ + +ͻAPPijŻˣϰ汾Ṳ棬ʱǵ```version```汾žóˣ```version```ð汾ơ + +## 34. ע淶 + +עһЩĴ뻵ζ +- ظ루鹫÷ģʽ +- ࣨɷװһDTO +- С +- ж̫ࣨŻif...else +- ûõĴ +- עشʽ +- + +ĻζҶд[25ִ뻵ζܽ+Żʾ](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490148&idx=1&sn=00a181bf74313f751b3ea15ebc303545&chksm=cf21c54df8564c5bc5b4600fce46619f175f7ae557956f449629c470a08e20580feef4ea8d53&token=162724582&lang=zh_CN&scene=21#wechat_redirect) + +## 35.֤ӿȷԣʵDZ֤ٵbug + +֤ӿڵȷԣǶȽDZ֤ٵbugûbugԽӿڿһҪ**Բһ**ȻĻӿڵȷڣ̲߳ʱ**֤ݵȷ**,ȵȡһת˽ףۼʱ򣬿ͨCASֹķʽ֤ۼȷɡ + +ʵɱӿڣ÷ֹɡʹRedisֲʽֹ⡣ʹRedisֲʽмעҪ㣬ҿԿ֮ǰƪ¹[ַ̽Redisֲʽȷʹ](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488142&idx=1&sn=79a304efae7a814b6f71bbbc53810c0c&chksm=cf21cda7f85644b11ff80323defb90193bc1780b45c1c6081f00da85d665fd9eb32cc934b5cf&token=162724582&lang=zh_CN&scene=21#wechat_redirect) + +## 36.ѧṵͨǰ˹ͨƷͨ + +ҰһŵѧṵͨǷdzdzҪġ㿪ӿʱ**һԼͷѽӿڶ****Ҫͻȶӿ**һЩѵʱleader뷽ʵĹУʲô⣬ʱƷͨ + +֮ǣӿڹУһҪͨ~ + + +## (ע) + +ƪ¶ĻӭעҵĹںţݵСк + + diff --git "a/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207\344\272\214\357\274\232\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\345\256\236\347\216\260\344\270\200\344\270\252\345\271\266\350\241\214\350\260\203\347\224\250\346\250\241\346\235\277.md" "b/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207\344\272\214\357\274\232\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\345\256\236\347\216\260\344\270\200\344\270\252\345\271\266\350\241\214\350\260\203\347\224\250\346\250\241\346\235\277.md" new file mode 100644 index 0000000..762cb1b --- /dev/null +++ "b/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207/\345\220\216\347\253\257\346\200\235\347\273\264\347\257\207\344\272\214\357\274\232\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\345\256\236\347\216\260\344\270\200\344\270\252\345\271\266\350\241\214\350\260\203\347\224\250\346\250\241\346\235\277.md" @@ -0,0 +1,599 @@ +## ǰ + +ҺãǼݵСк + +Ǻ˼άרĵڶƪһƪ[36ƽӿڵĽ](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499388&idx=1&sn=49a22120a3238e13ad7c3d3b73d9e453&chksm=cf222155f855a8434026b2c460d963c406186578c2527ca8f2bb829bbe849d87a2392a525a9b&token=1380536362&lang=zh_CN#rd)õdzСϿɡ +36ƽӿڵĽҲᵽһ㣺**ʹòеŻӿ**ԽͿӱޣдڶƪְֽдһеģ塣 + +- һеõӣAppҳϢѯ +- CompletionServiceʵֲе +- ȡͨõIJе÷ +- ˼ԼģʽӦ +- ˼ܽ +- ںţ**ݵСк** + + +## 1. һеõ + +һAPPҳѯĽӿڣҪûϢҪ```banner```ϢҪǩϢȵȡһСʵ£ + +``` +public AppHeadInfoResponse queryAppHeadInfo(AppInfoReq req) { + //ûϢ + UserInfoParam userInfoParam = buildUserParam(req); + UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam); + //bannerϢ + BannerParam bannerParam = buildBannerParam(req); + BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam); + //ǩϢ + LabelParam labelParam = buildLabelParam(req); + LabelDTO labelDTO = labelService.queryLabelInfo(labelParam); + //װ + return buildResponse(userInfoDTO,bannerDTO,labelDTO); +} +``` + +δʲô ʵһͦĴ룬ʵУѯûbannerǩϢ**Ǵе**ѯûϢ```200ms```ѯbannerϢ```100ms```ѯǩϢ```200ms```Ļʱ```500ms``` + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/35fa8e071a7048d5ae7d8e3e7f339532~tplv-k3u1fbpfcp-zoom-1.image) + +ʵΪŻܣǿ޸Ϊ**е**ķʽʱԽΪ```200ms```ͼʾ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e8559cfd7bb2449dbab91c0b38a3d78e~tplv-k3u1fbpfcp-zoom-1.image) + + +## 2. CompletionServiceʵֲе + +ӣ**ʵֲеأ** + +С˵ʹ```Future+Callable```ʵֶIJеáִ̳߳ʱֵ```Futureget()```ȡģǰһִбȽϺʱĻ```get```γŶӵȴ + +```CompletionService```ǶԶ```ExecutorService```˰װһ,һ߻ȡķֵ·ִֿ,֮䲻ụԻȡɵ + + +> ```CompletionService```ʵԭȽϼ򵥣ײͨFutureTask+УʵɵĻȻȡҲ˵ִнɵȺ˳ɿŻȡڲһȽȳУڱѾִɵFuture```CompletionService```polltakeɻȡһѾִɵFutureͨFutureӿʵ```get```ȡյĽ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/10e375f58f5d490193888b0c5375e0f0~tplv-k3u1fbpfcp-zoom-1.image) + + +£```CompletionService```ʵֲвѯAPPҳϢ˼£ + +1. ȰѲѯûϢ񣬷ŵ̳߳أ£ +``` +ExecutorService executor = Executors.newFixedThreadPool(10); +//ѯûϢ +CompletionService userDTOCompletionService = new ExecutorCompletionService(executor); +Callable userInfoDTOCallableTask = () -> { + UserInfoParam userInfoParam = buildUserParam(req); + return userService.queryUserInfo(userInfoParam); + }; +userDTOCompletionService.submit(userInfoDTOCallableTask); +``` + +2. Ѳѯ```banner```ϢҲŵ̳߳صĻֲ÷ˣΪͲһһ```UserInfoDTO```һ```BannerDTO```ʱDzǰѷΪObjectɣΪжǼ̳Objectģ£ + +``` +ExecutorService executor = Executors.newFixedThreadPool(10); +//ѯûϢ +CompletionService baseDTOCompletionService = new ExecutorCompletionService(executor); +Callable userInfoDTOCallableTask = () -> { + UserInfoParam userInfoParam = buildUserParam(req); + return userService.queryUserInfo(userInfoParam); +}; +//bannerϢ +Callable bannerDTOCallableTask = () -> { + BannerParam bannerParam = buildBannerParam(req); + return bannerService.queryBannerInfo(bannerParam); +}; + +//ύûϢ +baseDTOCompletionService.submit(userInfoDTOCallableTask); +//ύbannerϢ +baseDTOCompletionService.submit(bannerDTOCallableTask); +``` +3. и⣬ǻȡ**ֵʱ**Dz֪ĸ```Object```ûϢDTOĸ```BannerDTO```**ôأ**ʱǿڲչΪһBaseRspDTOٸͷObjectݵģȻBaseRspDTOиUserDTOBannerDTO**Ψһkey**£ + +``` +public class BaseRspDTO { + + //DTOصΨһǣUserInfoDTOBannerDTO + private String key; + //صdata + private T data; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } +} + +//вѯAppҳϢ +public AppHeadInfoResponse parallelQueryAppHeadPageInfo(AppInfoReq req) { + + long beginTime = System.currentTimeMillis(); + System.out.println("ʼвѯappҳϢʼʱ䣺" + beginTime); + + ExecutorService executor = Executors.newFixedThreadPool(10); + CompletionService> baseDTOCompletionService = new ExecutorCompletionService>(executor); + + //ѯûϢ + Callable> userInfoDTOCallableTask = () -> { + UserInfoParam userInfoParam = buildUserParam(req); + UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam); + BaseRspDTO userBaseRspDTO = new BaseRspDTO(); + userBaseRspDTO.setKey("userInfoDTO"); + userBaseRspDTO.setData(userInfoDTO); + return userBaseRspDTO; + }; + + //bannerϢѯ + Callable> bannerDTOCallableTask = () -> { + BannerParam bannerParam = buildBannerParam(req); + BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam); + BaseRspDTO bannerBaseRspDTO = new BaseRspDTO(); + bannerBaseRspDTO.setKey("bannerDTO"); + bannerBaseRspDTO.setData(bannerDTO); + return bannerBaseRspDTO; + }; + + //labelϢѯ + Callable> labelDTODTOCallableTask = () -> { + LabelParam labelParam = buildLabelParam(req); + LabelDTO labelDTO = labelService.queryLabelInfo(labelParam); + BaseRspDTO labelBaseRspDTO = new BaseRspDTO(); + labelBaseRspDTO.setKey("labelDTO"); + labelBaseRspDTO.setData(labelDTO); + return labelBaseRspDTO; + }; + + //ύûϢ + baseDTOCompletionService.submit(userInfoDTOCallableTask); + //ύbannerϢ + baseDTOCompletionService.submit(bannerDTOCallableTask); + //ύlabelϢ + baseDTOCompletionService.submit(labelDTODTOCallableTask); + + UserInfoDTO userInfoDTO = null; + BannerDTO bannerDTO = null; + LabelDTO labelDTO = null; + + try { + //Ϊύ3Իȡ3 + for (int i = 0; i < 3; i++) { + Future> baseRspDTOFuture = baseDTOCompletionService.poll(1, TimeUnit.SECONDS); + BaseRspDTO baseRspDTO = baseRspDTOFuture.get(); + if ("userInfoDTO".equals(baseRspDTO.getKey())) { + userInfoDTO = (UserInfoDTO) baseRspDTO.getData(); + } else if ("bannerDTO".equals(baseRspDTO.getKey())) { + bannerDTO = (BannerDTO) baseRspDTO.getData(); + } else if ("labelDTO".equals(baseRspDTO.getKey())) { + labelDTO = (LabelDTO) baseRspDTO.getData(); + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + System.out.println("вѯappҳϢ,ܺʱ" + (System.currentTimeMillis() - beginTime)); + return buildResponse(userInfoDTO, bannerDTO, labelDTO); +} +``` + +Ϊֹһ```CompletionService```ʵֲеõѾʵDzǺܿģ + +## 3. ȡͨõIJе÷ + +ǻع۲µ2СڣѯappҳϢdemo```CompletionService```ʵ˲еáûʲô뷨أ,ҵ񳡾ҲͨеŻDzҲøһƵ2СڵĴ롣ԣ**DzǿԳȡһͨõIJзñijҲã԰ɣǺ˼ά** + +ڵ2СڵĴ룬γȡͨòе÷ء + +ȣͨõIJе÷**ܸҵصԹҹ**԰ɣԷӦЩأ + +> Σ```Callable```԰ɡΪУ϶ǶCallableġԣӦһ```Callable```顣Ȼ󣬻APPҳѯӣ```Callable```ô```BaseRspDTO```ͣ԰ɣξ```List>> list``` + +Dzеõijأ ж```Callable```DzǵжӦķأˣijο```List>```dzȡͨòеģ壬Ϳдɽϣ + +``` + public List> executeTask(List>> taskList) { + + List> resultList = new ArrayList<>(); + //У + if (taskList == null || taskList.size() == 0) { + return resultList; + } + + ExecutorService executor = Executors.newFixedThreadPool(10); + CompletionService> baseDTOCompletionService = new ExecutorCompletionService>(executor); + //ύ + for (Callable> task : taskList) { + baseDTOCompletionService.submit(task); + } + + try { + //ȡ + for (int i = 0; i < taskList.size(); i++) { + Future> baseRspDTOFuture = baseDTOCompletionService.poll(2, TimeUnit.SECONDS); + resultList.add(baseRspDTOFuture.get()); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + return resultList; + } +``` +ȻdzȡͨõIJе÷ϵķǷ**ЩطҪĽ**أ + +- һŻĵط```executor̳߳```Щҵ񳡾```A̳߳```Щҵ```B̳߳```ôͲͨ԰ɡǿ԰̳߳Բʵṩ÷Լơ +- ڶŻĵط```CompletionService``````poll```ȡʱʱʱдġΪͬҵ񳡾ʱʱܲһԣʱʱҲǿԲʽų÷Լơ + +ٴŻһͨõIJеģ壬£ +``` +public List> executeTask(List>> taskList, long timeOut, ExecutorService executor) { + + List> resultList = new ArrayList<>(); + //У + if (taskList == null || taskList.size() == 0) { + return resultList; + } + if (executor == null) { + return resultList; + } + if (timeOut <= 0) { + return resultList; + } + + //ύ + CompletionService> baseDTOCompletionService = new ExecutorCompletionService>(executor); + for (Callable> task : taskList) { + baseDTOCompletionService.submit(task); + } + + try { + //ȡ + for (int i = 0; i < taskList.size(); i++) { + Future> baseRspDTOFuture = baseDTOCompletionService.poll(timeOut, TimeUnit.SECONDS); + resultList.add(baseRspDTOFuture.get()); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + return resultList; +} +``` + +ԺijҲҪõеõĻֱӵɣDzеССijɾ͸ + +## 4. ˼ԼģʽӦ + +ǰѳȡǸõIJе÷Ӧõ```AppҳϢѯ```ӣ£ + +``` +public AppHeadInfoResponse parallelQueryAppHeadPageInfo1(AppInfoReq req) { + + long beginTime = System.currentTimeMillis(); + System.out.println("ʼвѯappҳϢʼʱ䣺" + beginTime); + //ûϢѯ + Callable> userInfoDTOCallableTask = () -> { + UserInfoParam userInfoParam = buildUserParam(req); + UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam); + BaseRspDTO userBaseRspDTO = new BaseRspDTO(); + userBaseRspDTO.setKey("userInfoDTO"); + userBaseRspDTO.setData(userInfoDTO); + return userBaseRspDTO; + }; + + //bannerϢѯ + Callable> bannerDTOCallableTask = () -> { + BannerParam bannerParam = buildBannerParam(req); + BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam); + BaseRspDTO bannerBaseRspDTO = new BaseRspDTO(); + bannerBaseRspDTO.setKey("bannerDTO"); + bannerBaseRspDTO.setData(bannerDTO); + return bannerBaseRspDTO; + }; + + //labelϢѯ + Callable> labelDTODTOCallableTask = () -> { + LabelParam labelParam = buildLabelParam(req); + LabelDTO labelDTO = labelService.queryLabelInfo(labelParam); + BaseRspDTO labelBaseRspDTO = new BaseRspDTO(); + labelBaseRspDTO.setKey("labelDTO"); + labelBaseRspDTO.setData(labelDTO); + return labelBaseRspDTO; + }; + + List>> taskList = new ArrayList<>(); + taskList.add(userInfoDTOCallableTask); + taskList.add(bannerDTOCallableTask); + taskList.add(labelDTODTOCallableTask); + ExecutorService executor = Executors.newFixedThreadPool(10); + List> resultList = parallelInvokeCommonService.executeTask(taskList, 3, executor); + if (resultList == null || resultList.size() == 0) { + return new AppHeadInfoResponse(); + } + + UserInfoDTO userInfoDTO = null; + BannerDTO bannerDTO = null; + LabelDTO labelDTO = null; + + // + for (int i = 0; i < resultList.size(); i++) { + BaseRspDTO baseRspDTO = resultList.get(i); + if ("userInfoDTO".equals(baseRspDTO.getKey())) { + userInfoDTO = (UserInfoDTO) baseRspDTO.getData(); + } else if ("bannerDTO".equals(baseRspDTO.getKey())) { + bannerDTO = (BannerDTO) baseRspDTO.getData(); + } else if ("labelDTO".equals(baseRspDTO.getKey())) { + labelDTO = (LabelDTO) baseRspDTO.getData(); + } + } + + System.out.println("вѯappҳϢ,ܺʱ" + (System.currentTimeMillis() - beginTime)); + return buildResponse(userInfoDTO, bannerDTO, labelDTO); + } + +``` + +ϴ룬СǣǷŻ뷨أ ⼸```Callable```ѯDzҲԳȡһ£ôӼࡣ + +> ˵ֱӽһ```BaseTaskCommand```࣬ʵ```Callable```ӿڣѲѯûϢѯbannerϢlabelǩϢIJѯŽȥ + +£ + +``` +public class BaseTaskCommand implements Callable> { + + private String key; + private AppInfoReq req; + private IUserService userService; + private IBannerService bannerService; + private ILabelService labelService; + + public BaseTaskCommand(String key, AppInfoReq req, IUserService userService, IBannerService bannerService, ILabelService labelService) { + this.key = key; + this.req = req; + this.userService = userService; + this.bannerService = bannerService; + this.labelService = labelService; + } + + @Override + public BaseRspDTO call() throws Exception { + + if ("userInfoDTO".equals(key)) { + UserInfoParam userInfoParam = buildUserParam(req); + UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam); + BaseRspDTO userBaseRspDTO = new BaseRspDTO(); + userBaseRspDTO.setKey("userInfoDTO"); + userBaseRspDTO.setData(userInfoDTO); + return userBaseRspDTO; + } else if ("bannerDTO".equals(key)) { + BannerParam bannerParam = buildBannerParam(req); + BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam); + BaseRspDTO bannerBaseRspDTO = new BaseRspDTO(); + bannerBaseRspDTO.setKey("bannerDTO"); + bannerBaseRspDTO.setData(bannerDTO); + return bannerBaseRspDTO; + } else if ("labelDTO".equals(key)) { + LabelParam labelParam = buildLabelParam(req); + LabelDTO labelDTO = labelService.queryLabelInfo(labelParam); + BaseRspDTO labelBaseRspDTO = new BaseRspDTO(); + labelBaseRspDTO.setKey("labelDTO"); + labelBaseRspDTO.setData(labelDTO); + return labelBaseRspDTO; + } + + return null; + } + + + private UserInfoParam buildUserParam(AppInfoReq req) { + return new UserInfoParam(); + } + + private BannerParam buildBannerParam(AppInfoReq req) { + return new BannerParam(); + } + + private LabelParam buildLabelParam(AppInfoReq req) { + return new LabelParam(); + } +} +``` +룬캯**Ƚ϶IJ**```call()```Уж```if...else...```,һ֧**ѯϢ**ֵ```call```޸ˣ**BaseTaskCommandĹҲҪ޸** + +> Ƿӡ󣬶гֶif...else...ʱǾͿԿʹ**ģʽ+ģʽ**Ż + +ʵ࣬£ + +``` + +public interface IBaseTask { + + //ÿkey + String getTaskType(); + + BaseRspDTO execute(AppInfoReq req); + +} + +//ûϢ +@Service +public class UserInfoStrategyTask implements IBaseTask { + + @Autowired + private IUserService userService; + + @Override + public String getTaskType() { + return "userInfoDTO"; + } + + @Override + public BaseRspDTO execute(AppInfoReq req) { + UserInfoParam userInfoParam = userService.buildUserParam(req); + UserInfoDTO userInfoDTO = userService.queryUserInfo(userInfoParam); + BaseRspDTO userBaseRspDTO = new BaseRspDTO(); + userBaseRspDTO.setKey(getTaskType()); + userBaseRspDTO.setData(userBaseRspDTO); + return userBaseRspDTO; + } +} + +/** + * bannerϢʵ + **/ +@Service +public class BannerStrategyTask implements IBaseTask { + + @Autowired + private IBannerService bannerService; + + @Override + public String getTaskType() { + return "bannerDTO"; + } + + @Override + public BaseRspDTO execute(AppInfoReq req) { + BannerParam bannerParam = bannerService.buildBannerParam(req); + BannerDTO bannerDTO = bannerService.queryBannerInfo(bannerParam); + BaseRspDTO bannerBaseRspDTO = new BaseRspDTO(); + bannerBaseRspDTO.setKey(getTaskType()); + bannerBaseRspDTO.setData(bannerDTO); + return bannerBaseRspDTO; + } +} + +... +``` +Ȼ⼸ʵ࣬ô```spring```أ ǿʵ```ApplicationContextAware```ӿڣѲԵʵע뵽һmapȻ󷽲ͬIJ(DTOͣȥʵֲͬIJáʵڹģʽ˼롣£ + +``` +/** + * Թ + **/ +@Component +public class TaskStrategyFactory implements ApplicationContextAware { + + private Map map = new ConcurrentHashMap<>(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Map tempMap = applicationContext.getBeansOfType(IBaseTask.class); + tempMap.values().forEach(iBaseTask -> { + map.put(iBaseTask.getTaskType(), iBaseTask); + }); + } + + public BaseRspDTO executeTask(String key, AppInfoReq req) { + IBaseTask baseTask = map.get(key); + if (baseTask != null) { + System.out.println("ʵִ"); + return baseTask.execute(req); + } + return null; + } +} +``` + +˲Թ```TaskStrategyFactory```ٻŻ```BaseTaskCommand```Ĵ롣ĹѾҪ```IUserService userService, IBannerService bannerService, ILabelService labelService```ֻҪԹ```TaskStrategyFactory```ɡͬʱҲҪ```if...else...```жˣòԹ```TaskStrategyFactory```漴ɡŻĴ£ + +``` +public class BaseTaskCommand implements Callable> { + + private String key; + private AppInfoReq req; + private TaskStrategyFactory taskStrategyFactory; + + public BaseTaskCommand(String key, AppInfoReq req, TaskStrategyFactory taskStrategyFactory) { + this.key = key; + this.req = req; + this.taskStrategyFactory = taskStrategyFactory; + } + + @Override + public BaseRspDTO call() throws Exception { + return taskStrategyFactory.executeTask(key, req); + } +} +``` + +```appҳϢ```ѯͿŻ£ + +``` +public AppHeadInfoResponse parallelQueryAppHeadPageInfo2(AppInfoReq req) { + long beginTime = System.currentTimeMillis(); + System.out.println("ʼвѯappҳϢհ汾ʼʱ䣺" + beginTime); + List>> taskList = new ArrayList<>(); + //ûϢѯ + taskList.add(new BaseTaskCommand("userInfoDTO", req, taskStrategyFactory)); + //bannerѯ + taskList.add(new BaseTaskCommand("bannerDTO", req, taskStrategyFactory)); + //ǩѯ + taskList.add(new BaseTaskCommand("labelDTO", req, taskStrategyFactory)); + + ExecutorService executor = Executors.newFixedThreadPool(10); + List> resultList = parallelInvokeCommonService.executeTask(taskList, 3, executor); + + if (resultList == null || resultList.size() == 0) { + return new AppHeadInfoResponse(); + } + + UserInfoDTO userInfoDTO = null; + BannerDTO bannerDTO = null; + LabelDTO labelDTO = null; + + for (BaseRspDTO baseRspDTO : resultList) { + if ("userInfoDTO".equals(baseRspDTO.getKey())) { + userInfoDTO = (UserInfoDTO) baseRspDTO.getData(); + } else if ("bannerDTO".equals(baseRspDTO.getKey())) { + bannerDTO = (BannerDTO) baseRspDTO.getData(); + } else if ("labelDTO".equals(baseRspDTO.getKey())) { + labelDTO = (LabelDTO) baseRspDTO.getData(); + } + } + + System.out.println("вѯappҳϢհ汾,ܺʱ" + (System.currentTimeMillis() - beginTime)); + return buildResponse(userInfoDTO, bannerDTO, labelDTO); + } +``` + + +## 5. ˼ܽ + +ϴŻѾܼǻûбŻ˼·ء +> ʵеģ磬Ψһǵ```key```Ϊö٣дַ```"userInfoDTO""bannerDTO""labelDTO"```У```CompletionService```ЩСϲ```CompletableFuture```ʵвеá + +ĴѧЩ֪ʶأ +1. ŻӿܣijЩ£ʹòеô洮С +2. ʵֲеأ ʹ```CompletionService``` +3. ѧĺ˼άǣ ճУҪѧȡͨõķ߹ߡ +4. ģʽ͹ģʽӦ + +ĵĻģʽ黹ǺϸȻһƪҽдϣȡģʽĹȻҪĵĻԹעҵĹںţ**ݵСк**ҵϵʽ + + + + + diff --git "a/\345\244\247\345\216\202\351\235\242\350\257\225\347\234\237\351\242\230/README.MD" "b/\345\244\247\345\216\202\351\235\242\350\257\225\347\234\237\351\242\230/README.MD" new file mode 100644 index 0000000..04f4506 --- /dev/null +++ "b/\345\244\247\345\216\202\351\235\242\350\257\225\347\234\237\351\242\230/README.MD" @@ -0,0 +1,13 @@ +## 1. 面试真题 + +关注公众号:捡田螺的小男孩 +​ +- [oppo后端16连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498750&idx=1&sn=19fe8b4fff28fe81db14e733053bbc74&chksm=cf2224d7f855adc1d0984980a4e3de31fe33329164a472ca8d8255a8a80b69b2e23850811323&token=2001057130&lang=zh_CN#rd) +- [小厂后端十连问(附答案)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498316&idx=1&sn=7749b78293b7b2af51eda99844e08a56&chksm=cf222565f855ac7324232e2af459f8b6e6eb5fd5b272c2b29bda08cc579421b6704a0de94b2e&token=2001057130&lang=zh_CN#rd) +- [腾讯云后端15连问!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498171&idx=1&sn=f5a7ec25a569822be0f73fbcd413e8ba&chksm=cf222692f855af84fba419166fcd4235c0e78af3a2e1ec4c723a4efb1bd1ad6f8a5b9404c599&token=2001057130&lang=zh_CN#rd) +- [社招后端21连问(三年工作经验一面)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498084&idx=1&sn=96c8148cfeeeb16668ed9e03fa9131cc&chksm=cf22264df855af5b6e81b93738cca28989226a53ec702fcfaa0cc5004dded4208c5ee5ea844a&token=2001057130&lang=zh_CN#rd) +- [一份热乎乎的字节面试真题](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497742&idx=1&sn=18765e7356f446a7f2521f45b467d5d3&chksm=cf222727f855ae31dd2029e3219814211336c41d9228d271a583d3691ddadca586529aca9302&token=2001057130&lang=zh_CN#rd) +- [面试必备:虾皮服务端15连问](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497256&idx=1&sn=3b799c2d514aa25e85a6faa60d639a0b&chksm=cf222901f855a017b73356b99b830b8800a7a9172fab891c5759d8dd69a270872ea9480c0b7c&token=2001057130&lang=zh_CN#rd) +- [宇宙条一面:十道经典面试题解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495342&idx=1&sn=54e1c0c16a6467001524c34818025331&chksm=cf223187f855b89140db5ca429e6efc19d0111abf7f36b78a0ecd73b00fded1ff1e7ba32a6f1&token=2001057130&lang=zh_CN#rd) +- [蚂蚁金服一面:十道经典面试题解析](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247493270&idx=1&sn=1c78a81d6e1bd0f0fd947fe8c3a33e32&chksm=cf2239bff855b0a9627855f20a17799e0506eb7548a409bfa0ee0450328d7519ec70f7b962cc&token=2001057130&lang=zh_CN#rd) +- [田螺精品面试PDF发布](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499943&idx=1&sn=fe869c0a97a306e42830336fe74e17a6&chksm=cf221f8ef8559698781709bfbccbb85087286e48434905fb18bec3a3ec0af7329c2a1632c230&token=1990771297&lang=zh_CN#rd) \ No newline at end of file diff --git "a/\345\244\247\345\216\202\351\235\242\350\257\225\347\234\237\351\242\230/\351\230\277\351\207\214\344\270\200\351\235\242\357\274\214\347\273\231\344\272\206\345\207\240\346\235\241SQL\357\274\214\351\227\256\351\234\200\350\246\201\346\211\247\350\241\214\345\207\240\346\254\241\346\240\221\346\220\234\347\264\242\346\223\215\344\275\234\357\274\237.md" "b/\345\244\247\345\216\202\351\235\242\350\257\225\347\234\237\351\242\230/\351\230\277\351\207\214\344\270\200\351\235\242\357\274\214\347\273\231\344\272\206\345\207\240\346\235\241SQL\357\274\214\351\227\256\351\234\200\350\246\201\346\211\247\350\241\214\345\207\240\346\254\241\346\240\221\346\220\234\347\264\242\346\223\215\344\275\234\357\274\237.md" new file mode 100644 index 0000000..a695048 --- /dev/null +++ "b/\345\244\247\345\216\202\351\235\242\350\257\225\347\234\237\351\242\230/\351\230\277\351\207\214\344\270\200\351\235\242\357\274\214\347\273\231\344\272\206\345\207\240\346\235\241SQL\357\274\214\351\227\256\351\234\200\350\246\201\346\211\247\350\241\214\345\207\240\346\254\241\346\240\221\346\220\234\347\264\242\346\223\215\344\275\234\357\274\237.md" @@ -0,0 +1,246 @@ +### ǰ + +λȥԣ˵Թٸ˼ѯSQL:Ҫִмѵʱеµģ侲˼ŷ־ǿļ֪ʶ~~ Ƿ־Ÿ֪ʶ㣬һ̽һ¡вȷĻӭָһѧϰ~ + +- ںţ**ݵСк** +- githubַлÿstar +> https://github.com/whx123/JavaHome + +- Թٿ֮ʲô +- Թٿ֮ +- Թٿ֮ΪʲôѡB+Ϊṹ +- Թٿ֮һ +- Թٿ֮ +- Թٿ֮ʧЧ +- Թٿ֮ǰ׺ +- Թٿ֮ +- Թٿ֮ + +### һԹٿ֮ʲô + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/21eed26e34534b51aadf28d5defaed7e~tplv-k3u1fbpfcp-watermark.image) + +- һݿѯЧʵݽṹԱһֵĿ¼԰ҵӦļ¼ +- һ洢ڴ̵ļУռռġ +- νˮۣҲܸۡʵ߲ѯЧʣӰݿIJ͸¹ܡ + +### Щ + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf6725e1a8cb44b496cca8fa45e15c2f~tplv-k3u1fbpfcp-watermark.image) + +#### ݽṹά + +- B+ݴ洢Ҷӽڵ㣬ӶΪO(logn)ʺϷΧѯ +- ϣ: ʺϵֵѯЧʸߣһελ +- ȫMyISAMInnoDBж֧ʹȫһıchar,text,varcharϴ +- R-Tree: GISʹSPATIAL + +#### 洢ά + +- ۼۼҶӽڵ洢DZеݡ +- ǾۼǾۼԷҶӽڵ洢С + +#### ߼ά + +- һΨһпֵ +- ͨMySQLлֵͣظֵ +- ֶδʹʱѭǰ׺ԭ +- ΨһеֵΨһģΪֵ +- ռMySQL5.7ֿ֧֮ռڿռⷽѭOpenGISģ͹ + +### Թٿ֮ΪʲôѡB+Ϊṹ + +ԴӼάȥ⣬ѯǷ񹻿죬ЧǷȶ洢ݶ٣ԼҴ̴ȵȡΪʲôǹϣṹΪʲôǶΪʲôƽΪʲôBƫƫB+أ + +дҵSQLѯʱ£ǷΧѯģһSQL +``` +select * from employee where age between 18 and 28; +``` +#### Ϊʲôʹùϣṹ +֪ϣṹk-vṹҲǣkeyvalueһһϵ**ֵѯ**ԣǷΧѯΪŶ + +#### Ϊʲôʹöأ + +Ȼ¶֪ʶ~ ν**ص£** +- ÿֱΪ +- ӽڵֵСڵǰڵֵǰڵֵСӽڵֵ +- ˵ĽڵΪڵ㣬ûӽڵĽڵֵΪҶӽڵ㡣 + +ԺУ׾͸ֳֶṹͼ +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8d4c349a7bda4b3db1791f9cff9c093e~tplv-k3u1fbpfcp-watermark.image) + +أЩŶ +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/97b71a48d43545a0bc20a55adf2be207~tplv-k3u1fbpfcp-watermark.image) + +⻯Ϊһ൱ȫɨ衣ôҪѽˣһʺΪṹ + +#### Ϊʲôʹƽأ + +ƽص㣺ҲһŶκνڵ߶Ϊ1ԾͲ⻯һ + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74afa9b54e7f4c0996ff83e66016c09a~tplv-k3u1fbpfcp-watermark.image) + +أ +- ƽ߸ǣҪάƽ⣬ά۴ +- Ļĸ߶ȻܸߡΪǴڴ̵ģΪṹÿδӴ̶ȡһڵ㣬IOĴͶ + + +#### ΪʲôʹBأ + + +Ļƽĸ߶ȻܸߣIOΪʲôѡͬ**߶ȸB**أ + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/acc8f2e2cfb54092b4fb131e500334a3~tplv-k3u1fbpfcp-watermark.image) + +BƽͿԴ洢ݣ߶ȸ͡ΪѡB+أΪB+B棺 +- B+ҶӽڵDz洢ݵģ洢ֵBڵв洢ֵҲ洢ݡinnodbҳĬϴС16KB洢ݣôͻ洢ļֵӦĽڵӽڵͻͻ֣һDzݽд̵IOлٴμ٣ݲѯЧҲ졣 +- B+ݾ洢Ҷӽڵ㣬ǰ˳еģŵġôB+ʹ÷ΧңңԼȥزұ쳣򵥡 + +### ġԹٿ֮һB+ + +**Թ٣** ±ṹ⼸ +``` +CREATE TABLE `employee` ( + `id` int(11) NOT NULL, + `name` varchar(255) DEFAULT NULL, + `age` int(11) DEFAULT NULL, + `date` datetime DEFAULT NULL, + `sex` int(1) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_age` (`age`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +insert into employee values(100,'С',43,'2021-01-20','0'); +insert into employee values(200,'',48,'2021-01-21','0'); +insert into employee values(300,'',36,'2020-01-21','1'); +insert into employee values(400,'',32,'2020-01-21','0'); +insert into employee values(500,'Ѹ',37,'2020-01-21','1'); +insert into employee values(600,'С',49,'2021-01-21','0'); +insert into employee values(700,'С',28,'2021-01-21','1'); +``` + +**Թ٣** ִµIJѯSQLҪִмεԻ¶Ӧṹͼ~ +``` +select * from Temployee where age=32; +``` + +**** ʵԹپǿѡǷϤB+ṹͼϻش~ + +- Ȼ`idx_age`ṹͼ£ + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f88457b43e354ca18795fa0033ad075f~tplv-k3u1fbpfcp-watermark.image) + +- ٻidȻṹͼ£ + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/963a8dacb39345008c93b1d0ea079eec~tplv-k3u1fbpfcp-watermark.image) + + +ˣ SQL ѯִд̾ǽϣ +- 1. `idx_age`̿1صڴ棬32<37,·֧Ѱַ̿2 +- 2. ̿2صڴУڴҵage=32ļ¼ȡid = 400. +- 3. õid=400󣬻صid +- 4. `id`̿1ڴ棬ڴҵ400B+ҶӽڵDzݵġ400ҷ֧Ѱַ̿3. +- 5. ̿3ڴ棬ڴҵid=400ļ¼õR4һеݣõģ󹦸ɡ + +ˣSQLѯִ˼DzһȻѽ**ر**`idx_age`ҵ`id`󣬻صidĹ,ͳΪر +> ʲôǻرõٻصѯḶ́ͽ**ر** + +### 塢Թٿ֮ + +**Թ٣** `select *`, ʹ`select id,age`ϵĿִ˼أ + +**** ⣬Ҫѡ˵ĸ֪ʶ㡣ص`idx_age`ԷֲѯѡidageҶӽڵˡˣֱṩѯͲҪٻر~ + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/629b6cfd08614adcbb20154707c0c8e0~tplv-k3u1fbpfcp-watermark.image) + +> ڲѯ棬Ҫرȥ飬ֱӴоȡҪĽ仰˵SQLõݣ˲ѯУϸˡ + +ԣϸ⣬ʡȥ˻ر + +### Թٿ֮ʧЧ + +**Թ٣** ڸ`name`ֶμͨȻølikeģǻִжٴβѯأSQL£ +``` +select * from employee where name like '%%'; +``` +**** ֪ʶǣlikeǷᵼ²ȸSQLexplainִмƻɡʵlike ģᵼ²ģ£ + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b01bb44ff5744729e55939cc88bd424~tplv-k3u1fbpfcp-watermark.image) + +ˣSQLȫɨ~ճУ⼸ɧܻᵼʧЧ£ +- ѯorܵʧЧ +- ֶַwhereʱһʧЧ +- likeͨܵʧЧ +- ѯʱвеĵһУʧЧ +- ʹmysqlúʧЧ +- 㣨磬+-*/ʧЧ +- ֶʹã= < >not inʱܻᵼʧЧ +- ֶʹis null is not nullܵʧЧ +- ӲѯӲѯѯֶαʽһܵʧЧ +- mysqlʹȫɨҪʹ,ʹ + +### ߡԹٿ֮ǰ׺ԭ + +**Թ٣** ڸname,ageֶμSQLִжٴأȻ +``` +select * from employee where name like 'С%' order by age desc; +``` +**** ǰ׺ԭԼlikeǷ֪ʶ㡣ʾͼ£ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/29df075df94549b693a6a0ee06730666~tplv-k3u1fbpfcp-watermark.image) + +ȰnameСnameͬageСԹҪֵһǡСˣSQLlike 'С%'ǿ```idx_name_age```ġ + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58b49f88f75f48be873deb395431c2fa~tplv-k3u1fbpfcp-watermark.image) + +òѯidx_name_ageҵһСֵҵ```ССסСࡢ```ֱõId=```600100700```ȻαȥҶӦļ¼ ǰ׺```С```ַMַʵϣ +- ǰ׺NֶΡa,b,c൱ڽˣaa,b,(a,b,c) +- ǰ׺ҲַMַ + +### ˡԹٿ֮ + +**Թ٣** ǻǾ idx_name_ageSQLִмأ + +``` +select * from employee where name like 'С%' and age=28 and sex='0'; +``` + +**** Ƶ֪ʶ㣬**Mysql5.6֮ǰ**idx_name_ageҳֵһǡСˣõǵidȻرҳУȥԱԱֶΡͼ + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c5729b1a84fd4ee9b872717903ca6f75~tplv-k3u1fbpfcp-watermark.image) + +Щѿܾ֣name,age)ΪʲôѡСֺ󣬲˳㿴ageٻرأǸЧѽMySQL 5.6 **Ż**Уаֶжϣֱӹ˵ļ¼ٻر + + +ˣMySQL5.6汾֮ѡСֺ˳age=28,Ծֻһλر + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/168aecb4709d4c30bc93c489ad76d712~tplv-k3u1fbpfcp-watermark.image) + +### š Թٿ֮ + +**Թ٣** һűǧ򼶱ϵģôűҪôأ + +**** Ҫ֪һ㣬ʱǻԱġпܳ¹ʵġԲο· + +- 1.ȴһŸԭAݽṹͬ±B +- 2.±BҪϵ +- 3.ԭAݵ±B +- 4.rename±BΪԭıAԭAı + +### ܽϰ + +Ҫ9ؼ֪ʶ㣬ϣԴаأҳһйҵ񿪷ļSQL´ôشģȤϵҹ~Ŀ£ +``` + +select * from A where type ='1' and status ='s' order by create_time desc; +``` +type9ֶͣԻԣstatusֶȲߣ3ͣôμأ +- Ǹtypeӵ +- ǣtypestatuscreate_time +- ǣtypecreate_timeأ + +### οл +- [ MySQLЩ ?](https://segmentfault.com/q/1010000003832312) +- [](https://zhuanlan.zhihu.com/p/151460679) +- [MySQLʵս45](https://time.geekbang.org/column/article/69636) + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/Java\346\227\245\345\270\270\345\274\200\345\217\221\347\232\20421\344\270\252\345\235\221.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/Java\346\227\245\345\270\270\345\274\200\345\217\221\347\232\20421\344\270\252\345\235\221.md" new file mode 100644 index 0000000..8155c5c --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/Java\346\227\245\345\270\270\345\274\200\345\217\221\347\232\20421\344\270\252\345\235\221.md" @@ -0,0 +1,860 @@ +### 前言 +最近看了极客时间的《Java业务开发常见错误100例》,再结合平时踩的一些代码坑,写写总结,希望对大家有帮助,感谢阅读~ + +### 1. 六类典型空指针问题 + +- 包装类型的空指针问题 +- 级联调用的空指针问题 +- Equals方法左边的空指针问题 +- ConcurrentHashMap 这样的容器不支持 Key 和 Value 为 null。 +- 集合,数组直接获取元素 +- 对象直接获取属性 + +#### 1.1包装类型的空指针问题 + +``` +public class NullPointTest { + + public static void main(String[] args) throws InterruptedException { + System.out.println(testInteger(null)); + } + + private static Integer testInteger(Integer i) { + return i + 1; //包装类型,传参可能为null,直接计算,则会导致空指针问题 + } +} +``` + +#### 1.2 级联调用的空指针问题 + +``` +public class NullPointTest { + public static void main(String[] args) { + //fruitService.getAppleService() 可能为空,会导致空指针问题 + fruitService.getAppleService().getWeight().equals("OK"); + } +} +``` +#### 1.3 Equals方法左边的空指针问题 +``` +public class NullPointTest { + public static void main(String[] args) { + String s = null; + if (s.equals("666")) { //s可能为空,会导致空指针问题 + System.out.println("公众号:捡田螺的小男孩,666"); + } + } +} +``` + +#### 1.4 ConcurrentHashMap 这样的容器不支持 Key,Value 为 null。 +``` +public class NullPointTest { + public static void main(String[] args) { + Map map = new ConcurrentHashMap<>(); + String key = null; + String value = null; + map.put(key, value); + } +} +``` + +#### 1.5 集合,数组直接获取元素 +``` +public class NullPointTest { + public static void main(String[] args) { + int [] array=null; + List list = null; + System.out.println(array[0]); //空指针异常 + System.out.println(list.get(0)); //空指针一场 + } +} +``` + +#### 1.6 对象直接获取属性 +``` +public class NullPointTest { + public static void main(String[] args) { + User user=null; + System.out.println(user.getAge()); //空指针异常 + } +} +``` + +### 2. 日期YYYY格式设置的坑 + +日常开发,经常需要对日期格式化,但是呢,年份设置为YYYY大写的时候,是有坑的哦。 + +反例: +``` +Calendar calendar = Calendar.getInstance(); +calendar.set(2019, Calendar.DECEMBER, 31); + +Date testDate = calendar.getTime(); + +SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd"); +System.out.println("2019-12-31 转 YYYY-MM-dd 格式后 " + dtf.format(testDate)); + +``` +运行结果: +``` +2019-12-31 转 YYYY-MM-dd 格式后 2020-12-31 +``` + +**解析:** + +为什么明明是2019年12月31号,就转了一下格式,就变成了2020年12月31号了?因为YYYY是基于周来计算年的,它指向当天所在周属于的年份,一周从周日开始算起,周六结束,只要本周跨年,那么这一周就算下一年的了。正确姿势是使用yyyy格式。 + +![](https://imgkr2.cn-bj.ufileos.com/f9d23f03-215c-4d30-a19d-08bd5feb1c12.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=HuC%252F%252BLpyl4nGfl%252Bg%252BlAsxAfWvwM%253D&Expires=1609139925) + + +正例: +``` +Calendar calendar = Calendar.getInstance(); +calendar.set(2019, Calendar.DECEMBER, 31); + +Date testDate = calendar.getTime(); + +SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd"); +System.out.println("2019-12-31 转 yyyy-MM-dd 格式后 " + dtf.format(testDate)); + +``` + +### 3.金额数值计算精度的坑 + +看下这个浮点数计算的例子吧: +``` +public class DoubleTest { + public static void main(String[] args) { + System.out.println(0.1+0.2); + System.out.println(1.0-0.8); + System.out.println(4.015*100); + System.out.println(123.3/100); + + double amount1 = 3.15; + double amount2 = 2.10; + if (amount1 - amount2 == 1.05){ + System.out.println("OK"); + } + } +} +``` +运行结果: +``` +0.30000000000000004 +0.19999999999999996 +401.49999999999994 +1.2329999999999999 +``` + +可以发现,结算结果跟我们预期不一致,其实是因为计算机是以二进制存储数值的,对于浮点数也是。对于计算机而言,0.1无法精确表达,这就是为什么浮点数会导致精确度缺失的。因此,**金额计算,一般都是用BigDecimal 类型** + +对于以上例子,我们改为BigDecimal,再看看运行效果: + +``` +System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2))); +System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8))); +System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100))); +System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100))); +``` + +运行结果: +``` +0.3000000000000000166533453693773481063544750213623046875 +0.1999999999999999555910790149937383830547332763671875 +401.49999999999996802557689079549163579940795898437500 +1.232999999999999971578290569595992565155029296875 +``` + +发现结果还是不对,**其实**,使用 BigDecimal 表示和计算浮点数,必须使用**字符串的构造方法**来初始化 BigDecimal,正例如下: +``` +public class DoubleTest { + public static void main(String[] args) { + System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2"))); + System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8"))); + System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100"))); + System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100"))); + } +} +``` +在进行金额计算,使用BigDecimal的时候,我们还需要**注意BigDecimal的几位小数点,还有它的八种舍入模式哈**。 + + +### 4. FileReader默认编码导致乱码问题 + +看下这个例子: +``` +public class FileReaderTest { + public static void main(String[] args) throws IOException { + + Files.deleteIfExists(Paths.get("jay.txt")); + Files.write(Paths.get("jay.txt"), "你好,捡田螺的小男孩".getBytes(Charset.forName("GBK"))); + System.out.println("系统默认编码:"+Charset.defaultCharset()); + + char[] chars = new char[10]; + String content = ""; + try (FileReader fileReader = new FileReader("jay.txt")) { + int count; + while ((count = fileReader.read(chars)) != -1) { + content += new String(chars, 0, count); + } + } + System.out.println(content); + } +} +``` +运行结果: +``` +系统默认编码:UTF-8 +���,�����ݵ�С�к� +``` +从运行结果,可以知道,系统默认编码是utf8,demo中读取出来,出现乱码了。为什么呢? +> FileReader 是以当**前机器的默认字符集**来读取文件的,如果希望指定字符集的话,需要直接使用 InputStreamReader 和 FileInputStream。 + +正例如下: +``` +public class FileReaderTest { + public static void main(String[] args) throws IOException { + + Files.deleteIfExists(Paths.get("jay.txt")); + Files.write(Paths.get("jay.txt"), "你好,捡田螺的小男孩".getBytes(Charset.forName("GBK"))); + System.out.println("系统默认编码:"+Charset.defaultCharset()); + + char[] chars = new char[10]; + String content = ""; + try (FileInputStream fileInputStream = new FileInputStream("jay.txt"); + InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, Charset.forName("GBK"))) { + int count; + while ((count = inputStreamReader.read(chars)) != -1) { + content += new String(chars, 0, count); + } + } + System.out.println(content); + } +} +``` + +### 5. Integer缓存的坑 + +``` +public class IntegerTest { + + public static void main(String[] args) { + Integer a = 127; + Integer b = 127; + System.out.println("a==b:"+ (a == b)); + + Integer c = 128; + Integer d = 128; + System.out.println("c==d:"+ (c == d)); + } +} +``` +运行结果: +``` +a==b:true +c==d:false +``` + +为什么Integer值如果是128就不相等了呢?**编译器会把 Integer a = 127 转换为 Integer.valueOf(127)。** 我们看下源码。 + +``` +public static Integer valueOf(int i) { + if (i >= IntegerCache.low && i <= IntegerCache.high) + return IntegerCache.cache[i + (-IntegerCache.low)]; + return new Integer(i); + } +``` + +可以发现,i在一定范围内,是会返回缓存的。 +> 默认情况下呢,这个缓存区间就是[-128, 127],所以我们业务日常开发中,如果涉及Integer值的比较,需要注意这个坑哈。还有呢,设置 JVM 参数加上 -XX:AutoBoxCacheMax=1000,是可以调整这个区间参数的,大家可以自己试一下哈 + +### 6. static静态变量依赖spring实例化变量,可能导致初始化出错 + +之前看到过类似的代码。静态变量依赖于spring容器的bean。 +``` + private static SmsService smsService = SpringContextUtils.getBean(SmsService.class); +``` +这个静态的smsService有可能获取不到的,因为类加载顺序不是确定的,正确的写法可以这样,如下: +``` + private static SmsService smsService =null; + + //使用到的时候采取获取 + public static SmsService getSmsService(){ + if(smsService==null){ + smsService = SpringContextUtils.getBean(SmsService.class); + } + return smsService; + } +``` + +### 7. 使用ThreadLocal,线程重用导致信息错乱的坑 + +使用ThreadLocal缓存信息,有可能出现信息错乱的情况。看下下面这个例子吧。 + +``` +private static final ThreadLocal currentUser = ThreadLocal.withInitial(() -> null); + +@GetMapping("wrong") +public Map wrong(@RequestParam("userId") Integer userId) { + //设置用户信息之前先查询一次ThreadLocal中的用户信息 + String before = Thread.currentThread().getName() + ":" + currentUser.get(); + //设置用户信息到ThreadLocal + currentUser.set(userId); + //设置用户信息之后再查询一次ThreadLocal中的用户信息 + String after = Thread.currentThread().getName() + ":" + currentUser.get(); + //汇总输出两次查询结果 + Map result = new HashMap(); + result.put("before", before); + result.put("after", after); + return result; +} +``` + +按理说,每次获取的before应该都是null,但是呢,程序运行在 Tomcat 中,执行程序的线程是 Tomcat 的工作线程,而 Tomcat 的工作线程是基于线程池的。 +> 线程池会重用固定的几个线程,一旦线程重用,那么很可能首次从 ThreadLocal 获取的值是之前其他用户的请求遗留的值。这时,ThreadLocal 中的用户信息就是其他用户的信息。 + +把tomcat的工作线程设置为1 +``` +server.tomcat.max-threads=1 +``` + +用户1,请求过来,会有以下结果,符合预期: + +![](https://imgkr2.cn-bj.ufileos.com/662885f9-682b-4492-9857-0f18916e22ff.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=O3aIs4Fm54VUZJygN4qTLOgr590%253D&Expires=1609140055) + + +用户2请求过来,会有以下结果,**不符合预期**: + +![](https://imgkr2.cn-bj.ufileos.com/0e74d06e-f9a7-4a37-9991-8f7b4c9bf35f.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=D3YGQ%252FwMW%252B7cQnm1YDtA2Rjh61w%253D&Expires=1609140506) + +因此,使用类似 ThreadLocal 工具来存放一些数据时,需要特别注意在代码运行完后,显式地去清空设置的数据,正例如下: + +``` +@GetMapping("right") +public Map right(@RequestParam("userId") Integer userId) { + String before = Thread.currentThread().getName() + ":" + currentUser.get(); + currentUser.set(userId); + try { + String after = Thread.currentThread().getName() + ":" + currentUser.get(); + Map result = new HashMap(); + result.put("before", before); + result.put("after", after); + return result; + } finally { + //在finally代码块中删除ThreadLocal中的数据,确保数据不串 + currentUser.remove(); + } +} +``` + +### 8. 疏忽switch的return和break +这一点严格来说,应该不算坑,但是呢,大家写代码的时候,有些朋友容易疏忽了。直接看例子吧 +``` +/* + * 关注公众号: + * 捡田螺的小男孩 + */ +public class SwitchTest { + + public static void main(String[] args) throws InterruptedException { + System.out.println("testSwitch结果是:"+testSwitch("1")); + } + + private static String testSwitch(String key) { + switch (key) { + case "1": + System.out.println("1"); + case "2": + System.out.println(2); + return "2"; + case "3": + System.out.println("3"); + default: + System.out.println("返回默认值"); + return "4"; + } + } +} +``` +输出结果: +``` +测试switch +1 +2 +testSwitch结果是:2 +``` +switch 是会**沿着case一直往下匹配的,知道遇到return或者break。** 所以,在写代码的时候留意一下,是不是你要的结果。 + + +### 9. Arrays.asList的几个坑 + +#### 9.1 基本类型不能作为 Arrays.asList方法的参数,否则会被当做一个参数。 +``` +public class ArrayAsListTest { + public static void main(String[] args) { + int[] array = {1, 2, 3}; + List list = Arrays.asList(array); + System.out.println(list.size()); + } +} +``` +运行结果: +``` +1 +``` + Arrays.asList源码如下: +``` +public static List asList(T... a) { + return new ArrayList<>(a); +} +``` + +#### 9.2 Arrays.asList 返回的 List 不支持增删操作。 +``` +public class ArrayAsListTest { + public static void main(String[] args) { + String[] array = {"1", "2", "3"}; + List list = Arrays.asList(array); + list.add("5"); + System.out.println(list.size()); + } +} +``` +运行结果: +``` +Exception in thread "main" java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:148) + at java.util.AbstractList.add(AbstractList.java:108) + at object.ArrayAsListTest.main(ArrayAsListTest.java:11) +``` + +Arrays.asList 返回的 List 并不是我们期望的 java.util.ArrayList,而是 Arrays 的内部类 ArrayList。内部类的ArrayList没有实现add方法,而是父类的add方法的实现,是会抛出异常的呢。 + +#### 9.3 使用Arrays.asLis的时候,对原始数组的修改会影响到我们获得的那个List + +``` +public class ArrayAsListTest { + public static void main(String[] args) { + String[] arr = {"1", "2", "3"}; + List list = Arrays.asList(arr); + arr[1] = "4"; + System.out.println("原始数组"+Arrays.toString(arr)); + System.out.println("list数组" + list); + } +} +``` +运行结果: +``` +原始数组[1, 4, 3] +list数组[1, 4, 3] +``` +从运行结果可以看到,原数组改变,Arrays.asList转化来的list也跟着改变啦,大家使用的时候要注意一下哦,可以用new ArrayList(Arrays.asList(arr))包一下的。 + +#### 10. ArrayList.toArray() 强转的坑 +``` +public class ArrayListTest { + public static void main(String[] args) { + List list = new ArrayList(1); + list.add("公众号:捡田螺的小男孩"); + String[] array21 = (String[])list.toArray();//类型转换异常 + } +} +``` +因为返回的是Object类型,Object类型数组强转String数组,会发生ClassCastException。解决方案是,使用toArray()重载方法toArray(T[] a) +``` +String[] array1 = list.toArray(new String[0]);//可以正常运行 +``` + +### 11. 异常使用的几个坑 + +#### 11.1 不要弄丢了你的堆栈异常信息 + +``` +public void wrong1(){ + try { + readFile(); + } catch (IOException e) { + //没有把异常e取出来,原始异常信息丢失 + throw new RuntimeException("系统忙请稍后再试"); + } +} + +public void wrong2(){ + try { + readFile(); + } catch (IOException e) { + //只保留了异常消息,栈没有记录啦 + log.error("文件读取错误, {}", e.getMessage()); + throw new RuntimeException("系统忙请稍后再试"); + } +} +``` + +正确的打印方式,应该酱紫 +``` +public void right(){ + try { + readFile(); + } catch (IOException e) { + //把整个IO异常都记录下来,而不是只打印消息 + log.error("文件读取错误", e); + throw new RuntimeException("系统忙请稍后再试"); + } +} +``` + +#### 11.2 不要把异常定义为静态变量 +``` +public void testStaticExeceptionOne{ + try { + exceptionOne(); + } catch (Exception ex) { + log.error("exception one error", ex); + } + try { + exceptionTwo(); + } catch (Exception ex) { + log.error("exception two error", ex); + } +} + +private void exceptionOne() { + //这里有问题 + throw Exceptions.ONEORTWO; +} + +private void exceptionTwo() { + //这里有问题 + throw Exceptions.ONEORTWO; +} +``` +exceptionTwo抛出的异常,很可能是 exceptionOne的异常哦。正确使用方法,应该是new 一个出来。 +``` +private void exceptionTwo() { + throw new BusinessException("业务异常", 0001); +} +``` + + +#### 11.3 生产环境不要使用e.printStackTrace(); +``` +public void wrong(){ + try { + readFile(); + } catch (IOException e) { + //生产环境别用它 + e.printStackTrace(); + } +} +``` +因为它占用太多内存,造成锁死,并且,日志交错混合,也不易读。正确使用如下: +``` +log.error("异常日志正常打印方式",e); +``` + +#### 11.4 线程池提交过程中,出现异常怎么办? +``` +public class ThreadExceptionTest { + + public static void main(String[] args) { + ExecutorService executorService = Executors.newFixedThreadPool(10); + + IntStream.rangeClosed(1, 10).forEach(i -> executorService.submit(()-> { + if (i == 5) { + System.out.println("发生异常啦"); + throw new RuntimeException("error"); + } + System.out.println("当前执行第几:" + Thread.currentThread().getName() ); + } + )); + executorService.shutdown(); + } +} +``` +运行结果: +``` +当前执行第几:pool-1-thread-1 +当前执行第几:pool-1-thread-2 +当前执行第几:pool-1-thread-3 +当前执行第几:pool-1-thread-4 +发生异常啦 +当前执行第几:pool-1-thread-6 +当前执行第几:pool-1-thread-7 +当前执行第几:pool-1-thread-8 +当前执行第几:pool-1-thread-9 +当前执行第几:pool-1-thread-10 +``` +可以发现,如果是使用submit方法提交到线程池的异步任务,异常会被吞掉的,所以在日常发现中,如果会有可预见的异常,可以采取这几种方案处理: +- 1.在任务代码try/catch捕获异常 +- 2.通过Future对象的get方法接收抛出的异常,再处理 +- 3.为工作者线程设置UncaughtExceptionHandler,在uncaughtException方法中处理异常 +- 4.重写ThreadPoolExecutor的afterExecute方法,处理传递的异常引用 + +#### 11.5 finally重新抛出的异常也要注意啦 +``` +public void wrong() { + try { + log.info("try"); + //异常丢失 + throw new RuntimeException("try"); + } finally { + log.info("finally"); + throw new RuntimeException("finally"); + } +} +``` +一个方法是不会出现两个异常的呢,所以finally的异常会把try的**异常覆盖**。正确的使用方式应该是,finally 代码块**负责自己的异常捕获和处理**。 + +``` +public void right() { + try { + log.info("try"); + throw new RuntimeException("try"); + } finally { + log.info("finally"); + try { + throw new RuntimeException("finally"); + } catch (Exception ex) { + log.error("finally", ex); + } + } +} +``` + +### 12.JSON序列化,Long类型被转成Integer类型! +``` +public class JSONTest { + public static void main(String[] args) { + + Long idValue = 3000L; + Map data = new HashMap<>(2); + data.put("id", idValue); + data.put("name", "捡田螺的小男孩"); + + Assert.assertEquals(idValue, (Long) data.get("id")); + String jsonString = JSON.toJSONString(data); + + // 反序列化时Long被转为了Integer + Map map = JSON.parseObject(jsonString, Map.class); + Object idObj = map.get("id"); + System.out.println("反序列化的类型是否为Integer:"+(idObj instanceof Integer)); + Assert.assertEquals(idValue, (Long) idObj); + } +} +``` +**运行结果:** +``` +Exception in thread "main" 反序列化的类型是否为Integer:true +java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long + at object.JSONTest.main(JSONTest.java:24) +``` +> **注意啦**,序列化为Json串后,Josn串是没有Long类型呢。而且反序列化回来如果也是Object接收,数字小于Interger最大值的话,给转成Integer啦! + + +### 13. 使用Executors声明线程池,newFixedThreadPool的OOM问题 + +``` +ExecutorService executor = Executors.newFixedThreadPool(10); + for (int i = 0; i < Integer.MAX_VALUE; i++) { + executor.execute(() -> { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + //do nothing + } + }); + } +``` + +**IDE指定JVM参数:-Xmx8m -Xms8m :** + +![](https://imgkr2.cn-bj.ufileos.com/f9163a30-4c4c-4470-ac97-0c7383ac0cc0.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=uDMyGRF6Tbc537CWx3zhAhOBAVg%253D&Expires=1609141355) + + +运行结果: + +![](https://imgkr2.cn-bj.ufileos.com/fd925823-faea-479d-a6a1-ba604a1e8e35.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=BC3G94zA9iBbH0bkxoUEidZhB2M%253D&Expires=1609141373) + + +我们看下源码,其实newFixedThreadPool使用的是无界队列! +``` +public static ExecutorService newFixedThreadPool(int nThreads) { + return new ThreadPoolExecutor(nThreads, nThreads, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue()); +} + +public class LinkedBlockingQueue extends AbstractQueue + implements BlockingQueue, java.io.Serializable { + ... + + + /** + * Creates a {@code LinkedBlockingQueue} with a capacity of + * {@link Integer#MAX_VALUE}. + */ + public LinkedBlockingQueue() { + this(Integer.MAX_VALUE); + } +... +} +``` + +> newFixedThreadPool线程池的核心线程数是固定的,它使用了近乎于无界的LinkedBlockingQueue阻塞队列。当核心线程用完后,任务会入队到阻塞队列,如果任务执行的时间比较长,没有释放,会导致越来越多的任务堆积到阻塞队列,最后导致机器的内存使用不停的飙升,造成JVM OOM。 + + +### 14. 直接大文件或者一次性从数据库读取太多数据到内存,可能导致OOM问题 + +如果一次性把大文件或者数据库太多数据达到内存,是会导致OOM的。所以,为什么查询DB数据库,一般都建议分批。 + +读取文件的话,一般问文件不会太大,才使用```Files.readAllLines()```。为什么呢?因为它是直接把文件都读到内存的,预估下不会OOM才使用这个吧,可以看下它的源码: + +``` +public static List readAllLines(Path path, Charset cs) throws IOException { + try (BufferedReader reader = newBufferedReader(path, cs)) { + List result = new ArrayList<>(); + for (;;) { + String line = reader.readLine(); + if (line == null) + break; + result.add(line); + } + return result; + } +} +``` +如果是太大的文件,可以使用Files.line()按需读取,当时读取文件这些,一般是使用完需要**关闭资源流**的哈 + +### 15. 先查询,再更新/删除的并发一致性问题 + +再日常开发中,这种代码实现经常可见:先查询是否有剩余可用的票,再去更新票余量。 +``` +if(selectIsAvailable(ticketId){ + 1、deleteTicketById(ticketId) + 2、给现金增加操作 +}else{ + return “没有可用现金券” +} +``` +如果是并发执行,很可能有问题的,应该利用数据库更新/删除的原子性,正解如下: +``` +if(deleteAvailableTicketById(ticketId) == 1){ + 1、给现金增加操作 +}else{ + return “没有可用现金券” +} +``` + +### 16. 数据库使用utf-8存储, 插入表情异常的坑 + +低版本的MySQL支持的utf8编码,最大字符长度为 3 字节,但是呢,存储表情需要4个字节,因此如果用utf8存储表情的话,会报```SQLException: Incorrect string value: '\xF0\x9F\x98\x84' for column```,所以一般用utf8mb4编码去存储表情。 + +### 17. 事务未生效的坑 + +日常业务开发中,我们经常跟事务打交道,**事务失效**主要有以下几个场景: +- 底层数据库引擎不支持事务 +- 在非public修饰的方法使用 +- rollbackFor属性设置错误 +- 本类方法直接调用 +- 异常被try...catch吃了,导致事务失效。 + +其中,最容易踩的坑就是后面两个,**注解的事务方法给本类方法直接调用**,伪代码如下: + +``` +public class TransactionTest{ + public void A(){ + //插入一条数据 + //调用方法B (本地的类调用,事务失效了) + B(); + } + + @Transactional + public void B(){ + //插入数据 + } +} +``` + +如果用异常catch住,**那事务也是会失效呢**~,伪代码如下: +``` +@Transactional +public void method(){ + try{ + //插入一条数据 + insertA(); + //更改一条数据 + updateB(); + }catch(Exception e){ + logger.error("异常被捕获了,那你的事务就失效咯",e); + } +} +``` + +### 18. 当反射遇到方法重载的坑 +``` +/** + * 反射demo + * @author 捡田螺的小男孩 + */ +public class ReflectionTest { + + private void score(int score) { + System.out.println("int grade =" + score); + } + + private void score(Integer score) { + System.out.println("Integer grade =" + score); + } + + public static void main(String[] args) throws Exception { + ReflectionTest reflectionTest = new ReflectionTest(); + reflectionTest.score(100); + reflectionTest.score(Integer.valueOf(100)); + + reflectionTest.getClass().getDeclaredMethod("score", Integer.TYPE).invoke(reflectionTest, Integer.valueOf("60")); + reflectionTest.getClass().getDeclaredMethod("score", Integer.class).invoke(reflectionTest, Integer.valueOf("60")); + } +} +``` +运行结果: +``` +int grade =100 +Integer grade =100 +int grade =60 +Integer grade =60 +``` +如果**不通过反射**,传入```Integer.valueOf(100)```,走的是Integer重载。但是呢,反射不是根据入参类型确定方法重载的,而是**以反射获取方法时传入的方法名称和参数类型来确定**的,正例如下: +``` +getClass().getDeclaredMethod("score", Integer.class) +getClass().getDeclaredMethod("score", Integer.TYPE) +``` + +### 19. mysql 时间 timestamp的坑 +有更新语句的时候,timestamp可能会自动更新为当前时间,看个demo +``` +CREATE TABLE `t` ( + `a` int(11) DEFAULT NULL, + `b` timestamp NOT NULL, + `c` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8 +``` + +我们可以发现 **c列** 是有```CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP```,所以c列会随着记录更新而**更新为当前时间**。但是b列也会随着有记录更新为而**更新为当前时间**。 + +![](https://imgkr2.cn-bj.ufileos.com/0e5bad2d-d1b8-41a3-a40c-e697f2bfa8d1.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=BXTPAIbW04Nfy25lqi25PMPjbqI%253D&Expires=1609141018) + +可以使用datetime代替它,需要更新为当前时间,就把```now()```赋值进来,或者修改mysql的这个参数```explicit_defaults_for_timestamp```。 + +### 20. mysql8数据库的时区坑 + +之前我们对mysql数据库进行升级,新版本为8.0.12。但是升级完之后,发现now()函数,获取到的时间比北京时间早8小时,原来是因为mysql8默认为美国那边的时间,需要指定下时区 + +``` +jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8& +serverTimezone=Asia/Shanghai +``` + +### 参考与感谢 +- [Java业务开发常见错误100例](https://time.geekbang.org/column/article/220230 "Java业务开发常见错误100例") + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/Mysql\344\270\255\357\274\21421\344\270\252\345\206\231SQL\347\232\204\345\245\275\344\271\240\346\203\257.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/Mysql\344\270\255\357\274\21421\344\270\252\345\206\231SQL\347\232\204\345\245\275\344\271\240\346\203\257.md" new file mode 100644 index 0000000..bfece93 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/Mysql\344\270\255\357\274\21421\344\270\252\345\206\231SQL\347\232\204\345\245\275\344\271\240\346\203\257.md" @@ -0,0 +1,294 @@ +### 前言 +每一个好习惯都是一笔财富,本文分SQL后悔药, SQL性能优化,SQL规范优雅三个方向,分享写SQL的21个好习惯,谢谢阅读,加油哈~ + +github地址,感谢每颗star +> https://github.com/whx123/JavaHome + +公众号:**捡田螺的小男孩** + + +### 1. 写完SQL先explain查看执行计划(SQL性能优化) + +日常开发写SQL的时候,尽量养成这个好习惯呀:写完SQL后,用explain分析一下,尤其注意走不走索引。 +``` +explain select * from user where userid =10086 or age =18; +``` + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/758773bfbe904d5ba801bc83d81a6bbc~tplv-k3u1fbpfcp-watermark.image) + + +### 2、操作delete或者update语句,加个limit(SQL后悔药) + + +在执行删除或者更新语句,尽量加上limit,以下面的这条 SQL 为例吧: +``` +delete from euser where age > 30 limit 200; +``` + +因为加了limit 主要有这些好处: + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e14bfc881f21454ca40981b777f56e3e~tplv-k3u1fbpfcp-watermark.image) + + +- **降低写错SQL的代价**, 你在命令行执行这个SQL的时候,如果不加limit,执行的时候一个**不小心手抖**,可能数据全删掉了,如果**删错**了呢?加了limit 200,就不一样了。删错也只是丢失200条数据,可以通过binlog日志快速恢复的。 +- **SQL效率很可能更高**,你在SQL行中,加了limit 1,如果第一条就命中目标return, 没有limit的话,还会继续执行扫描表。 +- **避免了长事务**,delete执行时,如果age加了索引,MySQL会将所有相关的行加写锁和间隙锁,所有执行相关行会被锁住,如果删除数量大,会直接影响相关业务无法使用。 +- **数据量大的话,容易把CPU打满** ,如果你删除数据量很大时,不加 limit限制一下记录数,容易把cpu打满,导致越删越慢的。 + + +### 3. 设计表的时候,所有表和字段都添加相应的注释(SQL规范优雅) + +这个好习惯一定要养成啦,设计数据库表的时候,所有表和字段都添加相应的注释,后面更容易维护。 + +**正例:** +``` +CREATE TABLE `account` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id', + `name` varchar(255) DEFAULT NULL COMMENT '账户名', + `balance` int(11) DEFAULT NULL COMMENT '余额', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表'; +``` + +**反例:** +``` +CREATE TABLE `account` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `balance` int(11) DEFAULT NULL, + `create_time` datetime NOT NULL , + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8; +``` + +### 4. SQL书写格式,关键字大小保持一致,使用缩进。(SQL规范优雅) + +正例: +``` +SELECT stu.name, sum(stu.score) +FROM Student stu +WHERE stu.classNo = '1班' +GROUP BY stu.name +``` + +反例: +``` +SELECT stu.name, sum(stu.score) from Student stu WHERE stu.classNo = '1班' group by stu.name. +``` +显然,统一关键字大小写一致,使用缩进对齐,会使你的SQL看起来更优雅~ + +### 5. INSERT语句标明对应的字段名称(SQL规范优雅) + +反例: +``` +insert into Student values ('666','捡田螺的小男孩','100'); +``` +正例: +``` +insert into Student(student_id,name,score) values ('666','捡田螺的小男孩','100'); +``` + +### 6. 变更SQL操作先在测试环境执行,写明详细的操作步骤以及回滚方案,并在上生产前review。(SQL后悔药) + +- 变更SQL操作先在测试环境测试,避免有语法错误就放到生产上了。 +- 变更Sql操作需要写明详细操作步骤,尤其有依赖关系的时候,如:先修改表结构再补充对应的数据。 +- 变更Sql操作有回滚方案,并在上生产前,review对应变更SQL。 + +### 7.设计数据库表的时候,加上三个字段:主键,create_time,update_time。(SQL规范优雅) + + +反例: +``` +CREATE TABLE `account` ( + `name` varchar(255) DEFAULT NULL COMMENT '账户名', + `balance` int(11) DEFAULT NULL COMMENT '余额', +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表'; +``` +正例: +``` +CREATE TABLE `account` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id', + `name` varchar(255) DEFAULT NULL COMMENT '账户名', + `balance` int(11) DEFAULT NULL COMMENT '余额', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表'; +``` +理由: +- 主键一定要加上的,没有主键的表是没有灵魂的 +- 创建时间和更新时间的话,还是建议加上吧,详细审计、跟踪记录,都是有用的。 + +阿里开发手册也提到这个点,如图 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8a71648ba03c4f0fa6772a3c03cd99f2~tplv-k3u1fbpfcp-watermark.image) + + +### 8. 写完SQL语句,检查where,order by,group by后面的列,多表关联的列是否已加索引,优先考虑组合索引。(SQL性能优化) + +反例: +``` +select * from user where address ='深圳' order by age ; +``` +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1320d5f64b2640ff8221251505cacf14~tplv-k3u1fbpfcp-watermark.image) + +正例: +``` +添加索引 +alter table user add index idx_address_age (address,age) +``` +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/480d9d01a71c40228696fd22850e111d~tplv-k3u1fbpfcp-watermark.image) + +### 9.修改或删除重要数据前,要先备份,先备份,先备份(SQL后悔药) + +如果要修改或删除数据,在执行SQL前一定要先备份要修改的数据,万一误操作,还能吃口**后悔药**~ + +### 10. where后面的字段,留意其数据类型的隐式转换(SQL性能优化) + +**反例:** +``` +//userid 是varchar字符串类型 +select * from user where userid =123; +``` +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/17ad6b34e5a646028d9a086fd7ef7db0~tplv-k3u1fbpfcp-watermark.image) + +**正例:** +``` +select * from user where userid ='123'; +``` +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4bae5bd0b3564484b18e6f145ff65fd6~tplv-k3u1fbpfcp-watermark.image) + +**理由:** +- 因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较,最后导致索引失效 + + +### 11. 尽量把所有列定义为NOT NULL(SQL规范优雅) + +- **NOT NULL列更节省空间**,NULL列需要一个额外字节作为判断是否为 NULL 的标志位。 +- **NULL列需要注意空指针问题**,NULL列在计算和比较的时候,需要注意空指针问题。 + + +### 12.修改或者删除SQL,先写WHERE查一下,确认后再补充 delete 或 update(SQL后悔药) + +尤其在操作生产的数据时,遇到修改或者删除的SQL,先加个where查询一下,确认OK之后,再执行update或者delete操作 + +### 13.减少不必要的字段返回,如使用select <具体字段> 代替 select * (SQL性能优化) + +反例: +``` +select * from employee; +``` +正例: +``` +select id,name from employee; +``` +理由: + +- 节省资源、减少网络开销。 +- 可能用到覆盖索引,减少回表,提高查询效率。 + + +### 14.所有表必须使用Innodb存储引擎(SQL规范优雅) + +Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好,所以呢,没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用Innodb存储引擎 + +### 15.数据库和表的字符集统一使用UTF8(SQL规范优雅) + +统一使用UTF8编码 +- 可以避免乱码问题 +- 可以避免,不同字符集比较转换,导致的索引失效问题 + +如果是存储表情的,可以考虑 utf8mb4 + +### 16. 尽量使用varchar代替 char。(SQL性能优化) + +反例: +``` + `deptName` char(100) DEFAULT NULL COMMENT '部门名称' +``` +正例: +``` +`deptName` varchar(100) DEFAULT NULL COMMENT '部门名称' +``` +理由: +- 因为首先变长字段存储空间小,可以节省存储空间。 + + +### 17. 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。 (SQL规范优雅) + +这个点,是阿里开发手册中,Mysql的规约。你的字段,尤其是表示枚举状态时,如果含义被修改了,或者状态追加时,为了后面更好维护,需要即时更新字段的注释。 + + +### 18. SQL修改数据,养成begin + commit 事务的习惯;(SQL后悔药) +正例: +``` +begin; +update account set balance =1000000 +where name ='捡田螺的小男孩'; +commit; +``` +反例: +``` +update account set balance =1000000 +where name ='捡田螺的小男孩'; +``` + + +### 19. 索引命名要规范,主键索引名为 pk_ 字段名;唯一索引名为 uk _字段名 ; 普通索引名则为 idx _字段名。(SQL规范优雅) + +说明: pk_ 即 primary key;uk _ 即 unique key;idx _ 即 index 的简称。 + + +### 20. WHERE从句中不对列进行函数转换和表达式计算 + +假设loginTime加了索引 + +**反例:** +``` +select userId,loginTime from loginuser where Date_ADD(loginTime,Interval 7 DAY) >=now(); +``` +**正例:** +``` +explain select userId,loginTime from loginuser where loginTime >= Date_ADD(NOW(),INTERVAL - 7 DAY); +``` +**理由:** +- 索引列上使用mysql的内置函数,索引失效 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07bd6e743d974638a70c2db91c6cc9f2~tplv-k3u1fbpfcp-watermark.image) + + + +### 21.如果修改\更新数据过多,考虑批量进行。 + +反例: +``` +delete from account limit 100000; +``` +正例: +``` +for each(200次) +{ + delete from account limit 500; +} +``` + +理由: +- 大批量操作会会造成主从延迟。 +- 大批量操作会产生大事务,阻塞。 +- 大批量操作,数据量过大,会把cpu打满。 + +### 参考与感谢 +- [delete后加 limit是个好习惯么](https://blog.csdn.net/qq_39390545/article/details/107519747) +- 《阿里开发手册》 + +### 公众号 +后端技术栈公众号:捡田螺的小男孩 + + + + + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/READEME.MD" "b/\345\267\245\344\275\234\346\200\273\347\273\223/READEME.MD" new file mode 100644 index 0000000..6c258b9 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/READEME.MD" @@ -0,0 +1,17 @@ +## 工作总结 + +- [工作总结!日志打印的15个建议](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494838&idx=1&sn=cdb15fd346bddf3f8c1c99f0efbd67d8&chksm=cf22339ff855ba891616c79d4f4855e228e34a9fb45088d7acbe421ad511b8d090a90f5b019f&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [25种代码坏味道总结+优化示例](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490148&idx=1&sn=00a181bf74313f751b3ea15ebc303545&chksm=cf21c54df8564c5bc5b4600fce46619f175f7ae557956f449629c470a08e20580feef4ea8d53&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [聊聊日常开发中,如何减少bug呢?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490662&idx=1&sn=d38a090611af7f64ee3c6a31331d5228&chksm=cf21c34ff8564a59e505e6edf3065a0fc506c6d2c96f492c8d8873cd46dedbe0704e43cb9c2e&token=1990771297&lang=zh_CN#rd) +- [工作四年,分享50个让你代码更好的小建议](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488708&idx=1&sn=6e2e0a740f5d42a59641487a0bf1e3bf&chksm=cf21cbedf85642fbb485fa1c7bf9af21923d8503f2542b6f8283ce79ddc683f7d9e45da83100&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [写代码有这16个好习惯,可以减少80%非业务的bug](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488097&idx=1&sn=eaca1f92ca3ccd9de00dbc4ef3e4029a&chksm=cf21cd48f856445e4cc24c1f8bcf18d1479bad0a37a87a2fb70717d8a4e65dcf7b4d5f83d24f&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [Java日常开发的21个坑,你踩过几个?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488115&idx=1&sn=bdd4a4ca36bc7ea902106d058e8537fb&chksm=cf21cd5af856444cb36af600705615454b0aaa2b289b97ddb52d594556ac07a1915b73ecce19&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [CAS乐观锁解决并发问题的一次实践](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487937&idx=1&sn=206a37bf6d6a7aa1d05674c479ed7a72&chksm=cf21cee8f85647fe7a082049a41c0f640f54976d2cdf4302b24c5517ca42b854eb84b13ece10&token=1990771297&lang=zh_CN#rd) +- [写代码有这些想法,同事才不会认为你是复制粘贴程序员](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487961&idx=1&sn=e646231067968d9f58e6665914293f9a&chksm=cf21cef0f85647e6f3ff2feece004ac3bd979e37fe45103c88d0f299dfe632a5cf6dd547c1d9&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备:Java日期处理的十个坑](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487973&idx=1&sn=0f713413098fb579e5f200b829f71e89&chksm=cf21ceccf85647da450765d79bf5943da551c3be950447063b9f8c77c21bf2a39b99387a949b&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [内存泄漏问题的分析和解决方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487986&idx=1&sn=d681a585ac489703788e3baa48eb9aa3&chksm=cf21cedbf85647cd23bbab9dfec63e6877f83c34efb19bd16075d5d90fea91d3f4a20fc77921&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备基础:加签验签](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488022&idx=1&sn=70484a48173d36006c8db1dfb74ab64d&chksm=cf21cd3ff8564429a1205f6c1d78757faae543111c8461d16c71aaee092fe3e0fed870cc5e0e&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [记一次接口性能优化实践总结:优化接口性能的八个建议](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488004&idx=1&sn=00840efd9c0bd0a7f172b59eb2ca130f&chksm=cf21cd2df856443bf21d8e09cfe5c8452ecaf82e3c2210fca3b28829ded04defddcf63c0a59b&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [程序员必备基础:如何安全传输存储用户密码?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488117&idx=1&sn=5d3d0eda0ed45f3f576e211de31ca3a9&chksm=cf21cd5cf856444af1407a94a2abf445265ca7c5f5855cfa1c223cb209e99040c7889621f231&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [一次代码优化实践,用了模板方法+策略+工厂方法模式](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488061&idx=1&sn=1d9ab7954b03521ab81ecf033c0e5e50&chksm=cf21cd14f8564402b213f0ef908bbdb0e12fed4b281c5803b8e539cacb1551654194becfb7d6&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [保证接口数据安全的10种方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247500285&idx=1&sn=7d0723f25d46e858859cfd79acb6fb9d&chksm=cf221ed4f85597c2093f81baa5fdedc65817bf2d23a7951236836b0f54c2335695cbed61cd13&token=1990771297&lang=zh_CN#rd) \ No newline at end of file diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\344\270\200\346\254\241\344\273\243\347\240\201\344\274\230\345\214\226.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\344\270\200\346\254\241\344\273\243\347\240\201\344\274\230\345\214\226.md" new file mode 100644 index 0000000..6052878 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/\344\270\200\346\254\241\344\273\243\347\240\201\344\274\230\345\214\226.md" @@ -0,0 +1,204 @@ +## 前言 +好久没分享工作总结啦,今天来一份代码优化总结。用模板方法+策略+工厂方法模式优化了代码,耐心点看完,应该对大家有帮助的~ + +本文已经收录到github +> https://github.com/whx123/JavaHome + +**公众号:捡田螺的小男孩** + +### 优化代码前 +先来了解一下类似的业务场景,简言之,就是:多个商户接入我们系统,都是走一个类似的流程通过http请求出去的。 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b5ad1d5b5f57469894ecc83df77d68d9~tplv-k3u1fbpfcp-zoom-1.image) + +优化前,每个公司对应一个句柄服务,伪代码如下: +``` +// 商户A处理句柄 +CompanyAHandler implements RequestHandler { + Resp hander(req){ + //查询商户信息 + queryMerchantInfo(); + //加签 + signature(); + // http请求(走代理) + httpRequestbyProxy() + // 验签 + verify(); + } +} +// 商户B处理句柄 +CompanyBHandler implements RequestHandler { + Resp hander(Rreq){ + //查询商户信息 + queryMerchantInfo(); + //加签 + signature(); + // http请求(不走代理) + httpRequestbyDirect(); + // 验签 + verify(); + } +} +// 商户C处理句柄 +CompanyBHandler implements RequestHandler { + Resp hander(Rreq){ + //查询商户信息 + queryMerchantInfo(); + // webservice 方式调用 + requestByWebservice(); + } +} +``` +### 优化代码思路 +我的优化代码思路,是有**重复代码,先把它抽出来,或者公用变量,或者公用方法,伸着公用类**。所以呢,查询商户信息呀,加签呀,http请求呀先全部各抽成一个公用方法。你细心点会发现,连每个Handler处理过程都很类似的,大概都是查询商户信息+加签+http请求+验签,于是呢,可以直接把它们抽象成一个公用类呀~在这里就要引入模板方法模式咯 + +#### 模板方法模式 +``` +在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。 +这种类型的设计模式属于行为型模式。 +``` +既然每个Handler处理,都是类似的流程,那**定义一个抽象类,把查询商户信息,加签,http请求,验签什么的,都放到里面去,俨然一个模板一样**。然后,因为有些商户走http代理,有些又没走代理,怎么办呢? 定义**一个抽象方法,给子类实现**嘛,因为能共用就放到父类(当前的抽象类),不能共用就放到子类嘛~代码如下: +``` +abstract class AbstractCompanyCommonService implements ICompanyCommonService { + //模板方法 + Resp handlerTempPlate(req){ + //查询商户信息 + queryMerchantInfo(); + // 加签 + signature(); + //http 请求 + if(isRequestByProxy()){ + httpProxy(); + }else{ + httpDirect(); + } + // 验签 + verifySinature(); + } + // Http是否走代理 + abstract boolean isRequestByProxy(); +} +``` + +子类商户A实现: +``` +CompanyAServiceImpl extends AbstractCompanyCommonService{ + Resp hander(req){ + return handlerTempPlate(req); + } + //公司A是走代理的 + boolean isRequestByProxy(){ + return true; + } +``` +子类商户B实现: +``` +CompanyBServiceImpl extends AbstractCompanyCommonService{ + Resp hander(req){ + return handlerTempPlate(req); + } + //公司B是不走代理的 + boolean isRequestByProxy(){ + return false; + } +``` + +#### 策略模式 +心细的读者会发现,甚至提出疑问,**你的商户C的服务实现跟你定义的公用模板,不太一样呢**,那当然,实际开发中,不跟你定义的模板一样太常见了,需求是产品提的嘛,又不是根据你模板提的,是代码服务于需求的。好了,不多说啦,我使用了策略模式,来优化这个问题。 +``` +在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。 +``` +策略模式理解起来其好抽象对不对?我个人理解,其实策略模式就是定义一个方法(所谓算法),给子类自己去实现。实际上就是**定义个方法/接口,让子类自己去实现**。看代码吧: +``` +// 定义一个方法,把策略交给子类去实现。 +interface ICompanyCommonService{ + Resp hander(req); +} +``` +前面商户A和商户B还是不变,使用抽象类AbstractCompanyCommonService的模板,模板不满足商户C,商户C只能自己去实现咯,各个子类自己去实现的行为,就是策略模式的体现呢,如下: + +``` +CompanyCServiceImpl extends AbstractCompanyCommonService{ + Res hander(req){ + //查询商户信息 + queryMerchantInfo(); + requestByWebservice(); + } + //随意了,你都不走模板了 + boolean isRequestByProxy(){ + return false; + } +``` + +#### 工厂方法模式 + 商户A、B、C服务怎么被管理呢,之前分别给A,B,C服务实现handler的,现在好了,都不知道怎么管理了,怎么知道调用哪个呢?别慌,解决方案是**工厂方法模式**。 +``` +在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。 +``` +工厂方法模式具体实现就是:接口定义一个枚举,每个服务实现都重新实现枚举,设置唯一标志枚举,再交给spring容器管理。看代码咯: +``` +interface ICompanyCommonService{ + Resp hander(req); + CompanyEnum getCompanyEnum(); +} + +CompanyAServiceImpl extends AbstractCompanyCommonService{ + Resp hander(req){ + return handlerTempPlate(req); + } + //公司A是走代理的 + boolean isRequestByProxy(){ + return true; + } + CompanyEnum getCompanyEnum(){ + return CompanyEnum.A; + } + +CompanyBServiceImpl extends AbstractCompanyCommonService{ + Resp hander(req){ + return handlerTempPlate(req); + } + //公司B是不走代理的 + boolean isRequestByProxy(){ + return false; + } + CompanyEnum getCompanyEnum(){ + return CompanyEnum.B; + } +``` +来来来,工厂方法模式出炉咯: +``` +@Component +public class CompanyServiceFactory implements ApplicationContextAware { + + private static Map map = new HashMap<>(); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Map tempMap = applicationContext.getBeansOfType(ICompanyCommonService.class); + tempMap.values().forEach(iCompanyCommonService -> + map.put(iCompanyCommonService.getCompanyEnum(), iCompanyCommonService)); + } + + public Resp handler(req) { + return map.get(CompanyEnum.getCompanyEnum(req.getCompanyFlag()).hander(req); + } +} +``` +### 最后建议 +最后,不要为了使用设计模式生搬硬套,而是优化代码过程中,发现这个设计模式刚好适用,才去用的哈。附上最后的代码咯: +``` +@Service +public class CompanyHandler implements RequestHandler { + @Autowire + private CompanyServiceFactory companyServiceFactory; + + Resp hander(req){ + return companyServiceFactory.handler(req); + } +} +``` + +### 个人公众号 + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e531344ab1a417188a4d8ece4148c64~tplv-k3u1fbpfcp-zoom-1.image) + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\345\206\231\344\273\243\347\240\201\346\234\211\350\277\23116\344\270\252\344\271\240\346\203\257\357\274\214\350\247\204\351\201\27780%\347\232\204bug.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\345\206\231\344\273\243\347\240\201\346\234\211\350\277\23116\344\270\252\344\271\240\346\203\257\357\274\214\350\247\204\351\201\27780%\347\232\204bug.md" new file mode 100644 index 0000000..dcb3d26 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/\345\206\231\344\273\243\347\240\201\346\234\211\350\277\23116\344\270\252\344\271\240\346\203\257\357\274\214\350\247\204\351\201\27780%\347\232\204bug.md" @@ -0,0 +1 @@ +### ǰ ÿһϰ߶һʲƸд16ϰߣÿܾ䣬ЩϰߣԹܶҵbugϣԴаллĶŶ~ githubַлÿstar > https://github.com/whx123/JavaHome ںţ**ݵСк** ### 1. ޸룬ǵԲһ **룬Բһ** ÿλԱرĻ䲻Ҫֽ**ֻǸһֻһô룬Բ**룬ҪԼȥһ¹Թܺܶ಻Ҫbugġ ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c730a4fe0ebb47258af9fd31f8d9c890~tplv-k3u1fbpfcp-watermark.image) ### 2. ξ УҲÿԱرĻķ**У**ǷΪգγǷԤڳȡϰ߰ɣܶ**ͼbug****У**µġ > ݿֶΪvarchar(16),Էһ32λַ㲻У**ݿֱ쳣**ˡ ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1e3375dcfad46ca9ad88dafe7574de0~tplv-k3u1fbpfcp-watermark.image) ### 3. ޸Ͻӿڵʱ˼ӿڵļԡ ܶbugΪ޸˶Ͻӿڣȴ**ݵ**ġؼDZȽصģֱӵϵͳʧܵġֳԱ׾ͷŶ~ ԣԭӿ޸ģӿǶṩĻһҪǽӿڼݡٸӰɣdubboӿڣԭֻABһCͿԿ ``` //Ͻӿ void oldService(A,B);{ //½ӿڣnullC newService(A,B,null); } //½ӿڣʱɾϽӿڣҪݡ void newService(A,B,C); ``` ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8d4434f90314d24bdec6742c718a5f0~tplv-k3u1fbpfcp-watermark.image) ### 4.ڸӵĴ߼ע дʱûбҪд̫ע͵ģõķõע͡ǣ**ҵ߼ܸӵĴ**ķdzбҪд**ע**עͣںά ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a5d732fc2dbb4144a84b7d566ff4861b~tplv-k3u1fbpfcp-watermark.image) ### 5. ʹIOԴҪر ӦôҶйľwindowsϵͳ**̫ļ**ϵͳͻõԺܿȻlinuxҲһƽʱļݿӣIOԴûرգôIOԴͻᱻռţ˾ûа취ˣ**Դ˷** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e3081d16ec54487eb03a9bbd513565ec~tplv-k3u1fbpfcp-watermark.image) ʹIOʹfinallyرչ ``` FileInputStream fdIn = null; try { fdIn = new FileInputStream(new File("/jay.txt")); } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); }finally { try { if (fdIn != null) { fdIn.close(); } } catch (IOException e) { log.error(e); } } ``` JDK 7 ֮и˧Ĺرдʹ**try-with-resource** ``` /* * עںţݵСк */ try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) { // use resources } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); } ``` ### 6.ȡʩʱ߽ȣ ճУҪȡʩ**ָ߽**ʱ ƴȽϳ ``` String name = list.get(1).getName(); //listԽ磬Ϊһ2Ԫع ``` ԣӦ**ȡʩԤһ߽** ``` if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){ String name = list.get(1).getName(); } ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3757643c6e4044d797cc60646ce6b9d1~tplv-k3u1fbpfcp-watermark.image) ### 7.ѭԶ̵áݿȿС Զ̲ݿ**ȽϺ硢IOԴ**ģԾѭԶ̵áѭݿ⣬**һԲҪѭȥ**أҲҪһԲ̫ݹҪ500һνϣ ``` remoteBatchQuery(param); ``` ``` for(int i=0;iһӣһhttp˵ķҪconnect-timeretry ת˵Ҫĵ񣬻Ҫ**ǩǩ******ȡ֮ǰдһƪǩǩģȤѿԿһ¹ [Աرǩǩ](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484887&idx=1&sn=316cfd80f7c60b40998eab004211ebb0&chksm=977941f8a00ec8eea93bbcd7b47e7dc39c6d05117ac93f80363d171c34fd4ae64f2f5b46d0ce&token=1951383729&lang=zh_CN#rd) ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79e0932bca0143aa971e2b6a3de2d67b~tplv-k3u1fbpfcp-watermark.image) ### 13.ӿҪݵ ӿҪݵԵģתЩҪӿڡֱ۵ҵ񳡾**ûŵ**Ľӿûholdס > - ݵȣidempotentidempotenceһѧѧڳС > - ڱ.һݵȲصִӰһִеӰͬݵȺݵȷָʹͬظִУܻͬĺ һ**ݵȼ**⼸: - ѯ - Ψһ - tokenƣֹظύ - ݿdeleteɾ - ֹ - - Rediszookeeper ֲʽǰRedisֲʽ - ״̬ݵ ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a65c2e6c0c0840a982704a0982467771~tplv-k3u1fbpfcp-watermark.image) ### 14. ߳£԰ȫ **߲**£HashMapܻѭΪǷ԰ȫģԿʹConcurrentHashMap ҲϰߣҪ־һnew HashMap(); > - HashmapArraylistLinkedListTreeMapȶԲȫģ > - VectorHashtableConcurrentHashMapȶ԰ȫ ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2477f57cad1d4b8ab49a5d4d9ddce6cb~tplv-k3u1fbpfcp-watermark.image) ### 15.ӳ⿼ Ȳ룬žȥѯ,߼Ƚϳ****ġһݿⶼ⣬ӿġдĻд⣬һǶӿ⡣ӳ٣ܿܳɹˣѯ ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/72f32d1f00b94498adf139620f2decab~tplv-k3u1fbpfcp-watermark.image) - ҪҵҪǷǿƶ⣬޸Ʒ - أЩҵ񳡾ǿԽ΢ӳһģϰ߻Ҫаɡ - дݿĴ룬Ƿӳ⡣ ### 16.ʹûʱ򣬿ǸDBһԣУ洩͸ѩͻ ͨ׵˵ʹûΪ**ÿ죬ӿںʱС**أõ棬Ҫ**ע⻺ݿһ**⡣ͬʱҪܻ洩͸ѩͻ⡣ > - ѩָݴʱ䣬ѯ޴ݿѹdown > - 洩͸ָѯһһڵݣڻDzʱҪݿѯ鲻д뻺棬⽫ڵÿҪݿȥѯݿѹ > - ָȵkeyijʱڵʱ򣬶ǡʱKeyдIJӶdb ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/30bf76e99aab4d3aae46b48580041e90~tplv-k3u1fbpfcp-watermark.image) ### ˹ں ȤѣԹעҹںŹ > ԭں **ݵСк**רעֺ̽˼㣬Javaԡ硢ݿ⡢ݽṹ㷨ϵͳܽȷ档ͨ׶~ \ No newline at end of file diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\345\267\245\344\275\234\345\233\233\345\271\264\357\274\21450\344\270\252\350\256\251\344\275\240\344\273\243\347\240\201\346\233\264\345\245\275\347\232\204\345\260\217\345\273\272\350\256\256.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\345\267\245\344\275\234\345\233\233\345\271\264\357\274\21450\344\270\252\350\256\251\344\275\240\344\273\243\347\240\201\346\233\264\345\245\275\347\232\204\345\260\217\345\273\272\350\256\256.md" new file mode 100644 index 0000000..e47a0ba --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/\345\267\245\344\275\234\345\233\233\345\271\264\357\274\21450\344\270\252\350\256\251\344\275\240\344\273\243\347\240\201\346\233\264\345\245\275\347\232\204\345\260\217\345\273\272\350\256\256.md" @@ -0,0 +1,1478 @@ +### ǰ +꣬ܶ˼Ĵ룬дһܽɣ50õĽ顣еһЩ㣬ǰҲдҪһ¡ϣճд˼ܽᣬͣͬʱвԵģҲָл~ + +- ںţ**ݵСк** +- githubַhttps://github.com/whx123/JavaHome + +### 1. жǷʱselect count select Уá + +ǾƵҵ񳡾磬жijû```userId```ǷǻԱ + +**** һЩСʵ֣ȲûϢû¼ȻȥжǷǻԱ: +``` + + +boolean isVip (String userId){ + UserInfo userInfo = userInfoDAp.selectUserByUserId(userId); + return UserInfo!=null && "Y".equals(userInfo.getVipFlag()) +} +``` + +**** ҵ񳡾ʵõʵֱ֣```select count```һ££ + +``` + + + boolean isVip (String userId){ + int vipNum = userInfoDAp.countVipUserByUserId(userId); + return vipNum>0 +} +``` + + +### 2. ӵif߼Ե˳óЧ + + +ҵûǻԱҵһε½ʱҪһ֪ͨĶšûо˼ֱܿдˡ + +``` +if(isUserVip && isFirstLogin){ + sendMsgNotify(); +} +``` + +ܹ5isUserVipͨ3isFirstLoginͨ1 ôϴ룬isUserVipִеĴΪ5ΣisFirstLoginִеĴҲ3Σ£ + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b30c2899d10f4258bf1fc94a9ebf324c~tplv-k3u1fbpfcp-watermark.image) + + +һisUserVipisFirstLogin˳أ +``` +if(isFirstLogin && isUserVip ){ + sendMsg(); +} +``` +isFirstLoginִеĴ5ΣisUserVipִеĴ1Σ£ + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/103a20c642e04ea899d74c4cb8187b28~tplv-k3u1fbpfcp-watermark.image) + +isFirstLoginж߼ֻselect count һݿisUserVipҲselect count һݿĻȻisFirstLoginǰЧ + + +### 3. дѯSqlʱֻҪõֶΣͨõֶΣֵܾselect * + +**** + +``` +select * from user_info where user_id =#{userId}; +``` + +**** + + +``` + selct user_id , vip_flag from user_info where user_id =#{userId}; +``` + +**ɣ** + +- ʡԴ翪 +- õٻر߲ѯЧʡ + +### 4. Żij򣬾ܾҪĶ + + +ı߼жϣһᱻֵ˵ֻһֱַӳʼַͿˣûбҪҪnew String(). + + + +``` +String s = new String ("ӭעںţݵСк"); +``` + + + +``` +String s= "ӭעںţݵСк ; +``` + + +### 5. ʼʱָ + + +ĿֲᣬҲȷᵽ㣺 +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2ac43ab291794c608537f1c4da6a9caa~tplv-k3u1fbpfcp-watermark.image) + + +mapҪ洢Ԫظ15ңд +``` + //initialCapacity = 15/0.75+1=21 + Map map = new HashMap(21); + ΪhashMap2йأԿȡ32 + Map map = new HashMap(32); +``` + +### 6.catch쳣Ҫӡexceptionöλ + +**** +``` +try{ + // do something +}catch(Exception e){ + log.info("ݵСкij쳣"); +} +``` + +**** + +``` +try{ + // do something +}catch(Exception e){ + log.info("ݵСкij쳣",e); //exceptionӡ +} +``` + +**ɣ** +- УûаexceptionʱŲͲòSQlд쳣IO쳣أӦðexceptionӡ־Ŷ~ + +### 7. ӡ־ʱ򣬶ûиObjecttoStringķֱӰӡˡ + + +ڴӡ־ʱ򣬾뿴һrequestʲôǺЩ룺 + + +``` +publick Response dealWithRequest(Request request){ + log.info("ǣ".request.toString) +} +``` +ӡ£ + +``` +ǣlocal.Request@49476842 +``` + +ΪtoStringĬϵʵǡ@ɢ޷ʮơ㿴ɣӴӡ־ûɶ˼㶼֪ӡʲôݡ + +һ(ΪεĶ󣩣**дtoString()** + +``` +class Request { + + private String age; + + private String name; + + @Override + public String toString() { + return "Request{" + + "age='" + age + '\'' + + ", name='" + name + '\'' + + '}'; + } +} + +publick Response dealWithRequest(Request request){ + log.info("ǣ".request.toString) +} + + +``` +ӡ£ + +``` +ǣRequest{age='26', name='ںţݵСк'} +``` + +### 8. һܾIJб + +ôһзβĸ + +``` +public void getUserInfoString name,String age,String sex,String mobile){ + // do something ... +} +``` + +ҪഫһversionĹзdubboֶṩĽӿڵĻôĽӿDzҪϰ汾 + +``` +public void getUserInfoString name,String age,String sex,String mobile){ + // do something ... +} + +/** + * ½ӿڵ + */ +public void getNewUserInfoString name,String age,String sex,String mobileString version){ + // do something ... +} +``` + +أһһIJһ㲻˹IJбţҽӿʱܻҪϰ汾ݡʵǶôأøDTOװһЩ~£ + +``` +public void getUserInfoUserInfoParamDTO userInfoParamDTO){ + // do something ... +} + +class UserInfoParamDTO{ + private String name; + private String age; + private String sex; + private String mobile; +} +``` +øDTOװһ£ʹв䶯ҲԲöӿˣôܸܵġ + +### 9. ʹûIO + +**** + +``` +/** + * ںţݵСк + * @desc: һͼƬļ + */ +public class MainTest { + public static void main(String[] args) throws FileNotFoundException { + long begin = System.currentTimeMillis(); + try (FileInputStream input = new FileInputStream("C:/456.png"); + FileOutputStream output = new FileOutputStream("C:/789.png")) { + byte[] bytes = new byte[1024]; + int i; + while ((i = input.read(bytes)) != -1) { + output.write(bytes,0,i); + } + } catch (IOException e) { + log.error("ļ쳣",e); + } + log.info("дܹʱms"+(System.currentTimeMillis() - begin)); + } +} +``` + +н + +``` +дܹʱms:52 +``` + + + +ʹ```FileInputStream``````FileOutputStream```ʵļдܣûʲôġأʹû```BufferedReader``````BufferedWriter``````BufferedInputStream``````BufferedOutputStream```ȣIO߶дЧʡ +> DzȡһֽڻַģͻֱˡȡһֽڻַʱȲǵȴﵽһ + +**** + + +``` +/** + * ںţݵСк + * @desc: һͼƬļ + */ +public class MainTest { + public static void main(String[] args) throws FileNotFoundException { + long begin = System.currentTimeMillis(); + try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C:/456.png")); + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("C:/789.png"))) { + byte[] bytes = new byte[1024]; + int i; + while ((i = input.read(bytes)) != -1) { + output.write(bytes,0,i); + } + } catch (IOException e) { + log.error("ļ쳣",e); + } + log.info("ܹʱms"+(System.currentTimeMillis() - begin)); + } +} +``` +н + +``` +дܹʱms:12 +``` + +### 10. Żij߼ǰѾ鵽ݣںķҲõĻǿ԰´εģٷ/ + +**** +``` +public Response dealRequest(Request request){ + + UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId); + if(Objects.isNull(request)){ + return ; + } + + insertUserVip(request.getUserId); + +} + +private int insertUserVipString userId{ + //ֲһ + UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId); + //ûvipˮ + insertUserVipFlow(userInfo); + .... +} + +``` + +Ȼϳ룬Ѿ鵽 userInfoȻְuserIdȥֲһΡʵϣ԰userInfoȥģʡȥһβЧ + +**** + +``` +public Response dealRequest(Request request){ + + UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId); + if(Objects.isNull(request)){ + return ; + } + + insertUserVip(userInfo); +} + +private int insertUserVipUserInfo userInfo{ + //ûvipˮ + insertUserVipFlow(userInfo); + .... +} +``` + + +### 11. ҪΪ˷㣬ֱڴʹ0,1ħֵӦҪenumöٴ档 + + +**** + +``` +if("0".equals(userInfo.getVipFlag)){ + //ǻԱʾȥͨԱ + tipOpenVip(userInfo); +}else if("1".equals(userInfo.getVipFlag)){ + //Աѫ· + addMedaluserInfo; +} +``` + +**** + + +``` +if(UserVipEnum.NOT_VIP.getCode.equals(userInfo.getVipFlag)){ + //ǻԱʾȥͨԱ + tipOpenVip(userInfo); +}else if(UserVipEnum.VIP.getCode.equals(userInfo.getVipFlag)){ + //Աѫ· + addMedaluserInfo; +} + +public enum UserVipEnum { + + VIP("1","Ա"), + NOT_VIP("0","ǻԱ"),:; + + private String code; + private String desc; + + UserVipEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } +} +``` + +дʱ򣬲Ҫһʱ𣬾ֱʹħֵʹħֵάܵġ + +### 12. ԱֵıʱȶΪ̬ + +**** + + +``` +public class Task { + private final long timeout = 10L; + ... +} +``` + +**** + +``` +public class Task { + private static final long TIMEOUT = 10L; + ... +} +``` + +> ΪΪstaticྲ̬ÿʵУֻһݸdzԱÿʵУһݸȻĻΪ̬һЩ + +### 13. עָ룬Ҫҵ˵߼ijΪա + +NullPointerException ճзdzǴ뿪УһҪԿָ뱣 + +Ҫ⼸ָ⣺ + +- װ͵Ŀָ +- õĿָ +- EqualsߵĿָ +- ConcurrentHashMap ֧ k-vΪ null +- ϣֱӻȡԪ +- ֱӻȡ + +**** +``` +public class NullPointTest { + public static void main(String[] args) { + String s = null; + if (s.equals("666")) { //sΪգᵼ¿ָ + System.out.println("ںţݵСкɻ"); + } + } +} + +``` + +### 14񵽵쳣ܺٴ־ + +**** + +``` +public static void testIgnoreException() throws Exception { + try { + // + } catch (Exception e) { + //쳣ɶ鲻־Ҳ򣿣 + } +} + +``` + +**** + +``` +public static void testIgnoreException() { + try { + // + } catch (Exception e) { + log.error("쳣ˣϵС翴",e); + } +} +``` + +### 15. Lambdaʽ滻ڲ࣬ʹ + +JDK8-LambdaʽLambdaʽڲţڴУDzinvokeDynamicָʵ֣ڲ࣬ЧҲ + + +**** +``` + public void sortUserInfoList(List userInfoList){ + userInfoList.sort(new Comparator() { + @Override + public int compare(UserInfo user1, UserInfo user2) { + Long userId1 = user1.getUserId(); + Long userId2 = user2.getUserId(); + return userId1.compareTo(userId2); + }}); + } +``` + +**** + +``` + public void sortUserInfoList(List userInfoList){ + userInfoList.sort((user1, user2) -> { + Long userId1 = user1.getUserId(); + Long userId2 = user2.getUserId(); + return userId1.compareTo(userId2); + }); + } +``` + +### 16. ֪ͨࣨ緢ʼжţĴ룬첽 + +ҵҪû½ʱӸ֪ͨķ˿ 뵽ʵ£ + + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ed6daf4932b34452b1aacdb98aa2dcc8~tplv-k3u1fbpfcp-watermark.image) + +ṩsendMsgNotifyϵͳˣߵsendMsgNotifyʧˣôû½ʧˡ +һ֪ͨܵ˵½̲ãԵļ֥鶪ϡôûƼõķأеģŽӿڲ쳣߳첽£ + + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58a956dc04c6456395b39d02afe3187e~tplv-k3u1fbpfcp-watermark.image) + +ˣ֪ͨȲǷҪɽĽӿʱӦþǷӰҪ̣˼ôá + +### 17. JavaʱYYYYʽõ⡣ + +ճУǾҪڡҪʱڸʽʱǴд```YYYY```Ŀӡ + + +``` +Calendar calendar = Calendar.getInstance(); +calendar.set(2019, Calendar.DECEMBER, 31); + +Date testDate = calendar.getTime(); + +SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd"); +System.out.println("2019-12-31 ת YYYY-MM-dd ʽ " + dtf.format(testDate)); +``` +н + +``` +2019-12-31 ת YYYY-MM-dd ʽ 2020-12-31 +``` + +> Ϊʲô20191231ţתһ¸ʽͱ20201231ˣΪYYYYǻģָڵݣһܴտʼֻҪܿ꣬ôһܾһˡȷʹyyyyʽ + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2e3654c06cd4727b203ea5710cc8410~tplv-k3u1fbpfcp-watermark.image) + +### 18. һȷᱻ̳УAOPɧָfinalηfinalһࡣ + +**** +``` +public final class Tools { + public static void testFinal(){ + System.out.println("෽"); + } +} +``` + +һָfinalηᱻ̳ˣзfinalˡJavaһеfinalJavaЧʡ + + +### 19. static̬Ҫspringʵܻᵼ³ʼ + +֮ǰĿƵĴ롣̬springbean +``` + private static SmsService smsService = SpringContextUtils.getBean(SmsService.class); +``` + +̬smsServiceпܻȡģΪ˳ȷģϵĴ룬̬smsServiceʼǿspringʵˡȷд£ + + +``` + private static SmsService smsService =null; + + //ʹõʱȡȡ + public static SmsService getSmsService(){ + if(smsService==null){ + smsService = SpringContextUtils.getBean(SmsService.class); + } + return smsService; + } + +``` + +### 20. Ա޹صķӦɾ̬ + + + +ЩʵԱ޹أͿΪ̬һ㣬õúܶࡣ**** + +``` +/** + * BigDecimalĹ + */ +public class BigDecimalUtils { + + public BigDecimal ifNullSetZERO(BigDecimal in) { + return in != null in : BigDecimal.ZERO; + } + + public BigDecimal sum(BigDecimal ...in){ + BigDecimal result = BigDecimal.ZERO; + for (int i = 0; i < in.length; i++){ + result = result.add(ifNullSetZERO(in[i])); + } + return result; + } +``` + +ΪBigDecimalUtilsķûstaticΣԣҪʹõʱÿζҪnewһ,DzͺԴȥ**** + +``` +BigDecimalUtils bigDecimalUtils = new BigDecimalUtils; +bigDecimalUtils.sum(a,b); +``` +Կɾ̬ʹõʱֱ```.```üɣ£ + + +``` +/** + * BigDecimalĹ + */ +public class BigDecimalUtils { + + public static BigDecimal ifNullSetZERO(BigDecimal in) { + return in != null in : BigDecimal.ZERO; + } + + public static BigDecimal sum(BigDecimal ...in){ + BigDecimal result = BigDecimal.ZERO; + for (int i = 0; i < in.length; i++){ + result = result.add(ifNullSetZERO(in[i])); + } + return result; + } +``` + +### 21. ҪһException׽пܵ쳣 + +**:** + +``` + +public void test(){ + try{ + //׳ IOException Ĵ + //׳ SQLException Ĵ + }catch(Exception e){ + //û Exception ׽пܵ쳣ζ׽ᶪʧԭʼ쳣ЧϢŶ + log.info(Exception in test,exception:{}, e); + } +} + +``` + +**** + + +``` +public void test(){ + try{ + //׳ IOException Ĵ + //׳ SQLException Ĵ + }catch(IOException e){ + //׽ IOException + log.info(IOException in test,exception:{}, e); + }catch(SQLException e){ + //׽ SQLException + log.info(SQLException in test,exception:{}, e); + } +} +``` + +### 22. ҪȷװԼ༴ɡ + +**** + +``` +// װ +public static boolean isUserVip(Boolean isVip) { + return Boolean.TRUE.equals(isVip); +} + +// ʹô +boolean isVip = isVip(user.getUserVip()); +``` + +**** + + +``` +boolean isVip = Boolean.TRUE.equals(user.getUserVip()); +``` + +Ҫȷװ˼ɡңûջͳջĸCPUڴ棬ȷװܵģ + + +### 23. ijֵһᱻǣûбҪֵ + +**:** + +``` +List userList = new ArrayList<>(); +if (isAll) { + userList = userInfoDAO.queryAll(); +} else { + userList = userInfoDAO.queryActive(); +} + +``` +**** + + +``` +List userList ; +if (isAll) { + userList = userInfoDAO.queryAll(); +} else { + userList = userInfoDAO.queryActive(); +} + +``` + + +### 24.ֵҪʹBigDecimal + +Ӱɣ +``` +public class DoubleTest { + public static void main(String[] args) { + System.out.println(0.1+0.2); + System.out.println(1.0-0.8); + System.out.println(4.015*100); + System.out.println(123.3/100); + + double amount1 = 3.15; + double amount2 = 2.10; + if (amount1 - amount2 == 1.05){ + System.out.println("OK"); + } + } +} +``` +н + +``` +0.30000000000000004 +0.19999999999999996 +401.49999999999994 +1.2329999999999999 +``` +> ΪԶƴ洢ֵģڸҲǡڼԣ0.1޷ȷΪʲôᵼ¾ȷȱʧġˣ㣬һ㶼BigDecimal + + +``` +System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2))); +//output: +0.3000000000000000166533453693773481063544750213623046875 +``` +ʵʹ BigDecimal ʾͼ㸡ʹַĹ췽ʼ BigDecimalңҪעBigDecimalļλС㣬аģʽ + +### 25. עArrays.asListļ + +- **ͲΪ Arrays.asListIJᱻһ** + +``` +public class ArrayAsListTest { + public static void main(String[] args) { + int[] array = {1, 2, 3}; + List list = Arrays.asList(array); + System.out.println(list.size()); + } +} +//н +1 +``` +- **Arrays.asList ص List ֧ɾ** + + +``` +public class ArrayAsListTest { + public static void main(String[] args) { + String[] array = {"1", "2", "3"}; + List list = Arrays.asList(array); + list.add("5"); + System.out.println(list.size()); + } +} + +// н +Exception in thread "main" java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:148) + at java.util.AbstractList.add(AbstractList.java:108) + at object.ArrayAsListTest.main(ArrayAsListTest.java:11) + +``` + Arrays.asList ص List java.util.ArrayList Arrays ڲArrayListڲArrayListûʵaddǸaddʵ֣ǻ׳쳣ء + +- **ʹArrays.asLisʱ򣬶ԭʼ޸ĻӰ쵽ǻõǸList** + +``` +public class ArrayAsListTest { + public static void main(String[] args) { + String[] arr = {"1", "2", "3"}; + List list = Arrays.asList(arr); + arr[1] = "4"; + System.out.println("ԭʼ"+Arrays.toString(arr)); + System.out.println("list" + list); + } +} + +//н +ԭʼ[1, 4, 3] +list[1, 4, 3] +``` + +### 26ʱرIOԴ + +ӦôҶйľwindowsϵͳ̫ļϵͳͻõԺܿȻlinuxҲһƽʱļݿӣIOԴûرգôIOԴͻᱻռţ˾ûа취ˣԴ˷ѡ + + +ʹIOǵùرչʹtry-with-resourceرյģ +``` +/* + * עںţݵСк + */ +try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) { + // use resources +} catch (FileNotFoundException e) { + log.error(e); +} catch (IOException e) { + log.error(e); +} +``` + +### 27. ʹúڵĻʱ + +> - ڷڣͲԼʱDZջеģٶȱȽϿ졣 +> - ͵IJʱöջУݶڶУٶȽ +> - Уκ͵ijԱڶѣHeapУٶȽ + +``` +public class AccumulatorUtil { + + private double result = 0.0D; + // + public void addAllOne( double[] values) { + for(double value : values) { + result += value; + } + } + //ڷһֲʱۼٸֵijԱ + public void addAll1Two(double[] values) { + double sum = 0.0D; + for(double value : values) { + sum += value; + } + result += sum; + } +} + +``` + +### 28. ݿһβѯ࣬ҳ + +SqlһԲȽ϶࣬ҳ + + +**** +``` + +select user_id,name,age from user_info ; + +``` + +**** + +``` +select user_id,name,age from user_info limit #{offset},#{pageSize}; +``` + +ƫرʱ򣬲ѯЧʾͱõ¡ַʽŻ + +``` +//һ ϴβѯ¼(ƫ) +select idname from user_info where id>10000 limit #{pageSize}. + +//order by + +select idname from user_info order by id limit #{offset},#{pageSize} + +//ҵҳ + +``` + +### 29. ٶԱظ + +һдʱ򣬻µķʽʵֱ +``` +for (int i = 0; i < list.size; i++){ + +} +``` +listȽСǻálistȽϴʱŻ +``` +for (int i = 0, int length = list.size; i < length; i++){ + +} +``` + +ɣ +- Էĵãʹֻһ䣬Ҳĵģ紴ջ֡listȽϴʱεlist.sizeҲǻԴĵġ + + +### 30. ޸ĶϽӿڵʱ˼ӿڵļԡ + +ܶbugΪ޸˶ϽӿڣȴݵµġؼDZȽصģֱӵϵͳʧܵġֳԱ׾ͷŶ~ + +ԣԭӿ޸ģӿǶṩĻһҪǽӿڼݡٸӰɣdubboӿڣԭֻABһCͿԿ + +``` +//Ͻӿ +void oldService(A,B);{ + //½ӿڣnullC + newService(A,B,null); +} + +//½ӿڣʱɾϽӿڣҪݡ +void newService(A,B,C); +``` + + +### 31 ȡʩʱ߽ȣ + +ճУҪȡʩָ߽ʱ + + +ƴȽϳ: + +``` +String name = list.get(1).getName(); //listԽ磬Ϊһ2Ԫع +``` + +ԣӦòȡʩԤһ߽**** + +``` +if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){ + String name = list.get(1).getName(); +} +``` + + +### 32. ע ArrayList.toArray() ǿתĿ + +``` +public class ArrayListTest { + public static void main(String[] args) { + List list = new ArrayList(1); + list.add("ںţݵСк"); + String[] array21 = (String[])list.toArray();//ת쳣 + } +} + +``` + +ΪصObjectͣObjectǿתString飬ᷢClassCastExceptionǣʹtoArray()طtoArray(T[] a) + +``` +String[] array1 = list.toArray(new String[0]);// +``` + + +### 33. ѭԶ̵áݿȿС + +̲ݿDZȽϺ硢IOԴģԾѭԶ̵áѭݿ⣬һԲҪѭȥ顣أҲҪһԲ̫ݹҪ500һνϣ + + +**** +``` +remoteBatchQuery(param); +``` + +**** + +``` +for(int i=0;i userInfoList) { + for (int i = 0; i < userInfoList.size(); i++) { + //ظuserList.size() + } + } +``` + +**** + + +``` + public static void listDetail(List userInfoList) { + int length = userInfoList.size(); + for (int i = 0; i < length; i++) { + //ٵuserList.size()ֻlengthһΡ + } + } +``` + +### 37ֱӴļһԴݿȡ̫ݵڴ棬ܵOOM + +һ԰Ѵļݿ̫ݴﵽڴ棬ǻᵼOOMġԣΪʲôѯDBݿ⣬һ㶼 + +ȡļĻһļ̫󣬲ʹFiles.readAllLines()ΪʲôأΪֱӰļڴģԤ²OOMʹɣԿԴ룺 + + +``` +public static List readAllLines(Path path, Charset cs) throws IOException { + try (BufferedReader reader = newBufferedReader(path, cs)) { + List result = new ArrayList<>(); + for (;;) { + String line = reader.readLine(); + if (line == null) + break; + result.add(line); + } + return result; + } +} +``` +̫ļʹFiles.line()ȡʱȡļЩһʹҪرԴĹ + +### 38. õӿڣҪ쳣ȫԣʱ⼸㡣 + +ճУҪõ񣬻߷ֲʽԶ̷ĵĻҪǣ + + +- 쳣磬˵Ľӿڣ쳣ˣôԻǵʧܣ +- ʱûԤԷӿһ÷أһøʱϿʱ䣬ԱĽӿڣ +- ԴĽӿڵʧܣ費ҪԣҪվҵϽǶ˼⣩ + +> һӣһhttp˵ķҪconnect-timeretry + + +### 39 ҪʹѭϣʹJDKṩķ + +> - JDKṩԭAPIֱָϵܡ +> - ЩĵײSystem.arraycopyʵ֣ݵЧʸߡ + +**** + +``` +public List copyMergeList(List user1List, List user2List) { + List userList = new ArrayList<>(user1List.size() + user2List.size()); + for (UserInfo user : user1List) { + userList.add(user); + } + for (UserInfo user : user2List) { + userList.add(user); + } + + return user1List; + } +``` + +**** + +``` +public List copyMergeList(List user1List, List user2List) { + List userList = new ArrayList<>(user1List.size() + user2List.size()); + userList.addAll(user1List); + userList.addAll(user2List); + return user1List; + } +``` + +### 40. ڸӵĴ߼ע + +дʱûбҪд̫ע͵ģõķõע͡ǣҵ߼ܸӵĴ룬ķdzбҪдע͡עͣںά + + +### 41. ߳£԰ȫ + +ڸ߲£HashMapܻѭΪǷ԰ȫģԿʹConcurrentHashMap ҲϰߣҪ־һnew HashMap(); + +- HashmapArraylistLinkedListTreeMapȶԲȫģ +- VectorHashtableConcurrentHashMapȶ԰ȫ + +### 42. ʹspringʱע⼸δЧĿ + +ճҵ񿪷УǾ򽻵ʧЧҪ¼ + +- ײݿ治֧ +- ڷpublicεķʹ +- rollbackForô +- ෽ֱӵ +- 쳣try...catchˣʧЧ + +**** + + +``` +public class TransactionTest{ + public void A(){ + //һ + //÷B (صãʧЧ) + B(); + } + + @Transactional + public void B(){ + // + } +} +``` + +**ע񷽷෽ֱӵãʧЧ** + + +### 43. ʹExecutors̳߳أnewFixedThreadPoolOOM + + +``` + ExecutorService executor = Executors.newFixedThreadPool(10); + for (int i = 0; i < Integer.MAX_VALUE; i++) { + executor.execute(() -> { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + //do nothing + } + }); + } + +``` +IDEָJVM-Xmx8m -Xms8m : + +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b7154077d1074b3c8e1f5670e754adfd~tplv-k3u1fbpfcp-watermark.image) + +н + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa3de03ca1924787aa1775b0a94890b4~tplv-k3u1fbpfcp-watermark.image) + +ǿԴ룬ʵnewFixedThreadPoolʹõ޽У + + +``` +public static ExecutorService newFixedThreadPool(int nThreads) { + return new ThreadPoolExecutor(nThreads, nThreads, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue()); +} + +public class LinkedBlockingQueue extends AbstractQueue + implements BlockingQueue, java.io.Serializable { + ... + /** + * Creates a {@code LinkedBlockingQueue} with a capacity of + * {@link Integer#MAX_VALUE}. + */ + public LinkedBlockingQueue() { + this(Integer.MAX_VALUE); + } +... +} + +``` + +> newFixedThreadPool̳߳صĺ߳ǹ̶ģʹ˽޽LinkedBlockingQueueС߳ӵУִеʱȽϳûͷţᵼԽԽѻУ»ڴʹòͣJVM OOM + + +### 44. catchס쳣󣬾Ҫʹe.printStackTrace(),ʹlogӡ + +**** + +``` +try{ + // do what you want +}catch(Exception e){ + e.printStackTrace(); +} +``` + +**** + +``` +try{ + // do what you want +}catch(Exception e){ + log.info("ij쳣",e); +} +``` + +### 45. ӿҪݵ + +ӿҪݵԵģתЩҪӿڡֱ۵ҵ񳡾ûŵΣĽӿûholdס + +һݵȼ⼸: + +- ѯ +- Ψһ +- tokenƣֹظύ +- ݿdelete/update +- ֹ +- +- Rediszookeeper ֲʽǰRedisֲʽ +- ״̬ݵ + + +### 46. Ƚ϶ĺ黮Сǿɶԡ + +**** +``` +public class Test { + private String name; + private Vector orders = new Vector(); + + public void printOwing() { + //print banner + System.out.println("****************"); + System.out.println("*****customer Owes *****"); + System.out.println("****************"); + + //calculate totalAmount + Enumeration env = orders.elements(); + double totalAmount = 0.0; + while (env.hasMoreElements()) { + Order order = (Order) env.nextElement(); + totalAmount += order.getAmout(); + } + + //print details + System.out.println("name:" + name); + System.out.println("amount:" + totalAmount); + } +} + +``` + +**** + + +``` +public class Test { + private String name; + private Vector orders = new Vector(); + + public void printOwing() { + + //print banner + printBanner(); + //calculate totalAmount + double totalAmount = getTotalAmount(); + //print details + printDetail(totalAmount); + } + + void printBanner(){ + System.out.println("****************"); + System.out.println("*****customer Owes *****"); + System.out.println("****************"); + } + + double getTotalAmount(){ + Enumeration env = orders.elements(); + double totalAmount = 0.0; + while (env.hasMoreElements()) { + Order order = (Order) env.nextElement(); + totalAmount += order.getAmout(); + } + return totalAmount; + } + + void printDetail(double totalAmount){ + System.out.println("name:" + name); + System.out.println("amount:" + totalAmount); + } + +} + +``` + +һ߳ĺһҪעͲ;Ĵ룬ԿǰзֳһȷĺԪ̵ĺôøš + + +### 47. Ĺؼҵ룬һ㽨־ݻ + +ؼҵεأӦ㹻־ݻ + +> 磺ʵתҵתȻתʧˣſͻͶߣȻ㻹ûдӡ־ˮȵ£ȴް취 + +ôתҵҪЩ־Ϣأ٣ǰҪӡҪɣӿڵúҪһ쳣ɣͬʱӡ쳣־ɣ£ + +``` +public void transfer(TransferDTO transferDTO){ + log.info("invoke tranfer begin"); + //ӡ + log.info("invoke tranfer,paramters:{}",transferDTO); + try { + res= transferService.transfer(transferDTO); + }catch(Exception e){ + log.error("transfer fail,cifno:{}account{}",transferDTO.getCifno + transferDTO.getaccount) + log.error("transfer fail,exception:{}",e); + } + log.info("invoke tranfer end"); + } +``` + +˴ӡ㹻־ǻҪעһǣ־ʹã𱾸ôӡinfo־ȴӡerror𣬸澯ҹŲͲˡ + +### 48. ijЩɱأƤȵȣûǷء + +Ʒ˸ʥڵʱ򣬺ƤΪʥصģڵʱ򣬺Ƥȡ + +: + +``` +if(duringChristmas){ + img = redPacketChristmasSkin; +}else if(duringSpringFestival){ + img = redSpringFestivalSkin; +} +``` + +ԪڵʱӪСͻȻ뷨ƤɵصģʱDzҪȥ޸Ĵˣ·ˣһʼʵһźƤñƤûأƤֻ޸һ±ݾͺˡ + +### 49.ֱӵҪʹõļ,ڶ + +ֱӵҪʹõļϣͨȡݣȽϵ;Mapĵ + + +**** +``` +Map userMap = ...; +for (Long userId : userMap.keySet()) { + UserDO user = userMap.get(userId); + ... +} +``` + +**** + + +``` +Map userMap = ...; +for (Map.Entry userEntry : userMap.entrySet()) { + Long userId = userEntry.getKey(); + UserDO user = userEntry.getValue(); + ... +} +``` + +### 50. ģʽ+Żif else + + + + +``` + String medalType = "guest"; + if ("guest".equals(medalType)) { + System.out.println("αѫ"); + } else if ("vip".equals(medalType)) { + System.out.println("Աѫ"); + } else if ("guard".equals(medalType)) { + System.out.println("չʾػѫ"); + } + ... + +``` + +ȣǰÿ߼飬һĽӿڣǸÿ߼ӦIJʵ࣬ɵ´룺 + + +``` +//ѫ½ӿ +public interface IMedalService { + void showMedal(); +} + +//ػѫ²ʵ +public class GuardMedalServiceImpl implements IMedalService { + @Override + public void showMedal() { + System.out.println("չʾػѫ"); + } +} +//αѫ²ʵ +public class GuestMedalServiceImpl implements IMedalService { + @Override + public void showMedal() { + System.out.println("αѫ"); + } +} +//VIPѫ²ʵ +public class VipMedalServiceImpl implements IMedalService { + @Override + public void showMedal() { + System.out.println("Աѫ"); + } +} + +``` + +ٶԹ࣬Щѫʵֲ࣬£ + + +``` +//ѫ·񹤲 +public class MedalServicesFactory { + + private static final Map map = new HashMap<>(); + static { + map.put("guard", new GuardMedalServiceImpl()); + map.put("vip", new VipMedalServiceImpl()); + map.put("guest", new GuestMedalServiceImpl()); + } + public static IMedalService getMedalService(String medalType) { + return map.get(medalType); + } +} + +``` + +Ż£ + +``` +ublic class Test { + public static void main(String[] args) { + String medalType = "guest"; + IMedalService medalService = MedalServicesFactory.getMedalService(medalType); + medalService.showMedal(); + } +} +``` + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\346\227\245\345\277\227\346\211\223\345\215\260\350\247\204\350\214\203.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\346\227\245\345\277\227\346\211\223\345\215\260\350\247\204\350\214\203.md" new file mode 100644 index 0000000..4577332 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/\346\227\245\345\277\227\346\211\223\345\215\260\350\247\204\350\214\203.md" @@ -0,0 +1,269 @@ +## ǰ + +Һã**ݵСк**־ǿٶλĺð֣**˺ƺ˦**ӡ־dzҪ**־ӡ**15ý~ + +- ںţ**ݵСк** + + +## 1. ѡǡ־ + +־5ֱ֣errorwarninfodebugtraceճУҪѡǡ־𣬲Ҫ־Ǵӡinfo~ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c4502943568747e79ac4091b4d2868ea~tplv-k3u1fbpfcp-zoom-1.image) + +- error־ָȽصĴ󣬶ҵӰ죬Ҫ**άüص** +- warn־һĴ󣬶ҵӰ첻󣬵Ҫ**ע** +- infoϢ־¼ŲĹؼϢʱ䡢εȵȣ +- debugڿDEBUGģؼ߼ʱݣ +- traceϸϢһЩϢֻ¼־ļС + + +## 2. ־ҪӡΡ + +DzҪӡܶܶ־ֻҪӡ**ٶλЧ־**Ч־˦ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c59f66bfc09d42ffa8528c16145952b8~tplv-k3u1fbpfcp-zoom-1.image) + +Щõ**Чؼ**־أ˵ʱ򣬴ӡ****Ȼأڷصʱ򣬾**ӡΣֵ**εĻһ**userIdbizSeqЩؼ**Ϣ£ + +``` +public String testLogMethod(Document doc, Mode mode){ + log.debug(method enter param{},userId); + String id = "666"; + log.debug(method exit param{},id); + return id; +} +``` + + +## 3. ѡʵ־ʽ + +־ʽӦЩϢ統**ǰʱ**һ뾫ȷȣ**־****߳**ȵȡlogback־ôã + +``` + + + %d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n + + +``` + +ǵ־ʽǰʱ䶼]м¼**ʱ㶼֪** + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ecfebbe1a0b411e8186d46edaddbd38~tplv-k3u1fbpfcp-zoom-1.image) + + +## 4. if...else...ʱÿ֧жӡ־ + +**if...else...switch**ʱڷ֧оʹӡ־ŲʱͿͨ־ȷĸ֧߼ҲŲˡ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60bc12dfb6324c089b86a9dc05acc2f3~tplv-k3u1fbpfcp-zoom-1.image) + + + +``` +if(user.isVip()){ + log.info("ûǻԱ,Id:{},ʼԱ߼",user,getUserId()); + //Ա߼ +}else{ + log.info("ûǷǻԱ,Id:{},ʼǻԱ߼",user,getUserId()) + //ǻԱ߼ +} +``` + +## 5.־Ƚϵʱ־ж + +trace/debugЩȽϵ͵־𣬱־Ŀжϡ + + +``` +User user = new User(666L, "ں", "ݵСк"); +if (log.isDebugEnabled()) { + log.debug("userId is: {}", user.getId()); +} +``` + +Ϊǰµ־룺 +``` +logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); +``` + +**õ־warn**Ļ־ӡǻִַƴӲ```symbol```Ƕ +ִ```toString()```˷ϵͳԴִ־ȴûдӡ˽**־жϡ** + +## 6. ֱʹ־ϵͳLog4jLogbackе APIʹ־SLF4JеAPI + +SLF4J ģʽ־ܣά͸־ʽͳһҿڱ֤޸Ĵ£ܷʵֵײ־ܵĸ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9846b8d9ddd2485483e41b7134954f91~tplv-k3u1fbpfcp-zoom-1.image) + + +``` +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +private static final Logger logger = LoggerFactory.getLogger(TianLuoBoy.class); +``` + +## 7. ʹòռλ{}+ƴӡ + + +``` +logger.info("Processing trade with id: " + id + " and symbol: " + symbol); +``` + +Уʹ```+```ַƴӣһ**** + +£ +``` +logger.info("Processing trade with id: {} and symbol : {} ", id, symbol); +``` +ʹ˴```{}```Ϊ־еռλʹ```+```żࡣң**ڷ**ʹռλ滻Чܡ + +## 8. ʹ첽ķʽ־ + +- ־ջļеģIOܻҪġ첽ͿIOܡ +- ҪҪȻʹ첽ķʽ־logbackΪɣҪ첽ܼ򵥣ʹAsyncAppender +``` + + + +``` + +## 9. Ҫʹe.printStackTrace() + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2aeaca5e0f44c08ad25fb92e75222dc~tplv-k3u1fbpfcp-zoom-1.image) + + + +``` +try{ + // ҵ봦 +}catch(Exception e){ + e.printStackTrace(); +} +``` + +``` +try{ + // ҵ봦 +}catch(Exception e){ + log.error("ij쳣",e); +} +``` + +**ɣ** + +- e.printStackTrace()ӡĶջ־ҵ־ǽһģͨŲ쳣־̫㡣 +- e.printStackTrace()ַ¼ǶջϢϢַ̫̫࣬ڵڴûпռ,ڴˣôûͿס~ + +## 10. 쳣־Ҫֻһ룬ҪȫϢ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/420f0da65bac44b5b340a96facfd5c11~tplv-k3u1fbpfcp-zoom-1.image) + +1 + +``` +try { + //ҵ봦 +} catch (Exception e) { + // + LOG.error('ij쳣'); +} + +``` +- 쳣eûдӡѹ֪ʲô͵쳣 + +2 +``` +try { + //ҵ봦 +} catch (Exception e) { + // + LOG.error('ij쳣', e.getMessage()); +} +``` + +- ```e.getMessage()```¼ϸĶջ쳣Ϣֻ¼ϢŲ⡣ + + + +``` +try { + //ҵ봦 +} catch (Exception e) { + // + LOG.error('ij쳣', e); +} +``` + +## 11. ֹϻ debug + +ֹϻdebugһdzҪ + + +Ϊһϵͳdebug־ܶ࣬ҸֿҲʹ debug־Ͽdebugÿܻ̣ӰҵϵͳС + +## 12.Ҫ¼쳣׳쳣 + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/671289ecaa4b4eb39dca1139b657f8cd~tplv-k3u1fbpfcp-zoom-1.image) + + +£ +``` +log.error("IO exception", e); +throw new MyException(e); +``` + +- ʵֵĻͨջϢӡΡΪMyException쳣ĵطٴӡһΡ +- ־¼߰װ׳ȥҪͬʱʹã־˺Ի + + +## 13.ظӡ־ + +ظӡ־ϻ˷Ѵ̿ռ䡣Ѿһ־˼**ӡ**£ + +``` +if(user.isVip()){ + log.info("ûǻԱ,Id:{}",user,getUserId()); + //࣬Ըǰ־ϲһ + log.info("ʼԱ߼,id:{}",user,getUserId()); + //Ա߼ +}else{ + //ǻԱ߼ +} +``` + +ʹlog4j־ܣ```log4j.xml``` additivity=falseΪԱظӡ־ + + +``` + +``` + +## 14.־ļ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12a2cbe6cf7f4ba981ba7103f9d81858~tplv-k3u1fbpfcp-zoom-1.image) + + +- ǿ԰Ѳͬ͵־ȥaccess.logerrorerror.logԵӡһļ档 +- ȻҲԸݲͬҵģ飬ӡͬ־ļŲͳƵʱ򣬶ȽϷ + + +## 15. Ĺģ飬ӡ־ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6f26259cda042edb98400145d208d12~tplv-k3u1fbpfcp-zoom-1.image) + + +- ճУĻ߼ӵĴ룬ϸעͣԼϸ־ +- ־ҪϸأԶһ£ĺijһˣͨ־ԶλǾͿ + + + + + + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232\345\246\202\344\275\225\345\256\211\345\205\250\344\274\240\350\276\223\345\255\230\345\202\250\347\224\250\346\210\267\345\257\206\347\240\201.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232\345\246\202\344\275\225\345\256\211\345\205\250\344\274\240\350\276\223\345\255\230\345\202\250\347\224\250\346\210\267\345\257\206\347\240\201.md" new file mode 100644 index 0000000..978ae68 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232\345\246\202\344\275\225\345\256\211\345\205\250\344\274\240\350\276\223\345\255\230\345\202\250\347\224\250\346\210\267\345\257\206\347\240\201.md" @@ -0,0 +1,123 @@ +

前言

+

我们开发网站或者APP的时候,首先要解决的问题,就是如何安全传输和存储用户的密码。一些大公司的用户数据库泄露事件也时有发生,带来非常大的负面影响。因此,如何安全传输存储用户密码,是每位程序员必备的基础。本文将跟大家一起学习,如何安全传输存储用户的密码。

+
+

公众号:捡田螺的小男孩(一起讨论密码传输存储问题)

+

1. 如何安全地传输用户的密码

+

要拒绝用户密码在网络上裸奔,我们很容易就想到使用https协议,那先来回顾下https相关知识吧~

+

1.1 https 协议

+
+
    +
  • http的三大风险
+

为什么要使用https协议呢?http它不香吗? 因为http是明文信息传输的。如果在茫茫的网络海洋,使用http协议,有以下三大风险:

+
+
    +
  • 窃听/嗅探风险:第三方可以截获通信数据。
  • 数据篡改风险:第三方获取到通信数据后,会进行恶意修改。
  • 身份伪造风险:第三方可以冒充他人身份参与通信。
+
+

如果传输不重要的信息还好,但是传输用户密码这些敏感信息,那可不得了。所以一般都要使用https协议传输用户密码信息。

+
    +
  • https 原理
+

https原理是什么呢?为什么它能解决http的三大风险呢?

+
+

https = http + SSL/TLS, SSL/TLS 是传输层加密协议,它提供内容加密、身份认证、数据完整性校验,以解决数据传输的安全性问题。

+
+

为了加深https原理的理解,我们一起复习一下一次完整https的请求流程吧~

+
+
+
    +
    1. +
    2. 客户端发起https请求
    +
    1. +
    2. 服务器必须要有一套数字证书,可以自己制作,也可以向权威机构申请。这套证书其实就是一对公私钥。
    +
    1. +
    2. 服务器将自己的数字证书(含有公钥、证书的颁发机构等)发送给客户端。
    +
    1. +
    2. 客户端收到服务器端的数字证书之后,会对其进行验证,主要验证公钥是否有效,比如颁发机构,过期时间等等。如果不通过,则弹出警告框。如果证书没问题,则生成一个密钥(对称加密算法的密钥,其实是一个随机值),并且用证书的公钥对这个随机值加密。
    +
    1. +
    2. 客户端会发起https中的第二个请求,将加密之后的客户端密钥(随机值)发送给服务器。
    +
    1. +
    2. 服务器接收到客户端发来的密钥之后,会用自己的私钥对其进行非对称解密,解密之后得到客户端密钥,然后用客户端密钥对返回数据进行对称加密,这样数据就变成了密文。
    +
    1. +
    2. 服务器将加密后的密文返回给客户端。
    +
    1. +
    2. 客户端收到服务器发返回的密文,用自己的密钥(客户端密钥)对其进行对称解密,得到服务器返回的数据。
    +
+
+
    +
  • https一定安全吗?
+

https的数据传输过程,数据都是密文的,那么,使用了https协议传输密码信息,一定是安全的吗?其实不然~

+
+
    +
  • 比如,https 完全就是建立在证书可信的基础上的呢。但是如果遇到中间人伪造证书,一旦客户端通过验证,安全性顿时就没了哦!平时各种钓鱼不可描述的网站,很可能就是黑客在诱导用户安装它们的伪造证书!
  • 通过伪造证书,https也是可能被抓包的哦。
+
+

1.2 对称加密算法

+

既然使用了https协议传输用户密码,还是不一定安全,那么,我们就给用户密码加密再传输呗~

+

加密算法有对称加密非对称加密两大类。用哪种类型的加密算法靠谱呢?

+
+

对称加密:加密和解密使用相同密钥的加密算法。 +

+
+

常用的对称加密算法主要有以下几种哈: +

+

如果使用对称加密算法,需要考虑密钥如何给到对方,如果密钥还是网络传输给对方,传输过程,被中间人拿到的话,也是有风险的哦。

+

1.3 非对称加密算法

+

再考虑一下非对称加密算法呢?

+
+

非对称加密: 非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。

+
+
+

常用的非对称加密算法主要有以下几种哈: +

+
+

如果使用非对称加密算法,也需要考虑密钥公钥如何给到对方,如果公钥还是网络传输给对方,传输过程,被中间人拿到的话,会有什么问题呢?他们是不是可以伪造公钥,把伪造的公钥给客户端,然后,用自己的私钥等公钥加密的数据过来? 大家可以思考下这个问题哈~

+
+

我们直接登录一下百度,抓下接口请求,验证一发大厂是怎么加密的。可以发现有获取公钥接口,如下:

+
+

再看下登录接口,发现就是RSA算法,RSA就是非对称加密算法。其实百度前端是用了JavaScript库jsencrypt,在github的star还挺多的。

+
+

因此,我们可以用https + 非对称加密算法(如RSA) 传输用户密码~

+

2. 如何安全地存储你的密码?

+

假设密码已经安全到达服务端啦,那么,如何存储用户的密码呢?一定不能明文存储密码到数据库哦!可以用哈希摘要算法加密密码,再保存到数据库。

+
+

哈希摘要算法: 只能从明文生成一个对应的哈希值,不能反过来根据哈希值得到对应的明文。

+
+

2.1 MD5摘要算法保护你的密码

+

MD5 是一种非常经典的哈希摘要算法,被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等。但是仅仅使用 MD5 对密码进行摘要,并不安全。我们看个例子,如下:

+
public class MD5Test {
    public static void main(String[] args) {
        String password = "abc123456";
        System.out.println(DigestUtils.md5Hex(password));
    }
}
+

运行结果:

+
0659c7992e268962384eb17fafe88364
+

在MD5免费破解网站一输入,马上就可以看到原密码了。。。

+
+

试想一下,如果黑客构建一个超大的数据库,把所有20位数字以内的数字和字母组合的密码全部计算MD5哈希值出来,并且把密码和它们对应的哈希值存到里面去(这就是彩虹表)。在破解密码的时候,只需要查一下这个彩虹表就完事了。所以单单MD5对密码取哈希值存储,已经不安全啦~

+

2.2 MD5+盐摘要算法保护用户的密码

+

那么,为什么不试一下MD5+盐呢?什么是加盐

+
+

在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。

+
+

用户密码+盐之后,进行哈希散列,再保存到数据库。这样可以有效应对彩虹表破解法。但是呢,使用加盐,需要注意一下几点:

+
+
    +
  • 不能在代码中写死盐,且盐需要有一定的长度(盐写死太简单的话,黑客可能注册几个账号反推出来)
  • 每一个密码都有独立的盐,并且盐要长一点,比如超过 20 位。(盐太短,加上原始密码太短,容易破解)
  • 最好是随机的值,并且是全球唯一的,意味着全球不可能有现成的彩虹表给你用。
+
+

2.3 提升密码存储安全的利器登场,Bcrypt

+

即使是加了盐,密码仍有可能被暴力破解。因此,我们可以采取更慢一点的算法,让黑客破解密码付出更大的代价,甚至迫使他们放弃。提升密码存储安全的利器~Bcrypt,可以闪亮登场啦。

+
+

实际上,Spring Security 已经废弃了 MessageDigestPasswordEncoder,推荐使用BCryptPasswordEncoder,也就是BCrypt来进行密码哈希。BCrypt 生而为保存密码设计的算法,相比 MD5 要慢很多。

+
+

看个例子对比一下吧:

+
public class BCryptTest {

    public static void main(String[] args) {
        String password = "123456";
        long md5Begin = System.currentTimeMillis();
        DigestUtils.md5Hex(password);
        long md5End = System.currentTimeMillis();
        System.out.println("md5 time:"+(md5End - md5Begin));
        long bcrytBegin = System.currentTimeMillis();
        BCrypt.hashpw(password, BCrypt.gensalt(10));
        long bcrytEnd = System.currentTimeMillis();
        System.out.println("bcrypt Time:" + (bcrytEnd- bcrytBegin));
    }
}
+

运行结果:

+
md5 time:47
bcrypt Time:1597
+

粗略对比发现,BCrypt比MD5慢几十倍,黑客想暴力破解的话,就需要花费几十倍的代价。因此一般情况,建议使用Bcrypt来存储用户的密码

+

3. 总结

+
    +
  • 因此,一般使用https 协议 + 非对称加密算法(如RSA)来传输用户密码,为了更加安全,可以在前端构造一下随机因子哦。
  • 使用BCrypt + 盐存储用户密码。
  • 在感知到暴力破解危害的时候,开启短信验证、图形验证码、账号暂时锁定等防御机制来抵御暴力破解。
+

参考与感谢

+
    +
  • 如何正确保存和传输敏感数据? https://time.geekbang.org/column/article/239150[1]
  • 如何加密传输和存储用户密码 https://juejin.cn/post/6844903604944371726#heading-8[2]
+

公众号

+
    +
  • 公众号:捡田螺的小男孩
  • github地址:https://github.com/whx123/JavaHome
+ + +
+ \ No newline at end of file diff --git a/README.md.bak "b/\345\267\245\344\275\234\346\200\273\347\273\223/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232\345\246\202\344\275\225\345\256\211\345\205\250\344\274\240\350\276\223\345\255\230\345\202\250\347\224\250\346\210\267\345\257\206\347\240\201.md.bak" similarity index 100% rename from README.md.bak rename to "\345\267\245\344\275\234\346\200\273\347\273\223/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\237\272\347\241\200\357\274\232\345\246\202\344\275\225\345\256\211\345\205\250\344\274\240\350\276\223\345\255\230\345\202\250\347\224\250\346\210\267\345\257\206\347\240\201.md.bak" diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\350\201\212\350\201\212\346\227\245\345\270\270\345\274\200\345\217\221\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221bug\345\221\242\357\274\237.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\350\201\212\350\201\212\346\227\245\345\270\270\345\274\200\345\217\221\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221bug\345\221\242\357\274\237.md" new file mode 100644 index 0000000..0c47dce --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/\350\201\212\350\201\212\346\227\245\345\270\270\345\274\200\345\217\221\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221bug\345\221\242\357\274\237.md" @@ -0,0 +1,663 @@ +## ǰ + +Һѽ~ ǼݵСкճУμbugĽ**ݿ⡢桢ʹƪ**3ܽһ60ע㣬ҳΪ֮ǡ +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/41bd0d208d054f87a99f6c7df514f35d~tplv-k3u1fbpfcp-zoom-1.image) + +- ӭעںţ**ݵСк** +- [githubַ](https://github.com/whx123/JavaHome)лÿһstar + +## 1. ݿƪ + +![ѯ](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a05dccf50dc3403c8c665d3395cdcc91~tplv-k3u1fbpfcp-zoom-1.image) + +ݿƪĻЩط׵bugأܽ7棺**ѯݿֶע㡢ʧЧijӳ١ݼݡһЩSQLע** + +### 1.1 ѯ + +![ѯ.gif](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8d38c462aa484b47b9fcfded5f4f2140~tplv-k3u1fbpfcp-watermark.image) + +#### 1.1.1 Ƿ +ѯϾͻ뵽һSQLûûĻͻѯ + +**ЩʧЧ** + +- ѯorܵʧЧ +- ֶַwhereʱһʧЧ +- likeͨܵʧЧ +- ѯʱвеĵһУʧЧ +- ʹmysqlúʧЧ +- 㣨磬+-*/ʧЧ +- ֶʹã= < >not inʱܻᵼʧЧ +- ֶʹis null is not nullܵʧЧ +- ӲѯӲѯѯֶαʽһܵʧЧ +- mysqlʹȫɨҪʹ,ʹ + + +#### 1.1.2 󣬿Ƿֱֿ + +̫󣬾ͻӰSQLִܡ֪ݽṹһB+һø߶Ϊ3B+ſԴ洢ǧݡĻB+Ҫߣѯܻ½ + +ˣʱ򣬽ֱֱֿֿм**mycatsharding-jdbc** + + +#### 1.1.3 SQL + +ճУ߼ܶ಻SQLһSQLȻ**6**,̫ӰѯܣٱһȻ**10**ȵȡǻή˲͸SQLܣһ㲻̫࣬һ㲻ܳ + + +### 1.2 ݿֶע + +ݿֶݣ׳bug磬Ի޸˱ṹijֶΣǰѽűǷ϶ˡ + +#### 1.2.1 ֶǷᳬ + +ݿֶǣ + +``` +`name` varchar(255) DEFAULT NOT NULL +``` + +˱nameֶγ300Dzʱ****ˡҪУֶֹγ + +#### 1.2.2 ֶΪգǷᵼ¿ָ + +ݿֶεʱ,ֶΪ**not null** + +- Σһʹ0-1ΪĬֵ +- ַĬϿַ + +ݿֶΪ```NULL```ֵ׵³ָ룻ݿֶΪ```NULL```ֵҪע**count()** ʹãпӡ + +#### 1.2.3 ֶȱʧ + +ǵճڲԻԱ޸ģһֶΣҪSQLűֶȱʧ + + +#### 1.2.4 ֶǷֱ֧ + +һֶҪֱ֧洢ʹ**utf8mb4** + +#### 1.2.5 ʹtextblobֶ + +Ҫһֶδ洢ļ**洢ļ·**ļȥʹtextʱ漰ѯʱעⴴ**ǰ׺** + +### 1.3 ʧЧij + +#### 1.3.1 @Transactional ڷpublicεķʧЧ + + +@Transactionalע⣬ڷpublicεķϣDzЧġspringǽAOP˼룬Ҳ̬ͨʵֵġspringԼڵö̬֮ǰѾԷpublicˣԷpublicЧ + +#### 1.3.2 طֱӵ + @TransactionalҲЧ +``` +public class TransactionTest{ + public void A(){ + //һ + //÷B (صãʧЧ) + B(); + } + + @Transactional + public void B(){ + // + } +} +``` + +#### 1.3.3 쳣try...catchˣʧЧ + + +``` +@Transactional +public void method(){ + try{ + //һ + insertA(); + //һ + updateB(); + }catch(Exception e){ + logger.error("쳣ˣʧЧ",e); + } +} + +``` + +#### 1.3.4 rollbackForô + +SpringĬ׳δ```unchecked```쳣̳RuntimeException 쳣ErrorŻع쳣ᴥع׳͵쳣Ҫָ```rollbackFor```ԡ + +#### 1.3.5 ײݿ治֧ + +MyISAM洢治֧InnoDb֧ + +#### 1.3.6 springҵ߼һ߳ + +ҵҪspringԴͬһ߳УŻspringĿơ룬motheḍ߳ڲִеmothedspringĿƣһҪע⡣ΪspringʵʹThreadLocalʵͬһ߳ݹ + +``` +@Transactional +public void mothed() { + new Thread() { + + }.start(); +} +``` + + +### 1.4 + +ָͬһԴ໥ռãԷԴӶ¶ѭ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3513a3fd3ef64e318f157dd51ec280ef~tplv-k3u1fbpfcp-zoom-1.image) + + +MySQLڲһƣһعһһִȥ**Դʽ̵͡òȷ**Σ + +#### 1.4.1 9SQL + +ҪҪѧһSQLļνе?һSQLԷ9֣̽ + +- һidRC뼶 +- ϶idǶΨһRC뼶 +- idǶΨһRC뼶 +- ģidûRC뼶 +- 壺idRR뼶 +- idǶΨһRR뼶 +- ߣidǶΨһRR뼶 +- ϰˣidûRR뼶 +- ϾţSerializable뼶 + + +#### 1.4.2 η + +IJ£ + +- ģ +- show engine innodb status;鿴־ +- ҳSQL +- SQLȥ +- ־ʲôȴʲô +- ϤģʽݾInnoDB洢ļԾ + +ȤС飬Կ֮ǰдƪ£[ְֽMysql](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487979&idx=1&sn=588c83d77a8851f3b3c18cd68ed9c454&chksm=cf21cec2f85647d4a77cc239ae9a4cfd31bb8832be3d98540a08ea8b4a1f46b38cf736210a02&token=1327808550&lang=zh_CN#rd) + + +### 1.5 ӳ⿼ + +Ȳ룬žȥѯ,߼Ƚϳܻġһݿⶼ⣬ӿġдĻд⣬һǶӿ⡣ӳ٣ܿܳɹˣѯ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/560555c31cfe42a0b07dd9bcad24c0e1~tplv-k3u1fbpfcp-zoom-1.image) + + +#### 1.5.1 ҪǿһԣǶ + +ҪҵҪǿһԣֱӶ + +#### 1.5.2 Ҫǿһԣӿ + +һҵ񣬿ԽܶݵݲһµĻȿǶӿ⡣ΪӿԷֵĶдѹϵͳܡ + +### 1.6 ݼ + +#### 1.6.1 ¼ӵֶΣǴݵĬֵ + +ճУҵҪijݿӸֶΡijAPPñҪӸֶΣ```scene_type```,öֵ ```010203```ǾҪҵ룬ӵֶΣʲôĬֵΪջĬ01Ϊ```NULL```ĻҪÿָ봦 + +#### 1.6.2 ҵϵֶΣݵֵǷп + +ǿУҪݿֶΣдݣǾҪϴݿֵǷпӡDZиuser_role_code ֶΣϵУöֵ ``` 01Ա 02Ա 03һû```ҵ**һû**Ϊ**03ѯû04û**ڿУҪݵ + +### 1.7 һЩSQLľע + +#### 1.7.1 limitҳ + +limitҳһdzSQL⣬һ3ֶӦĽ + +**һ** idģϴβѯ¼(ƫ)limit + + +``` +select id,name from employee where id>1000000 limit 10. +``` + +**:** ҵҳ + +ҵۣûбҪôķҳΪû̫ҳȸҳҲҳ˲limitҳ⡣ + +**** ӳٹӲѯŻҳȿٶλҪȡidΣȻٹ + +``` +SELECT a.* FROM employee a, (select id from employee where LIMIT 1000000,10 ) b where a.id=b.id +``` + +#### 1.7.2 ޸ġѯʱǷС + +Ǹ»߲ѯݿʱѭȥݿ⣬ԿǷСҪ10ݵĻһβ500 + +**** +``` +remoteBatchQuery(param); +``` +**** + +``` + +for(int i=0;i<100000;i++){ + remoteSingleQuery(param) +} +``` + + +## 2. ƪ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/819c4665e0064db5bb3441c5bee4cb7a~tplv-k3u1fbpfcp-zoom-1.image) + +### 2.1 ϸ + +![ϸ.gif](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aaa1fee8efa745d39a2ef869caa55b4f~tplv-k3u1fbpfcp-watermark.image) + +#### 2.1.1 Ϳָ + +DZʱҪע͵Ŀָ + +- װ͵Ŀָ +- õĿָ +- EqualsߵĿָ +- ConcurrentHashMap ֧ k-vΪ null +- ϣֱӻȡԪ +- ֱӻȡ + + +``` +if(object!=null){ + String name = object.getName(); +} +``` + +#### 2.1.2 ̳߳ʹע + +- ʹ Executors.newFixedThreadPoolܻOOM⣬Ϊʹõ޽ +- ʹԶ̳߳أø̳߳һŲ +- ͬҵ̳߳ظ룬еҵһ̳߳ء +- ̳߳쳣ҪǺ + +#### 2.1.3 ԰ȫļϡ + +ڸ߲£```HashMap```ܻѭΪǷ԰ȫģԿʹ```ConcurrentHashMap```ʹЩϵʱҪעDz԰ȫġ + +- HashmapArraylistLinkedListTreeMapȶԲȫģ +- VectorHashtableConcurrentHashMapȶ԰ȫ + +#### 2.1.4 ڸʽȵ + +ճҪڸʽأΪYYYYдʱпӵŶ + + +``` +Calendar calendar = Calendar.getInstance(); +calendar.set(2019, Calendar.DECEMBER, 31); + +Date testDate = calendar.getTime(); + +SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd"); +System.out.println("2019-12-31 ת YYYY-MM-dd ʽ " + dtf.format(testDate)); +``` +н + +``` +2019-12-31 ת YYYY-MM-dd ʽ 2020-12-31 +``` + +нҲȽϳҪע⾫⣺ + + +``` +public class DoubleTest { + public static void main(String[] args) { + System.out.println(0.1+0.2); + System.out.println(1.0-0.8); + System.out.println(4.015*100); + System.out.println(123.3/100); + + double amount1 = 3.15; + double amount2 = 2.10; + if (amount1 - amount2 == 1.05){ + System.out.println("OK"); + } + } +} + +``` +н + + +``` +0.30000000000000004 +0.19999999999999996 +401.49999999999994 +1.2329999999999999 +``` + + + +#### 2.1.5 ļ + + +ȡļʱ򣬲Ҫ```Files.readAllBytes```ֱӶڴ棬OOMģʹ```BufferedReader ```һһʹ```NIO``` + + +#### 2.1.6 ʹIOԴҪر + + +ʹtry-with-resourceдļҪر + +``` +/* + * עںţݵСк + */ +try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) { + // use resources +} catch (FileNotFoundException e) { + log.error(e); +} catch (IOException e) { + log.error(e); +} +``` + + +#### 2.1.7 try...catch쳣ʹõһЩ + +- Ҫʹe.printStackTrace()ӡַܵڴռռ +- catch쳣ʹlogӡ +- ҪһException׽пܵ쳣 +- ҪѲ쳣ҵ߼ + + +#### 2.1.8 Ȳѯٸ/ɾIJһ + + +ճУִʵ־ɼȲѯǷʣõƱȥƱ + + +``` +if(selectIsAvailable(ticketId){ + 1deleteTicketById(ticketId) + 2ֽӲ +}else{ + return ûпֽȯ +} +``` + +DzִУܿģӦݿ/ɾԭԣ£ + +``` +if(deleteAvailableTicketById(ticketId) == 1){ + 1ֽӲ +}else{ + return ûпֽȯ +} +``` + + +### 2.2 ṩӿ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/818d79dfadab43e5a2bddf26a3ea2870~tplv-k3u1fbpfcp-zoom-1.image) + + +#### 2.2.1 УϷ + +ṩĽӿڣṩͻˡǰˣֻDZϵͳãҪУһεĺϷԡ + +> ݿֶΪvarchar(16),Էһ32λַ㲻Уȣݿֱ쳣ˡ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f4649f1bb3b43418ef808b8c5599c76~tplv-k3u1fbpfcp-watermark.image) + +#### 2.2.2 Ͻӿڼ + +ܶbugΪ޸˶ϽӿڣȴݵµġؼDZȽصģֱӵϵͳʧܵġֳԱ׷Ŷ~ + +иdubboķֲʽӿڣ޸ΣҪϽӿڼݡԭֻABһCͿԿ + + +``` +//Ͻӿ +void oldService(A,B){ + //½ӿڣnullC + newService(A,B,null); +} + +//½ӿڣʱɾϽӿڣҪݡ +void newService(A,B,C); +``` + + + +#### 2.2.3 ֹѹϵͳ + +˲ĴѹϵͳΪ˱ǵϵͳһҪʹ**guava ratelimiter** Ҳð↑Դ**Sentinel** + +#### 2.2.4 ӿڰȫԣǩǩȨ + + +ת˵͵ĽӿڣһҪעⰲȫԡһҪȨ**ǩǩ**Ϊûױݻ + + +#### 2.2.5 ǽӿݵ + +ӿҪݵԵģתЩҪӿڡֱ۵ҵ񳡾**ûŵ**Ľӿûholdס + +> 1. ݵȣidempotentidempotenceһѧѧڳС +> 2. ڱ.һݵȲصִӰһִеӰͬݵȺݵȷָʹͬظִУܻͬĺ + + +һ㡸ݵȼ⼸: + +1. ѯ +2. Ψһ +3. tokenƣֹظύ +4. ݿdeleteɾ +5. ֹ +6. +7. Rediszookeeper ֲʽǰRedisֲʽ +8. ״̬ݵ + +![ӿݵ.gif](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/55e09e293249431a85d68f8b217d9a8d~tplv-k3u1fbpfcp-watermark.image) + +### 2.3 õӿ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/41e0a0d34083431ca0d44f3354dca2f5~tplv-k3u1fbpfcp-zoom-1.image) + +#### 2.3.1 ʱ + +ǵñ˵Ľӿڣʱôأ + +> ٸӣǵһԶת˽ӿڣAͻBͻת100򣬳ɹʱͰѱתˮΪɹʧܵʱͰѱˮΪʧܡתϵͳʱأôأΪɹʧأ**ʱҪǺ**ҪȻʽʧˡֳ£ӿڳʱǾͿ**±תˮ**״̬·ѯԶת󣬲ѯת˳ɹļ¼ٸ±״̬״̬ + + + +#### 2.3.2 Ի + +ǵһԶhttpdubboӿڣʧˣǿԿԻơʱ·һ£ӿھ͵ʧˣԻƿû顣ԻҪЩӿڲ֧ݵȣͲʺԵġ + +#### 2.3.3 Ƿ񽵼 + + +ϵͳһṩעķûעɹ֮󣬵ԶAӿڷţԶBӿڷʼע״̬Ϊɹ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2a2180e624d4f8fadcf8fc8f63651bf~tplv-k3u1fbpfcp-zoom-1.image) + + +ýӿBʼʧܣûעʧܣҵܾͲͬˡʱǿԿǸBӿ****ṩ****Ҳ˵BӿʧܣȲʼûעɹʱʼͺ + +#### 2.3.4 Ƿ첽 + +һʹϸСڵ**ûע**ӡǿԿ첽߳ȥAӿڷţ첽BӿڷʼǼʹABӿڵʧܣǻǿԱ֤ûעɹ + +ѷЩ֪ͨӿڣŵ첽̴߳ԽͽӿںʱûŶ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07eef178cfe04048ade9fbed2f465d12~tplv-k3u1fbpfcp-zoom-1.image) + + + +#### 2.3.5 ӿ쳣 + +ǵһԶ̽ӿڣһҪ˼£˽ӿ쳣ҪôôףԻǵʧܣô֤ݵһԵȵȡ + + +## 3. ƪ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f84d96671e8e451d936e110a53bf9cf6~tplv-k3u1fbpfcp-zoom-1.image) + +### 3.1 ݿ뻺һ + +ʹû棬Խͺʱṩϵͳܡǣʹû棬һԵ⡣ + +#### 3.1.1 ֻʹģʽ + +- Cache-Aside Pattern·ģʽ +- Read-Through/Write-Throughд͸ +- Write- behind 첽д룩 + +һʹû棬**·ģʽ**: + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/38f7efc4d066409dbf65a56eb10eb90e~tplv-k3u1fbpfcp-zoom-1.image) + + +- ʱȶ棬еĻֱӷ +- ûеĻȥݿ⣬ݿȡݣ뻺ͬʱӦ + +·ģʽд̣ +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c80f0df22739439088d6a47ced7b6ae0~tplv-k3u1fbpfcp-zoom-1.image) + + +#### 3.1.2 ɾأǸ»棿 + +ڲʱ򣬵Ӧɾ滹Ǹ»أӣ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b0a52a30ec994ca7b66cf83fba5398a2~tplv-k3u1fbpfcp-zoom-1.image) + + +1. ߳Aȷһдһȸݿ +2. ߳Bٷһдڶݿ +3. ԭ߳Bȸ˻ +4. ߳A»档 + +ʱ򣬻汣AݣݣݿⱣBݣݣݲһˣݳɾȡ»򲻻⡣ + + +#### 3.1.3 Ȳݿ⻹Ȳ + +˫д£Ȳݿ⻹Ȳ棿һӣABA²Bѯȡ + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/95b0b2cd05d34ff1b66a8aa0a768cc04~tplv-k3u1fbpfcp-watermark.image) + +1. ߳Aһдһdel cache +2. ʱ߳Bһcache miss +3. ߳BDBһ +4. Ȼ߳Bcache +5. ߳AдDBµ + +Ͼݿݲһˡ汣ݣݿⱣݡˣCache-AsideģʽѡȲݿȲ档 + + +#### 3.1.4 α֤һ + +- ʱ˫ɾ +- ɾԻ +- ȡbiglog첽ɾ + + + +### 3.2 洩͸ + +> 洩͸ָѯһһڵݣڻ治ʱҪݿѯ鲻д뻺棬⽫ڵÿҪݿȥѯݿѹ + +洩͸һ㶼⼸ģ**ҵ񲻺ơҵ/ά/ʧIJڿͷǷ󹥻** +α⻺洩͸أ һַ + +- ǷǷAPIڣ**ԲУ**˷Ƿֵ +- ѯݿΪգǿ**øֵĬֵ**дĻҪ»Ա֤һԣͬʱʵĹʱ䡣ҵϱȽϳãЧ +- ʹ**¡**жǷڡһѯʱͨ¡жֵǷڣڲż²顣 + +### 3.3 ѩ + +> ѩָݴʱ䣬ѯ޴ݿѹdown + +- ѩһڴͬʱɵģԭ򣬿ͨ**ùʱùʱɢһ**һϴ̶ֵ+һСֵ5Сʱ+01800뽴ϡ +- **Redis 崻Ҳ𻺴ѩ**ҪRedis߿üȺ + + +### 3.4 + +> ָȵkeyijʱڵʱ򣬶ǡʱKeyдIJӶdb + +е񻺴ѩʵǣѩָݿѹdownֻǴDBݿ档ΪǻѩһӼɡЩΪڻijһȵkey棬ѩǺܶkey + + +֣ + +1. **ʹû**ʧЧʱȥdbݣʹijЩɹصԭӲ(Redissetnxȥɹʱȥdbݿݺû档ȥԻȡ档 +2. **ڡ**ָûùʱ䣬ȵݿҪʱ첽߳ȥºùʱ䡣 + + +### 3.5 Key + +RedisУǰѷƵʸߵkeyΪȵkeyijһȵkey󵽷ʱر󣬿ܻᵼԴ㣬崻ӶӰķ + +νkey⣿ + +- **RedisȺ**ӷƬ +- **keyhashɢ**罫һkeyΪkey1,key2keyNͬNݣNݷֲͬƬʱNеһһֵ +- **ʹö**JVMػ,RedisĶ + +### 3.6 ڴ濼 + +#### 3.6.1 + +ʹõRedisRedisڴDZȽϰģDzҪʲôݶRedisһRedisֻѯȽƵݡͬʱҪRedisҲƵsetǣ˹ʱkeyʧЧ + +ʹõDZػ棬guavaıػ棬ҲҪ + + +#### 3.6.2 Redisİڴ̭ + +Ϊ˱Redisڴ治ãRedis8ڴ̭ԱԼ~ + +> - volatile-lruڴ治дʱ˹ʱkeyʹLRUʹã㷨̭ +> - allkeys-lruڴ治дʱkeyʹLRUʹã㷨̭ +> - volatile-lfu4.0汾ڴ治дʱڹڵkeyУʹLFU㷨ɾkey +> - allkeys-lfu4.0汾ڴ治дʱkeyʹLFU㷨̭ +> - volatile-randomڴ治дʱ˹ʱkeyУ̭ݣ +> - allkeys-randomڴ治дʱkey̭ݡ +> - volatile-ttlڴ治дʱ˹ʱkeyУݹʱ̭Խڵȱ̭ +> - noevictionĬϲԣڴ治дʱдᱨ + +#### 3.6.3 ͬҵ񳡾Redisѡʺϵݽṹ + +- аʺzset +- ûϢһhash +- ϢУбlist +- ûǩ罻һset +- ֲʽһString + +### 3.7 RedisһЩпӵ + +1. ʹ keysָ +2. O(n)Ӷhgetall +3. Redismonitor +4. ֹʹflushallflushdb +5. עʹdel + +## + +ܽ60bugıע㣬ճķϣԴа⣬ +עںţ**ݵСк**ظ**˼άͼ****ȡĵĸ˼άͼ** + + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\350\201\212\350\201\212\346\227\245\345\270\270\345\274\200\345\217\221\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221bug\345\221\242\357\274\237.md.bak" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\350\201\212\350\201\212\346\227\245\345\270\270\345\274\200\345\217\221\344\270\255\357\274\214\345\246\202\344\275\225\345\207\217\345\260\221bug\345\221\242\357\274\237.md.bak" new file mode 100644 index 0000000..e69de29 diff --git "a/\346\226\271\346\241\210\350\256\276\350\256\241/README.MD" "b/\346\226\271\346\241\210\350\256\276\350\256\241/README.MD" new file mode 100644 index 0000000..e986b15 --- /dev/null +++ "b/\346\226\271\346\241\210\350\256\276\350\256\241/README.MD" @@ -0,0 +1,9 @@ +- [实现一个刷数任务,需要思考哪些维度?](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247508890&idx=1&sn=919b8a794eb4902d958ae13d1f424737&chksm=c1e05e16f697d700ee9f79e087279de6312222b8e45887d976a572b01599f1177b358ade265b&token=337310304&lang=zh_CN#rd) +- [手把手教你写设计方案](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247507937&idx=1&sn=33fd37f28675ce756e5d048b99254fcb&chksm=c1e0226df697ab7b4907fb2815c8dd2d195ea04c03a2f8fd0697c9a15a81fc639e5c5f7dab1b&token=337310304&lang=zh_CN#rd) +- [简易版,基于JWT 实现登录认证](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247508057&idx=1&sn=06b6fee69c63afbe7ebd2f81a3627341&chksm=c1e05dd5f697d4c32e38bcb58c2ecba8115ea7f94a17bc197bcb7b042a18ef07fef0f0e03878&token=337310304&lang=zh_CN#rd) +- [高并发系统设计的15个建议](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247508062&idx=1&sn=71e9647479ea71e8660d6ba48616c122&chksm=c1e05dd2f697d4c45ffd09e07fd40770e3d11591fa2b53161cd38da908cd55e41a38d5192605&token=337310304&lang=zh_CN#rd) +- [面试必备:聊聊分布式锁的多种实现!](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247506434&idx=1&sn=c6ae1ec19558626897295bbe41304b62&chksm=c1e0278ef697ae989b14f4746d1049be976d1d5744e4f9c7ec6e17d006f206edcc1c47a3e862&token=337310304&lang=zh_CN#rd) +- [并发环境下,先操作数据库还是先操作缓存?](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247508208&idx=1&sn=ac92523e33b478ad83560471338742f4&chksm=c1e05d7cf697d46aba95dc6661a8acbea0c894e44a793d054648b552a73b404aa3344d8a4826&token=337310304&lang=zh_CN#rd) + + + diff --git "a/\347\224\237\344\272\247\351\227\256\351\242\230\345\210\206\346\236\220/README.MD" "b/\347\224\237\344\272\247\351\227\256\351\242\230\345\210\206\346\236\220/README.MD" new file mode 100644 index 0000000..9d21f55 --- /dev/null +++ "b/\347\224\237\344\272\247\351\227\256\351\242\230\345\210\206\346\236\220/README.MD" @@ -0,0 +1,9 @@ +## 生产问题分析 + +- [内存泄漏问题的分析和解决方案](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487986&idx=1&sn=d681a585ac489703788e3baa48eb9aa3&chksm=cf21cedbf85647cd23bbab9dfec63e6877f83c34efb19bd16075d5d90fea91d3f4a20fc77921&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [生产问题分析!delete in子查询不走索引?!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495170&idx=1&sn=ce914de3abdb0d887e286b680b25111f&chksm=cf22312bf855b83d31a00da110626747df8e69fca1bc310642c56e39d663b006a8105f9fb1e1&token=1495321435&lang=zh_CN&scene=21#wechat_redirect) +- [手把手教你分析Mysql死锁问题](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487979&idx=1&sn=588c83d77a8851f3b3c18cd68ed9c454&chksm=cf21cec2f85647d4a77cc239ae9a4cfd31bb8832be3d98540a08ea8b4a1f46b38cf736210a02&token=1495321435&lang=zh_CN&scene=21#wechat_redirect) +- [线程池运用不当的一次线上事故](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487992&idx=1&sn=733335f2f69d743712915abc99f83b1d&chksm=cf21ced1f85647c7ab8c5d8bc4e8206b04acb5fd4feb94b8d088a782ed458b82aab69dba82aa&token=1990771297&lang=zh_CN#rd) +- [盘点MySQL慢查询的12个原因](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247499624&idx=1&sn=561b9cb7fe831ca7cb2d9fd65691e85e&chksm=cf222041f855a957ac50c0a53baaec6d26be32427259b2974450620f33a8c834419fe535e83d&token=1990771297&lang=zh_CN#rd) +- [线程池如何监控,才能帮助开发者快速定位线上错误?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497444&idx=1&sn=1b2cc8b4685413149e46c814e468c6e6&chksm=cf2229cdf855a0db5f2da881d27c69f11c69480552985baa2a08cbe4d5a48bad7fb31a78dd5a&token=1990771297&lang=zh_CN#rd) +- [数据库死锁排查思路分享](https://mp.weixin.qq.com/s?__biz=MzkyMzU5Mzk1NQ==&mid=2247507770&idx=1&sn=b84b20aca057b34d511a501ff91941b5&chksm=c1e022b6f697aba05248128cb82f93aed341b1cc80e6d568c7150a4ffa6775692c7c9fa423a3&token=337310304&lang=zh_CN#rd) diff --git "a/\347\250\213\345\272\217\344\272\272\347\224\237&\351\235\242\350\257\225\345\273\272\350\256\256/README.MD" "b/\347\250\213\345\272\217\344\272\272\347\224\237&\351\235\242\350\257\225\345\273\272\350\256\256/README.MD" new file mode 100644 index 0000000..ae9424d --- /dev/null +++ "b/\347\250\213\345\272\217\344\272\272\347\224\237&\351\235\242\350\257\225\345\273\272\350\256\256/README.MD" @@ -0,0 +1,6 @@ +## 程序人生 + +- [跟大家聊聊天,我周末都在干啥](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247493222&idx=1&sn=29eb95b01b54bed2abbcf5a72285b38a&chksm=cf22394ff855b059b29ffb562e22d8ecc048caa743eb5c6257ad474676940ba8d36840f075ed&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [跟大家聊聊如何学习](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247495313&idx=1&sn=7f521db08e84b07177d847c60071d709&chksm=cf2231b8f855b8ae765f2dd584994836c0b74ce0ef761653233c3af04f38b4a1aa1833f7a55a&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [写了两年文章,终于破万!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247489788&idx=1&sn=66efbc1b718915bfd8996b521d317a55&chksm=cf21c7d5f8564ec3928957d3c23959f5cb99d9f9bd2c1bab0dcf1750a6a017c3869189a3651a&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [夏天的风,我永远记得~](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247487989&idx=2&sn=9eb923d4c8c22bee1a408e4f86983f65&chksm=cf21cedcf85647cac6fe4bfa6d732856fd0335f4fcadad4d1e0dd10702e95905e06c9e38e8e8&token=162724582&lang=zh_CN&scene=21#wechat_redirect) \ No newline at end of file diff --git "a/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\267\245\345\205\267\347\257\207/README.MD" "b/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\267\245\345\205\267\347\257\207/README.MD" new file mode 100644 index 0000000..73e3dce --- /dev/null +++ "b/\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\345\267\245\345\205\267\347\257\207/README.MD" @@ -0,0 +1,8 @@ + +## 程序员工具篇 + +- [用代码画时序图!YYDS](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247500478&idx=1&sn=ec674e3eadba9bb87849292f46f84989&chksm=cf221d97f8559481fae8f0e1871ae19499568b3e49980e92018c4a5acdcf743a37da79c2436d&token=1990771297&lang=zh_CN#rd) +- [程序员必备基础:Git 命令全方位学习](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488013&idx=1&sn=7011a51a347e3da2cf8f8540b4d9a5d6&chksm=cf21cd24f8564432d74bc13551ebdeae71a71ea31e339c7a8f1f42f181078b5192475d598626&token=1569911403&lang=zh_CN&scene=21#wechat_redirect) +- [MyBatis 插件原理与实战](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498815&idx=1&sn=737e8f92ff526dac408af7a409f3a3d4&chksm=cf222316f855aa007fe16f7bca0636c552f238deb766bb54c34db7b633c13451fc91a4fe8a3e&token=1990771297&lang=zh_CN#rd) +- [更快的Maven来了,速度提升了8倍!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497470&idx=1&sn=7a3a5bb48f7d3b1a627460b698e7e9a0&chksm=cf2229d7f855a0c1e892c23f7690e6ab1a745040142672b982a3934c8307901d0be03dff3cff&token=1990771297&lang=zh_CN#rd) +- [因为知道了30+款在线工具,我的工作效率提升500%!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488041&idx=1&sn=26d55c23ecd439860c4d9865bec61976&chksm=cf21cd00f8564416fe991974d24a51798d925b2e79d62935accf02aa6895c7b02adf48e9e207&token=1990771297&lang=zh_CN#rd) \ No newline at end of file diff --git "a/\347\274\223\345\255\230Redis\346\200\273\347\273\223/README.MD" "b/\347\274\223\345\255\230Redis\346\200\273\347\273\223/README.MD" new file mode 100644 index 0000000..996dc81 --- /dev/null +++ "b/\347\274\223\345\255\230Redis\346\200\273\347\273\223/README.MD" @@ -0,0 +1,7 @@ +## 缓存 + +- [大厂经典面试题:Redis为什么这么快?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490736&idx=1&sn=95377e729b27f0afefbaa5f20239fc9d&chksm=cf21c399f8564a8ff5239fbaa86d616a48086b47b3bb03c8ccc1d3cc066e41c75e16638c3fc8&token=1495321435&lang=zh_CN&scene=21#wechat_redirect) +- [美团二面:Redis与MySQL双写一致性如何保证?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490243&idx=1&sn=ff11c3aab9ada3b16d7f2b57c846d567&chksm=cf21c5eaf8564cfc59e3d0d56fd02b0f5513015005f498381be4d12db462442a49aabe4159ef&token=1495321435&lang=zh_CN&scene=21#wechat_redirect) +- [使用Redis,你必须知道的21个注意要点](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488325&idx=1&sn=6d9bbe5bf2f2f2904755de5c786fb21b&chksm=cf21cc6cf856457a9d23b3e25ec48107a582e709f05964dfdb5ba77e9a239d8307334c485fdf&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [七种方案!探讨Redis分布式锁的正确使用姿势](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488142&idx=1&sn=79a304efae7a814b6f71bbbc53810c0c&chksm=cf21cda7f85644b11ff80323defb90193bc1780b45c1c6081f00da85d665fd9eb32cc934b5cf&token=162724582&lang=zh_CN&scene=21#wechat_redirect) +- [2W字!详解20道Redis经典面试题!(珍藏版)](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247494124&idx=1&sn=c185f7d999d5f006608d05707a8a7eea&chksm=cf2236c5f855bfd329c6e2ee27f23f8131ebcd312960190a10f1a819d67f07a21a08ad17f263&token=162724582&lang=zh_CN&scene=21#wechat_redirect) \ No newline at end of file diff --git "a/\347\274\223\345\255\230Redis\346\200\273\347\273\223/\344\270\203\347\247\215\346\226\271\346\241\210\345\257\271\346\257\224\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/\347\274\223\345\255\230Redis\346\200\273\347\273\223/\344\270\203\347\247\215\346\226\271\346\241\210\345\257\271\346\257\224\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 0000000..d861cdb --- /dev/null +++ "b/\347\274\223\345\255\230Redis\346\200\273\347\273\223/\344\270\203\347\247\215\346\226\271\346\241\210\345\257\271\346\257\224\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,225 @@ +### 前言 +日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方,欢迎大家指出哈,一起学习一起进步。 + + +公众号:**捡田螺的小男孩** + +- 什么是分布式锁 +- 方案一:SETNX + EXPIRE +- 方案二:SETNX + value值是(系统时间+过期时间) +- 方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令) +- 方案四:SET的扩展命令(SET EX PX NX) +- 方案五:SET EX PX NX + 校验唯一随机值,再释放锁 +- 方案六: 开源框架:Redisson +- 方案七:多机实现的分布式锁Redlock + +- github地址,感谢每颗star +> https://github.com/whx123/JavaHome + + +### 什么是分布式锁 + +> 分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。 + +我们先来看下,一把靠谱的分布式锁应该有哪些特征: + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/42884a1613344c11be5fef3b9e8ed7c5~tplv-k3u1fbpfcp-zoom-1.image) + +- **互斥性**: 任意时刻,只有一个客户端能持有锁。 +- **锁超时释放**:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。 +- **可重入性**:一个线程如果获取了锁之后,可以再次对其请求加锁。 +- **高性能和高可用**:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。 +- **安全性**:锁只能被持有的客户端删除,不能被其他客户端删除 + +### Redis分布式锁方案一:SETNX + EXPIRE + +提到Redis的分布式锁,很多小伙伴马上就会想到```setnx```+ ```expire```命令。即先用```setnx```来抢锁,如果抢到之后,再用```expire```给锁设置一个过期时间,防止锁忘记了释放。 + +> SETNX 是SET IF NOT EXISTS的简写.日常命令格式是SETNX key value,如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。 + +假设某电商网站的某商品做秒杀活动,key可以设置为key_resource_id,value设置任意值,伪代码如下: + +``` +if(jedis.setnx(key_resource_id,lock_value) == 1){ //加锁 + expire(key_resource_id,100); //设置过期时间 + try { + do something //业务请求 + }catch(){ +  } +  finally { + jedis.del(key_resource_id); //释放锁 + } +} +``` +但是这个方案中,```setnx```和```expire```两个命令分开了,**不是原子操作**。如果执行完```setnx```加锁,正要执行```expire```设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,**别的线程永远获取不到锁啦**。 + + +### Redis分布式锁方案二:SETNX + value值是(系统时间+过期时间) + +为了解决方案一,**发生异常锁得不到释放的场景**,有小伙伴认为,可以把过期时间放到```setnx```的value值里面。如果加锁失败,再拿出value值校验一下即可。加锁代码如下: +``` +long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间 +String expiresStr = String.valueOf(expires); + +// 如果当前锁不存在,返回加锁成功 +if (jedis.setnx(key_resource_id, expiresStr) == 1) { + return true; +} +// 如果锁已经存在,获取锁的过期时间 +String currentValueStr = jedis.get(key_resource_id); + +// 如果获取到的过期时间,小于系统当前时间,表示已经过期 +if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { + + // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈) + String oldValueStr = jedis.getSet(key_resource_id, expiresStr); + + if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { + // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁 + return true; + } +} + +//其他情况,均返回加锁失败 +return false; +} +``` + +这个方案的优点是,巧妙移除```expire```单独设置过期时间的操作,把**过期时间放到setnx的value值**里面来。解决了方案一发生异常,锁得不到释放的问题。但是这个方案还有别的缺点: + +> - 过期时间是客户端自己生成的(System.currentTimeMillis()是当前系统的时间),必须要求分布式环境下,每个客户端的时间必须同步。 +> - 如果锁过期的时候,并发多个客户端同时请求过来,都执行jedis.getSet(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖 +> - 该锁没有保存持有者的唯一标识,可能被别的客户端释放/解锁。 + + +### Redis分布式锁方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令) + +实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令),lua脚本如下: +``` +if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then + redis.call('expire',KEYS[1],ARGV[2]) +else + return 0 +end; +``` +加锁代码如下: + +``` + String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" + + " redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"; +Object result = jedis.eval(lua_scripts, Collections.singletonList(key_resource_id), Collections.singletonList(values)); +//判断是否成功 +return result.equals(1L); +``` +这个方案还是有缺点的哦,至于哪些缺点,你先思考一下。也可以想下。跟方案二对比,哪个更好? + +### Redis分布式锁方案方案四:SET的扩展命令(SET EX PX NX) + +除了使用,使用Lua脚本,保证```SETNX + EXPIRE```两条指令的原子性,我们还可以巧用Redis的SET指令扩展参数!(```SET key value[EX seconds][PX milliseconds][NX|XX]```),它也是原子性的! + +> SET key value[EX seconds][PX milliseconds][NX|XX] +> - NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。 +> - EX seconds :设定key的过期时间,时间单位是秒。 +> - PX milliseconds: 设定key的过期时间,单位为毫秒 +> - XX: 仅当key存在时设置值 + +伪代码demo如下: +``` +if(jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1){ //加锁 + try { + do something //业务处理 + }catch(){ +  } +  finally { + jedis.del(key_resource_id); //释放锁 + } +} +``` + +但是呢,这个方案还是可能存在问题: + +- 问题一:**锁过期释放了,业务还没执行完**。假设线程a获取锁成功,一直在执行临界区的代码。但是100s过去后,它还没执行完。但是,这时候锁已经过期了,此时线程b又请求过来。显然线程b就可以获得锁成功,也开始执行临界区的代码。那么问题就来了,临界区的业务代码都不是严格串行执行的啦。 +- 问题二:**锁被别的线程误删**。假设线程a执行完后,去释放锁。但是它不知道当前的锁可能是线程b持有的(线程a去释放锁时,有可能过期时间已经到了,此时线程b进来占有了锁)。那线程a就把线程b的锁释放掉了,但是线程b临界区业务代码可能都还没执行完呢。 + +### 方案五:SET EX PX NX + 校验唯一随机值,再删除 + +既然锁可能被别的线程误删,那我们给value值设置一个标记当前线程唯一的随机数,在删除的时候,校验一下,不就OK了嘛。伪代码如下: +``` +if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加锁 + try { + do something //业务处理 + }catch(){ +  } +  finally { + //判断是不是当前线程加的锁,是才释放 + if (uni_request_id.equals(jedis.get(key_resource_id))) { + jedis.del(lockKey); //释放锁 + } + } +} +``` + +在这里,**判断是不是当前线程加的锁**和**释放锁**不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁。 + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9237655e6b1a47038d2774231e507e11~tplv-k3u1fbpfcp-watermark.image) + +为了更严谨,一般也是用lua脚本代替。lua脚本如下: +``` +if redis.call('get',KEYS[1]) == ARGV[1] then + return redis.call('del',KEYS[1]) +else + return 0 +end; +``` + +### Redis分布式锁方案六:Redisson框架 + +方案五还是可能存在**锁过期释放,业务没执行完**的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。 + +当前开源框架Redisson解决了这个问题。我们一起来看下Redisson底层原理图吧: + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/367cd1a7a3fb4d398988e4166416d71d~tplv-k3u1fbpfcp-zoom-1.image) + + +只要线程一加锁成功,就会启动一个```watch dog```看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了**锁过期释放,业务没执行完**问题。 + +### Redis分布式锁方案七:多机实现的分布式锁Redlock+Redisson + +前面六种方案都只是基于单机版的讨论,还不是很完美。其实Redis一般都是集群部署的: + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7349794feeee458aa71c27f27a0b2428~tplv-k3u1fbpfcp-watermark.image) + +如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。 + +为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock。Redlock核心思想是这样的: +> 搞多个Redis master部署,以保证它们不会同时宕掉。并且这些master节点是完全相互独立的,相互之间不存在数据同步。同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁。 + +我们假设当前有5个Redis master节点,在5台服务器上面运行这些Redis实例。 + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0df0a36c7ccd439291a8a869ff4ddad3~tplv-k3u1fbpfcp-watermark.image) + +RedLock的实现步骤:如下 +> - 1.获取当前时间,以毫秒为单位。 +> - 2.按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。 +> - 3.客户端使用当前时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都获得锁,并且使用的时间小于锁失效时间时,锁才算获取成功。(如上图,10s> 30ms+40ms+50ms+4m0s+50ms) +> - 如果取到了锁,key的真正有效时间就变啦,需要减去获取锁所使用的时间。 +> - 如果获取锁失败(没有在至少N/2+1个master实例取到锁,有或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)。 + +简化下步骤就是: +- 按顺序向5个master节点请求加锁 +- 根据设置的超时时间来判断,是不是要跳过该master节点。 +- 如果大于等于三个节点加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。 +- 如果获取锁失败,解锁! + +Redisson实现了redLock版本的锁,有兴趣的小伙伴,可以去了解一下哈~ + +### 公众号 +- 欢迎关注公众号:捡田螺的小男孩 + +### 参考与感谢 + +- [redis系列:分布式锁](https://juejin.cn/post/6844903656911798285 "redis系列:分布式锁") +- [浅析 Redis 分布式锁解决方案](https://www.infoq.cn/article/dvaaj71f4fbqsxmgvdce "浅析 Redis 分布式锁解决方案") +- [细说Redis分布式锁🔒](https://juejin.cn/post/6844904082860146695#heading-3 "细说Redis分布式锁🔒") +- [Redlock:Redis分布式锁最牛逼的实现](https://mp.weixin.qq.com/s?__biz=MzU5ODUwNzY1Nw==&mid=2247484155&idx=1&sn=0c73f45f2f641ba0bf4399f57170ac9b&scene=21#wechat_redirect) + diff --git "a/\347\274\223\345\255\230Redis\346\200\273\347\273\223/\344\275\277\347\224\250Redis\347\232\20421\344\270\252\346\263\250\346\204\217\347\202\271.md" "b/\347\274\223\345\255\230Redis\346\200\273\347\273\223/\344\275\277\347\224\250Redis\347\232\20421\344\270\252\346\263\250\346\204\217\347\202\271.md" new file mode 100644 index 0000000..6a4364e --- /dev/null +++ "b/\347\274\223\345\255\230Redis\346\200\273\347\273\223/\344\275\277\347\224\250Redis\347\232\20421\344\270\252\346\263\250\346\204\217\347\202\271.md" @@ -0,0 +1,386 @@ +### ǰ + +ѧϰRedis֪ʶ˰redis淶ԼRedisάⱾ顣ʹù淶пӵĿʵսάĸʹRedis21ע㣬ϣԴаһѧϰ + +ںţ**ݵСк** +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c59661f74a014a63a4527939096f11aa~tplv-k3u1fbpfcp-watermark.image) + +## 1Redisʹù淶 + +### 1.1 keyĹ淶Ҫ + +RediskeyʱҪע⼸㣺 + +> - ҵΪkeyǰ׺ðŸԷֹkeyͻǡ磬live:rank:1 +> - ȷkey£keyijȾС30ַ +> - keyַֹո񡢻С˫Լתַ +> - RediskeyttlԱ֤ʹõKeyܱʱ̭ + +### 1.2valueĹ淶Ҫ + +RedisvalueֵõŶ + +**һ**洢bigKeyǻģᵼѯڴȵȡ +> - StringͣvalueС10kڡ +> - hashlistsetzsetͣԪظһ㲻5000 + + +**ڶ**Ҫѡʺϵ͡СֻRedisStringͣsetgetʵϣRedis ṩ**ḻݽṹ**Щҵ񳡾ʺ```hashzset```ݽ + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1874e69c709940fd80d66cc8ab683135~tplv-k3u1fbpfcp-watermark.image) + +**** + +``` +set user:666:name jay +set user:666:age 18 +``` + +**** + +``` +hmset user:666 name jay age 18 +``` + +### 1.3. Keyùʱ䣬ͬʱעⲻͬҵkeyʱɢһ + +- ΪRedisǴڴеģڴԴǺܱġ +- һǰRedisã**ݿ**keyھͲ̫ +- ˣkeyһ㽨**expireùʱ** + + +keyijʱ㼯йڣڵǸʱ㣬Redisܻڿ٣**ѩ**һ㲻ͬҵkeyʱӦ÷ɢһЩʱͬҵģҲʱϼһֵùʱɢһЩ + + +### 1.4.ʹЧ + +ճдSQLʱ򣬶֪Чʻߣһθ50ѭ50ΣÿθһЧʸߡʵRedisҲ + +RedisͻִһɷΪ4̣1.-> 2.Ŷ-> 3.ִ-> 4. ؽ14 ΪRRTִʱ䣩 Redisṩ**mgetmset**ȣЧԼRRTأ󲿷ֵDz֧ģhgetallûmhgetallڡ**Pipeline** Խ⡣ + +> Pipelineʲô?ܽһRedisװͨһRTTRedisٽRedisִн˳򷵻ظͻ. + +ûʹPipelineִnģͣ + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b71283614a3344c4afac8ac82438fa44~tplv-k3u1fbpfcp-watermark.image) + +ʹPipelineִnҪ1RTTģ£ + +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b865d241c02d45e7a88540ab7f70280a~tplv-k3u1fbpfcp-watermark.image) + +## 2Redis пӵЩ + + +### 2.1. ```O(n)```Ӷ```hgetall``````smember``````lrange``` + +ΪRedisǵִ߳ġhgetallsmemberʱ临ӶΪO(n)nʱᵼ Redis CPU 쭸ߣִС + +> hgetallsmemberlrangeЩһʹãҪۺȷnֵȥ +> hgetallϣԪnȽ϶Ļȿʹ**hscan** + + +### 2.2 Redismonitor + +Redis Monitor ʵʱӡRedisյ֪ͻ˶redisЩͿMonitor 鿴һ****öѣҪãΪ**monitorܵredisڴ** + +> monitorģǽϵģὫRedisִеһRedisQPSǺܸߵģҲִmonitorRedisMonitorͻ˵ֻдҲռ˴Redisڴ档 + + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3610a4a95fe4995862d9ddd878269fe~tplv-k3u1fbpfcp-watermark.image) + + +### 2.3ʹ keysָ + +Redis Keys ڲзϸģʽpatternkey鿴Redis ij͵keyжٸС뵽keys£ + +``` +keys keyǰ׺* +``` + +ǣredis```keys```DZƥģӶ```On```ݿԽԽ֪redisǵ̵߳ģݱȽ϶Ļkeysָͻᵼredis߳ϷҲͣˣֱִָ꣬Żָˣ**һҪʹkeysָ**ٷĵҲ + +> Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using sets. + +ʵʹscanָͬkeysһṩģʽƥ书ܡĸӶҲ O(n)ͨαֲУ**redis߳**;ǻһ**ظ**Ҫ**ͻһȥ** + +> scan֧ʽʽҲȱģٸӣ ʹ SMEMBERS ԷؼϼǰԪأ Ƕ SCAN ʽ˵ ΪڶԼʽĹУ ܻᱻ޸ģ ʽֻܶԱصԪṩ޵ı֤ + + +### 2.4 ֹʹflushallflushdb + +> - Flushall Redis (ɾݿ key ) +> - Flushdb յǰݿе key + +ԭԵģִֹСһʼִУִʧܵġ + +### 2.5 עʹdel + +ɾkeyһʹʲôֱdelɾһkeyֱʹdelȻû⡣ǣdelʱ临ӶǶǷ̽һ£ +- ɾһString͵keyʱ临ӶȾ```O1```**ֱdel** +- ɾһList/Hash/Set/ZSetʱĸӶ```O(n)```, nʾԪظ + +ˣɾһList/Hash/Set/ZSet͵keyʱԪԽ࣬Խ**nܴʱҪע**̵߳ġôdelӦôɾأ + +> - Listִͣ```lpoprpop```ֱԪɾɡ +> - Hash/Set/ZSetִͣ```hscan/sscan/scan```ѯִ```hdel/srem/zrem```ɾÿԪء + +### 2.6 ʹSORTSINTERȸӶȹߵ + +ִиӶȽϸߵĸ CPU Դ̡߳Ҫִ```SORTSINTERSINTERSTOREZUNIONSTOREZINTERSTORE```Ⱦۺһ㽨ŵͻִС + +## 3ĿʵսܿӲ + +### 3.1 ֲʽʹõע + +ֲʽʵǣƷֲʽϵͳ̹ͬͬʹԴһʵ֡ɱµȵҵ񳡾ҪõֲʽǾʹRedisΪֲʽҪЩע㣺 + +#### 3.1.1 SETNX + EXPIREֿдʹʵַ +``` +ifjedis.setnx(key_resource_id,lock_value) == 1{ // + expirekey_resource_id100; //ùʱ + try { + do something //ҵ + }catch(){ +} +finally { + jedis.del(key_resource_id); //ͷ + } +} +``` +ִ```setnx```ҪִexpireùʱʱcrashҪάˣô͡ϡˣ**߳Զȡ**һֲʽôʵ֡ + +#### 3.1.2 SETNX + valueֵǹʱ (ЩСôʵ֣п) + + +``` +long expires = System.currentTimeMillis() + expireTime; //ϵͳʱ+õĹʱ +String expiresStr = String.valueOf(expires); + +// ǰڣؼɹ +if (jedis.setnx(key_resource_id, expiresStr) == 1) { + return true; +} +// ѾڣȡĹʱ +String currentValueStr = jedis.get(key_resource_id); + +// ȡĹʱ䣬Сϵͳǰʱ䣬ʾѾ +if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { + + // ѹڣȡһĹʱ䣬Ĺʱ䣨˽redisgetSetС飬ȥ¹ + String oldValueStr = jedis.getSet(key_resource_id, expiresStr); + + if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { + // Ƕֻ̲߳һ̵ֵ߳͵ǰֵͬſԼ + return true; + } +} + +//ؼʧ +return false; +} +``` +ַ**ȱ** +> - ʱǿͻԼɵģֲʽ£ÿͻ˵ʱͬ +> - ûбߵΨһʶܱĿͻͷ/ +> - ڵʱ򣬲ͻͬʱִ```jedis.getSet()```ֻһͻ˼ɹǸÿͻĹʱ䣬ܱĿͻ˸ǡ + +#### 3.1.3 SETչSET EX PX NXעܴڵ⣩ + +``` +ifjedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1{ // + try { + do something //ҵ + }catch(){ +} +finally { + jedis.del(key_resource_id); //ͷ + } +} +``` + +ǿܴ⣺ +- ͷˣҵûִꡣ +- ߳ɾ + + +#### 3.1.4 SET EX PX NX + УΨһֵ,ɾɾ⣬Ǵڣҵûִ⣩ + +``` +ifjedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1{ // + try { + do something //ҵ + }catch(){ +} +finally { + //жDzǵǰ̼߳ӵ,Dzͷ + if (uni_request_id.equals(jedis.get(key_resource_id))) { + jedis.del(lockKey); //ͷ + } + } +} +``` + +жDzǵǰ̼߳ӵͷһԭӲjedis.del()ͷʱ򣬿Ѿڵǰͻˣ˼ӵ + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/88518412ca20489cbd38498d883639db~tplv-k3u1fbpfcp-watermark.image) + + +һҲluaű档luaű£ + +``` +if redis.call('get',KEYS[1]) == ARGV[1] then + return redis.call('del',KEYS[1]) +else + return 0 +end; +``` + + +#### 3.1.5 Redisson + Redlock㷨 ͷţҵûִ+ + +Redisson ʹһ```Watch dog```ͷţҵûִ⣬Redissonԭͼ: +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a80964e2ed1d4e739dc2a62ac73110da~tplv-k3u1fbpfcp-watermark.image) + +ϵķֲʽڵ⣺ +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b9e6b6050874be98f6a256291552459~tplv-k3u1fbpfcp-watermark.image) +> ߳һRedismasterڵõǼkeyûͬslaveڵ㡣ǡʱmasterڵ㷢ϣһslaveڵͻΪmasterڵ㡣̶߳ͿԻȡͬkey߳һҲѾõˣİȫԾûˡ + +Ե⣬ʹRedlock㷨ȤѿԿƪ¹[ַ̽Redisֲʽȷʹ](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488142&idx=1&sn=79a304efae7a814b6f71bbbc53810c0c&chksm=cf21cda7f85644b11ff80323defb90193bc1780b45c1c6081f00da85d665fd9eb32cc934b5cf&token=1120875912&lang=zh_CN#rd) + +### 3.2 һע + +- Ƕȶ棬ݿ +- дȸݿ⣬д +- ÿθݺҪ +- һ㶼ҪһĹʧЧ +- һҪߵĻʹbiglog+MQ֤ + +ȤѣԿƪ¹[£Ȳݿ⻹Ȳ棿](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247488079&idx=1&sn=49255f6c0c540deeb3333bcf86d6c77c&chksm=cf21cd66f856447061b5eca47f51199e120a9eaa83fa7546b4bd2667218403ccc97e726ab456&token=1120875912&lang=zh_CN#rd) + +### 3.3 RedisƵsetǣ֮ǰõĹʱЧ + +֪RedisݽṹͣǿùʱġһַѾ˹ʱ䣬ȥͻᵼ֮ǰĹʱЧ + +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/29a81b1775e044e2b1198dff2e3b1ca1~tplv-k3u1fbpfcp-watermark.image) + +Redis ```setKey```Դ£ +``` +void setKey(redisDb *db,robj *key,robj *val) { + if(lookupKeyWrite(db,key)==NULL) { + dbAdd(db,key,val); + }else{ + dbOverwrite(db,key,val); + } + incrRefCount(val); + removeExpire(db,key); //ȥʱ + signalModifiedKey(db,key); +} +``` + +ʵҵ񿪷УͬʱҪRedisƵsetǣ˹ʱkeyʧЧС׷ + +### 3.4 洩͸ + +һĻʹ÷ʽˣȲ»棬ֵУֱӷأûУȥݿ⣬Ȼݿֵµ棬ٷء + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59c69359b2c249ad8954b7da7c0e6fb8~tplv-k3u1fbpfcp-watermark.image) + +**洩͸**ָѯһһڵݣڻDzʱҪݿѯ鲻д뻺棬⽫ڵÿҪݿȥѯݿѹ + +> ͨ׵˵ʱݿⶼûijֵͻᵼÿζֵIJѯ󶼻ᴩ͸ݿ⣬ǻ洩͸ + +洩͸һ㶼⼸ģ + +- **ҵ񲻺**ûûػÿȥ棬ѯijuseridѯûػ +- **ҵ/ά/ʧIJ**绺ݿݶɾˡ +- **ڿͷǷ󹥻**ڿ͹ǷԶȡڵҵݡ + +**α⻺洩͸أ** һַ + +- 1. ǷǷAPIڣԲУ飬˷Ƿֵ +- 2. ѯݿΪգǿԸøֵĬֵдĻҪ»Ա֤һԣͬʱʵĹʱ䡣ҵϱȽϳãЧ +- 3. ʹò¡жǷڡһѯʱͨ¡жֵǷڣڲż²顣 +> ¡ԭɳʼֵΪ0λͼNϣɡһһkeyNhash㷨ȡNֵڱнNֵɢк趨Ϊ1Ȼʱض⼸λöΪ1ô¡жϸkeyڡ + + +### 3.5 ѩ + +**ѩ** ָݴʱ䣬ѯ޴ֱӷݿ⣬ݿѹdown + +- ѩһڴͬʱɵģԭ򣬿ͨùʱùʱɢһ㡣һϴ̶ֵ+һСֵ5Сʱ+01800뽴ϡ +- Redis 崻Ҳ𻺴ѩҪRedis߿üȺ + + +### 3.6 + +**** ָȵkeyijʱڵʱ򣬶ǡʱKeyдIJӶdb + +еʵǣѩָݿѹdownֻǴDBݿ档ΪǻѩһӼɡЩΪڻijһȵkey棬ѩǺܶkey + +֣ + +- **1.ʹû**ʧЧʱȥdbݣʹijЩɹصԭӲ(Redissetnxȥɹʱȥdbݿݺû档ȥԻȡ档 +- **2. ڡ**ָûùʱ䣬ȵݿҪʱ첽߳ȥºùʱ䡣 + +### 3.7key + +RedisУǰѷƵʸߵkeyΪȵkeyijһȵkey󵽷ʱر󣬿ܻᵼԴ㣬崻ӶӰķ + +ȵKeyôأҪԭ +> - ûѵԶݣɱȵŵȶдٵij +> - ƬУRediܣ̶keyHashͬһ̨˲󣬳ƿȵKey⡣ + +ôճУʶȵkeyأ +> - ƾжЩKey +> - ͻͳϱ +> - ϱ + +νkey⣿ + +> - RedisȺݣӷƬ +> - keyhashɢУ罫һkeyΪkey1,key2keyNͬNݣNݷֲͬƬʱNеһһֵ +> - ʹö棬JVMػ,RedisĶ + +## 4. Redisά + +### 4.1 ʹóӶǶӣҺÿͻ˵ӳ + +- ʹöӣÿζҪ TCP ֡Ĵλ֣ӺʱȻӵĻһӣredisһֱʹãϿԼٽredisʱ䡣 +- ӳؿʵڿͻ˽ӲҲͷţҪʹӵʱ򣬲ÿζӣʡ˺ʱҪòʱ䲻 RedisʱҲ輰ʱͷԴ + +### 4.2 ֻʹ db0 + +Redis-standaloneֹܹʹ÷db0.ԭ + +- һӣRedisִselect 0select 1лܡ +- Redis Cluster ֻ֧ db0ҪǨƵĻɱ + +### 4.3 maxmemory + ǡ̭ԡ + +Ϊ˷ֹڴѹ͡ЩʱҵˣrediskeyʹãڴֱӲˣάСҲǼӴڴˡѵredisֱҵҪʵҵѡmaxmemory-policy(ڴ̭)úùʱ䡣һ8ڴ̭ԣ + + - volatile-lruڴ治дʱ˹ʱkeyʹLRUʹã㷨̭ +- allkeys-lruڴ治дʱkeyʹLRUʹã㷨̭ +- volatile-lfu4.0汾ڴ治дʱڹڵkeyУʹLFU㷨ɾkey +- allkeys-lfu4.0汾ڴ治дʱkeyʹLFU㷨̭ +- volatile-randomڴ治дʱ˹ʱkeyУ̭ݣ +- allkeys-randomڴ治дʱkey̭ݡ +- volatile-ttlڴ治дʱ˹ʱkeyУݹʱ̭Խڵȱ̭ +- noevictionĬϲԣڴ治дʱдᱨ + +### 4.4 lazy-free + +Redis4.0+汾֧lazy-freeƣRedisbigKeyڣlazy-freeRedis ɾһ bigkey ʱͷڴĺʱŵ̨߳ȥִУٶ̵߳Ӱ졣 + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f6c5e7c58fa488ea0540f671c08081b~tplv-k3u1fbpfcp-watermark.image) + +### οл + +- [Redis ǧҪKEYSȻᰤ](https://www.cnblogs.com/tonyY/p/12175032.html) +- [Redis淶](https://developer.aliyun.com/article/531067) +- [Redis ʵָϣ7ά+43ʹù淶](https://mp.weixin.qq.com/s/2sUWnpJCvkJ8-7XSGLdesA) +- [RedisĻ洩͸¡BloomFilter](https://blog.csdn.net/wx1528159409/article/details/88357728) +- [ Redis ʵܽ](https://www.shangmayuan.com/a/d2f178b548a64c25854a9750.html) + diff --git "a/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\347\274\226\347\250\213\345\221\250\346\212\245\344\272\214.md" "b/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\347\274\226\347\250\213\345\221\250\346\212\245\344\272\214.md" new file mode 100644 index 0000000..b5bb492 --- /dev/null +++ "b/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\347\274\226\347\250\213\345\221\250\346\212\245\344\272\214.md" @@ -0,0 +1,27 @@ +**问题一:** + +我现在用了一个静态线程池,用于整个请求共享。设置的参数是核心线程10个,最大线程147个,阻塞队列2048,策略是超过就丢弃并且抛异常。 +当qps在1000的时候,由于一个请求我需要分成三次去请求不同的接口,就相当于有3000个调用接口的请求。 但是请求用的是实时接口,如果该接口响应很慢比如10秒钟,那么这147个线程就全部挂起了,没有空余的线程去处理新的业务了。整个系统的响应就非常慢了。 + +群上回复答案: +> - 响应很慢的接口走异步,把返回的数据丢到queue里面。 +> - 应该是10秒的接口得改 +> - 接口慢了的话,看下能不能用redis +> - 合并请求,减少网络传输时间 +> - 优化接口肯定需要,从源头解决 + +我觉得,先理解好线程池原理吧,就是,来个请求就拿个核心线程去处理,如果核心线程用完了,就放队列,队列满了,就拿非核心线程去处理,处理不过来之后,就根据策略去处理,线程干完活又会被拉取干别的~然后呢,就算每秒以前个请求过来,最多也是线程池供应不过来,然后抛弃一部分请求,跟响应慢不是一个因果关系。 + +![](https://imgkr2.cn-bj.ufileos.com/5f138381-05f3-4aa2-a344-98e8b970a920.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=yJuf5adRW5I%252BSl3%252FWX1oyVr9%252Ffo%253D&Expires=1603008626) + + +其实,如果每个请求30毫秒处理完,可能你的线程池也够用了。但是呢,如果你一个请求,接口处理10s,也就是说10秒,它没干完活,没法接别的活,肯定系统就处理不过来那么多请求啦,所以最后结果就是,一部分请求就会抛弃,因为线程池抛弃策略抛弃啦。回过头,你的系统响应慢,就是你的接口耗时问题呀,你需要优化它。 + +可以看下我这两篇文章。线程池和如何接口性能优化的~ + + + +关于线程池和接口性能优化的,可以看下我之前这两篇文章哈 +- [面试必备:Java线程池解析](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247483728&idx=1&sn=0221dfd5eb7862c0aa749a7038b39307&chksm=9779457fa00ecc69d3bb554ccc1daa8aa204b16b15587759cf1f148bda51907f6f57418e150f&token=1319249232&lang=zh_CN#rd) + +- [记一次接口性能优化实践总结:优化接口性能的八个建议](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484426&idx=1&sn=7265be5a5c37e71e65a2e42c999d3f72&chksm=97794025a00ec93379ad537353dd58f9149f801e100786a2b9c8cf37257e6d863d7ce4b87a5e&token=1319249232&lang=zh_CN#rd) \ No newline at end of file diff --git "a/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\221\250\346\212\245\344\270\211.md" "b/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\221\250\346\212\245\344\270\211.md" new file mode 100644 index 0000000..947f1ef --- /dev/null +++ "b/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\221\250\346\212\245\344\270\211.md" @@ -0,0 +1,22 @@ +### 问题一: netty内存泄漏 + +**Magee:有没有大佬了解这个异常的啊,使用的springcloudgateway, 底层没有改动** +> ERROR io.netty.util.ResourceLeakDetector -LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information. + + +**Shawn:** +> ByteBuf.release()之后没有通知垃圾回收 + +**百里半:** +> - 内存泄漏了,应该是你们重写了他们的方法,在最后没有release. +> - netty有自己的内存检测机制,你这个报错不速度解决,过一会服务就不可用了 +> - 我遇到过,docker直接挂掉 +> - 解决方法:找到你们重写的地方,把release加上,怎么加可以参考netty源码里面release怎么用的就好了 + +### 问题二:消息队列应用场景 +**spring:** +> 请教下,消息队列你们在项目中实际用在哪些场景? + +**张大树:** +> - 削峰,减少数据库压力. +> - 假设有10万个请求过来了,然后数据需要入库,但是如果同时写库会有压力,就可以自己生产,自己消费mq ,异步入库,前提是可以异步入库 diff --git "a/\351\253\230\345\271\266\345\217\221/README.MD" "b/\351\253\230\345\271\266\345\217\221/README.MD" new file mode 100644 index 0000000..0ce39a5 --- /dev/null +++ "b/\351\253\230\345\271\266\345\217\221/README.MD" @@ -0,0 +1,7 @@ +## 高并发 +- [美团二面:Redis与MySQL双写一致性如何保证?](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247490243&idx=1&sn=ff11c3aab9ada3b16d7f2b57c846d567&chksm=cf21c5eaf8564cfc59e3d0d56fd02b0f5513015005f498381be4d12db462442a49aabe4159ef&token=1495321435&lang=zh_CN&scene=21#wechat_redirect) +- [Redis主从、哨兵、 Cluster集群一锅端!](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247498254&idx=1&sn=6489360c3b226df9811e66cb40ec7656&chksm=cf222527f855ac3112628bcec7730064fee3fdbe869fbd0a7410c22766a0c036a7e5c1a69fa0&token=1990771297&lang=zh_CN#rd) +- [面试必备:聊聊MySQL的主从](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497982&idx=1&sn=bb589329cceb5462fc41f66ec63dbf56&chksm=cf2227d7f855aec16dd4d3b3425c0401850eeaf2c9cdc82e82722d38a00c24ee9ccfa3353774&token=1990771297&lang=zh_CN#rd) +- [聊聊幂等设计](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497427&idx=1&sn=2ed160c9917ad989eee1ac60d6122855&chksm=cf2229faf855a0ecf5eb34c7335acdf6420426490ee99fc2b602d54ff4ffcecfdab24eeab0a3&token=1990771297&lang=zh_CN#rd) +- [聊聊接口性能优化的11个小技巧](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247497361&idx=1&sn=a0a2b0f92804921ba3d31b6236f275c2&chksm=cf2229b8f855a0aec650f4e0c3f105aa08e52fabbc54807dd37fefc4873749698b2b1445b59f&token=1990771297&lang=zh_CN#rd) +- [面试必备:秒杀场景九个细节](https://mp.weixin.qq.com/s?__biz=Mzg3NzU5NTIwNg==&mid=2247493227&idx=1&sn=10e5064d7d224c69dce400e90cd44de6&chksm=cf223942f855b0541ada22a312e0d4ffbc99df463678247a0dede3ef16eb81e3344a4a54ceaf&token=1990771297&lang=zh_CN#rd) \ No newline at end of file