diff --git "a/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java 345円274円202円345円270円270円345円244円204円347円220円206円347円232円204円345円215円201円344円270円252円345円273円272円350円256円256円.md" "b/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java 345円274円202円345円270円270円345円244円204円347円220円206円347円232円204円345円215円201円344円270円252円345円273円272円350円256円256円.md" new file mode 100644 index 0000000..3f04c77 --- /dev/null +++ "b/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java 345円274円202円345円270円270円345円244円204円347円220円206円347円232円204円345円215円201円344円270円252円345円273円272円350円256円256円.md" @@ -0,0 +1,359 @@ +## 前言 +Java异常处理的十个建议,希望对大家有帮助~ + +本文已上传github: + +> https://github.com/whx123/JavaHome + +**公众号:捡田螺的小男孩** + +### 一、尽量不要使用e.printStackTrace(),而是使用log打印。 +**反例:** +``` +try{ + // do what you want +}catch(Exception e){ + e.printStackTrace(); +} +``` +**正例:** +``` +try{ + // do what you want +}catch(Exception e){ + log.info("你的程序有异常啦,{}",e); +} +``` +**理由:** +- printStackTrace()打印出的堆栈日志跟业务代码日志是交错混合在一起的,通常排查异常日志不太方便。 +- e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,那么,用户的请求就卡住啦~ + + +### 二、catch了异常,但是没有打印出具体的exception,无法更好定位问题 +**反例:** +``` +try{ + // do what you want +}catch(Exception e){ + log.info("你的程序有异常啦"); +} +``` +**正例:** + +``` +try{ + // do what you want +}catch(Exception e){ + log.info("你的程序有异常啦,{}",e); +} +``` +**理由:** +- 反例中,并没有把exception出来,到时候排查问题就不好查了啦,到底是SQl写错的异常还是IO异常,还是其他呢?所以应该把exception打印到日志中哦~ + +### 三、不要用一个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); + } +} +``` +理由: +- 用基类 Exception 捕捉的所有可能的异常,如果多个层次都这样捕捉,会丢失原始异常的有效信息哦 + +### 四、记得使用finally关闭流资源或者直接使用try-with-resource +**反例:** +``` +FileInputStream fdIn = null; +try { + fdIn = new FileInputStream(new File("/jay.txt")); + //在这里关闭流资源?有没有问题呢?如果发生异常了呢? + fdIn.close(); +} catch (FileNotFoundException e) { + log.error(e); +} catch (IOException e) { + log.error(e); +} +``` +**正例1:** + +需要使用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); + } +} +``` +**正例2:** + +当然,也可以使用JDK7的新特性try-with-resource来处理,它是Java7提供的一个新功能,它用于自动资源管理。 +- 资源是指在程序用完了之后必须要关闭的对象。 +- try-with-resources保证了每个声明了的资源在语句结束的时候会被关闭 +- 什么样的对象才能当做资源使用呢?只要实现了java.lang.AutoCloseable接口或者java.io.Closeable接口的对象,都OK。 + +``` +try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) { + // use resources +} catch (FileNotFoundException e) { + log.error(e); +} catch (IOException e) { + log.error(e); +} +``` + +**理由:** +- 如果不使用finally或者try-with-resource,当程序发生异常,IO资源流没关闭,那么这个IO资源就会被他一直占着,这样别人就没有办法用了,这就造成资源浪费。 + +### 五、捕获异常与抛出异常必须是完全匹配,或者捕获异常是抛异常的父类 + +**反例:** + +``` +//BizException 是 Exception 的子类 +public class BizException extends Exception {} +//抛出父类Exception +public static void test() throws Exception {} + +try { + test(); //编译错误 +} catch (BizException e) { //捕获异常子类是没法匹配的哦 + log.error(e); +} +``` +**正例:** + +``` +//抛出子类Exception +public static void test() throws BizException {} + +try { + test(); +} catch (Exception e) { + log.error(e); +} +``` + +### 六、捕获到的异常,不能忽略它,至少打点日志吧 +**反例:** + +``` +public static void testIgnoreException() throws Exception { + try { + // 搞事情 + } catch (Exception e) { //一般不会有这个异常 + + } +} +``` +**正例:** + +``` +public static void testIgnoreException() { + try { + // 搞事情 + } catch (Exception e) { //一般不会有这个异常 + log.error("这个异常不应该在这里出现的,{}",e); + } +} +``` + +**理由:** +- 虽然一个正常情况都不会发生的异常,但是如果你捕获到它,就不要忽略呀,至少打个日志吧~ + + +### 七、注意异常对你的代码层次结构的侵染(早发现早处理) +**反例:** + +``` +public UserInfo queryUserInfoByUserId(Long userid) throw SQLException { + //根据用户Id查询数据库 +} +``` +**正例:** + +``` +public UserInfo queryUserInfoByUserId(Long userid) { + try{ + //根据用户Id查询数据库 + }catch(SQLException e){ + log.error("查询数据库异常啦,{}",e); + }finally{ + //关闭连接,清理资源 + } +} +``` +**理由:** +- 我们的项目,一般都会把代码分 Action、Service、Dao 等不同的层次结构,如果你是DAO层处理的异常,尽早处理吧,如果往上 throw SQLException,上层代码就还是要try catch处理啦,这就污染了你的代码~ + +### 八、自定义封装异常,不要丢弃原始异常的信息Throwable cause + +我们常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。公司的框架提供统一异常处理就用到异常链,我们自定义封装异常,不要丢弃原始异常的信息,否则排查问题就头疼啦 + +**反例:** + +``` +public class TestChainException { + public void readFile() throws MyException{ + try { + InputStream is = new FileInputStream("jay.txt"); + Scanner in = new Scanner(is); + while (in.hasNext()) { + System.out.println(in.next()); + } + } catch (FileNotFoundException e) { + //e 保存异常信息 + throw new MyException("文件在哪里呢"); + } + } + public void invokeReadFile() throws MyException{ + try { + readFile(); + } catch (MyException e) { + //e 保存异常信息 + throw new MyException("文件找不到"); + } + } + public static void main(String[] args) { + TestChainException t = new TestChainException(); + try { + t.invokeReadFile(); + } catch (MyException e) { + e.printStackTrace(); + } + } +} +//MyException 构造器 +public MyException(String message) { + super(message); + } + +``` +运行结果如下,没有了Throwable cause,不好排查是什么异常了啦 +![](https://user-gold-cdn.xitu.io/2020/6/14/172b156657087952?w=891&h=253&f=png&s=46752) + +**正例:** +``` + +public class TestChainException { + public void readFile() throws MyException{ + try { + InputStream is = new FileInputStream("jay.txt"); + Scanner in = new Scanner(is); + while (in.hasNext()) { + System.out.println(in.next()); + } + } catch (FileNotFoundException e) { + //e 保存异常信息 + throw new MyException("文件在哪里呢", e); + } + } + public void invokeReadFile() throws MyException{ + try { + readFile(); + } catch (MyException e) { + //e 保存异常信息 + throw new MyException("文件找不到", e); + } + } + public static void main(String[] args) { + TestChainException t = new TestChainException(); + try { + t.invokeReadFile(); + } catch (MyException e) { + e.printStackTrace(); + } + } +} +//MyException 构造器 +public MyException(String message, Throwable cause) { + super(message, cause); + } +``` + +![](https://user-gold-cdn.xitu.io/2020/6/14/172b154f01b322e4?w=1030&h=469&f=png&s=558089) + + +### 九、运行时异常RuntimeException ,不应该通过catch 的方式来处理,而是先预检查,比如:NullPointerException处理 +**反例:** + +``` +try { + obj.method() +} catch (NullPointerException e) { +... +} +``` +**正例:** + +``` +if (obj != null){ + ... +} +``` + +### 十、注意异常匹配的顺序,优先捕获具体的异常 +注意异常的匹配顺序,因为只有第一个匹配到异常的catch块才会被执行。如果你希望看到,是NumberFormatException异常,就抛出NumberFormatException,如果是IllegalArgumentException就抛出IllegalArgumentException。 + +**反例:** +``` +try { + doSomething("test exception"); +} catch (IllegalArgumentException e) { + log.error(e); +} catch (NumberFormatException e) { + log.error(e); +} +``` + +**正例:** + +``` +try { + doSomething("test exception"); +} catch (NumberFormatException e) { + log.error(e); +} catch (IllegalArgumentException e) { + log.error(e); +} +``` + +理由: +- 因为NumberFormatException是IllegalArgumentException 的子类,反例中,不管是哪个异常,都会匹配到IllegalArgumentException,就不会再往下执行啦,因此不知道是否是NumberFormatException。所以需要优先捕获具体的异常,把NumberFormatException放前面~ + +## 公众号 +![](https://user-gold-cdn.xitu.io/2020/5/16/1721b50d00331393?w=900&h=500&f=png&s=389569) +- 欢迎关注我个人公众号,交个朋友,一起学习哈~ +- 如果答案整理有错,欢迎指出哈,感激不尽~ diff --git "a/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java347円250円213円345円272円217円345円221円230円345円277円205円345円244円207円345円237円272円347円241円200円357円274円232円JDK 5-15351円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/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java347円250円213円345円272円217円345円221円230円345円277円205円345円244円207円345円237円272円347円241円200円357円274円232円JDK 5-15351円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/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java347円250円213円345円272円217円345円221円230円345円277円205円345円244円207円345円237円272円347円241円200円357円274円232円JDK 5-15351円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" + + "

    AltStyle によって変換されたページ (->オリジナル) /

    \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/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java347円250円213円345円272円217円345円221円230円351円235円242円350円257円225円345円277円205円345円244円207円357円274円232円Volatile345円205円250円346円226円271円344円275円215円350円247円243円346円236円220円.md" "b/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java347円250円213円345円272円217円345円221円230円351円235円242円350円257円225円345円277円205円345円244円207円357円274円232円Volatile345円205円250円346円226円271円344円275円215円350円247円243円346円236円220円.md" new file mode 100644 index 0000000..05c9e70 --- /dev/null +++ "b/Java345円237円272円347円241円200円345円255円246円344円271円240円/Java347円250円213円345円272円217円345円221円230円351円235円242円350円257円225円345円277円205円345円244円207円357円274円232円Volatile345円205円250円346円226円271円344円275円215円350円247円243円346円236円220円.md" @@ -0,0 +1,488 @@ +### 前言 +volatile是Java程序员必备的基础,也是面试官非常喜欢问的一个话题,本文跟大家一起开启volatile学习之旅,如果有不正确的地方,也麻烦大家指出哈,一起相互学习~ + +- 1.volatile的用法 +- 2.volatile变量的作用 +- 3.现代计算机的内存模型(计算机模型,总线,MESI协议,嗅探技术) +- 4.Java内存模型(JMM) +- 5.并发编程的3个特性(原子性、可见性、有序性、happen-before、as-if-serial、指令重排) +- 6.volatile的底层原理(如何保证可见性,如何保证指令重排,内存屏障) +- 7.volatile的典型场景(状态标志,DCL单例模式) +- 8.volatile常见面试题&&答案解析 +- 公众号:捡田螺的小男孩 + +**github 地址** +> https://github.com/whx123/JavaHome + +### 1.volatile的用法 +volatile关键字是Java虚拟机提供的的**最轻量级的同步机制**,它作为一个修饰符出现,用来**修饰变量**,但是这里不包括局部变量哦。我们来看个demo吧,代码如下: +``` +/** + * @Author 捡田螺的小男孩 + * @Date 2020年08月02日 + * @Desc volatile的可见性探索 + */ +public class VolatileTest { + + public static void main(String[] args) throws InterruptedException { + Task task = new Task(); + + Thread t1 = new Thread(task, "线程t1"); + Thread t2 = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1000); + System.out.println("开始通知线程停止"); + task.stop = true; //修改stop变量值。 + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + }, "线程t2"); + t1.start(); //开启线程t1 + t2.start(); //开启线程t2 + Thread.sleep(1000); + } +} + +class Task implements Runnable { + boolean stop = false; + int i = 0; + + @Override + public void run() { + long s = System.currentTimeMillis(); + while (!stop) { + i++; + } + System.out.println("线程退出" + (System.currentTimeMillis() - s)); + } +} +``` +**运行结果:** +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/057e2eec568a40fe9f47e05f3b47bdb7~tplv-k3u1fbpfcp-zoom-1.image) +可以发现线程t2,虽然把stop设置为true了,但是线程t1对t2的**stop变量视而不可见**,因此,它一直在死循环running中。如果给变量stop加上volatile修饰,线程t1是可以停下来的,运行结果如下: +``` +volatile boolean stop = false; +``` +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/81326a66e9cc4350af71b0e9ff4dc304~tplv-k3u1fbpfcp-zoom-1.image) + + +### 2. vlatile修饰变量的作用 + +从以上例子,我们可以发现变量stop,加了vlatile修饰之后,线程t1对stop就可见了。其实,vlatile的作用就是:**保证变量对所有线程可见性**。当然,vlatile还有个作用就是,**禁止指令重排**,但是它**不保证原子性**。 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b5177218cf2b491da99d43392c7bd271~tplv-k3u1fbpfcp-zoom-1.image) + +所以当面试官问你**volatile的作用或者特性**,都可以这么回答: +- 保证变量对所有线程可见性; +- 禁止指令重排序 +- 不保证原子性 + +### 3. 现代计算机的内存模型(计算机模型,MESI协议,嗅探技术,总线) +为了更好理解volatile,先回顾一下计算机的内存模型与JMM(Java内存模型)吧~ + +#### 计算机模型 +计算机执行程序时,指令是由CPU处理器执行的,而打交道的数据是在主内存当中的。 + +由于计算机的存储设备与处理器的运算速度有几个数量级的差距,总不能每次CPU执行完指令,然后等主内存慢悠悠存取数据吧, +所以现代计算机系统加入一层读写速度接近处理器运算速度的高速缓存(Cache),以作为来作为内存与处理器之间的缓冲。 + +在多路处理器系统中,每个处理器都有自己的高速缓存,而它们共享同一主内存。**计算机抽象内存模型**如下: + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f995fb284a7241b9b95431e79a1c37b0~tplv-k3u1fbpfcp-zoom-1.image) + +- 程序执行时,把需要用到的数据,从主内存拷贝一份到高速缓存。 +- CPU处理器计算时,从它的高速缓存中读取,把计算完的数据写入高速缓存。 +- 当程序运算结束,把高速缓存的数据刷新会主内存。 + +随着科学技术的发展,为了效率,高速缓存又衍生出一级缓存(L1),二级缓存(L2),甚至三级缓存(L3); +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/05fa9ff22d7f42ae82122bfc6c233bd9~tplv-k3u1fbpfcp-zoom-1.image) + +当多个处理器的运算任务都涉及同一块主内存区域,可能导致**缓存数据不一致**问题。如何解决这个问题呢?有两种方案 + +> - 1、通过在总线加LOCK#锁的方式。 +> - 2、通过缓存一致性协议(Cache Coherence Protocol) + +#### 总线 + +> 总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束, 按照计算机所传输的信息种类,计算机的总线可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。 + +CPU和其他功能部件是通过总线通信的,如果在总线加LOCK#锁,那么在锁住总线期间,其他CPU是无法访问内存,这样一来,**效率就比较低了**。 + +#### MESI协议 +为了解决一致性问题,还可以通过缓存一致性协议。即各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol等。比较著名的就是Intel的MESI(Modified Exclusive Shared Or Invalid)协议,它的核心思想是: + +> 当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。 + +CPU中每个缓存行标记的4种状态(M、E、S、I),也了解一下吧: + +| 缓存状态 | 描述 | +|-----|-----| +| M,被修改(Modified) | 该缓存行只被该CPU缓存,与主存的值不同,会在它被其他CPU读取之前写入内存,并设置为Shared | +| E,独享的(Exclusive) | 该缓存行只被该CPU缓存,与主存的值相同,被其他CPU读取时置为Shared,被其他CPU写时置为Modified | +| S,共享的(Shared) | 该缓存行可能被多个CPU缓存,各个缓存中的数据与主存数据相同| +| I,无效的(Invalid) | 该缓存行数据是无效,需要时需重新从主存载入 | + + +MESI协议是如何实现的?如何保证当前处理器的内部缓存、主内存和其他处理器的缓存数据在总线上保持一致的?**多处理器总线嗅探** + +#### 嗅探技术 +> 在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据库读到处理器缓存中。 + +### 4. Java内存模型(JMM) + +- Java虚拟机规范试图定义一种Java内存模型,来**屏蔽掉各种硬件和操作系统的内存访问差异**,以实现让Java程序在各种平台上都能达到一致的内存访问效果。 +- Java内存模型**类比**于计算机内存模型。 +- 为了更好的执行性能,java内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存打交道,也没有限制编译器进行调整代码顺序优化。所以Java内存模型**会存在缓存一致性问题和指令重排序问题的**。 +- Java内存模型规定所有的变量都是存在主内存当中(类似于计算机模型中的物理内存),每个线程都有自己的工作内存(类似于计算机模型的高速缓存)。这里的**变量**包括实例变量和静态变量,但是**不包括局部变量**,因为局部变量是线程私有的。 +- 线程的工作内存保存了被该线程使用的变量的主内存副本,**线程对变量的所有操作都必须在工作内存中进行**,而不能直接操作操作主内存。并且每个线程不能访问其他线程的工作内存。 + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6ca8c98a8cc42c998dd4fe3324ded79~tplv-k3u1fbpfcp-zoom-1.image) + +举个例子吧,假设i的初始值是0,执行以下语句: +``` +i = i+1; +``` +首先,执行线程t1从主内存中读取到i=0,到工作内存。然后在工作内存中,赋值i+1,工作内存就得到i=1,最后把结果写回主内存。因此,如果是单线程的话,该语句执行是没问题的。但是呢,线程t2的本地工作内存还没过期,那么它读到的数据就是脏数据了。如图: +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0abe171336e4f89a58722a1569d64ce~tplv-k3u1fbpfcp-zoom-1.image) + +Java内存模型是围绕着如何在并发过程中如何处理**原子性、可见性和有序性**这3个特征来建立的,我们再来一起回顾一下~ + +### 5.并发编程的3个特性(原子性、可见性、有序性) + +#### 原子性 + +原子性,指操作是不可中断的,要么执行完成,要么不执行,基本数据类型的访问和读写都是具有原子性,当然(long和double的非原子性协定除外)。我们来看几个小例子: +``` +i =666; // 语句1 +i = j; // 语句2 +i = i+1; //语句 3 +i++; // 语句4 +``` +- 语句1操作显然是原子性的,将数值666赋值给i,即线程执行这个语句时,直接将数值666写入到工作内存中。 +- 语句2操作看起来也是原子性的,但是它实际上涉及两个操作,先去读j的值,再把j的值写入工作内存,两个操作分开都是原子操作,但是合起来就不满足原子性了。 +- 语句3读取i的值,加1,再写回主存,这个就不是原子性操作了。 +- 语句4 等同于语句3,也是非原子性操作。 + +#### 可见性 + +- 可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。 +- Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此。 +- volatile变量,保证新值能立即同步回主内存,以及每次使用前立即从主内存刷新,所以我们说volatile保证了多线程操作变量的可见性。 +- synchronized和Lock也能够保证可见性,线程在释放锁之前,会把共享变量值都刷回主存。final也可以实现可见性。 + + +#### 有序性 + +Java虚拟机这样描述Java程序的有序性的:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中,观察另一个线程,所有的操作都是无序的。 + +后半句意思就是,在Java内存模型中,**允许编译器和处理器对指令进行重排序**,会影响到多线程并发执行的正确性;前半句意思就是**as-if-serial**的语义,即不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不会被改变。 + +比如以下程序代码: +``` +double pi = 3.14; //A +double r = 1.0; //B +double area = pi * r * r; //C +``` +步骤C依赖于步骤A和B,因为指令重排的存在,程序执行顺讯可能是A->B->C,也可能是B->A->C,但是C不能在A或者B前面执行,这将违反as-if-serial语义。 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/87387195b86749ddbe1f36582a562988~tplv-k3u1fbpfcp-zoom-1.image) + +看段代码吧,假设程序先执行read方法,再执行add方法,结果一定是输出sum=2嘛? +``` +bool flag = false; +int b = 0; + +public void read() { + b = 1; //1 + flag = true; //2 +} + +public void add() { + if (flag) { //3 + int sum =b+b; //4 + System.out.println("bb sum is"+sum); + } +} + +``` + +如果是单线程,结果应该没问题,如果是多线程,线程t1对步骤1和2进行了**指令重排序**呢?结果sum就不是2了,而是0,如下图所示: +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/093c6e37453c462985bed78c4d239979~tplv-k3u1fbpfcp-zoom-1.image) + +这是为啥呢?**指令重排序**了解一下,指令重排是指在程序执行过程中,**为了提高性能**, **编译器和CPU可能会对指令进行重新排序**。CPU重排序包括指令并行重排序和内存系统重排序,重排序类型和重排序执行过程如下: + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d8a24934eab24bf9b80c402039371aa2~tplv-k3u1fbpfcp-zoom-1.image) + +实际上,可以给flag加上volatile关键字,来保证有序性。当然,也可以通过synchronized和Lock来保证有序性。synchronized和Lock保证某一时刻是只有一个线程执行同步代码,相当于是让线程顺序执行程序代码了,自然就保证了有序性。 + +实际上Java内存模型的有序性并不是仅靠volatile、synchronized和Lock来保证有序性的。这是因为Java语言中,有一个先行发生原则(happens-before): +- **程序次序规则**:在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。 +- **管程锁定规则**:一个unLock操作先行发生于后面对同一个锁额lock操作 +- **volatile变量规则**:对一个变量的写操作先行发生于后面对这个变量的读操作 +- **线程启动规则**:Thread对象的start()方法先行发生于此线程的每个一个动作 +- **线程终止规则**:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行 +- **线程中断规则**:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生 +- **对象终结规则**:一个对象的初始化完成先行发生于他的finalize()方法的开始 +- **传递性**:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C + +根据happens-before的八大规则,我们回到刚的例子,一起分析一下。给flag加上volatile关键字,look look它是如何保证有序性的, +``` +volatile bool flag = false; +int b = 0; + +public void read() { + b = 1; //1 + flag = true; //2 +} + +public void add() { + if (flag) { //3 + int sum =b+b; //4 + System.out.println("bb sum is"+sum); + } +} +``` +- 首先呢,flag加上volatile关键字,那就禁止了指令重排,也就是1 happens-before 2了 +- 根据**volatile变量规则**,2 happens-before 3 +- 由**程序次序规则**,得出 3 happens-before 4 +- 最后由**传递性**,得出1 happens-before 4,因此妥妥的输出sum=2啦~ + +### 6.volatile底层原理 + +以上讨论学习,我们知道volatile的语义就是保证变量对所有线程可见性以及禁止指令重排优化。那么,它的底层是如何保证可见性和禁止指令重排的呢? + +#### 图解volatile是如何保证可见性的? +在这里,先看几个图吧,哈哈~ + +假设flag变量的初始值false,现在有两条线程t1和t2要访问它,就可以简化为以下图: +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7ddbe230c8dc4501a77ffbe0587b5ba6~tplv-k3u1fbpfcp-zoom-1.image) + +如果线程t1执行以下代码语句,并且flag没有volatile修饰的话;t1刚修改完flag的值,还没来得及刷新到主内存,t2又跑过来读取了,很容易就数据flag不一致了,如下: + +``` +flag=true; +``` +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cc065cf75803496aa1efafd6d68ba968~tplv-k3u1fbpfcp-zoom-1.image) + +如果flag变量是由volatile修饰的话,就不一样了,如果线程t1修改了flag值,volatile能保证修饰的flag变量后,可以**立即同步回主内存**。如图: + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/27e9e195810a4a71bdeb38dd128b27e4~tplv-k3u1fbpfcp-zoom-1.image) + +细心的朋友会发现,线程t2不还是flag旧的值吗,这不还有问题嘛?其实volatile还有一个保证,就是**每次使用前立即先从主内存刷新最新的值**,线程t1修改完后,线程t2的变量副本会过期了,如图: +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e67dcdfe9d9412dab89961bf92b5b53~tplv-k3u1fbpfcp-zoom-1.image) + +显然,这里还不是底层,实际上volatile保证可见性和禁止指令重排都跟**内存屏障**有关,我们编译volatile相关代码看看~ + +#### DCL单例模式(volatile)&编译对比 +DCL单例模式(Double Check Lock,双重检查锁)比较常用,它是需要volatile修饰的,所以就拿这段代码编译吧 +``` +public class Singleton { + private volatile static Singleton instance; + private Singleton (){} + public static Singleton getInstance() { + if (instance == null) { + synchronized (Singleton.class) { + if (instance == null) { + instance = new Singleton(); + } + } + } + return instance; + } +} +``` +编译这段代码后,观察有volatile关键字和没有volatile关键字时的instance所生成的汇编代码发现,有volatile关键字修饰时,会多出一个lock addl 0ドルx0,(%esp),即多出一个lock前缀指令 +``` +0x01a3de0f: mov 0ドルx3375cdb0,%esi ;...beb0cd75 33 + ; {oop('Singleton')} +0x01a3de14: mov %eax,0x150(%esi) ;...89865001 0000 +0x01a3de1a: shr 0ドルx9,%esi ;...c1ee09 +0x01a3de1d: movb 0ドルx0,0x1104800(%esi) ;...c6860048 100100 +0x01a3de24: lock addl 0ドルx0,(%esp) ;...f0830424 00 + ;*putstatic instance + ; - Singleton::getInstance@24 +``` + lock指令相当于一个**内存屏障**,它保证以下这几点: +> - 1.重排序时不能把后面的指令重排序到内存屏障之前的位置 +> - 2.将本处理器的缓存写入内存 +> - 3.如果是写入动作,会导致其他处理器中对应的缓存无效。 + +显然,第2、3点不就是volatile保证可见性的体现嘛,第1点就是禁止指令重排列的体现。 + +#### 内存屏障 + +内存屏障四大分类:(Load 代表读取指令,Store代表写入指令) + +| 内存屏障类型 | 抽象场景| 描述| +|-----|-----|-----| +|LoadLoad屏障| Load1; LoadLoad; Load2|在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。| +|StoreStore屏障|Store1; StoreStore; Store2|在Store2写入执行前,保证Store1的写入操作对其它处理器可见| +|LoadStore屏障|Load1; LoadStore; Store2| 在Store2被写入前,保证Load1要读取的数据被读取完毕。| +|StoreLoad屏障|Store1; StoreLoad; Load2|在Load2读取操作执行前,保证Store1的写入对所有处理器可见。| + +为了实现volatile的内存语义,Java内存模型采取以下的保守策略 +- 在每个volatile写操作的前面插入一个StoreStore屏障。 +- 在每个volatile写操作的后面插入一个StoreLoad屏障。 +- 在每个volatile读操作的后面插入一个LoadLoad屏障。 +- 在每个volatile读操作的后面插入一个LoadStore屏障。 + +有些小伙伴,可能对这个还是有点疑惑,内存屏障这玩意太抽象了。我们照着代码看下吧(LoadLoad内存屏障也是在flag后面哈,图片有误): + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a85eff53f99f420c8139bb69b2d4f6ae~tplv-k3u1fbpfcp-zoom-1.image) + +内存屏障保证前面的指令先执行,所以这就保证了禁止了指令重排啦,同时内存屏障保证缓存写入内存和其他处理器缓存失效,这也就保证了可见性,哈哈~ + +### 7.volatile的典型场景 +通常来说,使用volatile必须具备以下2个条件: +- 1)对变量的写操作不依赖于当前值 +- 2)该变量没有包含在具有其他变量的不变式中 + +实际上,volatile场景一般就是**状态标志**,以及**DCL单例模式**。 + +#### 7.1 状态标志 +深入理解Java虚拟机,书中的例子: +``` +Map configOptions; +char[] configText; +// 此变量必须定义为 volatile +volatile boolean initialized = false; + +// 假设以下代码在线程 A 中运行 +// 模拟读取配置信息, 当读取完成后将 initialized 设置为 true 以告知其他线程配置可用 +configOptions = new HashMap(); +configText = readConfigFile(fileName); +processConfigOptions(configText, configOptions); +initialized = true; + +// 假设以下代码在线程 B 中运行 +// 等待 initialized 为 true, 代表线程 A 已经把配置信息初始化完成 +while(!initialized) { + sleep(); +} +// 使用线程 A 中初始化好的配置信息 +doSomethingWithConfig(); +``` +#### 7.2 DCL单例模式 +``` +class Singleton{ + private volatile static Singleton instance = null; + + private Singleton() { + } + + public static Singleton getInstance() { + if(instance==null) { + synchronized (Singleton.class) { + if(instance==null) + instance = new Singleton(); + } + } + return instance; + } +} + +``` + +### 8. volatile相关经典面试题 +- 谈谈volatile的特性 +- volatile的内存语义 +- 说说并发编程的3大特性 +- 什么是内存可见性,什么是指令重排序? +- volatile是如何解决java并发中可见性的问题 +- volatile如何防止指令重排 +- volatile可以解决原子性嘛?为什么? +- volatile底层的实现机制 +- volatile和synchronized的区别? + +#### 8.1 谈谈volatile的特性 + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8e624231e978455ebec33c7380ffba0d~tplv-k3u1fbpfcp-zoom-1.image) + +#### 8.2 volatile的内存语义 + +- 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。 +- 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。 + +#### 8.3 说说并发编程的3大特性 +- 原子性 +- 可见性 +- 有序性 + +#### 8.4 什么是内存可见性,什么是指令重排序? +- 可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。 +- 指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。 + +#### 8.5 volatile是如何解决java并发中可见性的问题 + +底层是通过内存屏障实现的哦,volatile能保证修饰的变量后,可以立即同步回主内存,每次使用前立即先从主内存刷新最新的值。 + +#### 8.6 volatile如何防止指令重排 + +也是内存屏障哦,跟面试官讲下Java内存的保守策略: +- 在每个volatile写操作的前面插入一个StoreStore屏障。 +- 在每个volatile写操作的后面插入一个StoreLoad屏障。 +- 在每个volatile读操作的后面插入一个LoadLoad屏障。 +- 在每个volatile读操作的后面插入一个LoadStore屏障。 + +再讲下volatile的语义哦,重排序时不能把内存屏障后面的指令重排序到内存屏障之前的位置 + +#### 8.7 volatile可以解决原子性嘛?为什么? + +不可以,可以直接举i++那个例子,原子性需要synchronzied或者lock保证 + +``` +public class Test { + public volatile int race = 0; + + public void increase() { + race++; + } + + public static void main(String[] args) { + final Test test = new Test(); + for(int i=0;i<10;i++){ + new Thread(){ + public void run() { + for(int j=0;j<100;j++) + test.increase(); + }; + }.start(); + } + + //等待所有累加线程结束 + while(Thread.activeCount()>1) + Thread.yield(); + System.out.println(test.race); + } +} +``` + + #### 8.8 volatile底层的实现机制 + + 可以看本文的第六小节,volatile底层原理哈,主要你要跟面试官讲述,volatile如何保证可见性和禁止指令重排,需要讲到内存屏障~ + + #### 8.9 volatile和synchronized的区别? + - volatile修饰的是变量,synchronized一般修饰代码块或者方法 + - volatile保证可见性、禁止指令重排,但是不保证原子性;synchronized可以保证原子性 + - volatile不会造成线程阻塞,synchronized可能会造成线程的阻塞,所以后面才有锁优化那么多故事~ + - 哈哈,你还有补充嘛~ + +推荐之前写的一篇文章: +[Synchronized解析——如果你愿意一层一层剥开我的心](https://juejin.im/post/6844903918653145102) + + +### 公众号 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07e9fd0521c244adab8556fee99a2011~tplv-k3u1fbpfcp-zoom-1.image) + +### 参考与感谢 +- <<深入理解java虚拟机>> +- [Java并发编程:volatile关键字解析](https://www.cnblogs.com/dolphin0520/p/3920373.html) +- [面试官最爱的volatile关键字](https://juejin.im/post/6844903520760496141) +- [ 面试官没想到一个Volatile,我都能跟他扯半小时](https://juejin.im/post/6844904149536997384) +- [再有人问你Java内存模型是什么,就把这篇文章发给他。](http://47.103.216.138/archives/2550) +- [【并发编程】MESI--CPU缓存一致性协议](https://www.cnblogs.com/z00377750/p/9180644.html) +- [漫画:volatile对指令重排的影响 ](https://www.sohu.com/a/211287207_684445) +- [volatile三大特性详解](https://www.jianshu.com/p/765e3abbe89a) + diff --git "a/Java345円237円272円347円241円200円345円255円246円344円271円240円/README.MD" "b/Java345円237円272円347円241円200円345円255円246円344円271円240円/README.MD" new file mode 100644 index 0000000..a038f88 --- /dev/null +++ "b/Java345円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/Java345円237円272円347円241円200円345円255円246円344円271円240円/README.MD.bak" "b/Java345円237円272円347円241円200円345円255円246円344円271円240円/README.MD.bak" new file mode 100644 index 0000000..4ba4024 --- /dev/null +++ "b/Java345円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/Java347円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円/Java347円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/Java347円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円/Java347円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/Java347円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円/Java347円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/Java347円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/Java347円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/Java347円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/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java 345円271円266円345円217円221円 && 345円244円232円347円272円277円347円250円213円/344円270円252円344円272円272円347円217円215円350円227円217円347円232円20480円351円201円223円345円244円232円347円272円277円347円250円213円345円271円266円345円217円221円351円235円242円350円257円225円351円242円230円357円274円2101円-10347円255円224円346円241円210円350円247円243円346円236円220円357円274円211円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java 345円271円266円345円217円221円 && 345円244円232円347円272円277円347円250円213円/344円270円252円344円272円272円347円217円215円350円227円217円347円232円20480円351円201円223円345円244円232円347円272円277円347円250円213円345円271円266円345円217円221円351円235円242円350円257円225円351円242円230円357円274円2101円-10347円255円224円346円241円210円350円247円243円346円236円220円357円274円211円.md" new file mode 100644 index 0000000..621dce7 --- /dev/null +++ "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java 345円271円266円345円217円221円 && 345円244円232円347円272円277円347円250円213円/344円270円252円344円272円272円347円217円215円350円227円217円347円232円20480円351円201円223円345円244円232円347円272円277円347円250円213円345円271円266円345円217円221円351円235円242円350円257円225円351円242円230円357円274円2101円-10347円255円224円346円241円210円350円247円243円346円236円220円357円274円211円.md" @@ -0,0 +1,284 @@ +

    前言

    +

    个人珍藏的80道Java多线程/并发经典面试题,因为篇幅太长,现在先给出1-10的答案解析哈,后面一起完善,并且上传github哈~

    +
    +

    https://github.com/whx123/JavaHome

    +
    +

    公众号:捡田螺的小男孩

    +

    1. synchronized的实现原理以及锁优化?

    +

    synchronized的实现原理

    +
      +
    • synchronized作用于方法或者代码块,保证被修饰的代码在同一时间只能被一个线程访问。
    • synchronized修饰代码块时,JVM采用monitorenter、monitorexit两个指令来实现同步
    • synchronized修饰同步方法时,JVM采用ACC_SYNCHRONIZED标记符来实现同步
    • monitorenter、monitorexit或者ACC_SYNCHRONIZED都是基于Monitor实现
    • 实例对象里有对象头,对象头里面有Mark Word,Mark Word指针指向了monitor
    • Monitor其实是一种同步工具,也可以说是一种同步机制
    • 在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的。ObjectMonitor体现出Monitor的工作原理~
    +
    ObjectMonitor() {
    + _header = NULL;
    + _count = 0; // 记录线程获取锁的次数
    + _waiters = 0,
    + _recursions = 0; //锁的重入次数
    + _object = NULL;
    + _owner = NULL; // 指向持有ObjectMonitor对象的线程
    + _WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet
    + _WaitSetLock = 0 ;
    + _Responsible = NULL ;
    + _succ = NULL ;
    + _cxq = NULL ;
    + FreeNext = NULL ;
    + _EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
    + _SpinFreq = 0 ;
    + _SpinClock = 0 ;
    + OwnerIsThread = 0 ;
    + }
    +
    +

    ObjectMonitor的几个关键属性 _count、_recursions、_owner、_WaitSet、 _EntryList 体现了monitor的工作原理 +

    +

    锁优化

    +

    在讨论锁优化前,先看看JAVA对象头(32位JVM)中Mark Word的结构图吧~

    + +

    Mark Word存储对象自身的运行数据,如哈希码、GC分代年龄、锁状态标志、偏向时间戳(Epoch) 等,为什么区分偏向锁、轻量级锁、重量级锁等几种锁状态呢?

    +
    +

    在JDK1.6之前,synchronized的实现直接调用ObjectMonitor的enter和exit,这种锁被称之为重量级锁。从JDK6开始,HotSpot虚拟机开发团队对Java中的锁进行优化,如增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略。

    +
    +
      +
    • 偏向锁:在无竞争的情况下,把整个同步都消除掉,CAS操作都不做。
    • 轻量级锁:在没有多线程竞争时,相对重量级锁,减少操作系统互斥量带来的性能消耗。但是,如果存在锁竞争,除了互斥量本身开销,还额外有CAS操作的开销。
    • 自旋锁:减少不必要的CPU上下文切换。在轻量级锁升级为重量级锁时,就使用了自旋加锁的方式
    • 锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
    +
    +

    举个例子,买门票进动物园。老师带一群小朋友去参观,验票员如果知道他们是个集体,就可以把他们看成一个整体(锁租化),一次性验票过,而不需要一个个找他们验票。

    +
    +
      +
    • 锁消除:虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。
    +

    有兴趣的朋友们可以看看我这篇文章: +Synchronized解析——如果你愿意一层一层剥开我的心[1]

    +

    2. ThreadLocal原理,使用注意点,应用场景有哪些?

    +

    回答四个主要点:

    +
      +
    • ThreadLocal是什么?
    • ThreadLocal原理
    • ThreadLocal使用注意点
    • ThreadLocal的应用场景
    +

    ThreadLocal是什么?

    +

    ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。

    +
    //创建一个ThreadLocal变量
    +static ThreadLocal<String> localVariable = new ThreadLocal<>();
    +
    +

    ThreadLocal原理

    +

    ThreadLocal内存结构图:

    +

    +由结构图是可以看出:

    +
      +
    • Thread对象中持有一个ThreadLocal.ThreadLocalMap的成员变量。
    • ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
    +

    对照着几段关键源码来看,更容易理解一点哈~

    +
    public class Thread implements Runnable {
    + //ThreadLocal.ThreadLocalMap是Thread的属性
    + ThreadLocal.ThreadLocalMap threadLocals = null;
    +}
    +
    +

    ThreadLocal中的关键方法set()和get()

    +
     public void set(T value) {
    + Thread t = Thread.currentThread(); //获取当前线程t
    + ThreadLocalMap map = getMap(t); //根据当前线程获取到ThreadLocalMap
    + if (map != null)
    + map.set(this, value); //K,V设置到ThreadLocalMap中
    + else
    + createMap(t, value); //创建一个新的ThreadLocalMap
    + }
    +
    + public T get() {
    + Thread t = Thread.currentThread();//获取当前线程t
    + ThreadLocalMap map = getMap(t);//根据当前线程获取到ThreadLocalMap
    + if (map != null) {
    + //由this(即ThreadLoca对象)得到对应的Value,即ThreadLocal的泛型值
    + ThreadLocalMap.Entry e = map.getEntry(this);
    + if (e != null) {
    + @SuppressWarnings("unchecked")
    + T result = (T)e.value; 
    + return result;
    + }
    + }
    + return setInitialValue();
    + }
    +
    +

    ThreadLocalMap的Entry数组

    +
    static class ThreadLocalMap {
    + static class Entry extends WeakReference<ThreadLocal<?>> {
    + /** The value associated with this ThreadLocal. */
    + Object value;
    +
    + Entry(ThreadLocal<?> k, Object v) {
    + super(k);
    + value = v;
    + }
    + }
    +}
    +
    +

    所以怎么回答ThreadLocal的实现原理?如下,最好是能结合以上结构图一起说明哈~

    +
    +
      +
    • Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,即每个线程都有一个属于自己的ThreadLocalMap。
    • ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
    • 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
    +
    +

    ThreadLocal 内存泄露问题

    +

    先看看一下的TreadLocal的引用示意图哈,

    + +

    ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用,如下 +

    +
    +

    弱引用:只要垃圾回收机制一运行,不管JVM的内存空间是否充足,都会回收该对象占用的内存。

    +
    +

    弱引用比较容易被回收。因此,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但是因为ThreadLocalMap生命周期和Thread是一样的,它这时候如果不被回收,就会出现这种情况:ThreadLocalMap的key没了,value还在,这就会造成了内存泄漏问题

    +

    如何解决内存泄漏问题?使用完ThreadLocal后,及时调用remove()方法释放内存空间。

    +

    ThreadLocal的应用场景

    +
      +
    • 数据库连接池
    • 会话管理中使用
    +

    3. synchronized和ReentrantLock的区别?

    +

    我记得校招的时候,这道面试题出现的频率还是挺高的~可以从锁的实现、功能特点、性能等几个维度去回答这个问题,

    +
      +
    • 锁的实现: synchronized是Java语言的关键字,基于JVM实现。而ReentrantLock是基于JDK的API层面实现的(一般是lock()和unlock()方法配合try/finally 语句块来完成。)
    • 性能: 在JDK1.6锁优化以前,synchronized的性能比ReenTrantLock差很多。但是JDK6开始,增加了适应性自旋、锁消除等,两者性能就差不多了。
    • 功能特点: ReentrantLock 比 synchronized 增加了一些高级功能,如等待可中断、可实现公平锁、可实现选择性通知。
    +
    +
      +
    • ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
    • ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
    • synchronized与wait()和notify()/notifyAll()方法结合实现等待/通知机制,ReentrantLock类借助Condition接口与newCondition()方法实现。
    • ReentrantLock需要手工声明来加锁和释放锁,一般跟finally配合释放锁。而synchronized不用手动释放锁。
    +
    +

    4. 说说CountDownLatch与CyclicBarrier区别

    +
      +
    • CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
    • CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行。 +
    +

    举个例子吧:

    +
    +
      +
    • CountDownLatch:假设老师跟同学约定周末在公园门口集合,等人齐了再发门票。那么,发门票(这个主线程),需要等各位同学都到齐(多个其他线程都完成),才能执行。
    • CyclicBarrier:多名短跑运动员要开始田径比赛,只有等所有运动员准备好,裁判才会鸣枪开始,这时候所有的运动员才会疾步如飞。
    +
    +

    5. Fork/Join框架的理解

    +
    +

    Fork/Join框架是Java7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

    +
    +

    Fork/Join框架需要理解两个点,分而治之工作窃取算法

    +

    分而治之

    +

    以上Fork/Join框架的定义,就是分而治之思想的体现啦 +

    +

    工作窃取算法

    +

    把大任务拆分成小任务,放到不同队列执行,交由不同的线程分别执行时。有的线程优先把自己负责的任务执行完了,其他线程还在慢慢悠悠处理自己的任务,这时候为了充分提高效率,就需要工作盗窃算法啦~

    + +

    工作盗窃算法就是,某个线程从其他队列中窃取任务进行执行的过程。一般就是指做得快的线程(盗窃线程)抢慢的线程的任务来做,同时为了减少锁竞争,通常使用双端队列,即快线程和慢线程各在一端。

    +

    6. 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

    +

    看看Thread的start方法说明哈~

    +
     /**
    + * Causes this thread to begin execution; the Java Virtual Machine
    + * calls the <code>run</code> method of this thread.
    + * <p>
    + * The result is that two threads are running concurrently: the
    + * current thread (which returns from the call to the
    + * <code>start</code> method) and the other thread (which executes its
    + * <code>run</code> method).
    + * <p>
    + * It is never legal to start a thread more than once.
    + * In particular, a thread may not be restarted once it has completed
    + * execution.
    + *
    + * @exception IllegalThreadStateException if the thread was already
    + * started.
    + * @see #run()
    + * @see #stop()
    + */
    + public synchronized void start() {
    + ......
    + }
    +
    +

    JVM执行start方法,会另起一条线程执行thread的run方法,这才起到多线程的效果~ 为什么我们不能直接调用run()方法? +如果直接调用Thread的run()方法,其方法还是运行在主线程中,没有起到多线程效果。

    +

    7. CAS?CAS 有什么缺陷,如何解决?

    +

    CAS,Compare and Swap,比较并交换;

    +
    +

    CAS 涉及3个操作数,内存地址值V,预期原值A,新值B; +如果内存位置的值V与预期原A值相匹配,就更新为新值B,否则不更新

    +
    +

    CAS有什么缺陷?

    + +

    ABA 问题

    +
    +

    并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况。此时A已经非彼A,数据即使成功修改,也可能有问题。

    +
    +

    可以通过AtomicStampedReference解决ABA问题,它,一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性。

    +

    循环时间长开销

    +
    +

    自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。

    +
    +

    很多时候,CAS思想体现,是有个自旋次数的,就是为了避开这个耗时问题~

    +

    只能保证一个变量的原子操作。

    +
    +

    CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。

    +
    +

    可以通过这两个方式解决这个问题:

    +
    +
      +
    • 使用互斥锁来保证原子性;
    • 将多个变量封装成对象,通过AtomicReference来保证原子性。
    +
    +

    有兴趣的朋友可以看看我之前的这篇实战文章哈~ +CAS乐观锁解决并发问题的一次实践[2]

    +

    9. 如何保证多线程下i++ 结果正确?

    + +
      +
    • 使用循环CAS,实现i++原子操作
    • 使用锁机制,实现i++原子操作
    • 使用synchronized,实现i++原子操作
    +

    没有代码demo,感觉是没有灵魂的~ 如下:

    +
    /**
    + * @Author 捡田螺的小男孩
    + */
    +public class AtomicIntegerTest {
    +
    + private static AtomicInteger atomicInteger = new AtomicInteger(0);
    +
    + public static void main(String[] args) throws InterruptedException {
    + testIAdd();
    + }
    +
    + private static void testIAdd() throws InterruptedException {
    + //创建线程池
    + ExecutorService executorService = Executors.newFixedThreadPool(2);
    + for (int i = 0; i < 1000; i++) {
    + executorService.execute(() -> {
    + for (int j = 0; j < 2; j++) {
    + //自增并返回当前值
    + int andIncrement = atomicInteger.incrementAndGet();
    + System.out.println("线程:" + Thread.currentThread().getName() + " count=" + andIncrement);
    + }
    + });
    + }
    + executorService.shutdown();
    + Thread.sleep(100);
    + System.out.println("最终结果是 :" + atomicInteger.get());
    + }
    + 
    +}
    +
    +

    运行结果:

    +
    ...
    +线程:pool-1-thread-1 count=1997
    +线程:pool-1-thread-1 count=1998
    +线程:pool-1-thread-1 count=1999
    +线程:pool-1-thread-2 count=315
    +线程:pool-1-thread-2 count=2000
    +最终结果是 :2000
    +
    +

    10. 如何检测死锁?怎么预防死锁?死锁四个必要条件

    +

    死锁是指多个线程因竞争资源而造成的一种互相等待的僵局。如图感受一下: + +死锁的四个必要条件:

    +
      +
    • 互斥:一次只有一个进程可以使用一个资源。其他进程不能访问已分配给其他进程的资源。
    • 占有且等待:当一个进程在等待分配得到其他资源时,其继续占有已分配得到的资源。
    • 非抢占:不能强行抢占进程中已占有的资源。
    • 循环等待:存在一个封闭的进程链,使得每个资源至少占有此链中下一个进程所需要的一个资源。
    +

    如何预防死锁?

    +
      +
    • 加锁顺序(线程按顺序办事)
    • 加锁时限 (线程请求所加上权限,超时就放弃,同时释放自己占有的锁)
    • 死锁检测
    +

    参考与感谢

    +

    牛顿说,我之所以看得远,是因为我站在巨人的肩膀上~ 谢谢以下各位前辈哈~

    +
      +
    • 面试必问的CAS,你懂了吗?[3]
    • Java多线程:死锁[4]
    • ReenTrantLock可重入锁(和synchronized的区别)总结[5]
    • 聊聊并发(八)——Fork/Join 框架介绍[6]
    +

    个人公众号

    + +
      +
    • 觉得写得好的小伙伴给个点赞+关注啦,谢谢~
    • 如果有写得不正确的地方,麻烦指出,感激不尽。
    • 同时非常期待小伙伴们能够关注我公众号,后面慢慢推出更好的干货~嘻嘻
    • github地址:https://github.com/whx123/JavaHome
    +

    Reference

    +
    +[1]

    Synchronized解析——如果你愿意一层一层剥开我的心: https://juejin.im/post/5d5374076fb9a06ac76da894#comment

    +
    +[2]

    CAS乐观锁解决并发问题的一次实践: https://juejin.im/post/5d0616ade51d457756536791

    +
    +[3]

    面试必问的CAS,你懂了吗?: https://blog.csdn.net/v123411739/article/details/79561458

    +
    +[4]

    Java多线程:死锁: https://www.cnblogs.com/xiaoxi/p/8311034.html

    +
    +[5]

    ReenTrantLock可重入锁(和synchronized的区别)总结: https://blog.csdn.net/qq838642798/article/details/65441415

    +
    +[6]

    聊聊并发(八)——Fork/Join 框架介绍: https://www.infoq.cn/article/fork-join-introduction

    +
    +
    diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java 345円271円266円345円217円221円 && 345円244円232円347円272円277円347円250円213円/344円270円252円344円272円272円347円217円215円350円227円217円347円232円20480円351円201円223円345円244円232円347円272円277円347円250円213円345円271円266円345円217円221円351円235円242円350円257円225円351円242円230円357円274円21011円-20347円255円224円346円241円210円350円247円243円346円236円220円357円274円211円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java 345円271円266円345円217円221円 && 345円244円232円347円272円277円347円250円213円/344円270円252円344円272円272円347円217円215円350円227円217円347円232円20480円351円201円223円345円244円232円347円272円277円347円250円213円345円271円266円345円217円221円351円235円242円350円257円225円351円242円230円357円274円21011円-20347円255円224円346円241円210円350円247円243円346円236円220円357円274円211円.md" new file mode 100644 index 0000000..2a71671 --- /dev/null +++ "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java 345円271円266円345円217円221円 && 345円244円232円347円272円277円347円250円213円/344円270円252円344円272円272円347円217円215円350円227円217円347円232円20480円351円201円223円345円244円232円347円272円277円347円250円213円345円271円266円345円217円221円351円235円242円350円257円225円351円242円230円357円274円21011円-20347円255円224円346円241円210円350円247円243円346円236円220円357円274円211円.md" @@ -0,0 +1,694 @@ +## 前言 +个人珍藏的80道Java多线程/并发经典面试题,现在给出11-20的答案解析哈,并且上传github哈~ + +> https://github.com/whx123/JavaHome + +[个人珍藏的80道多线程并发面试题(1-10答案解析)](https://juejin.im/post/6854573221258199048) + +### 11、为什么要用线程池?Java的线程池内部机制,参数作用,几种工作阻塞队列,线程池类型以及使用场景 +回答这些点: +- 为什么要用线程池? +- Java的线程池原理 +- 线程池核心参数 +- 几种工作阻塞队列 +- 线程池使用不当的问题 +- 线程池类型以及使用场景 + +#### 为什么要用线程池? +线程池:一个管理线程的池子。 +- 管理线程,避免增加创建线程和销毁线程的资源损耗。 +- 提高响应速度。 +- 重复利用。 + +#### Java的线程池执行原理 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/efe9ed82093e4c8bab768eac79dffed3~tplv-k3u1fbpfcp-zoom-1.image) +为了形象描述线程池执行,打个比喻: +- 核心线程比作公司正式员工 +- 非核心线程比作外包员工 +- 阻塞队列比作需求池 +- 提交任务比作提需求 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ed3df3db91941e9b8d3e1078fdd02b5~tplv-k3u1fbpfcp-zoom-1.image) + +#### 线程池核心参数 +``` +public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory, + RejectedExecutionHandler handler) +``` +- corePoolSize: 线程池核心线程数最大值 +- maximumPoolSize: 线程池最大线程数大小 +- keepAliveTime: 线程池中非核心线程空闲的存活时间大小 +- unit: 线程空闲存活时间单位 +- workQueue: 存放任务的阻塞队列 +- threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。 +- handler:线城池的饱和策略事件,主要有四种类型拒绝策略。 + +**四种拒绝策略** +- AbortPolicy(抛出一个异常,默认的) +- DiscardPolicy(直接丢弃任务) +- DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池) +- CallerRunsPolicy(交给线程池调用所在的线程进行处理) + +#### 几种工作阻塞队列 + +- ArrayBlockingQueue(用数组实现的有界阻塞队列,按FIFO排序量) +- LinkedBlockingQueue(基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列) +- DelayQueue(一个任务定时周期的延迟执行的队列) +- PriorityBlockingQueue(具有优先级的无界阻塞队列) +- SynchronousQueue(一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态) + + +#### 线程池使用不当的问题 +线程池适用不当可能导致内存飙升问题哦 + +有兴趣可以看我这篇文章哈:[源码角度分析-newFixedThreadPool线程池导致的内存飙升问题](https://juejin.im/post/6844903930502070285) + +#### 线程池类型以及使用场景 +- newFixedThreadPool +> 适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。 +- newCachedThreadPool +> 用于并发执行大量短期的小任务。 +- newSingleThreadExecutor +> 适用于串行执行任务的场景,一个任务一个任务地执行。 +- newScheduledThreadPool +> 周期性执行任务的场景,需要限制线程数量的场景 +- newWorkStealingPool +> 建一个含有足够多线程的线程池,来维持相应的并行级别,它会通过工作窃取的方式,使得多核的 CPU 不会闲置,总会有活着的线程让 CPU 去运行,本质上就是一个 ForkJoinPool。) + + +有兴趣可以看我这篇文章哈:[面试必备:Java线程池解析](https://juejin.im/post/6844903889678893063) + +### 12、谈谈volatile关键字的理解 +volatile是面试官非常喜欢问的一个问题,可以回答以下这几点: +- vlatile变量的作用 +- 现代计算机的内存模型(嗅探技术,MESI协议,总线) +- Java内存模型(JMM) +- 什么是可见性? +- 指令重排序 +- volatile的内存语义 +- as-if-serial +- Happens-before +- volatile可以解决原子性嘛?为什么? +- volatile底层原理,如何保证可见性和禁止指令重排(内存屏障) + +#### vlatile变量的作用? +- 保证变量对所有线程可见性 +- 禁止指令重排 +#### 现代计算机的内存模型 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4bb647387f34af09ce1096ef916be9a~tplv-k3u1fbpfcp-zoom-1.image) +- 其中高速缓存包括L1,L2,L3缓存~ +- 缓存一致性协议,可以了解MESI协议 +- 总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,CPU和其他功能部件是通过总线通信的。 +- 处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存数据在总线上保持一致。 +#### Java内存模型(JMM) +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/275f5b038d1d4e9ba308ab129df4aef3~tplv-k3u1fbpfcp-zoom-1.image) + +#### 什么是可见性? +可见性就是当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值。 + +#### 指令重排序 +指令重排是指在程序执行过程中,为了提高性能, 编译器和CPU可能会对指令进行重新排序。 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/063d5ff09b604add8bbb25b3d9346169~tplv-k3u1fbpfcp-zoom-1.image) + +#### volatile的内存语义 +- 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。 +- 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。 + +#### as-if-serial +如果在本线程内观察,所有的操作都是有序的;即不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不会被改变。 +``` +double pi = 3.14; //A +double r = 1.0; //B +double area = pi * r * r; //C +``` +步骤C依赖于步骤A和B,因为指令重排的存在,程序执行顺讯可能是A->B->C,也可能是B->A->C,但是C不能在A或者B前面执行,这将违反as-if-serial语义。 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50dd857cc9d94ec8853531fdeae52497~tplv-k3u1fbpfcp-zoom-1.image) + +#### Happens-before +Java语言中,有一个先行发生原则(happens-before): +- **程序次序规则**:在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。 +- **管程锁定规则**:一个unLock操作先行发生于后面对同一个锁额lock操作 +- **volatile变量规则**:对一个变量的写操作先行发生于后面对这个变量的读操作 +- **线程启动规则**:Thread对象的start()方法先行发生于此线程的每个一个动作 +- **线程终止规则**:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行 +- **线程中断规则**:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生 +- **对象终结规则**:一个对象的初始化完成先行发生于他的finalize()方法的开始 +- **传递性**:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C + +#### volatile可以解决原子性嘛?为什么? +不可以,可以直接举i++那个例子,原子性需要synchronzied或者lock保证 +``` +public class Test { + public volatile int race = 0; + + public void increase() { + race++; + } + + public static void main(String[] args) { + final Test test = new Test(); + for(int i=0;i<10;i++){ + new Thread(){ + public void run() { + for(int j=0;j<100;j++) + test.increase(); + }; + }.start(); + } + + //等待所有累加线程结束 + while(Thread.activeCount()>1) + Thread.yield(); + System.out.println(test.race); + } +} + +``` + +#### volatile底层原理,如何保证可见性和禁止指令重排(内存屏障) + +volatile 修饰的变量,转成汇编代码,会发现多出一个lock前缀指令。lock指令相当于一个内存屏障,它保证以下这几点: +- 1.重排序时不能把后面的指令重排序到内存屏障之前的位置 +- 2.将本处理器的缓存写入内存 +- 3.如果是写入动作,会导致其他处理器中对应的缓存无效。 + +2、3点保证可见性,第1点禁止指令重排~ + +有兴趣的朋友可以看我这篇文章哈:[Java程序员面试必备:Volatile全方位解析](https://juejin.im/post/6859390417314512909) + + +### 13、AQS组件,实现原理 +AQS,即AbstractQueuedSynchronizer,是构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。可以回答以下这几个关键点哈: +- state 状态的维护。 +- CLH队列 +- ConditionObject通知 +- 模板方法设计模式 +- 独占与共享模式。 +- 自定义同步器。 +- AQS全家桶的一些延伸,如:ReentrantLock等。 + +#### state 状态的维护 + +- state,int变量,锁的状态,用volatile修饰,保证多线程中的可见性。 +- getState()和setState()方法采用final修饰,限制AQS的子类重写它们两。 +- compareAndSetState()方法采用乐观锁思想的CAS算法操作确保线程安全,保证状态 +设置的原子性。 + +对CAS有兴趣的朋友,可以看下我这篇文章哈~ +[CAS乐观锁解决并发问题的一次实践](https://juejin.im/post/6844903869340712967#comment) + + +#### CLH队列 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3f37b908ad9b482fb60de6478817a7dc~tplv-k3u1fbpfcp-zoom-1.image) + +> **CLH(Craig, Landin, and Hagersten locks) 同步队列** 是一个FIFO双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。AQS依赖它来完成同步状态state的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。 + + +#### ConditionObject通知 + +我们都知道,synchronized控制同步的时候,可以配合Object的wait()、notify(),notifyAll() 系列方法可以实现等待/通知模式。而Lock呢?它提供了条件Condition接口,配合await(),signal(),signalAll() 等方法也可以实现等待/通知机制。ConditionObject实现了Condition接口,给AQS提供条件变量的支持 。 + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/385e31246e8c4e1e8e8a9dacb183da74~tplv-k3u1fbpfcp-zoom-1.image) + +ConditionObject队列与CLH队列的爱恨情仇: + +- 调用了await()方法的线程,会被加入到conditionObject等待队列中,并且唤醒CLH队列中head节点的下一个节点。 +- 线程在某个ConditionObject对象上调用了singnal()方法后,等待队列中的firstWaiter会被加入到AQS的CLH队列中,等待被唤醒。 +- 当线程调用unLock()方法释放锁时,CLH队列中的head节点的下一个节点(在本例中是firtWaiter),会被唤醒。 + + +#### 模板方法设计模式 +什么是模板设计模式? +> 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 + +AQS的典型设计模式就是模板方法设计模式啦。AQS全家桶(ReentrantLock,Semaphore)的衍生实现,就体现出这个设计模式。如AQS提供tryAcquire,tryAcquireShared等模板方法,给子类实现自定义的同步器。 + +#### 独占与共享模式 +- 独占式: 同一时刻仅有一个线程持有同步状态,如ReentrantLock。又可分为公平锁和非公平锁。 +- 共享模式:多个线程可同时执行,如Semaphore/CountDownLatch等都是共享式的产物。 + +#### 自定义同步器 + +你要实现自定义锁的话,首先需要确定你要实现的是独占锁还是共享锁,定义原子变量state的含义,再定义一个内部类去继承AQS,重写对应的模板方法即可啦 + +#### AQS全家桶的一些延伸。 +Semaphore,CountDownLatch,ReentrantLock + +可以看下之前我这篇文章哈,[AQS解析与实战](https://juejin.im/post/6844903903188746247) + +### 14、什么是多线程环境下的伪共享 +- 什么是伪共享 +- 如何解决伪共享问题 + +#### 什么是伪共享 +伪共享定义? +> CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改相互独立的变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。这就是伪共享 + +现代计算机计算模型,大家都有印象吧?我之前这篇文章也有讲过,有兴趣可以看一下哈,[Java程序员面试必备:Volatile全方位解析](https://juejin.im/post/6859390417314512909) + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a3bcae0d0fb44b1b9fb2db5093e6dd5d~tplv-k3u1fbpfcp-zoom-1.image) +- CPU执行速度比内存速度快好几个数量级,为了提高执行效率,现代计算机模型演变出CPU、缓存(L1,L2,L3),内存的模型。 +- CPU执行运算时,如先从L1缓存查询数据,找不到再去L2缓存找,依次类推,直到在内存获取到数据。 +- 为了避免频繁从内存获取数据,聪明的科学家设计出缓存行,缓存行大小为64字节。 + +也正是因为缓存行,就导致伪共享问题的存在,如图所示: +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/886ab0227a174842a2976581472eec06~tplv-k3u1fbpfcp-zoom-1.image) + +假设数据a、b被加载到同一个缓存行。 +- 当线程1修改了a的值,这时候CPU1就会通知其他CPU核,当前缓存行(Cache line)已经失效。 +- 这时候,如果线程2发起修改b,因为缓存行已经失效了,所以**core2 这时会重新从主内存中读取该 Cache line 数据**。读完后,因为它要修改b的值,那么CPU2就通知其他CPU核,当前缓存行(Cache line)又已经失效。 +- 酱紫,如果同一个Cache line的内容被多个线程读写,就很容易产生相互竞争,频繁回写主内存,会大大降低性能。 + + +#### 如何解决伪共享问题 +既然伪共享是因为相互独立的变量存储到相同的Cache line导致的,一个缓存行大小是64字节。那么,我们就可以**使用空间换时间**,即数据填充的方式,把独立的变量分散到不同的Cache line~ + +共享内存demo例子: +``` +public class FalseShareTest { + + public static void main(String[] args) throws InterruptedException { + Rectangle rectangle = new Rectangle(); + long beginTime = System.currentTimeMillis(); + Thread thread1 = new Thread(() -> { + for (int i = 0; i < 100000000; i++) { + rectangle.a = rectangle.a + 1; + } + }); + + Thread thread2 = new Thread(() -> { + for (int i = 0; i < 100000000; i++) { + rectangle.b = rectangle.b + 1; + } + }); + + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + System.out.println("执行时间" + (System.currentTimeMillis() - beginTime)); + } + +} + +class Rectangle { + volatile long a; + volatile long b; +} +``` + +运行结果: +``` +执行时间2815 +``` +一个long类型是8字节,我们在变量a和b之间不上7个long类型变量呢,输出结果是啥呢?如下: +``` +class Rectangle { + volatile long a; + long a1,a2,a3,a4,a5,a6,a7; + volatile long b; +} +``` +运行结果: +``` +执行时间1113 +``` +可以发现利用填充数据的方式,让读写的变量分割到不同缓存行,可以很好挺高性能~ + + +### 15、 说一下 Runnable和 Callable有什么区别? +- Callable接口方法是call(),Runnable的方法是run(); +- Callable接口call方法有返回值,支持泛型,Runnable接口run方法无返回值。 +- Callable接口call()方法允许抛出异常;而Runnable接口run()方法不能继续上抛异常; + +``` +@FunctionalInterface +public interface Callable { + /** + * 支持泛型V,有返回值,允许抛出异常 + */ + V call() throws Exception; +} + +@FunctionalInterface +public interface Runnable { + /** + * 没有返回值,不能继续上抛异常 + */ + public abstract void run(); +} + +``` + +看下demo代码吧,这样应该好理解一点哈~ +``` +/* + * @Author 捡田螺的小男孩 + * @date 2020年08月18日 + */ +public class CallableRunnableTest { + + public static void main(String[] args) { + ExecutorService executorService = Executors.newFixedThreadPool(5); + + Callable callable =new Callable() { + @Override + public String call() throws Exception { + return "你好,callable"; + } + }; + + //支持泛型 + Future futureCallable = executorService.submit(callable); + + try { + System.out.println(futureCallable.get()); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + Runnable runnable = new Runnable() { + @Override + public void run() { + System.out.println("你好呀,runnable"); + } + }; + + Future futureRunnable = executorService.submit(runnable); + try { + System.out.println(futureRunnable.get()); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + executorService.shutdown(); + + } +} +``` +运行结果: +``` +你好,callable +你好呀,runnable +null +``` + + +### 16、wait(),notify()和suspend(),resume()之间的区别 + +- wait() 使得线程进入阻塞等待状态,并且释放锁 +- notify()唤醒一个处于等待状态的线程,它一般跟wait()方法配套使用。 +- suspend()使得线程进入阻塞状态,并且不会自动恢复,必须对应的resume() 被调用,才能使得线程重新进入可执行状态。suspend()方法很容易引起死锁问题。 +- resume()方法跟suspend()方法配套使用。 + +**suspend()不建议使用**,suspend()方法在调用后,线程不会释放已经占有的资 源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。 + + +### 17.Condition接口及其实现原理 +- Condition接口与Object监视器方法对比 +- Condition接口使用demo +- Condition实现原理 + +#### Condition接口与Object监视器方法对比 +Java对象(Object),提供wait()、notify(),notifyAll() 系列方法,配合synchronized,可以实现等待/通知模式。而Condition接口配合Lock,通过await(),signal(),signalAll() 等方法,也可以实现类似的等待/通知机制。 + +| 对比项 | 对象监视方法| Condition | +|-----|-----|------| +| 前置条件 | 获得对象的锁 | 调用Lock.lock()获取锁,调用Lock.newCondition()获得Condition对象| +| 调用方式 | 直接调用,object.wait() | 直接调用,condition.await() | +| 等待队列数 | 1个 | 多个 | +| 当前线程释放锁并进入等待状态 | 支持 | 支持 | +| 在等待状态中不响应中断 | 不支持 | 支持 | +| 当前线程释放锁并进入超时等待状态| 支持 | 支持 | +| 当前线程释放锁并进入等待状态到将来的某个时间| 不支持 | 支持 | +| 唤醒等待队列中的一个线程| 支持 | 支持 | +| 唤醒等待队列中的全部线程| 支持 | 支持 | + + +#### Condition接口使用demo +``` +public class ConditionTest { + Lock lock = new ReentrantLock(); + Condition condition = lock.newCondition(); + + public void conditionWait() throws InterruptedException { + lock.lock(); + try { + condition.await(); + } finally { + lock.unlock(); + } + } + + public void conditionSignal() throws InterruptedException { + lock.lock(); + try { + condition.signal(); + } finally { + lock.unlock(); + } + } +} + +``` +#### Condition实现原理 +其实,同步队列和等待队列中节点类型都是同步器的静态内部类 AbstractQueuedSynchronizer.Node,接下来我们图解一下Condition的实现原理~ + +**等待队列的基本结构图** +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6f62d7c11ea4907b84924c4a02cee7f~tplv-k3u1fbpfcp-zoom-1.image) +> 一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾节点 (lastWaiter)。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队 + +**AQS 结构图** + +ConditionI是跟Lock一起结合使用的,底层跟同步器(AQS)相关。同步器拥有一个同步队列和多个等待队列~ +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f342c548da8c42d6a60a0c19aeee8489~tplv-k3u1fbpfcp-zoom-1.image) + + +**等待** + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/677bf2a7edd8447b9f21e626e6667aa3~tplv-k3u1fbpfcp-zoom-1.image) +> 当调用await()方法时,相当于同步队列的首节点(获取了锁的节点)移动到Condition的等待队列中。 + + +**通知** + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bac0705fa308413b97eed7a9d8b938c6~tplv-k3u1fbpfcp-zoom-1.image) +> 调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在 +唤醒节点之前,会将节点移到同步队列中。 + + +### 18、线程池如何调优,最大数目如何确认? + +在《Java Concurrency in Practice》一书中,有一个评估线程池线程大小的公式 +> **Nthreads=Ncpu*Ucpu*(1+w/c)** +> +> - Ncpu = CPU总核数 +- Ucpu =cpu使用率,0~1 +- W/C=等待时间与计算时间的比率 + +假设cpu 100%运转,则公式为 +``` +Nthreads=Ncpu*(1+w/c) +``` + +**估算的话,酱紫:** +- 如果是**IO密集型应用**(如数据库数据交互、文件上传下载、网络数据传输等等),IO操作一般比较耗时,等待时间与计算时间的比率(w/c)会大于1,所以最佳线程数估计就是 Nthreads=Ncpu*(1+1)= 2Ncpu 。 +- 如果是**CPU密集型应用**(如算法比较复杂的程序),最理想的情况,没有等待,w=0,Nthreads=Ncpu。又对于计算密集型的任务,在拥有N个处理器的系统上,当线程池的大小为N+1时,通常能实现最优的效率。所以 Nthreads = Ncpu+1 + +有具体指参考呢?举个例子 +> 比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:线程池大小=(1+1.5/05)*8 =32。 + +参考了网上这篇文章,写得很棒,有兴趣的朋友可以去看一下哈: +- [根据CPU核心数确定线程池并发线程数](https://www.cnblogs.com/dennyzhangdd/p/6909771.html) + +### 19、 假设有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? + +可以使用**join方法**解决这个问题。比如在线程A中,调用线程B的join方法表示的意思就是**:A等待B线程执行完毕后(释放CPU执行权),在继续执行。** + +代码如下: +``` +public class ThreadTest { + + public static void main(String[] args) { + + Thread spring = new Thread(new SeasonThreadTask("春天")); + Thread summer = new Thread(new SeasonThreadTask("夏天")); + Thread autumn = new Thread(new SeasonThreadTask("秋天")); + + try + { + //春天线程先启动 + spring.start(); + //主线程等待线程spring执行完,再往下执行 + spring.join(); + //夏天线程再启动 + summer.start(); + //主线程等待线程summer执行完,再往下执行 + summer.join(); + //秋天线程最后启动 + autumn.start(); + //主线程等待线程autumn执行完,再往下执行 + autumn.join(); + } catch (InterruptedException e) + { + e.printStackTrace(); + } + } +} + +class SeasonThreadTask implements Runnable{ + + private String name; + + public SeasonThreadTask(String name){ + this.name = name; + } + + @Override + public void run() { + for (int i = 1; i <4; i++) { + System.out.println(this.name + "来了: " + i + "次"); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} + +``` + +运行结果: +``` +春天来了: 1次 +春天来了: 2次 +春天来了: 3次 +夏天来了: 1次 +夏天来了: 2次 +夏天来了: 3次 +秋天来了: 1次 +秋天来了: 2次 +秋天来了: 3次 +``` +### 20. LockSupport作用是? + +- LockSupport作用 +- park和unpark,与wait,notify的区别 +- Object blocker作用? + +LockSupport是个工具类,它的主要作用是挂起和唤醒线程, 该工具类是创建锁和其他同步类的基础。 + +``` +public static void park(); //挂起当前线程,调用unpark(Thread thread)或者当前线程被中断,才能从park方法返回 +public static void parkNanos(Object blocker, long nanos); // 挂起当前线程,有超时时间的限制 +public static void parkUntil(Object blocker, long deadline); // 挂起当前线程,直到某个时间 +public static void park(Object blocker); //挂起当前线程 +public static void unpark(Thread thread); // 唤醒当前thread线程 +``` + +看个例子吧: +``` +public class LockSupportTest { + + public static void main(String[] args) { + + CarThread carThread = new CarThread(); + carThread.setName("劳斯劳斯"); + carThread.start(); + + try { + Thread.currentThread().sleep(2000); + carThread.park(); + Thread.currentThread().sleep(2000); + carThread.unPark(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + static class CarThread extends Thread{ + + private boolean isStop = false; + + @Override + public void run() { + + System.out.println(this.getName() + "正在行驶中"); + + while (true) { + + if (isStop) { + System.out.println(this.getName() + "车停下来了"); + LockSupport.park(); //挂起当前线程 + } + System.out.println(this.getName() + "车还在正常跑"); + + try { + Thread.sleep(1000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + } + + public void park() { + isStop = true; + System.out.println("停车啦,检查酒驾"); + + } + + public void unPark(){ + isStop = false; + LockSupport.unpark(this); //唤醒当前线程 + System.out.println("老哥你没酒驾,继续开吧"); + } + + } +} + +``` +运行结果: +``` +劳斯劳斯正在行驶中 +劳斯劳斯车还在正常跑 +劳斯劳斯车还在正常跑 +停车啦,检查酒驾 +劳斯劳斯车停下来了 +老哥你没酒驾,继续开吧 +劳斯劳斯车还在正常跑 +劳斯劳斯车还在正常跑 +劳斯劳斯车还在正常跑 +劳斯劳斯车还在正常跑 +劳斯劳斯车还在正常跑 +劳斯劳斯车还在正常跑 +``` + +LockSupport的park和unpark的实现,有点类似wait和notify的功能。但是 +> - park不需要获取对象锁 +> - 中断的时候park不会抛出InterruptedException异常,需要在park之后自行判断中断状态 +> - 使用park和unpark的时候,可以不用担心park的时序问题造成死锁 +> - LockSupport不需要在同步代码块里 +> - unpark却可以唤醒一个指定的线程,notify只能随机选择一个线程唤醒 + +Object blocker作用? +> 方便在线程dump的时候看到具体的阻塞对象的信息。 + +### 公众号 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07e9fd0521c244adab8556fee99a2011~tplv-k3u1fbpfcp-zoom-1.image) + + +### 参考与感谢 +- 《java并发编程的艺术》 +- [杂谈 什么是伪共享(false sharing)?](https://juejin.im/post/6844903841964507150) +- [根据CPU核心数确定线程池并发线程数](https://www.cnblogs.com/dennyzhangdd/p/6909771.html) +- [LockSupport的用法及原理](https://www.jianshu.com/p/f1f2cd289205) +- [探讨缓存行与伪共享](https://mp.weixin.qq.com/s/f9CTFc4H9Q0x4K-02beUoQ) diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円.md" deleted file mode 100644 index 79a437b..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円.md" +++ /dev/null @@ -1,51 +0,0 @@ -### Java 集合 -1. Arraylist与LinkedList区别 -2. Collections.sort和Arrays.sort的实现原理 -3. HashMap原理,java8做了什么改变 -4. List 和 Set,Map 的区别 -5. poll()方法和 remove()方法的区别? -6. HashMap,HashTable,ConcurrentHash的共同点和区别 -7. 写一段代码在遍历 ArrayList 时移除一个元素 -8. Java中怎么打印数组? -9. TreeMap底层? -10. HashMap 的扩容过程 -11. HashSet是如何保证不重复的 -12. HashMap 是线程安全的吗,为什么不是线程安全的?死循环问题? -13. LinkedHashMap的应用,底层,原理 -14. 哪些集合类是线程安全的?哪些不安全? -15. ArrayList 和 Vector 的区别是什么? -16. Collection与Collections的区别是什么? -17. 如何决定使用 HashMap 还是TreeMap? -18. 如何实现数组和 List之间的转换? -19. 迭代器 Iterator 是什么?怎么用,有什么特点? -20. Iterator 和 ListIterator 有什么区别? -21. 怎么确保一个集合不能被修改? -22. 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么? -23. 什么是Java优先级队列(Priority Queue)? -24. JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。 -25. 阻塞队列的实现,ArrayBlockingQueue的底层实现? -26. Java 中的 LinkedList是单向链表还是双向链表? -27. 说一说ArrayList 的扩容机制吧 -28. HashMap 的长度为什么是2的幂次方,以及其他常量定义的含义~ -29. ConcurrenHashMap 原理?1.8 中为什么要用红黑树? -30. ArrayList的默认大小 -31. 为何Collection不从Cloneable和Serializable接口继承? -32. Enumeration和Iterator接口的区别? -33. 我们如何对一组对象进行排序? -34. 当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它? -35. 说一下 HashSet 的实现原理? -36. Array 和 ArrayList 有何区别? -37. 在 Queue中poll()和 remove()有什么区别? -38. ArrayList 如何删除重复的元素或者指定的元素; -39. 讲讲红黑树的特点? -40. Java集合类框架的最佳实践有哪些? -41. Enumeration接口和Iterator 接口的区别有哪些? -42. HashSet和TreeSet有什么区别? -43. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? -44. 说出ArrayList,LinkedList的存储性能和特性 -45. Java中HashMap的key值要是为类对象则该类需要满足什么条件? -46. ArrayList集合加入1万条数据,应该怎么提高效率 -47. 如何对Object的list排序 -48. ArrayList 和 HashMap 的默认大小是多数? -49. 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的 -50. HashMap是怎么解决哈希冲突的 diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円.md" new file mode 100644 index 0000000..ca19fa4 --- /dev/null +++ "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円.md" @@ -0,0 +1,858 @@ +## 前言 +来了来了,50道Java集合面试题也来啦~ 已经上传github: +> https://github.com/whx123/JavaHome + +### 1. Arraylist与LinkedList区别 +可以从它们的底层数据结构、效率、开销进行阐述哈 +- ArrayList是数组的数据结构,LinkedList是链表的数据结构。 +- 随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于索引(index)的数据结构,可以直接映射到。 +- 插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。 +- LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。 + +### 2. Collections.sort和Arrays.sort的实现原理 + +Collection.sort是对list进行排序,Arrays.sort是对数组进行排序。 + +#### Collections.sort底层实现 + +Collections.sort方法调用了list.sort方法 +![](https://user-gold-cdn.xitu.io/2020/5/27/1725341b6dbcca6a?w=787&h=139&f=png&s=13963) +list.sort方法调用了Arrays.sort的方法 +![](https://user-gold-cdn.xitu.io/2020/5/27/1725341d6c861c7a?w=530&h=140&f=png&s=11349) +因此,**Collections.sort方法底层就是调用的Array.sort方法** + +#### Arrays.sort底层实现 +Arrays的sort方法,如下: +![](https://user-gold-cdn.xitu.io/2020/5/27/17253551bc95b431?w=912&h=307&f=png&s=32161) +如果比较器为null,进入sort(a)方法。如下: +![](https://user-gold-cdn.xitu.io/2020/5/27/17253549f9433160?w=956&h=213&f=png&s=25612) +因此,Arrays的sort方法底层就是: +- legacyMergeSort(a),归并排序, +- ComparableTimSort.sort():即Timsort排序。 + +#### Timesort排序 +Timsort排序是结合了合并排序(merge.sort)和插入排序(insertion sort)而得出的排序方法; + +1.当数组长度小于某个值,采用的是二分插入排序算法,如下: + +![](https://user-gold-cdn.xitu.io/2020/5/27/172567bc65ac1d96?w=755&h=425&f=png&s=50372) + +2. 找到各个run,并入栈。 + +![](https://user-gold-cdn.xitu.io/2020/5/27/172568f27aa6725d?w=905&h=767&f=png&s=70722) + +3. 按规则合并run。 + +![](https://user-gold-cdn.xitu.io/2020/5/27/172568e815ffcd33?w=765&h=379&f=png&s=31321) + +### 3. HashMap原理,java8做了什么改变 +- HashMap是以键值对存储数据的集合容器 +- HashMap是非线性安全的。 +- HashMap底层数据结构:数组+(链表、红黑树),jdk8之前是用数组+链表的方式实现,jdk8引进了红黑树 +- Hashmap数组的默认初始长度是16,key和value都允许null的存在 +- HashMap的内部实现数组是Node[]数组,上面存放的是key-value键值对的节点。HashMap通过put和get方法存储和获取。 +- HashMap的put方法,首先计算key的hashcode值,定位到对应的数组索引,然后再在该索引的单向链表上进行循环遍历,用equals比较key是否存在,如果存在则用新的value覆盖原值,如果没有则向后追加。 +- jdk8中put方法:先判断Hashmap是否为空,为空就扩容,不为空计算出key的hash值i,然后看table[i]是否为空,为空就直接插入,不为空判断当前位置的key和table[i]是否相同,相同就覆盖,不相同就查看table[i]是否是红黑树节点,如果是的话就用红黑树直接插入键值对,如果不是开始遍历链表插入,如果遇到重复值就覆盖,否则直接插入,如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束。 +- Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。 +- HashMap的get方法就是计算出要获取元素的hash值,去对应位置获取即可。 +- HashMap的扩容机制,Hashmap的扩容中主要进行两步,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中,jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二部一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。 +- HashMap大小为什么是2的幂次方?效率高+空间分布均匀 + +有关于HashMap这些常量设计目的,也可以看我这篇文章: +[面试加分项-HashMap源码中这些常量的设计目的](https://juejin.im/post/5d7195f9f265da03a6533942) + +### 4. List 和 Set,Map 的区别 +- List 以索引来存取元素,有序的,元素是允许重复的,可以插入多个null。 +- Set 不能存放重复元素,无序的,只允许一个null +- Map 保存键值对映射,映射关系可以一对一、多对一 +- List 有基于数组、链表实现两种方式 +- Set、Map 容器有基于哈希存储和红黑树两种方式实现 +- Set 基于 Map 实现,Set 里的元素值就是 Map的键值 + +### 5. poll()方法和 remove()方法的区别? +Queue队列中,poll() 和 remove() 都是从队列中取出一个元素,在队列元素为空的情况下,remove() 方法会抛出异常,poll() 方法只会返回 null 。 + +看一下源码的解释吧: +``` + /** + * Retrieves and removes the head of this queue. This method differs + * from {@link #poll poll} only in that it throws an exception if this + * queue is empty. + * + * @return the head of this queue + * @throws NoSuchElementException if this queue is empty + */ + E remove(); + + /** + * Retrieves and removes the head of this queue, + * or returns {@code null} if this queue is empty. + * + * @return the head of this queue, or {@code null} if this queue is empty + */ + E poll(); +``` + +### 6. HashMap,HashTable,ConcurrentHash的共同点和区别 + +**HashMap** +- 底层由链表+数组+红黑树实现 +- 可以存储null键和null值 +- 线性不安全 +- 初始容量为16,扩容每次都是2的n次幂 +- 加载因子为0.75,当Map中元素总数超过Entry数组的0.75,触发扩容操作. +- 并发情况下,HashMap进行put操作会引起死循环,导致CPU利用率接近100% +- HashMap是对Map接口的实现 + + +**HashTable** +- HashTable的底层也是由链表+数组+红黑树实现。 +- 无论key还是value都不能为null +- 它是线性安全的,使用了synchronized关键字。 +- HashTable实现了Map接口和Dictionary抽象类 +- Hashtable初始容量为11 + +**ConcurrentHashMap** +- ConcurrentHashMap的底层是数组+链表+红黑树 +- 不能存储null键和值 +- ConcurrentHashMap是线程安全的 +- ConcurrentHashMap使用锁分段技术确保线性安全 +- JDK8为何又放弃分段锁,是因为多个分段锁浪费内存空间,竞争同一个锁的概率非常小,分段锁反而会造成效率低。 + +### 7. 写一段代码在遍历 ArrayList 时移除一个元素 +因为foreach删除会导致快速失败问题,fori顺序遍历会导致重复元素没删除,所以正确解法如下: + +第一种遍历,倒叙遍历删除 +``` +for(int i=list.size()-1; i>-1; i--){ + if(list.get(i).equals("jay")){ + list.remove(list.get(i)); + } +} +``` +第二种,迭代器删除 +``` +Iterator itr = list.iterator(); +while(itr.hasNext()) { + if(itr.next().equals("jay") { + itr.remove(); + } +} +``` + +### 8. Java中怎么打印数组? +数组是不能直接打印的哈,如下: + +``` +public class Test { + + public static void main(String[] args) { + String[] jayArray = {"jay", "boy"}; + System.out.println(jayArray); + } +} +//output +[Ljava.lang.String;@1540e19d +``` + +打印数组可以用流的方式Strem.of().foreach(),如下: +``` +public class Test { + + public static void main(String[] args) { + String[] jayArray = {"jay", "boy"}; + Stream.of(jayArray).forEach(System.out::println); + } +} +//output +jay +boy +``` +打印数组,最优雅的方式可以用这个APi,Arrays.toString() +``` +public class Test { + public static void main(String[] args) { + String[] jayArray = {"jay", "boy"}; + System.out.println(Arrays.toString(jayArray)); + } +} +//output +[jay, boy] +``` + +### 9. TreeMap底层? +- TreeMap实现了SotredMap接口,它是有序的集合。 +- TreeMap底层数据结构是一个红黑树,每个key-value都作为一个红黑树的节点。 +- 如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序。 + +![](https://user-gold-cdn.xitu.io/2020/6/12/172a4245313c7162?w=864&h=431&f=png&s=31334) + +### 10. HashMap 的扩容过程 +Hashmap的扩容: +- 第一步把数组长度变为原来的两倍, +- 第二步把旧数组的元素重新计算hash插入到新数组中。 +- jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二步一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。 + +### 11. HashSet是如何保证不重复的 +可以看一下HashSet的add方法,元素E作为HashMap的key,我们都知道HashMap的可以是不允许重复的,哈哈。 +``` + public boolean add(E e) { + return map.put(e, PRESENT)==null; +} +``` +### 12. HashMap 是线程安全的吗,为什么不是线程安全的?死循环问题? +不是线性安全的。 + +并发的情况下,扩容可能导致死循环问题。 + +### 13. LinkedHashMap的应用,底层,原理 +- LinkedHashMap维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序(insert-order)或者是访问顺序,其中默认的迭代访问顺序就是插入顺序,即可以按插入的顺序遍历元素,这点和HashMap有很大的不同。 +- LRU算法可以用LinkedHashMap实现。 + +### 14. 哪些集合类是线程安全的?哪些不安全? +线性安全的 +- Vector:比Arraylist多了个同步化机制。 +- Hashtable:比Hashmap多了个线程安全。 +- ConcurrentHashMap:是一种高效但是线程安全的集合。 +- Stack:栈,也是线程安全的,继承于Vector。 + +线性不安全的 +- Hashmap +- Arraylist +- LinkedList +- HashSet +- TreeSet +- TreeMap + +### 15. ArrayList 和 Vector 的区别是什么? +- Vector是线程安全的,ArrayList不是线程安全的。 +- ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。 +- Vector只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。 + +![](https://user-gold-cdn.xitu.io/2020/5/28/172588218a417dc8?w=604&h=210&f=png&s=18179) + +### 16. Collection与Collections的区别是什么? +- Collection是Java集合框架中的基本接口,如List接口也是继承于它 + +``` +public interface List extends Collection { +``` +- Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。如下: + +``` +public static > void sort(List list) { + list.sort(null); +} +``` + +### 17. 如何决定使用 HashMap 还是TreeMap? +这个点,主要考察HashMap和TreeMap的区别。 + +TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按key的升序排序,也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的记录是排过序的。 + +### 18. 如何实现数组和 List之间的转换? + +#### List 转 Array +List 转Array,必须使用集合的 toArray(T[] array),如下: +``` +List list = new ArrayList(); +list.add("jay"); +list.add("tianluo"); + +// 使用泛型,无需显式类型转换 +String[] array = list.toArray(new String[list.size()]); +System.out.println(array[0]); +``` + +如果直接使用 toArray 无参方法,返回值只能是 Object[] 类,强转其他类型可能有问题,demo如下: + +``` +List list = new ArrayList(); +list.add("jay"); +list.add("tianluo"); + +String[] array = (String[]) list.toArray(); +System.out.println(array[0]); +``` +运行结果: + +``` +Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String; + at Test.main(Test.java:14) +``` + +#### Array 转List +使用Arrays.asList() 把数组转换成集合时,不能使用修改集合相关的方法啦,如下: + +``` +String[] str = new String[] { "jay", "tianluo" }; +List list = Arrays.asList(str); +list.add("boy"); +``` +运行结果如下: + +``` +Exception in thread "main" java.lang.UnsupportedOperationException + at java.util.AbstractList.add(AbstractList.java:148) + at java.util.AbstractList.add(AbstractList.java:108) + at Test.main(Test.java:13) +``` + +因为 Arrays.asList不是返回java.util.ArrayList,而是一个内部类ArrayList。 +![](https://user-gold-cdn.xitu.io/2020/5/31/1726a89f75bf8725?w=804&h=281&f=png&s=31691) + + +可以这样使用弥补这个缺点: + +``` +//方式一: +ArrayList< String> arrayList = new ArrayList(strArray.length); +Collections.addAll(arrayList, strArray); +//方式二: +ArrayList list = new ArrayList(Arrays.asList(strArray)) ; +``` +### 19. 迭代器 Iterator 是什么?怎么用,有什么特点? + +``` +public interface Collection extends Iterable { + +Iterator iterator(); +``` + +方法如下: + +``` +next() 方法获得集合中的下一个元素 +hasNext() 检查集合中是否还有元素 +remove() 方法将迭代器新返回的元素删除 +forEachRemaining(Consumer action) 方法,遍历所有元素 +``` + +Iterator 主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。 + +使用demo如下: +``` +List list = new ArrayList(); +Iterator it = list. iterator(); +while(it. hasNext()){ + String obj = it. next(); + System. out. println(obj); +} +``` + +### 20. Iterator 和 ListIterator 有什么区别? + +![](https://user-gold-cdn.xitu.io/2020/6/6/1728696f00e6cc3e?w=547&h=189&f=png&s=17728) + +![](https://user-gold-cdn.xitu.io/2020/6/6/1728698274a8137f?w=527&h=363&f=png&s=34263) + +- ListIterator 比 Iterator有更多的方法。 +- ListIterator只能用于遍历List及其子类,Iterator可用来遍历所有集合, +- ListIterator遍历可以是逆向的,因为有previous()和hasPrevious()方法,而Iterator不可以。 +- ListIterator有add()方法,可以向List添加对象,而Iterator却不能。 +- ListIterator可以定位当前的索引位置,因为有nextIndex()和previousIndex()方法,而Iterator不可以。 +- ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改哦。 + +### 21. 怎么确保一个集合不能被修改? +很多朋友很可能想到用final关键字进行修饰,final修饰的这个成员变量,如果是基本数据类型,表示这个变量的值是不可改变的,如果是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变滴~验证一下,如下: +``` +public class Test { + //final 修饰 + private static final Map map = new HashMap(); + { + map.put(1, "jay"); + map.put(2, "tianluo"); + } + + public static void main(String[] args) { + map.put(1, "boy"); + System.out.println(map.get(1)); + } +} +``` +运行结果如下: + +``` +//可以洗发现,final修饰,集合还是会被修改呢 +boy +``` + +嘻嘻,那么,到底怎么确保一个集合不能被修改呢,看以下这三哥们~ +- unmodifiableMap +- unmodifiableList +- unmodifiableSet + +再看一下demo吧 +``` +public class Test { + + private static Map map = new HashMap(); + { + map.put(1, "jay"); + map.put(2, "tianluo"); + + } + + public static void main(String[] args) { + map = Collections.unmodifiableMap(map); + map.put(1, "boy"); + System.out.println(map.get(1)); + } +} + +``` +运行结果: + +``` +// 可以发现,unmodifiableMap确保集合不能修改啦,抛异常了 +Exception in thread "main" java.lang.UnsupportedOperationException + at java.util.Collections$UnmodifiableMap.put(Collections.java:1457) + at Test.main(Test.java:14) +``` + +### 22. 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么? +#### 快速失败 +在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。 + +``` +public class Test { + + public static void main(String[] args) { + List list = new ArrayList(); + list.add(1); + list.add(2); + + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + System.out.println(iterator.next()); + list.add(3); + System.out.println(list.size()); + } + + } +} +``` +运行结果: + +``` +1 +Exception in thread "main" java.util.ConcurrentModificationException +3 + at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) + at java.util.ArrayList$Itr.next(ArrayList.java:859) + at Test.main(Test.java:12) +``` +#### 安全失败 +采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。 + +``` +public class Test { + + public static void main(String[] args) { + List list = new CopyOnWriteArrayList(); + list.add(1); + list.add(2); + + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + System.out.println(iterator.next()); + list.add(3); + System.out.println("list size:"+list.size()); + } + + } +} + +``` +运行结果: + +``` +1 +list size:3 +2 +list size:4 +``` +其实,在java.util.concurrent 并发包的集合,如 ConcurrentHashMap, CopyOnWriteArrayList等,默认为都是安全失败的。 + +### 23. 什么是Java优先级队列(Priority Queue)? + +优先队列PriorityQueue是Queue接口的实现,可以对其中元素进行排序 +- 优先队列中元素默认排列顺序是升序排列 +- 但对于自己定义的类来说,需要自己定义比较器 +``` +public class PriorityQueue extends AbstractQueue + implements java.io.Serializable { + ... + private final Comparator comparator; +``` + +方法: + +``` +peek()//返回队首元素 +poll()//返回队首元素,队首元素出队列 +add()//添加元素 +size()//返回队列元素个数 +isEmpty()//判断队列是否为空,为空返回true,不空返回false +``` + +特点: +- 1.基于优先级堆 +- 2.不允许null值 +- 3.线程不安全 +- 4.出入队时间复杂度O(log(n)) +- 5.调用remove()返回堆内最小值 + +### 24. JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。 + +jdk8 放弃了分段锁而是用了Node锁,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。 + +可以跟面试官聊聊悲观锁和CAS乐观锁的区别,优缺点哈~ + + +### 25. 阻塞队列的实现,ArrayBlockingQueue的底层实现? +ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。常用的操作包括 add ,offer,put,remove,poll,take,peek。 + +![](https://user-gold-cdn.xitu.io/2020/6/12/172a58069511d5db?w=808&h=537&f=png&s=41200) + +可以结合线程池跟面试官讲一下哦~ + +### 26. Java 中的 LinkedList是单向链表还是双向链表? + +哈哈,看源码吧,是双向链表 +``` + private static class Node { + E item; + Node next; + Node prev; + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } +``` + +### 27. 说一说ArrayList 的扩容机制吧 +ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。 + +``` + + public boolean add(E e) { + //扩容 + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; + } + private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); +} + +private static int calculateCapacity(Object[] elementData, int minCapacity) { + //如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); + } + return minCapacity; + } + + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + // 如果最小需要空间比elementData的内存空间要大,则需要扩容 + // overflow-conscious code + if (minCapacity - elementData.length> 0) + grow(minCapacity); + } + + private void grow(int minCapacity) { + // 获取elementData数组的内存空间长度 + int oldCapacity = elementData.length; + // 扩容至原来的1.5倍 + int newCapacity = oldCapacity + (oldCapacity>> 1); + //校验容量是否够 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + //若预设值大于默认的最大值,检查是否溢出 + if (newCapacity - MAX_ARRAY_SIZE> 0) + newCapacity = hugeCapacity(minCapacity); + // 调用Arrays.copyOf方法将elementData数组指向新的内存空间 + //并将elementData的数据复制到新的内存空间 + elementData = Arrays.copyOf(elementData, newCapacity); + } +``` +### 28. HashMap 的长度为什么是2的幂次方,以及其他常量定义的含义~ +为了能让HashMap存取高效,数据分配均匀。 + +看着呢,以下等式相等,但是位移运算比取余效率高很多呢~ + +``` +hash%length=hash&(length-1) +``` + + +可以看下我这篇文章哈~ +[面试加分项-HashMap源码中这些常量的设计目的](https://juejin.im/post/5d7195f9f265da03a6533942) + +### 29. ConcurrenHashMap 原理?1.8 中为什么要用红黑树? + +聊到ConcurrenHashMap,需要跟面试官聊到安全性,分段锁segment,为什么放弃了分段锁,与及选择CAS,其实就是都是从效率和安全性触发,嘻嘻~ + +``` +java8不是用红黑树来管理hashmap,而是在hash值相同的情况下(且重复数量大于8),用红黑树来管理数据。 +红黑树相当于排序数据。可以自动的使用二分法进行定位。性能较高。 +``` + + + +### 30. ArrayList的默认大小 +ArrayList 的默认大小是 10 个元素 + +``` +/** + * Default initial capacity. + */ +private static final int DEFAULT_CAPACITY = 10; +``` +### 31. 为何Collection不从Cloneable和Serializable接口继承? +> - Collection表示一个集合,包含了一组对象元素。如何维护它的元素对象是由具体实现来决定的。因为集合的具体形式多种多样,例如list允许重复,set则不允许。而克隆(clone)和序列化(serializable)只对于具体的实体,对象有意义,你不能说去把一个接口,抽象类克隆,序列化甚至反序列化。所以具体的collection实现类是否可以克隆,是否可以序列化应该由其自身决定,而不能由其超类强行赋予。 +> - 如果collection继承了clone和serializable,那么所有的集合实现都会实现这两个接口,而如果某个实现它不需要被克隆,甚至不允许它序列化(序列化有风险),那么就与collection矛盾了。 + +### 32. Enumeration和Iterator接口的区别? + +``` +public interface Enumeration { + boolean hasMoreElements(); + E nextElement(); +} +public interface Iterator { + boolean hasNext(); + E next(); + void remove(); +} +``` +- 函数接口不同 +- Enumeration速度快,占用内存少,但是不是快速失败的,线程不安全。 +- Iterator允许删除底层数据,枚举不允许 +- Iterator安全性高,因为其他线程不能够修改正在被Iterator遍历的集合里面的对象。 + +### 33. 我们如何对一组对象进行排序? +可以用 Collections.sort()+ Comparator.comparing(),因为对对象排序,实际上是对对象的属性排序哈~ + +``` +public class Student { + + private String name; + private int score; + + public Student(String name, int score){ + this.name = name; + this.score = score; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + @Override + public String toString() { + return "Student: " + this.name + " 分数:" + Integer.toString( this.score ); + } +} + +public class Test { + + public static void main(String[] args) { + + List studentList = new ArrayList(); + studentList.add(new Student("D", 90)); + studentList.add(new Student("C", 100)); + studentList.add(new Student("B", 95)); + studentList.add(new Student("A", 95)); + + Collections.sort(studentList, Comparator.comparing(Student::getScore).reversed().thenComparing(Student::getName)); + studentList.stream().forEach(p -> System.out.println(p.toString())); + } +} +``` +### 34. 当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它? +这个跟之前那个不可变集合一样道理哈~ + +> 在作为参数传递之前,使用Collections.unmodifiableCollection(Collection c)方法创建一个只读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。 + + +### 35. 说一下HashSet的实现原理? +- 不能保证元素的排列顺序,顺序有可能发生变化。 +- 元素可以为null +- hashset保证元素不重复~ (这个面试官很可能会问什么原理,这个跟HashMap有关的哦) +- HashSet,需要谈谈它俩hashcode()和equles()哦~ +- 实际是基于HashMap实现的,HashSet 底层使用HashMap来保存所有元素的 + +看看它的add方法吧~ +``` + public boolean add(E e) { + return map.put(e, PRESENT)==null; + } +``` + +### 36. Array 和 ArrayList 有何区别? +- 定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。 +- ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。 + +### 37. 为什么HashMap中String、Integer这样的包装类适合作为key? +String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率~ + +因为 +- 它们都是final修饰的类,不可变性,保证key的不可更改性,不会存在获取hash值不同的情况~ +- 它们内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范 + + +### 38. 如果想用Object作为hashMap的Key?; +重写hashCode()和equals()方法啦~ (这个答案来自互联网哈~) +> - 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; +> - 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性; + +### 39. 讲讲红黑树的特点? + +- 每个节点或者是黑色,或者是红色。 +- 根节点是黑色。 +- 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!] +- 如果一个节点是红色的,则它的子节点必须是黑色的。 +- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 + +### 40. Java集合类框架的最佳实践有哪些? +其实这些点,结合平时工作,代码总结讲出来,更容易吸引到面试官呢 (这个答案来自互联网哈~) +> - 1.根据应用需要正确选择要使用的集合类型对性能非常重要,比如:假如知道元素的大小是固定的,那么选用Array类型而不是ArrayList类型更为合适。 +> - 2.有些集合类型允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以指定初始容量来避免重新计算hash值或者扩容等。 +> - 3.为了类型安全、可读性和健壮性等原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。 +> - 4.使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。 +> - 5.编程的时候接口优于实现 +> - 6.底层的集合实际上是空的情况下,返回为长度是0的集合或数组而不是null。 + + +### 41.谈谈线程池阻塞队列吧~ +- ArrayBlockingQueue +- LinkedBlockingQueue +- DelayQueue +- PriorityBlockingQueue +- SynchronousQueue + +**ArrayBlockingQueue:** (有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。 + +**LinkedBlockingQueue:** (可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列 + +**DelayQueue:**(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。 + +**PriorityBlockingQueue:**(优先级队列)是具有优先级的无界阻塞队列; + +**SynchronousQueue:**(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。 +针对面试题:线程池都有哪几种工作队列? + +我觉得,回答以上几种ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue等,说出它们的特点,并结合使用到对应队列的常用线程池(如newFixedThreadPool线程池使用LinkedBlockingQueue),进行展开阐述, 就可以啦。 + +有兴趣的朋友,可以看看我的这篇文章哦~ + +[面试必备:Java线程池解析](https://juejin.im/post/5d1882b1f265da1ba84aa676#heading-15) + +### 42. HashSet和TreeSet有什么区别? +- Hashset 的底层是由哈希表实现的,Treeset 底层是由红黑树实现的。 +- HashSet中的元素没有顺序,TreeSet保存的元素有顺序性(实现Comparable接口) +- HashSet的add(),remove(),contains()方法的时间复杂度是O(1);TreeSet中,add(),remove(),contains()方法的时间复杂度是O(logn) + + +### 43. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? +元素重复与否是使用equals()方法进行判断的,这个可以跟面试官说说==和equals()的区别,hashcode()和equals + +### 44. 说出ArrayList,LinkedList的存储性能和特性 +这道面试题,跟ArrayList,LinkedList,就是换汤不换药的~ +- ArrayList,使用数组方式存储数据,查询时,ArrayList是基于索引(index)的数据结构,可以直接映射到,速度较快;但是插入数据需要移动数据,效率就比LinkedList慢一点~ +- LinkedList,使用双向链表实现存储,按索引数据需要进行前向或后向遍历,查询相对ArrayList慢一点;但是插入数据速度较快。 +- LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。 + +### 45. HashMap在JDK1.7和JDK1.8中有哪些不同? +互联网上这个答案太详细啦(来源[Java集合必会14问](https://www.jianshu.com/p/939b8a672070)) +![](https://user-gold-cdn.xitu.io/2020/6/12/172a5cecccd07908?w=975&h=583&f=png&s=154020) + +### 46. ArrayList集合加入1万条数据,应该怎么提高效率 + +> 因为ArrayList的底层是数组实现,并且数组的默认值是10,如果插入10000条要不断的扩容,耗费时间,所以我们调用ArrayList的指定容量的构造器方法ArrayList(int size) 就可以实现不扩容,就提高了性能。 + +### 47. 如何对Object的list排序 +看例子吧,哈哈,这个跟对象排序也是一样的呢~ +``` +public class Person { + + private String name; + private Integer age; + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Integer getAge() { + return age; + } + public void setAge(Integer age) { + this.age = age; + } + public Person(String name, Integer age) { + this.name = name; + this.age = age; + } +} + +public class Test { + + public static void main(String[] args) { + + List list = new ArrayList(); + list.add(new Person("jay", 18)); + list.add(new Person("tianLuo", 10)); + + list.stream().forEach(p -> System.out.println(p.getName()+" "+p.getAge())); + // 用comparing比较对象属性 + list.sort(Comparator.comparing(Person::getAge)); + + System.out.println("排序后"); + + list.stream().forEach(p -> System.out.print(p.getName()+" "+p.getAge()+" ")); + } +} +``` + + +### 48. ArrayList 和 HashMap 的默认大小是多数? + +在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。 + +### 49. 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的 +- Hashmap和Hashtable 都不是有序的。 +- TreeMap和LinkedHashmap都是有序的。(TreeMap默认是key升序,LinkedHashmap默认是数据插入顺序) +- TreeMap是基于比较器Comparator来实现有序的。 +- LinkedHashmap是基于链表来实现数据插入有序的。 + +### 50. HashMap是怎么解决哈希冲突的 + +> Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。 + + +### 个人公众号 + +![](https://user-gold-cdn.xitu.io/2019/7/28/16c381c89b127bbb?w=344&h=344&f=jpeg&s=8943) + +- 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论。 +- 如果你觉得本文有哪些不正确的地方,可以评论,也可以关注我公众号,私聊我,大家一起学习进步哈。 + diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/1. Arraylist 344円270円216円 LinkedList 345円214円272円345円210円253円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/1. Arraylist 344円270円216円 LinkedList 345円214円272円345円210円253円.md" deleted file mode 100644 index 3f38b55..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/1. Arraylist 344円270円216円 LinkedList 345円214円272円345円210円253円.md" +++ /dev/null @@ -1,6 +0,0 @@ -思路:从它们的底层数据结构、效率、开销进行阐述 - -- ArrayList是数组的数据结构,LinkedList是链表的数据结构。 -- 随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于索引(index)的数据结构,可以直接映射到。 -- 插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。 -- LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。 diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/14. 345円223円252円344円272円233円351円233円206円345円220円210円347円261円273円346円230円257円347円272円277円347円250円213円345円256円211円345円205円250円347円232円204円357円274円237円345円223円252円344円272円233円344円270円215円345円256円211円345円205円250円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/14. 345円223円252円344円272円233円351円233円206円345円220円210円347円261円273円346円230円257円347円272円277円347円250円213円345円256円211円345円205円250円347円232円204円357円274円237円345円223円252円344円272円233円344円270円215円345円256円211円345円205円250円357円274円237円.md" deleted file mode 100644 index bb8deff..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/14. 345円223円252円344円272円233円351円233円206円345円220円210円347円261円273円346円230円257円347円272円277円347円250円213円345円256円211円345円205円250円347円232円204円357円274円237円345円223円252円344円272円233円344円270円215円345円256円211円345円205円250円357円274円237円.md" +++ /dev/null @@ -1 +0,0 @@ -Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/15. ArrayList 345円222円214円 Vector 347円232円204円345円214円272円345円210円253円346円230円257円344円273円200円344円271円210円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/15. ArrayList 345円222円214円 Vector 347円232円204円345円214円272円345円210円253円346円230円257円344円273円200円344円271円210円357円274円237円.md" deleted file mode 100644 index e320d13..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/15. ArrayList 345円222円214円 Vector 347円232円204円345円214円272円345円210円253円346円230円257円344円273円200円344円271円210円357円274円237円.md" +++ /dev/null @@ -1,3 +0,0 @@ -- 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。 -- 性能:ArrayList 在性能方面要优于 Vector。 -- 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/18. 345円246円202円344円275円225円345円256円236円347円216円260円346円225円260円347円273円204円345円222円214円 List 344円271円213円351円227円264円347円232円204円350円275円254円346円215円242円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/18. 345円246円202円344円275円225円345円256円236円347円216円260円346円225円260円347円273円204円345円222円214円 List 344円271円213円351円227円264円347円232円204円350円275円254円346円215円242円.md" deleted file mode 100644 index fc81653..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/18. 345円246円202円344円275円225円345円256円236円347円216円260円346円225円260円347円273円204円345円222円214円 List 344円271円213円351円227円264円347円232円204円350円275円254円346円215円242円.md" +++ /dev/null @@ -1,16 +0,0 @@ - -- 数组转 List:使用 Arrays. asList(array) 进行转换。 -- List 转数组:使用 List 自带的 toArray() 方法。 -demo: - -``` -// list to array -List list = new ArrayList(); -list. add("jay"); -list. add("wei"); -list. toArray(); -// array to list -String[] array = new String[]{"jay","wei"}; -Arrays. asList(array); -``` - diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/19. 350円277円255円344円273円243円345円231円250円 Iterator 346円230円257円344円273円200円344円271円210円357円274円237円346円200円216円344円271円210円347円224円250円357円274円214円346円234円211円344円273円200円344円271円210円347円211円271円347円202円271円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/19. 350円277円255円344円273円243円345円231円250円 Iterator 346円230円257円344円273円200円344円271円210円357円274円237円346円200円216円344円271円210円347円224円250円357円274円214円346円234円211円344円273円200円344円271円210円347円211円271円347円202円271円357円274円237円.md" deleted file mode 100644 index f6a4b13..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/19. 350円277円255円344円273円243円345円231円250円 Iterator 346円230円257円344円273円200円344円271円210円357円274円237円346円200円216円344円271円210円347円224円250円357円274円214円346円234円211円344円273円200円344円271円210円347円211円271円347円202円271円357円274円237円.md" +++ /dev/null @@ -1,15 +0,0 @@ -Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。 - - -Iterator 使用代码如下: - -``` -List list = new ArrayList(); -Iterator it = list. iterator(); -while(it. hasNext()){ - String obj = it. next(); - System. out. println(obj); -} -``` - -Iterator 的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。 diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/20. Iterator 345円222円214円 ListIterator 346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/20. Iterator 345円222円214円 ListIterator 346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" deleted file mode 100644 index 3f9cd2e..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/20. Iterator 345円222円214円 ListIterator 346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" +++ /dev/null @@ -1,3 +0,0 @@ -- Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。 -- Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。 -- ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/21. 346円200円216円344円271円210円347円241円256円344円277円235円344円270円200円344円270円252円351円233円206円345円220円210円344円270円215円350円203円275円350円242円253円344円277円256円346円224円271円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/21. 346円200円216円344円271円210円347円241円256円344円277円235円344円270円200円344円270円252円351円233円206円345円220円210円344円270円215円350円203円275円350円242円253円344円277円256円346円224円271円357円274円237円.md" deleted file mode 100644 index 398795b..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/21. 346円200円216円344円271円210円347円241円256円344円277円235円344円270円200円344円270円252円351円233円206円345円220円210円344円270円215円350円203円275円350円242円253円344円277円256円346円224円271円357円274円237円.md" +++ /dev/null @@ -1,11 +0,0 @@ -可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。 - -示例代码如下: - -``` -List list = new ArrayList(); -list. add("x"); -Collection clist = Collections. unmodifiableCollection(list); -clist. add("y"); // 运行时此行报错 -System. out. println(list. size()); -``` \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/22. 345円277円253円351円200円237円345円244円261円350円264円245円(fail-fast)345円222円214円345円256円211円345円205円250円345円244円261円350円264円245円(fail-safe)347円232円204円345円214円272円345円210円253円346円230円257円344円273円200円344円271円210円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/22. 345円277円253円351円200円237円345円244円261円350円264円245円(fail-fast)345円222円214円345円256円211円345円205円250円345円244円261円350円264円245円(fail-safe)347円232円204円345円214円272円345円210円253円346円230円257円344円273円200円344円271円210円357円274円237円.md" deleted file mode 100644 index e81c8e5..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/22. 345円277円253円351円200円237円345円244円261円350円264円245円(fail-fast)345円222円214円345円256円211円345円205円250円345円244円261円350円264円245円(fail-safe)347円232円204円345円214円272円345円210円253円346円230円257円344円273円200円344円271円210円357円274円237円.md" +++ /dev/null @@ -1,5 +0,0 @@ -Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。 - -java.util包下面的所有的集合类都是快速失败的; - -java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。 diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/23. 344円273円200円344円271円210円346円230円257円Java344円274円230円345円205円210円347円272円247円351円230円237円345円210円227円(Priority Queue)357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/23. 344円273円200円344円271円210円346円230円257円Java344円274円230円345円205円210円347円272円247円351円230円237円345円210円227円(Priority Queue)357円274円237円.md" deleted file mode 100644 index ba7a449..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/23. 344円273円200円344円271円210円346円230円257円Java344円274円230円345円205円210円347円272円247円351円230円237円345円210円227円(Priority Queue)357円274円237円.md" +++ /dev/null @@ -1,3 +0,0 @@ -PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。 -在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。 - diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/3.HashMap345円216円237円347円220円206円357円274円214円java8345円201円232円347円232円204円346円224円271円345円217円230円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/3.HashMap345円216円237円347円220円206円357円274円214円java8345円201円232円347円232円204円346円224円271円345円217円230円.md" deleted file mode 100644 index b4a3a92..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/3.HashMap345円216円237円347円220円206円357円274円214円java8345円201円232円347円232円204円346円224円271円345円217円230円.md" +++ /dev/null @@ -1,2 +0,0 @@ - -[HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化?](https://blog.csdn.net/weixin_37356262/article/details/80543218) \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/31. 344円270円272円344円275円225円Collection344円270円215円344円273円216円Cloneable345円222円214円Serializable346円216円245円345円217円243円347円273円247円346円211円277円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/31. 344円270円272円344円275円225円Collection344円270円215円344円273円216円Cloneable345円222円214円Serializable346円216円245円345円217円243円347円273円247円346円211円277円357円274円237円.md" deleted file mode 100644 index 82c2c6b..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/31. 344円270円272円344円275円225円Collection344円270円215円344円273円216円Cloneable345円222円214円Serializable346円216円245円345円217円243円347円273円247円346円211円277円357円274円237円.md" +++ /dev/null @@ -1 +0,0 @@ -克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/35. 350円257円264円344円270円200円344円270円213円 HashSet 347円232円204円345円256円236円347円216円260円345円216円237円347円220円206円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/35. 350円257円264円344円270円200円344円270円213円 HashSet 347円232円204円345円256円236円347円216円260円345円216円237円347円220円206円357円274円237円.md" deleted file mode 100644 index fad3feb..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/35. 350円257円264円344円270円200円344円270円213円 HashSet 347円232円204円345円256円236円347円216円260円345円216円237円347円220円206円357円274円237円.md" +++ /dev/null @@ -1 +0,0 @@ -HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/36. Array 345円222円214円 ArrayList 346円234円211円344円275円225円345円214円272円345円210円253円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/36. Array 345円222円214円 ArrayList 346円234円211円344円275円225円345円214円272円345円210円253円357円274円237円.md" deleted file mode 100644 index a044026..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/36. Array 345円222円214円 ArrayList 346円234円211円344円275円225円345円214円272円345円210円253円357円274円237円.md" +++ /dev/null @@ -1,3 +0,0 @@ -- Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。 -- Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。 -- Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/37. 345円234円250円 Queue 344円270円255円 poll()345円222円214円 remove()346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/37. 345円234円250円 Queue 344円270円255円 poll()345円222円214円 remove()346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" deleted file mode 100644 index 184cdc0..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/37. 345円234円250円 Queue 344円270円255円 poll()345円222円214円 remove()346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" +++ /dev/null @@ -1,11 +0,0 @@ -- 相同点:都是返回第一个元素,并在队列中删除返回的对象。 -- 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。 -代码示例: - -``` -Queue queue = new LinkedList(); -queue. offer("string"); // add -System. out. println(queue. poll()); -System. out. println(queue. remove()); -System. out. println(queue. size()); -``` \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/38. ArrayList 345円246円202円344円275円225円345円210円240円351円231円244円351円207円215円345円244円215円347円232円204円345円205円203円347円264円240円346円210円226円350円200円205円346円214円207円345円256円232円347円232円204円345円205円203円347円264円240円357円274円233円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/38. ArrayList 345円246円202円344円275円225円345円210円240円351円231円244円351円207円215円345円244円215円347円232円204円345円205円203円347円264円240円346円210円226円350円200円205円346円214円207円345円256円232円347円232円204円345円205円203円347円264円240円357円274円233円.md" deleted file mode 100644 index f7e56b4..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/38. ArrayList 345円246円202円344円275円225円345円210円240円351円231円244円351円207円215円345円244円215円347円232円204円345円205円203円347円264円240円346円210円226円350円200円205円346円214円207円345円256円232円347円232円204円345円205円203円347円264円240円357円274円233円.md" +++ /dev/null @@ -1,2 +0,0 @@ -- 使用Set删除重复元素 -- 迭代器删除指定元素 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/39. 350円256円262円350円256円262円347円272円242円351円273円221円346円240円221円347円232円204円347円211円271円347円202円271円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/39. 350円256円262円350円256円262円347円272円242円351円273円221円346円240円221円347円232円204円347円211円271円347円202円271円357円274円237円.md" deleted file mode 100644 index 68c22e6..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/39. 350円256円262円350円256円262円347円272円242円351円273円221円346円240円221円347円232円204円347円211円271円347円202円271円357円274円237円.md" +++ /dev/null @@ -1,3 +0,0 @@ -- root节点和叶子节点是黑色 -- 红色节点后必须为黑色节点 -- 从root到叶子每条路径的黑节点数量相同 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/40. Java351円233円206円345円220円210円347円261円273円346円241円206円346円236円266円347円232円204円346円234円200円344円275円263円345円256円236円350円267円265円346円234円211円345円223円252円344円272円233円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/40. Java351円233円206円345円220円210円347円261円273円346円241円206円346円236円266円347円232円204円346円234円200円344円275円263円345円256円236円350円267円265円346円234円211円345円223円252円344円272円233円357円274円237円.md" deleted file mode 100644 index 9b17f2a..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/40. Java351円233円206円345円220円210円347円261円273円346円241円206円346円236円266円347円232円204円346円234円200円344円275円263円345円256円236円350円267円265円346円234円211円345円223円252円344円272円233円357円274円237円.md" +++ /dev/null @@ -1,7 +0,0 @@ -- 根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。 -- 有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。 -- 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。 -- 使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。 -- 编程的时候接口优于实现。 -- 底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。 - diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/41. Enumeration346円216円245円345円217円243円345円222円214円Iterator 346円216円245円345円217円243円347円232円204円345円214円272円345円210円253円346円234円211円345円223円252円344円272円233円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/41. Enumeration346円216円245円345円217円243円345円222円214円Iterator 346円216円245円345円217円243円347円232円204円345円214円272円345円210円253円346円234円211円345円223円252円344円272円233円357円274円237円.md" deleted file mode 100644 index ec4795e..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/41. Enumeration346円216円245円345円217円243円345円222円214円Iterator 346円216円245円345円217円243円347円232円204円345円214円272円345円210円253円346円234円211円345円223円252円344円272円233円357円274円237円.md" +++ /dev/null @@ -1,2 +0,0 @@ -Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。 - diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/42. HashSet345円222円214円TreeSet346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/42. HashSet345円222円214円TreeSet346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" deleted file mode 100644 index a3ffac7..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/Java351円233円206円345円220円210円/Java351円233円206円345円220円210円351円235円242円350円257円225円351円242円230円347円255円224円346円241円210円/42. HashSet345円222円214円TreeSet346円234円211円344円273円200円344円271円210円345円214円272円345円210円253円357円274円237円.md" +++ /dev/null @@ -1 +0,0 @@ -160+ 50 +80 + 51 + 100 + 70+ 40+ 30 + 20 +20 +50 +20 +20 +12 +10 +25+10 +20+10+33 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/README.MD" "b/Java351円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/Java351円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/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/README.MD.bak" "b/Java351円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/Java351円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/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/java 345円237円272円347円241円200円/345円237円272円347円241円200円351円242円230円347円255円224円346円241円210円/21. 345円256円210円346円212円244円347円272円277円347円250円213円346円230円257円344円273円200円344円271円210円357円274円237円347円224円250円344円273円200円344円271円210円346円226円271円346円263円225円345円256円236円347円216円260円345円256円210円346円212円244円347円272円277円347円250円213円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/java 345円237円272円347円241円200円/345円237円272円347円241円200円351円242円230円347円255円224円346円241円210円/21. 345円256円210円346円212円244円347円272円277円347円250円213円346円230円257円344円273円200円344円271円210円357円274円237円347円224円250円344円273円200円344円271円210円346円226円271円346円263円225円345円256円236円347円216円260円345円256円210円346円212円244円347円272円277円347円250円213円.md" index f1e660b..716972b 100644 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/java 345円237円272円347円241円200円/345円237円272円347円241円200円351円242円230円347円255円224円346円241円210円/21. 345円256円210円346円212円244円347円272円277円347円250円213円346円230円257円344円273円200円344円271円210円357円274円237円347円224円250円344円273円200円344円271円210円346円226円271円346円263円225円345円256円236円347円216円260円345円256円210円346円212円244円347円272円277円347円250円213円.md" +++ "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/java 345円237円272円347円241円200円/345円237円272円347円241円200円351円242円230円347円255円224円346円241円210円/21. 345円256円210円346円212円244円347円272円277円347円250円213円346円230円257円344円273円200円344円271円210円357円274円237円347円224円250円344円273円200円344円271円210円346円226円271円346円263円225円345円256円236円347円216円260円345円256円210円346円212円244円347円272円277円347円250円213円.md" @@ -1 +1,2 @@ -守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。 \ No newline at end of file +守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。 + diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/zookeeper/ZooKeeper347円232円204円345円215円201円344円272円214円350円277円236円351円227円256円357円274円214円344円275円240円351円241円266円345円276円227円344円272円206円345円230円233円357円274円237円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/zookeeper/ZooKeeper347円232円204円345円215円201円344円272円214円350円277円236円351円227円256円357円274円214円344円275円240円351円241円266円345円276円227円344円272円206円345円230円233円357円274円237円.md" new file mode 100644 index 0000000..4504b5e --- /dev/null +++ "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/zookeeper/ZooKeeper347円232円204円345円215円201円344円272円214円350円277円236円351円227円256円357円274円214円344円275円240円351円241円266円345円276円227円344円272円206円345円230円233円357円274円237円.md" @@ -0,0 +1,298 @@ +## 前言 +一线大厂ZooKeeper的十二连问,你顶得了嘛? + +本文已经收录到github +> https://github.com/whx123/JavaHome + +### 1. 面试官:工作中使用过Zookeeper嘛?你知道它是什么,有什么用途呢? + +**小菜鸡的我:** + +- 有使用过的,使用ZooKeeper作为**dubbo的注册中心**,使用ZooKeeper实现**分布式锁**。 +- ZooKeeper,它是一个开放源码的**分布式协调服务**,它是一个集群的管理者,它将简单易用的接口提供给用户。 +- 可以基于Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列**等功能**。 +- Zookeeper的**用途**:命名服务、配置管理、集群管理、分布式锁、队列管理 + +用途跟功能不是一个意思咩?给我一个眼神,让我自己体会 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa22060efba341a391c1ef9ead212e95~tplv-k3u1fbpfcp-zoom-1.image) + +### 2. 面试官:说下什么是命名服务,什么是配置管理,又什么是集群管理吧 + +**小菜鸡的我(幸好我刷过面试题),无所畏惧** +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/49f59ab796684aed93b0c6f1b6cade91~tplv-k3u1fbpfcp-zoom-1.image) + - **命名服务就是**: +> 命名服务是指通过**指定的名字**来获取资源或者服务地址。Zookeeper可以创建一个**全局唯一的路径**,这个路径就可以作为一个名字。被命名的实体可以是**集群中的机器,服务的地址,或者是远程的对象**等。一些分布式服务框架(RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据特定的名字来获取资源的实体、服务地址和提供者信息等。 + + - **配置管理:** : +> 实际项目开发中,我们经常使用.properties或者xml需要配置很多信息,如数据库连接信息、fps地址端口等等。因为你的程序一般是分布式部署在不同的机器上(如果你是单机应用当我没说),如果把程序的这些配置信息**保存在zk的znode节点**下,当你要修改配置,即znode会发生变化时,可以通过改变zk中某个目录节点的内容,利用**watcher通知给各个客户端**,从而更改配置。 + +- **集群管理** +> 集群管理包括集群监控和集群控制,其实就是监控集群机器状态,剔除机器和加入机器。zookeeper可以方便集群机器的管理,它可以实时监控znode节点的变化,一旦发现有机器挂了,该机器就会与zk断开连接,对用的临时目录节点会被删除,其他所有机器都收到通知。新机器加入也是类似酱紫,所有机器收到通知:有新兄弟目录加入啦。 + + +### 3. 面试官:你提到了znode节点,那你知道znode有几种类型呢?zookeeper的数据模型是怎样的呢? + +**小菜鸡的我(我先想想):** + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc8d387fe5764ecca82917190801bf66~tplv-k3u1fbpfcp-zoom-1.image) + +#### zookeeper的数据模型 +ZooKeeper的视图数据结构,很像Unix文件系统,也是树状的,这样可以确定每个路径都是唯一的。zookeeper的节点统一叫做**znode**,它是可以通过**路径来标识**,结构图如下: +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8d7058978e614f7f8ee4832cb3b9c7cb~tplv-k3u1fbpfcp-zoom-1.image) + +#### znode的4种类型 +根据节点的生命周期,znode可以分为4种类型,分别是持久节点(PERSISTENT)、持久顺序节点(PERSISTENT_SEQUENTIAL)、临时节点(EPHEMERAL)、临时顺序节点(EPHEMERAL_SEQUENTIAL) + +- 持久节点(PERSISTENT) +> 这类节点被创建后,就会一直存在于Zk服务器上。直到手动删除。 + +- 持久顺序节点(PERSISTENT_SEQUENTIAL) +> 它的基本特性同持久节点,不同在于增加了顺序性。父节点会维护一个自增整性数字,用于子节点的创建的先后顺序。 + +- 临时节点(EPHEMERAL) +> 临时节点的生命周期与客户端的会话绑定,一旦客户端会话失效(非TCP连接断开),那么这个节点就会被自动清理掉。zk规定临时节点只能作为叶子节点。 + +- 临时顺序节点(EPHEMERAL_SEQUENTIAL) +> 基本特性同临时节点,添加了顺序的特性。 + +### 4、面试官:你知道znode节点里面存储的是什么吗?每个节点的数据最大不能超过多少呢? +**小菜鸡的我:** +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/92a4f38edb734cd195a2cae0e00a1d7a~tplv-k3u1fbpfcp-zoom-1.image) +#### znode节点里面存储的是什么? + +Znode数据节点的代码如下 +``` +public class DataNode implements Record { + byte data[]; + Long acl; + public StatPersisted stat; + private Set children = null; +} +``` +哈哈,Znode包含了**存储数据、访问权限、子节点引用、节点状态信息**,如图: +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/91b23e62a6424ae2848a050556ed34df~tplv-k3u1fbpfcp-zoom-1.image) + +- **data:** znode存储的业务数据信息 +- **ACL:** 记录客户端对znode节点的访问权限,如IP等。 +- **child:** 当前节点的子节点引用 +- **stat:** 包含Znode节点的状态信息,比如**事务id、版本号、时间戳**等等。 + + +#### 每个节点的数据最大不能超过多少呢 + +为了保证高吞吐和低延迟,以及数据的一致性,znode只适合存储非常小的数据,不能超过1M,最好都小于1K。 + + +### 5、面试官:你知道znode节点上的监听机制嘛?讲下Zookeeper watch机制吧。 +**小菜鸡的我:** + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fd8dc23431b24ec38fcd5b142d4ff453~tplv-k3u1fbpfcp-zoom-1.image) +- Watcher机制 +- 监听机制的工作原理 +- Watcher特性总结 + +#### Watcher监听机制 +Zookeeper 允许客户端向服务端的某个Znode注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher通知状态和事件类型做出业务上的改变。 +> 可以把Watcher理解成客户端注册在某个Znode上的触发器,当这个Znode节点发生变化时(增删改查),就会触发Znode对应的注册事件,注册的客户端就会收到异步通知,然后做出业务的改变。 + +#### Watcher监听机制的工作原理 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9477e0dada834eee9a997313a14a963b~tplv-k3u1fbpfcp-zoom-1.image) + +- ZooKeeper的Watcher机制主要包括客户端线程、客户端 WatcherManager、Zookeeper服务器三部分。 +- 客户端向ZooKeeper服务器注册Watcher的同时,会将Watcher对象存储在客户端的WatchManager中。 +- 当zookeeper服务器触发watcher事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑。 + +#### Watcher特性总结 +- **一次性:**一个Watch事件是一个一次性的触发器。一次性触发,客户端只会收到一次这样的信息。 +- **异步的: ** Zookeeper服务器发送watcher的通知事件到客户端是异步的,不能期望能够监控到节点每次的变化,Zookeeper只能保证最终的一致性,而无法保证强一致性。 +- **轻量级:** Watcher 通知非常简单,它只是通知发生了事件,而不会传递事件对象内容。 +- **客户端串行:** 执行客户端 Watcher 回调的过程是一个串行同步的过程。 +- 注册 watcher用getData、exists、getChildren方法 +- 触发 watcher用create、delete、setData方法 + + +### 6、面试官:你对Zookeeper的数据结构都有一定了解,那你讲下Zookeeper的特性吧 +**小菜鸡的我:(我背过书,啊哈哈)** +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/975294fd4807485e87adcb50f37fdc26~tplv-k3u1fbpfcp-zoom-1.image) +Zookeeper 保证了如下分布式一致性特性: +- **顺序一致性**:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 +- **原子性**:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 +- **单一视图**:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 +- **可靠性:** 一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来。 +- **实时性(最终一致性):** Zookeeper 仅仅能保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。 + +### 7、面试官:你刚提到顺序一致性,那zookeeper是如何保证事务的顺序一致性的呢? +**小菜鸡的我:(完蛋了这题不会)** +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb1eac96fd474f8e8e2317b6bf8b0641~tplv-k3u1fbpfcp-zoom-1.image) + +这道题可以看下这篇文章(本题答案来自该文章):[聊一聊ZooKeeper的顺序一致性](https://time.geekbang.org/column/article/239261) +> 需要了解事务ID,即zxid。ZooKeeper的在选举时通过比较各结点的zxid和机器ID选出新的主结点的。zxid由Leader节点生成,有新写入事件时,Leader生成新zxid并随提案一起广播,每个结点本地都保存了当前最近一次事务的zxid,zxid是递增的,所以谁的zxid越大,就表示谁的数据是最新的。 + +ZXID的生成规则如下: +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ea46600cd60d462ca982c60fa854419b~tplv-k3u1fbpfcp-zoom-1.image) + +ZXID有两部分组成: + +- 任期:完成本次选举后,直到下次选举前,由同一Leader负责协调写入; +- 事务计数器:单调递增,每生效一次写入,计数器加一。 + +> ZXID的低32位是计数器,所以同一任期内,ZXID是连续的,每个结点又都保存着自身最新生效的ZXID,通过对比新提案的ZXID与自身最新ZXID是否相差"1",来保证事务严格按照顺序生效的。 + +### 8、面试官:你提到了Leader,你知道Zookeeper的服务器有几种角色嘛?Zookeeper下Server工作状态又有几种呢? + +**小菜鸡的我:** +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b965992267c4fcbbaca532089548cad~tplv-k3u1fbpfcp-zoom-1.image) + +#### Zookeeper 服务器角色 +Zookeeper集群中,有Leader、Follower和Observer三种角色 + +**Leader** + +> Leader服务器是整个ZooKeeper集群工作机制中的核心,其主要工作: +- 事务请求的唯一调度和处理者,保证集群事务处理的顺序性 +- 集群内部各服务的调度者 + +**Follower** + +> Follower服务器是ZooKeeper集群状态的跟随者,其主要工作: +- 处理客户端非事务请求,转发事务请求给Leader服务器 +- 参与事务请求Proposal的投票 +- 参与Leader选举投票 + +**Observer** +> Observer是3.3.0 版本开始引入的一个服务器角色,它充当一个观察者角色——观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。其工作: +- 处理客户端的非事务请求,转发事务请求给 Leader 服务器 +- 不参与任何形式的投票 + + +#### Zookeeper下Server工作状态 +> 服务器具有四种状态,分别是 LOOKING、FOLLOWING、LEADING、OBSERVING。 +- 1.LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。 +- 2.FOLLOWING:跟随者状态。表明当前服务器角色是Follower。 +- 3.LEADING:领导者状态。表明当前服务器角色是Leader。 +- 4.OBSERVING:观察者状态。表明当前服务器角色是Observer。 + + +### 9、面试官:你说到服务器角色是基于ZooKeeper集群的,那你画一下ZooKeeper集群部署图吧?ZooKeeper是如何保证主从节点数据一致性的呢? +**小菜鸡的我:** +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2037f26385ef479281e004084f1cd8da~tplv-k3u1fbpfcp-zoom-1.image) +#### ZooKeeper集群部署图 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cf565bd494b4262baee09cfadfee24d~tplv-k3u1fbpfcp-zoom-1.image) +ZooKeeper集群是一主多从的结构: +- 如果是写入数据,先写入主服务器(主节点),再通知从服务器。 +- 如果是读取数据,既读主服务器的,也可以读从服务器的。 + +#### ZooKeeper如何保证主从节点数据一致性 +我们知道集群是主从部署结构,要保证主从节点一致性问题,无非就是两个主要问题: +- **主服务器挂了,或者重启了** +- **主从服务器之间同步数据**~ + +Zookeeper是采用ZAB协议(Zookeeper Atomic Broadcast,Zookeeper原子广播协议)来保证主从节点数据一致性的,ZAB协议支持**崩溃恢复和消息广播**两种模式,很好解决了这两个问题: +- 崩溃恢复:Leader挂了,进入该模式,选一个新的leader出来 +- 消息广播: 把更新的数据,从Leader同步到所有Follower + +> Leader服务器挂了,所有集群中的服务器进入LOOKING状态,首先,它们会选举产生新的Leader服务器;接着,新的Leader服务器与集群中Follower服务进行数据同步,当集群中超过半数机器与该 Leader服务器完成数据同步之后,退出恢复模式进入消息广播模式。Leader 服务器开始接收客户端的事务请求生成事务Proposal进行事务请求处理。 + +### 10、面试官:Leader挂了,进入崩溃恢复,是如何选举Leader的呢?你讲一下ZooKeeper选举机制吧 +**小菜鸡的我:** +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de58c6ae02c04f008b6abad08178fcb9~tplv-k3u1fbpfcp-zoom-1.image) + +服务器启动或者服务器运行期间(Leader挂了),都会进入Leader选举,我们来看一下~假设现在ZooKeeper集群有五台服务器,它们myid分别是服务器1、2、3、4、5,如图: +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1b578b765d4b4500a83d7ad63bb35e2f~tplv-k3u1fbpfcp-zoom-1.image) +#### 服务器启动的Leader选举 + +zookeeper集群初始化阶段,服务器(myid=1-5)**依次**启动,开始zookeeper选举Leader~ +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5791e745136d4da18aa4254e7c116f23~tplv-k3u1fbpfcp-zoom-1.image) +- 服务器1(myid=1)启动,当前只有一台服务器,无法完成Leader选举 +- 服务器2(myid=2)启动,此时两台服务器能够相互通讯,开始进入Leader选举阶段 +> 1. 每个服务器发出一个投票 +>> 服务器1 和 服务器2都将自己作为Leader服务器进行投票,投票的基本元素包括:服务器的myid和ZXID,我们以(myid,ZXID)形式表示。初始阶段,服务器1和服务器2都会投给自己,即服务器1的投票为(1,0),服务器2的投票为(2,0),然后各自将这个投票发给集群中的其他所有机器。 +> 2. 接受来自各个服务器的投票 +>> 每个服务器都会接受来自其他服务器的投票。同时,服务器会校验投票的有效性,是否本轮投票、是否来自LOOKING状态的服务器。 +> 3. 处理投票 +>> 收到其他服务器的投票,会将被人的投票跟自己的投票PK,PK规则如下: +>>> - 优先检查ZXID。ZXID比较大的服务器优先作为leader。 +>>> - 如果ZXID相同的话,就比较myid,myid比较大的服务器作为leader。 +>> 服务器1的投票是(1,0),它收到投票是(2,0),两者zxid都是0,因为收到的myid=2,大于自己的myid=1,所以它更新自己的投票为(2,0),然后重新将投票发出去。对于服务器2呢,即不再需要更新自己的投票,把上一次的投票信息发出即可。 +> 4. 统计投票 +>> 每次投票后,服务器会统计所有投票,判断是否有过半的机器接受到相同的投票信息。服务器2收到两票,少于3(n/2+1,n为总服务器),所以继续保持LOOKING状态 +- 服务器3(myid=3)启动,继续进入Leader选举阶段 +> 跟前面流程一致,服务器1和2先投自己一票,因为服务器3的myid最大,所以大家把票改投给它。此时,服务器为3票(大于等于n/2+1),所以服务器3当选为Leader。 服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING; +- 服务器4启动,发起一次选举。 +> 此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。选票信息结果:服务器3为4票,服务器4为1票。服务器4并更改状态为FOLLOWING; +- 服务器5启动,发起一次选举。 +> 同理,服务器也是把票投给服务器3,服务器5并更改状态为FOLLOWING; +- 投票结束,服务器3当选为Leader + + +#### 服务器运行期间的Leader选举 + +zookeeper集群的五台服务器(myid=1-5)正在运行中,突然某个瞬间,Leader服务器3挂了,这时候便开始Leader选举~ +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c974c58e0d4842cf8c7ef59d9fb28af2~tplv-k3u1fbpfcp-zoom-1.image) + +- 1.变更状态 +> Leader 服务器挂了之后,余下的非Observer服务器都会把自己的服务器状态更改为LOOKING,然后开始进入Leader选举流程。 +- 2.每个服务器发起投票 +> 每个服务器都把票投给自己,因为是运行期间,所以每台服务器的ZXID可能不相同。假设服务1,2,4,5的zxid分别为333,666,999,888,则分别产生投票(1,333),(2,666),(4,999)和(5,888),然后各自将这个投票发给集群中的其他所有机器。 +- 3.接受来自各个服务器的投票 +- 4.处理投票 +> 投票规则是跟Zookeeper集群启动期间一致的,优先检查ZXID,大的优先作为Leader,所以显然服务器zxid=999具有优先权。 +- 5.统计投票 +- 6.改变服务器状态 + +### 11、面试官: 你前面提到在项目中使用过Zookeeper的分布式锁,讲一下zk分布式锁的实现原理吧? +**小菜鸡的我:** +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/84050ae342c040b69f657cabf2359a07~tplv-k3u1fbpfcp-zoom-1.image) +Zookeeper就是使用临时顺序节点特性实现分布式锁的。 +- 获取锁过程 (创建临时节点,检查序号最小) +- 释放锁 (删除临时节点,监听通知) + +#### 获取锁过程 + +- 当第一个客户端请求过来时,Zookeeper客户端会创建一个持久节点/locks。如果它(Client1)想获得锁,需要在locks节点下创建一个顺序节点lock1.如图 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/39e5ee84f901453fb894600c331693d6~tplv-k3u1fbpfcp-zoom-1.image) +- 接着,客户端Client1会查找locks下面的所有临时顺序子节点,判断自己的节点lock1是不是排序最小的那一个,如果是,则成功获得锁。 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e958178a196742b480a20dff85dfef4f~tplv-k3u1fbpfcp-zoom-1.image) +- 这时候如果又来一个客户端client2前来尝试获得锁,它会在locks下再创建一个临时节点lock2 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/34a3a0f7221b4de79e1ed5595d7c177c~tplv-k3u1fbpfcp-zoom-1.image) +- 客户端client2一样也会查找locks下面的所有临时顺序子节点,判断自己的节点lock2是不是最小的,此时,发现lock1才是最小的,于是获取锁失败。获取锁失败,它是不会甘心的,client2向它排序靠前的节点lock1注册Watcher事件,用来监听lock1是否存在,也就是说client2抢锁失败进入等待状态。 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a17880856e1546bba4f2571dbf8b3b38~tplv-k3u1fbpfcp-zoom-1.image) +- 此时,如果再来一个客户端Client3来尝试获取锁,它会在locks下再创建一个临时节点lock3 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/abda17df527b4dbe8faa7e3a3a9e859e~tplv-k3u1fbpfcp-zoom-1.image) +- 同样的,client3一样也会查找locks下面的所有临时顺序子节点,判断自己的节点lock3是不是最小的,发现自己不是最小的,就获取锁失败。它也是不会甘心的,它会向在它前面的节点lock2注册Watcher事件,以监听lock2节点是否存在。 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a8725119723434daddca761b0d7be83~tplv-k3u1fbpfcp-zoom-1.image) + +#### 释放锁 + +我们再来看看释放锁的流程,zookeeper的**客户端业务完成或者故障**,都会删除临时节点,释放锁。如果是任务完成,Client1会显式调用删除lock1的指令 +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ecf2bec179f64ef79b180f66a327e634~tplv-k3u1fbpfcp-zoom-1.image) +如果是客户端故障了,根据临时节点得特性,lock1是会自动删除的 +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e87703be60a3407e9e570c86bd4c180d~tplv-k3u1fbpfcp-zoom-1.image) +lock1节点被删除后,Client2可开心了,因为它一直监听着lock1。lock1节点删除,Client2立刻收到通知,也会查找locks下面的所有临时顺序子节点,发下lock2是最小,就获得锁。 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be3c7146f2934dd69a598cdf974ab390~tplv-k3u1fbpfcp-zoom-1.image) + +同理,Client2获得锁之后,Client3也对它虎视眈眈,啊哈哈~ + +### 12. 面试官:好的,最后一道题,你说说dubbo和Zookeeper的关系吧,为什么选择Zookeeper作为注册中心? +小菜鸡的我(答了这么多道题,不会还不给我过吧?): +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fd81e6dd9b0a40f9811c98e3c8e3a424~tplv-k3u1fbpfcp-zoom-1.image) + +dubbo的注册中心可以选Zookeeper,memcached,redis等。为什么选择Zookeeper,因为它的功能特性咯~ +- 命名服务,服务提供者向Zookeeper指定节点写入url,完成服务发布。 +- 负载均衡,注册中心的承载能力有限,而Zookeeper集群配合web应用很容易达到负载均衡。 +- zk支持监听事件,特别适合发布/订阅的场景,dubbo的生产者和消费者就类似这场景。 +- 数据模型简单,数据存在内存,可谓高性能 +- Zookeeper其他特点都可以搬出来讲一下~ + + +### 个人公众号 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6b23c704fc94ca09207b779f953cce6~tplv-k3u1fbpfcp-zoom-1.image) + +### 参考与感谢 +- [zookeeper面试题](https://segmentfault.com/a/1190000014479433) +- [28道进阶必备ZooKeeper面试真题(建议收藏!)](https://zhuanlan.zhihu.com/p/102168788) +- [漫画:什么是ZooKeeper?](https://juejin.im/post/6844903608685707271) +- [聊一聊ZooKeeper的顺序一致性](https://time.geekbang.org/column/article/239261) +- [Zookeeper——一致性协议:Zab协议](https://www.jianshu.com/p/2bceacd60b8a) +- [Zookeeper的选举机制原理(图文深度讲解)](https://blog.csdn.net/wx1528159409/article/details/84622762) +- [漫画:如何用Zookeeper实现分布式锁?](https://mp.weixin.qq.com/s/u8QDlrDj3Rl1YjY4TyKMCA) diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/100Mysql347円255円224円346円241円210円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/100Mysql347円255円224円346円241円210円.md" new file mode 100644 index 0000000..862767a --- /dev/null +++ "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/100Mysql347円255円224円346円241円210円.md" @@ -0,0 +1,1005 @@ +## 前言 +100道MySQL数据库经典面试题解析,已经上传github啦 +> https://github.com/whx123/JavaHome/tree/master/Java%E9%9D%A2%E8%AF%95%E9%A2%98%E9%9B%86%E7%BB%93%E5%8F%B7 + +**公众号:捡田螺的小男孩** + +## 数据库 +### 1. MySQL 索引使用有哪些注意事项呢? +可以从三个维度回答这个问题:索引哪些情况会失效,索引不适合哪些场景,索引规则 + +#### 索引哪些情况会失效 +- 查询条件包含or,可能导致索引失效 +- 如何字段类型是字符串,where时一定用引号括起来,否则索引失效 +- like通配符可能导致索引失效。 +- 联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。 +- 在索引列上使用mysql的内置函数,索引失效。 +- 对索引列运算(如,+、-、*、/),索引失效。 +- 索引字段上使用(!= 或者 ,not in)时,可能会导致索引失效。 +- 索引字段上使用is null, is not null,可能导致索引失效。 +- 左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。 +- mysql估计使用全表扫描要比使用索引快,则不使用索引。 + +[后端程序员必备:索引失效的十大杂症](https://juejin.im/post/5de99dd2518825125e1ba49d) + +#### 索引不适合哪些场景 +- 数据量少的不适合加索引 +- 更新比较频繁的也不适合加索引 +- 区分度低的字段不适合加索引(如性别) + +#### 索引的一些潜规则 +- 覆盖索引 +- 回表 +- 索引数据结构(B+树) +- 最左前缀原则 +- 索引下推 + +### 2. MySQL 遇到过死锁问题吗,你是如何解决的? +我排查死锁的一般步骤是酱紫的: +- 查看死锁日志show engine innodb status; +- 找出死锁Sql +- 分析sql加锁情况 +- 模拟死锁案发 +- 分析死锁日志 +- 分析死锁结果 + +可以看我这两篇文章哈: + +- [手把手教你分析Mysql死锁问题](https://juejin.im/post/5e8b269f518825739379e82c) +- [Mysql死锁如何排查:insert on duplicate死锁一次排查分析过程](https://juejin.im/post/5d483e66518825052734b15a#heading-4) + +### 3. 日常工作中你是怎么优化SQL的? +可以从这几个维度回答这个问题: +- 加索引 +- 避免返回不必要的数据 +- 适当分批量进行 +- 优化sql结构 +- 分库分表 +- 读写分离 + +可以看我这篇文章哈: +[后端程序员必备:书写高质量SQL的30条建议](https://juejin.im/post/5e624d156fb9a07ca80ab6f2) + +### 4. 说说分库与分表的设计 + +分库分表方案,分库分表中间件,分库分表可能遇到的问题 + +**分库分表方案:** +- 水平分库:以字段为依据,按照一定策略(hash、range等),将一个库中的数据拆分到多个库中。 +- 水平分表:以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。 +- 垂直分库:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。 +- 垂直分表:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。 + +**常用的分库分表中间件:** +- sharding-jdbc(当当) +- Mycat +- TDDL(淘宝) +- Oceanus(58同城数据库中间件) +- vitess(谷歌开发的数据库中间件) +- Atlas(Qihoo 360) + +**分库分表可能遇到的问题** +- 事务问题:需要用分布式事务啦 +- 跨节点Join的问题:解决这一问题可以分两次查询实现 +- 跨节点的count,order by,group by以及聚合函数问题:分别在各个节点上得到结果后在应用程序端进行合并。 +- 数据迁移,容量规划,扩容等问题 +- ID问题:数据库被切分后,不能再依赖数据库自身的主键生成机制啦,最简单可以考虑UUID +- 跨分片的排序分页问题(后台加大pagesize处理?) + +个人觉得网上这两篇文章不错,小伙伴们可以去看一下哈: + +- [MySQL数据库之互联网常用分库分表方案](https://www.cnblogs.com/littlecharacter/p/9342129.html) +- [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c) + +### 5. InnoDB与MyISAM的区别 + +- InnoDB支持事务,MyISAM不支持事务 +- InnoDB支持外键,MyISAM不支持外键 +- InnoDB 支持 MVCC(多版本并发控制),MyISAM 不支持 +- select count(*) from table时,MyISAM更快,因为它有一个变量保存了整个表的总行数,可以直接读取,InnoDB就需要全表扫描。 +- Innodb不支持全文索引,而MyISAM支持全文索引(5.7以后的InnoDB也支持全文索引) +- InnoDB支持表、行级锁,而MyISAM支持表级锁。 +- InnoDB表必须有主键,而MyISAM可以没有主键 +- Innodb表需要更多的内存和存储,而MyISAM可被压缩,存储空间较小,。 +- Innodb按主键大小有序插入,MyISAM记录插入顺序是,按记录插入顺序保存。 +- InnoDB 存储引擎提供了具有提交、回滚、崩溃恢复能力的事务安全,与 MyISAM 比 InnoDB 写的效率差一些,并且会占用更多的磁盘空间以保留数据和索引 + +### 6. 数据库索引的原理,为什么要用 B+树,为什么不用二叉树? +可以从几个维度去看这个问题,查询是否够快,效率是否稳定,存储数据多少,以及查找磁盘次数,为什么不是二叉树,为什么不是平衡二叉树,为什么不是B树,而偏偏是B+树呢? + +**为什么不是一般二叉树?** + +如果二叉树特殊化为一个链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。 + +**为什么不是平衡二叉树呢?** + +我们知道,在内存比在磁盘的数据,查询效率快得多。如果树这种数据结构作为索引,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是B树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快啦。 + +**那为什么不是B树而是B+树呢?** + +1)B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。innodb中页的默认大小是16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。 + +2)B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。 + +可以看这篇文章哈: +[再有人问你为什么MySQL用B+树做索引,就把这篇文章发给她](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484085&idx=1&sn=92639430ac7ef3e412b550a09bde0115&chksm=9779469aa00ecf8c157e899fe0d5c5b060b282a4e5f2f2f63c187eb3c04d04ef6fad7a1e09e3&token=472896045&lang=zh_CN#rd) + +### 7. 聚集索引与非聚集索引的区别 +- 一个表中只能拥有一个聚集索引,而非聚集索引一个表可以存在多个。 +- 聚集索引,索引中键值的逻辑顺序决定了表中相应行的物理顺序;非聚集索引,索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。 +- 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。 +- 聚集索引:物理存储按照索引排序;非聚集索引:物理存储不按照索引排序; + + +**何时使用聚集索引或非聚集索引?** +![](https://user-gold-cdn.xitu.io/2020/5/21/172346f5e5f0ffab?w=1332&h=689&f=png&s=77400) + +### 8. limit 1000000 加载很慢的话,你是怎么解决的呢? + +#### 方案一:如果id是连续的,可以这样,返回上次查询的最大记录(偏移量),再往下limit + +``` +select id,name from employee where id>1000000 limit 10. +``` + +#### 方案二:在业务允许的情况下限制页数: +建议跟业务讨论,有没有必要查这么后的分页啦。因为绝大多数用户都不会往后翻太多页。 + +#### 方案三:order by + 索引(id为索引) + +``` +select id,name from employee order by id limit 1000000,10 +``` +#### 方案四:利用延迟关联或者子查询优化超多分页场景。(先快速定位需要获取的id段,然后再关联) + +``` +SELECT a.* FROM employee a, (select id from employee where 条件 LIMIT 1000000,10 ) b where a.id=b.id +``` + +### 9. 如何选择合适的分布式主键方案呢? + +- 数据库自增长序列或字段。 +- UUID。 +- Redis生成ID +- Twitter的snowflake算法 +- 利用zookeeper生成唯一ID +- MongoDB的ObjectId + + +### 10. 事务的隔离级别有哪些?MySQL的默认隔离级别是什么? +- 读未提交(Read Uncommitted) +- 读已提交(Read Committed) +- 可重复读(Repeatable Read) +- 串行化(Serializable) + +Mysql默认的事务隔离级别是可重复读(Repeatable Read) + +可以看我这篇文章哈:[一文彻底读懂MySQL事务的四大隔离级别](https://juejin.im/post/5e800a1d6fb9a03c6568d06f#heading-8) + +### 11. 什么是幻读,脏读,不可重复读呢? +- 事务A、B交替执行,事务A被事务B干扰到了,因为事务A读取到事务B未提交的数据,这就是**脏读** +- 在一个事务范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是**不可重复读**。 +- 事务A查询一个范围的结果集,另一个并发事务B往这个范围中插入/删除了数据,并静悄悄地提交,然后事务A再次查询相同的范围,两次读取得到的结果集不一样了,这就是**幻读**。 + +可以看我这篇文章哈:[一文彻底读懂MySQL事务的四大隔离级别](https://juejin.im/post/5e800a1d6fb9a03c6568d06f#heading-8) + +### 12. 在高并发情况下,如何做到安全的修改同一行数据? + +要安全的修改同一行数据,就要保证一个线程在修改时其它线程无法更新这行记录。一般有悲观锁和乐观锁两种方案~ + +#### 使用悲观锁 +悲观锁思想就是,当前线程要进来修改数据时,别的线程都得拒之门外~ +比如,可以使用select...for update ~ + +``` +select * from User where name=‘jay’ for update +``` + 以上这条sql语句会锁定了User表中所有符合检索条件(name=‘jay’)的记录。本次事务提交之前,别的线程都无法修改这些记录。 +#### 使用乐观锁 +乐观锁思想就是,有线程过来,先放过去修改,如果看到别的线程没修改过,就可以修改成功,如果别的线程修改过,就修改失败或者重试。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。 + +可以看一下我这篇文章,主要是思路哈~ +[CAS乐观锁解决并发问题的一次实践](https://juejin.im/post/5d0616ade51d457756536791) + +### 13. 数据库的乐观锁和悲观锁。 + +#### 悲观锁: +悲观锁她专一且缺乏安全感了,她的心只属于当前事务,每时每刻都担心着它心爱的数据可能被别的事务修改,所以一个事务拥有(获得)悲观锁后,其他任何事务都不能对数据进行修改啦,只能等待锁被释放才可以执行。 + +![](https://user-gold-cdn.xitu.io/2020/5/23/1723d313056fce6a?w=899&h=563&f=png&s=50589) +#### 乐观锁: +乐观锁的"乐观情绪"体现在,它认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。 + +![](https://user-gold-cdn.xitu.io/2020/5/23/1723e7d3872c9406?w=931&h=657&f=png&s=68807) + +之前转载了的这篇文章,觉得作者写得挺详细的~ + +[图文并茂的带你彻底理解悲观锁与乐观锁](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484293&idx=1&sn=21378368e5e723cda5a4792bb882672c&chksm=977947aaa00ecebc225c18d813d6537337b95fa79055b20e1835546a17738b0f3d16bb30b9df&token=893517802&lang=zh_CN#rd) + +#### 14. SQL优化的一般步骤是什么,怎么看执行计划(explain),如何理解其中各个字段的含义。 +- show status 命令了解各种 sql 的执行频率 +- 通过慢查询日志定位那些执行效率较低的 sql 语句 +- explain 分析低效 sql 的执行计划(这点非常重要,日常开发中用它分析Sql,会大大降低Sql导致的线上事故) + +看过这篇文章,觉得很不错: +[优化sql 语句的一般步骤](https://juejin.im/entry/59a7b22651882524491361eb) + +### 15. select for update有什么含义,会锁表还是锁行还是其他。 + +#### select for update 含义 +select查询语句是不会加锁的,但是select for update除了有查询的作用外,还会加锁呢,而且它是悲观锁哦。至于加了是行锁还是表锁,这就要看是不是用了索引/主键啦。 + +没用索引/主键的话就是表锁,否则就是是行锁。 + +#### select for update 加锁验证 +表结构: +``` +//id 为主键,name为唯一索引 +CREATE TABLE `account` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `balance` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 +``` +id为主键,select for update 1270070这条记录时,再开一个事务对该记录更新,发现更新阻塞啦,其实是加锁了。如下图: + +![](https://user-gold-cdn.xitu.io/2020/5/23/1723f1df3f7cd93d?w=1338&h=337&f=png&s=47172) +我们再开一个事务对另外一条记录1270071更新,发现更新成功,因此,如果查询条件用了索引/主键,**会加行锁**~ +![](https://user-gold-cdn.xitu.io/2020/5/23/1723f21ad2567560?w=1419&h=315&f=png&s=54135) + +我们继续一路向北吧,换普通字段balance吧,发现又阻塞了。因此,没用索引/主键的话,**select for update加的就是表锁** + +![](https://user-gold-cdn.xitu.io/2020/5/23/1723f297aeeb95d5?w=1421&h=384&f=png&s=56635) + +### 16. MySQL事务得四大特性以及实现原理 + +![](https://user-gold-cdn.xitu.io/2020/5/21/17237f45a661cbb7?w=626&h=466&f=png&s=127652) +- 原子性: 事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。 +- 一致性: 指在事务开始之前和事务结束以后,数据不会被破坏,假如A账户给B账户转10块钱,不管成功与否,A和B的总金额是不变的。 +- 隔离性: 多个事务并发访问时,事务之间是相互隔离的,即一个事务不影响其它事务运行效果。简言之,就是事务之间是进水不犯河水的。 +- 持久性: 表示事务完成以后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。 + +**事务ACID特性的实现思想** + +- 原子性:是使用 undo log来实现的,如果事务执行过程中出错或者用户执行了rollback,系统通过undo log日志返回事务开始的状态。 +- 持久性:使用 redo log来实现,只要redo log日志持久化了,当系统崩溃,即可通过redo log把数据恢复。 +- 隔离性:通过锁以及MVCC,使事务相互隔离开。 +- 一致性:通过回滚、恢复,以及并发情况下的隔离性,从而实现一致性。 + +### 17. 如果某个表有近千万数据,CRUD比较慢,如何优化。 +#### 分库分表 +某个表有近千万数据,可以考虑优化表结构,分表(水平分表,垂直分表),当然,你这样回答,需要准备好面试官问你的分库分表相关问题呀,如 +- 分表方案(水平分表,垂直分表,切分规则hash等) +- 分库分表中间件(Mycat,sharding-jdbc等) +- 分库分表一些问题(事务问题?跨节点Join的问题) +- 解决方案(分布式事务等) + +#### 索引优化 +除了分库分表,优化表结构,当然还有所以索引优化等方案~ + +有兴趣可以看我这篇文章哈~ +[后端程序员必备:书写高质量SQL的30条建议](https://juejin.im/post/5e624d156fb9a07ca80ab6f2) + +### 18. 如何写sql能够有效的使用到复合索引。 +复合索引,也叫组合索引,用户可以在多个列上建立索引,这种索引叫做复合索引。 + +当我们创建一个组合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。 +``` +select * from table where k1=A AND k2=B AND k3=D +``` +有关于复合索引,我们需要关注查询Sql条件的顺序,确保最左匹配原则有效,同时可以删除不必要的冗余索引。 + +### 19. mysql中in 和exists的区别。 + +这个,跟一下demo来看更刺激吧,啊哈哈 + +假设表A表示某企业的员工表,表B表示部门表,查询所有部门的所有员工,很容易有以下SQL: + +``` +select * from A where deptId in (select deptId from B); +``` +这样写等价于: +> 先查询部门表B +> +> select deptId from B +> +> 再由部门deptId,查询A的员工 +> +> select * from A where A.deptId = B.deptId + +可以抽象成这样的一个循环: + +``` + List resultSet ; + for(int i=0;i select * from A,先从A表做循环 +> +> select * from B where A.deptId = B.deptId,再从B表做循环. + +同理,可以抽象成这样一个循环: +``` + List resultSet ; + for(int i=0;i - Bytes_received和Bytes_sent 和服务器之间来往的流量。 +> - Com_*服务器正在执行的命令。 +> - Created_*在查询执行期限间创建的临时表和文件。 +> - Handler_*存储引擎操作。 +> - Select_*不同类型的联接执行计划。 +> - Sort_*几种排序信息。 +- Show profiles 是MySql用来分析当前会话SQL语句执行的资源消耗情况 + +### 31. Blob和text有什么区别? +- Blob用于存储二进制数据,而Text用于存储大字符串。 +- Blob值被视为二进制字符串(字节字符串),它们没有字符集,并且排序和比较基于列值中的字节的数值。 +- text值被视为非二进制字符串(字符字符串)。它们有一个字符集,并根据字符集的排序规则对值进行排序和比较。 + +### 32. mysql里记录货币用什么字段类型比较好? +- 货币在数据库中MySQL常用Decimal和Numric类型表示,这两种类型被MySQL实现为同样的类型。他们被用于保存与金钱有关的数据。 +- salary DECIMAL(9,2),9(precision)代表将被用于存储值的总的小数位数,而2(scale)代表将被用于存储小数点后的位数。存储在salary列中的值的范围是从-9999999.99到9999999.99。 +- DECIMAL和NUMERIC值作为字符串存储,而不是作为二进制浮点数,以便保存那些值的小数精度。 + +### 33. Mysql中有哪几种锁,列举一下? + +![](https://user-gold-cdn.xitu.io/2020/5/23/17240a13fa147bea?w=1280&h=687&f=png&s=248056) + +如果按锁粒度划分,有以下3种: +- 表锁: 开销小,加锁快;锁定力度大,发生锁冲突概率高,并发度最低;不会出现死锁。 +- 行锁: 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。 +- 页锁: 开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般 + +有兴趣的小伙伴可以看我这篇文章,有介绍到各种锁哈: + +[后端程序员必备:mysql数据库相关流程图/原理图](https://juejin.im/post/5d42f48cf265da03ab422e08) + +### 34. Hash索引和B+树区别是什么?你在设计索引是怎么抉择的? +- B+树可以进行范围查询,Hash索引不能。 +- B+树支持联合索引的最左侧原则,Hash索引不支持。 +- B+树支持order by排序,Hash索引不支持。 +- Hash索引在等值查询上比B+树效率更高。 +- B+树使用like 进行模糊查询的时候,like后面(比如%开头)的话可以起到优化的作用,Hash索引根本无法进行模糊查询。 + +### 35. mysql 的内连接、左连接、右连接有什么区别? +- Inner join 内连接,在两张表进行连接查询时,只保留两张表中完全匹配的结果集 +- left join 在两张表进行连接查询时,会返回左表所有的行,即使在右表中没有匹配的记录。 +- right join 在两张表进行连接查询时,会返回右表所有的行,即使在左表中没有匹配的记录。 + +### 36. 说说MySQL 的基础架构图 + +![](https://user-gold-cdn.xitu.io/2020/5/23/17240afafdc289e5?w=661&h=500&f=png&s=289132) +Mysql逻辑架构图主要分三层: +- 第一层负责连接处理,授权认证,安全等等 +- 第二层负责编译并优化SQL +- 第三层是存储引擎。 + +### 37. 什么是内连接、外连接、交叉连接、笛卡尔积呢? +- 内连接(inner join):取得两张表中满足存在连接匹配关系的记录。 +- 外连接(outer join):取得两张表中满足存在连接匹配关系的记录,以及某张表(或两张表)中不满足匹配关系的记录。 +- 交叉连接(cross join):显示两张表所有记录一一对应,没有匹配关系进行筛选,也被称为:笛卡尔积。 + + +### 38. 说一下数据库的三大范式 +- 第一范式:数据表中的每一列(每个字段)都不可以再拆分。 +- 第二范式:在第一范式的基础上,分主键列完全依赖于主键,而不能是依赖于主键的一部分。 +- 第三范式:在满足第二范式的基础上,表中的非主键只依赖于主键,而不依赖于其他非主键。 + + +### 39. mysql有关权限的表有哪几个呢? +MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。 + +- user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。 +- db权限表:记录各个帐号在各个数据库上的操作权限。 +- table_priv权限表:记录数据表级的操作权限。 +- columns_priv权限表:记录数据列级的操作权限。 +- host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。 + +### 40. Mysql的binlog有几种录入格式?分别有什么区别? +> 有三种格式哈,statement,row和mixed。 +> - statement,每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。 +> - row,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。 +> - mixed,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。 + +### 41. InnoDB引擎的4大特性,了解过吗 +- 插入缓冲(insert buffer) +- 二次写(double write) +- 自适应哈希索引(ahi) +- 预读(read ahead) + +### 42. 索引有哪些优缺点? +**优点:** +- 唯一索引可以保证数据库表中每一行的数据的唯一性 +- 索引可以加快数据查询速度,减少查询时间 + +**缺点:** +- 创建索引和维护索引要耗费时间 +- 索引需要占物理空间,除了数据表占用数据空间之外,每一个索引还要占用一定的物理空间 +- 以表中的数据进行增、删、改的时候,索引也要动态的维护。 + +### 43. 索引有哪几种类型? +- 主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。 +- 唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。 +- 普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。 +- 全文索引:是目前搜索引擎使用的一种关键技术,对文本的内容进行分词、搜索。 +- 覆盖索引:查询列要被所建的索引覆盖,不必读取数据行 +- 组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并 + +### 44. 创建索引有什么原则呢? +- 最左前缀匹配原则 +- 频繁作为查询条件的字段才去创建索引 +- 频繁更新的字段不适合创建索引 +- 索引列不能参与计算,不能有函数操作 +- 优先考虑扩展索引,而不是新建索引,避免不必要的索引 +- 在order by或者group by子句中,创建索引需要注意顺序 +- 区分度低的数据列不适合做索引列(如性别) +- 定义有外键的数据列一定要建立索引。 +- 对于定义为text、image数据类型的列不要建立索引。 +- 删除不再使用或者很少使用的索引 + +### 45. 创建索引的三种方式 +- 在执行CREATE TABLE时创建索引 +``` +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_name` (`name`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` +- 使用ALTER TABLE命令添加索引 +``` +ALTER TABLE table_name ADD INDEX index_name (column); +``` +- 使用CREATE INDEX命令创建 + +``` +CREATE INDEX index_name ON table_name (column); +``` +### 46. 百万级别或以上的数据,你是如何删除的? +- 我们想要删除百万数据的时候可以先删除索引 +- 然后批量删除其中无用数据 +- 删除完成后重新创建索引。 + +### 47. 什么是最左前缀原则?什么是最左匹配原则? +> - 最左前缀原则,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。 +> - 当我们创建一个组合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。。 + +### 48. B树和B+树的区别,数据库为什么使用B+树而不是B树? +- 在B树中,键和值即存放在内部节点又存放在叶子节点;在B+树中,内部节点只存键,叶子节点则同时存放键和值。 +- B+树的叶子节点有一条链相连,而B树的叶子节点各自独立的。 + +> - B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。. +> - B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。innodb中页的默认大小是16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快. + +### 49. 覆盖索引、回表等这些,了解过吗? +- 覆盖索引: 查询列要被所建的索引覆盖,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。 +- 回表:二级索引无法直接查询所有列的数据,所以通过二级索引查询到聚簇索引后,再查询到想要的数据,这种通过二级索引查询出来的过程,就叫做回表。 + +网上这篇文章讲得很清晰: +[mysql覆盖索引与回表](https://www.jianshu.com/p/8991cbca3854) + +### 50. B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据? +> - 在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。 +> - 当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。 + +### 51. 何时使用聚簇索引与非聚簇索引 + +![](https://user-gold-cdn.xitu.io/2020/5/23/1724114378044d25?w=1332&h=689&f=png&s=77400) + +### 52. 非聚簇索引一定会回表查询吗? +不一定,如果查询语句的字段全部命中了索引,那么就不必再进行回表查询(哈哈,覆盖索引就是这么回事)。 + +> 举个简单的例子,假设我们在学生表的上建立了索引,那么当进行select age from student where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。 + +### 53. 组合索引是什么?为什么需要注意组合索引中的顺序? +组合索引,用户可以在多个列上建立索引,这种索引叫做组合索引。 + +因为InnoDB引擎中的索引策略的最左原则,所以需要注意组合索引中的顺序。 + +### 54. 什么是数据库事务? +数据库事务(简称:事务),是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 + +### 55. 隔离级别与锁的关系 +回答这个问题,可以先阐述四种隔离级别,再阐述它们的实现原理。隔离级别就是依赖锁和MVCC实现的。 + +可以看我这篇文章哈:[一文彻底读懂MySQL事务的四大隔离级别](https://juejin.im/post/5e800a1d6fb9a03c6568d06f#heading-8) + +### 56. 按照锁的粒度分,数据库锁有哪些呢?锁机制与InnoDB锁算法 + +![](https://user-gold-cdn.xitu.io/2020/5/23/1724129062f57007?w=1280&h=687&f=png&s=248056) + +- 按锁粒度分有:表锁,页锁,行锁 +- 按锁机制分有:乐观锁,悲观锁 + +### 57. 从锁的类别角度讲,MySQL都有哪些锁呢? +从锁的类别上来讲,有共享锁和排他锁。 + +- 共享锁: 又叫做读锁。当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。 +- 排他锁: 又叫做写锁。当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。 + +锁兼容性如下: +![](https://user-gold-cdn.xitu.io/2020/5/23/172412db1d202759?w=1045&h=229&f=png&s=68561) + +### 58. MySQL中InnoDB引擎的行锁是怎么实现的? +基于索引来完成行锁的。 + +``` +select * from t where id = 666 for update; +``` + +for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将实行表锁。 + +### 59. 什么是死锁?怎么解决? +死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。看图形象一点,如下: +![](https://user-gold-cdn.xitu.io/2020/5/23/17241317342078b9?w=641&h=575&f=png&s=133003) +死锁有四个必要条件:互斥条件,请求和保持条件,环路等待条件,不剥夺条件。 + +解决死锁思路,一般就是切断环路,尽量避免并发形成环路。 + +> - 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。 +> - 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率; +> - 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率; +> - 如果业务处理不好可以用分布式事务锁或者使用乐观锁 +> - 死锁与索引密不可分,解决索引问题,需要合理优化你的索引, + +有兴趣的朋友,可以看我的这篇死锁分析: +[手把手教你分析Mysql死锁问题](https://juejin.im/post/5e8b269f518825739379e82c#heading-16) + +### 60. 为什么要使用视图?什么是视图? +**为什么要使用视图?** + +为了提高复杂SQL语句的复用性和表操作的安全性,MySQL数据库管理系统提供了视图特性。 + +**什么是视图?** +> 视图是一个虚拟的表,是一个表中的数据经过某种筛选后的显示方式,视图由一个预定义的查询select语句组成。 + +### 61. 视图有哪些特点?哪些使用场景? + +**视图特点:** +- 视图的列可以来自不同的表,是表的抽象和在逻辑意义上建立的新关系。 +- 视图是由基本表(实表)产生的表(虚表)。 +- 视图的建立和删除不影响基本表。 +- 对视图内容的更新(添加,删除和修改)直接影响基本表。 +- 当视图来自多个基本表时,不允许添加和删除数据。 + +**视图用途:** 简化sql查询,提高开发效率,兼容老的表结构。 + +**视图的常见使用场景:** +- 重用SQL语句; +- 简化复杂的SQL操作。 +- 使用表的组成部分而不是整个表; +- 保护数据 +- 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。 + +### 62. 视图的优点,缺点,讲一下? +- 查询简单化。视图能简化用户的操作 +- 数据安全性。视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护 +- 逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性 + +### 63. count(1)、count(*) 与 count(列名) 的区别? +- count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL +- count(1)包括了忽略所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL +- count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。 + +### 64. 什么是游标? +游标提供了一种对从表中检索出的数据进行操作的灵活手段,就本质而言,游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。 + +### 65. 什么是存储过程?有哪些优缺点? +**存储过程**,就是一些编译好了的SQL语句,这些SQL语句代码像一个方法一样实现一些功能(对单表或多表的增删改查),然后给这些代码块取一个名字,在用到这个功能的时候调用即可。 + +**优点:** +- 存储过程是一个预编译的代码块,执行效率比较高 +- 存储过程在服务器端运行,减少客户端的压力 +- 允许模块化程序设计,只需要创建一次过程,以后在程序中就可以调用该过程任意次,类似方法的复用 +- 一个存储过程替代大量T_SQL语句 ,可以降低网络通信量,提高通信速率 +- 可以一定程度上确保数据安全 + +**缺点:** +- 调试麻烦 +- 可移植性不灵活 +- 重新编译问题 + +### 66. 什么是触发器?触发器的使用场景有哪些? +**触发器**,指一段代码,当触发某个事件时,自动执行这些代码。 + +**使用场景:** +- 可以通过数据库中的相关表实现级联更改。 +- 实时监控某张表中的某个字段的更改而需要做出相应的处理。 +- 例如可以生成某些业务的编号。 +- 注意不要滥用,否则会造成数据库及应用程序的维护困难。 + +### 67. MySQL中都有哪些触发器? +MySQL 数据库中有六种触发器: + +- Before Insert +- After Insert +- Before Update +- After Update +- Before Delete +- After Delete + +### 68. 超键、候选键、主键、外键分别是什么? +- 超键:在关系模式中,能唯一知标识元组的属性集称为超键。 +- 候选键:是最小超键,即没有冗余元素的超键。 +- 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。 +- 外键:在一个表中存在的另一个表的主键称此表的外键。。 + + +### 69. SQL 约束有哪几种呢? +- NOT NULL: 约束字段的内容一定不能为NULL。 +- UNIQUE: 约束字段唯一性,一个表允许有多个 Unique 约束。 +- PRIMARY KEY: 约束字段唯一,不可重复,一个表只允许存在一个。 +- FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键。 +- CHECK: 用于控制字段的值范围。 + +### 70. 谈谈六种关联查询,使用场景。 +- 交叉连接 +- 内连接 +- 外连接 +- 联合查询 +- 全连接 +- 交叉连接 + +### 71. varchar(50)中50的涵义 +- 字段最多存放 50 个字符 +- 如 varchar(50) 和 varchar(200) 存储 "jay" 字符串所占空间是一样的,后者在排序时会消耗更多内存 + +### 72. mysql中int(20)和char(20)以及varchar(20)的区别 +- int(20) 表示字段是int类型,显示长度是 20 +- char(20)表示字段是固定长度字符串,长度为 20 +- varchar(20) 表示字段是可变长度字符串,长度为 20 + +### 73. drop、delete与truncate的区别 +| | delete| truncate | drop | +|-----|-----|------|------| +| 类型 | DML | DDL| DDL | +| 回滚 | 可回滚 | 不可回滚 |不可回滚 | +|删除内容 | 表结构还在,删除表的全部或者一部分数据行 | 表结构还在,删除表中的所有数据 |从数据库中删除表,所有的数据行,索引和权限也会被删除 | +| 删除速度 | 删除速度慢,逐行删除 |删除速度快 |删除速度最快 | + +### 74. UNION与UNION ALL的区别? +- Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序; +- Union All:对两个结果集进行并集操作,包括重复行,不进行排序; +- UNION的效率高于 UNION ALL + +### 75. SQL的生命周期? +> - 服务器与数据库建立连接 +> - 数据库进程拿到请求sql +> - 解析并生成执行计划,执行 +> - 读取数据到内存,并进行逻辑处理 +> - 通过步骤一的连接,发送结果到客户端 +> - 关掉连接,释放资源 + +### 76. 一条Sql的执行顺序? + +![](https://user-gold-cdn.xitu.io/2020/5/24/17243f19b75d3514?w=790&h=877&f=png&s=85634) + +### 77. 列值为NULL时,查询是否会用到索引? +列值为NULL也是可以走索引的 + +计划对列进行索引,应尽量避免把它设置为可空,因为这会让 MySQL 难以优化引用了可空列的查询,同时增加了引擎的复杂度 + +### 78. 关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过? +- 我们平时写Sql时,都要养成用explain分析的习惯。 +- 慢查询的统计,运维会定期统计给我们 + +**优化慢查询:** +- 分析语句,是否加载了不必要的字段/数据。 +- 分析SQl执行句话,是否命中索引等。 +- 如果SQL很复杂,优化SQL结构 +- 如果表数据量太大,考虑分表 + +可以看我这篇文章哈: +[后端程序员必备:书写高质量SQL的30条建议](https://juejin.im/post/5e624d156fb9a07ca80ab6f2) + +### 79. 主键使用自增ID还是UUID,为什么? + +如果是单机的话,选择自增ID;如果是分布式系统,优先考虑UUID吧,但还是最好自己公司有一套分布式唯一ID生产方案吧。 + +- 自增ID:数据存储空间小,查询效率高。但是如果数据量过大,会超出自增长的值范围,多库合并,也有可能有问题。 +- uuid:适合大量数据的插入和更新操作,但是它无序的,插入数据效率慢,占用空间大。 + +### 80. mysql自增主键用完了怎么办? +自增主键一般用int类型,一般达不到最大值,可以考虑提前分库分表的。 + +### 81. 字段为什么要求定义为not null? +null值会占用更多的字节,并且null有很多坑的。 + +### 82. 如果要存储用户的密码散列,应该使用什么字段进行存储? +> 密码散列,盐,用户身份证号等固定长度的字符串,应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。 + +### 83. Mysql驱动程序是什么? +这个jar包: mysql-connector-java-5.1.18.jar + +Mysql驱动程序主要帮助编程语言与 MySQL服务端进行通信,如连接、传输数据、关闭等。 + +### 84. 如何优化长难的查询语句?有实战过吗? +- 将一个大的查询分为多个小的相同的查询 +- 减少冗余记录的查询。 +- 一个复杂查询可以考虑拆成多个简单查询 +- 分解关联查询,让缓存的效率更高。 + +### 85. 优化特定类型的查询语句 + +平时积累吧: +- 比如使用select 具体字段代替 select * +- 使用count(*) 而不是count(列名) +- 在不影响业务的情况,使用缓存 +- explain 分析你的SQL + +可以看我这篇文章哈: +[后端程序员必备:书写高质量SQL的30条建议](https://juejin.im/post/5e624d156fb9a07ca80ab6f2) + +### 86. MySQL数据库cpu飙升的话,要怎么处理呢? +**排查过程:** +- 使用top 命令观察,确定是mysqld导致还是其他原因。 +- 如果是mysqld导致的,show processlist,查看session情况,确定是不是有消耗资源的sql在运行。 +- 找出消耗高的 sql,看看执行计划是否准确, 索引是否缺失,数据量是否太大。 + +**处理:** +- kill 掉这些线程(同时观察 cpu 使用率是否下降), +- 进行相应的调整(比如说加索引、改 sql、改内存参数) +- 重新跑这些 SQL。 + +**其他情况:** + +也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等 + +### 87. 读写分离常见方案? +- 应用程序根据业务逻辑来判断,增删改等写操作命令发给主库,查询命令发给备库。 +- 利用中间件来做代理,负责对数据库的请求识别出读还是写,并分发到不同的数据库中。(如:amoeba,mysql-proxy) + +### 88. MySQL的复制原理以及流程 +**主从复制原理,简言之,就三步曲,如下:** + +- 主数据库有个bin-log二进制文件,纪录了所有增删改Sql语句。(binlog线程) +- 从数据库把主数据库的bin-log文件的sql语句复制过来。(io线程) +- 从数据库的relay-log重做日志文件中再执行一次这些sql语句。(Sql执行线程) + +**如下图所示:** + +![](https://user-gold-cdn.xitu.io/2019/8/1/16c4d9dd1b8235c3?w=1176&h=552&f=png&s=98700) + +上图主从复制分了五个步骤进行: + +步骤一:主库的更新事件(update、insert、delete)被写到binlog + +步骤二:从库发起连接,连接到主库。 + +步骤三:此时主库创建一个binlog dump thread,把binlog的内容发送到从库。 + +步骤四:从库启动之后,创建一个I/O线程,读取主库传过来的binlog内容并写入到relay log + +步骤五:还会创建一个SQL线程,从relay log里面读取内容,从Exec_Master_Log_Pos位置开始执行读取到的更新事件,将更新内容写入到slave的db + +### 89. MySQL中DATETIME和TIMESTAMP的区别 +存储精度都为秒 + +区别: +- DATETIME 的日期范围是 1001——9999 年;TIMESTAMP 的时间范围是 1970——2038 年 +- DATETIME 存储时间与时区无关;TIMESTAMP 存储时间与时区有关,显示的值也依赖于时区 +- DATETIME 的存储空间为 8 字节;TIMESTAMP 的存储空间为 4 字节 +- DATETIME 的默认值为 null;TIMESTAMP 的字段默认不为空(not null),默认值为当前时间(CURRENT_TIMESTAMP) + +### 90. Innodb的事务实现原理? +- 原子性:是使用 undo log来实现的,如果事务执行过程中出错或者用户执行了rollback,系统通过undo log日志返回事务开始的状态。 +- 持久性:使用 redo log来实现,只要redo log日志持久化了,当系统崩溃,即可通过redo log把数据恢复。 +- 隔离性:通过锁以及MVCC,使事务相互隔离开。 +- 一致性:通过回滚、恢复,以及并发情况下的隔离性,从而实现一致性。 + +### 91. 谈谈MySQL的Explain +Explain 执行计划包含字段信息如下:分别是 id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、Extra 等12个字段。 + +我们重点关注的是type,它的属性排序如下: +``` +system> const> eq_ref> ref> ref_or_null> +index_merge> unique_subquery> index_subquery> +range> index> ALL +``` + +推荐大家看这篇文章哈: +[面试官:不会看 Explain执行计划,简历敢写 SQL 优化?](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484319&idx=1&sn=17c98e757c24a853374cb7e06c9c9302&chksm=977947b0a00ecea66f3971c723cd844158a24c6c602c22c562223f6932b5b7ad1eee8b700255&token=1596384379&lang=zh_CN#rd) + +### 92. Innodb的事务与日志的实现方式 +#### 有多少种日志 +innodb两种日志redo和undo。 +#### 日志的存放形式 +- redo:在页修改的时候,先写到 redo log buffer 里面, 然后写到 redo log 的文件系统缓存里面(fwrite),然后再同步到磁盘文件( fsync)。 +- Undo:在 MySQL5.5 之前, undo 只能存放在 ibdata文件里面, 5.6 之后,可以通过设置 innodb_undo_tablespaces 参数把 undo log 存放在 ibdata之外。 + +#### 事务是如何通过日志来实现的 +- 因为事务在修改页时,要先记 undo,在记 undo 之前要记 undo 的 redo, 然后修改数据页,再记数据页修改的 redo。 Redo(里面包括 undo 的修改) 一定要比数据页先持久化到磁盘。 +- 当事务需要回滚时,因为有 undo,可以把数据页回滚到前镜像的 状态,崩溃恢复时,如果 redo log 中事务没有对应的 commit 记录,那么需要用 undo把该事务的修改回滚到事务开始之前。 +- 如果有 commit 记录,就用 redo 前滚到该事务完成时并提交掉。 + +### 93. MySQL中TEXT数据类型的最大长度 +- TINYTEXT:256 bytes +- TEXT:65,535 bytes(64kb) +- MEDIUMTEXT:16,777,215 bytes(16MB) +- LONGTEXT:4,294,967,295 bytes(4GB) + +### 94. 500台db,在最快时间之内重启。 +- 可以使用批量 ssh 工具 pssh 来对需要重启的机器执行重启命令。 +- 也可以使用 salt(前提是客户端有安装 salt)或者 ansible( ansible 只需要 ssh 免登通了就行)等多线程工具同时操作多台服务 + +### 95. 你是如何监控你们的数据库的?你们的慢日志都是怎么查询的? +监控的工具有很多,例如zabbix,lepus,我这里用的是lepus +### 96. 你是否做过主从一致性校验,如果有,怎么做的,如果没有,你打算怎么做? +主从一致性校验有多种工具 例如checksum、mysqldiff、pt-table-checksum等 +### 97. 你们数据库是否支持emoji表情存储,如果不支持,如何操作? +更换字符集utf8-->utf8mb4 +### 98. MySQL如何获取当前日期? +SELECT CURRENT_DATE(); +### 99. 一个6亿的表a,一个3亿的表b,通过外间tid关联,你如何最快的查询出满足条件的第50000到第50200中的这200条数据记录。 +1、如果A表TID是自增长,并且是连续的,B表的ID为索引 +select * from a,b where a.tid = b.id and a.tid>500000 limit 200; + +2、如果A表的TID不是连续的,那么就需要使用覆盖索引.TID要么是主键,要么是辅助索引,B表ID也需要有索引。 +select * from b , (select tid from a limit 50000,200) a where b.id = a .tid; + +### 100. Mysql一条SQL加锁分析 +一条SQL加锁,可以分9种情况进行: +- 组合一:id列是主键,RC隔离级别 +- 组合二:id列是二级唯一索引,RC隔离级别 +- 组合三:id列是二级非唯一索引,RC隔离级别 +- 组合四:id列上没有索引,RC隔离级别 +- 组合五:id列是主键,RR隔离级别 +- 组合六:id列是二级唯一索引,RR隔离级别 +- 组合七:id列是二级非唯一索引,RR隔离级别 +- 组合八:id列上没有索引,RR隔离级别 +- 组合九:Serializable隔离级别 + +## 公众号 +![](https://user-gold-cdn.xitu.io/2020/5/16/1721b50d00331393?w=900&h=500&f=png&s=389569) +- 欢迎关注我个人公众号,交个朋友,一起学习哈~ +- 如果答案整理有错,欢迎指出哈,感激不尽~ diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/16. MySQL 344円272円213円345円212円241円345円233円233円345円244円247円347円211円271円346円200円247円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/16. MySQL 344円272円213円345円212円241円345円233円233円345円244円247円347円211円271円346円200円247円.md" deleted file mode 100644 index 0ad2076..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/16. MySQL 344円272円213円345円212円241円345円233円233円345円244円247円347円211円271円346円200円247円.md" +++ /dev/null @@ -1,5 +0,0 @@ -![](https://user-gold-cdn.xitu.io/2020/3/30/1712b5213446a402?w=626&h=466&f=png&s=127652) -- **原子性:** 事务作为一个整体被执行,包含在其中的对数据库的操作要么全部都执行,要么都不执行。 -- **一致性:** 指在事务开始之前和事务结束以后,数据不会被破坏,假如A账户给B账户转10块钱,不管成功与否,A和B的总金额是不变的。 -- **隔离性:** 多个事务并发访问时,事务之间是相互隔离的,一个事务不应该被其他事务干扰,多个并发事务之间要相互隔离。。 -- **持久性:** 表示事务完成提交后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。 \ No newline at end of file diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/36. MySQL 345円237円272円347円241円200円346円236円266円346円236円204円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/36. MySQL 345円237円272円347円241円200円346円236円266円346円236円204円.md" deleted file mode 100644 index 4f30553..0000000 --- "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/346円225円260円346円215円256円345円272円223円/36. MySQL 345円237円272円347円241円200円346円236円266円346円236円204円.md" +++ /dev/null @@ -1,2 +0,0 @@ - -![](https://user-gold-cdn.xitu.io/2020/5/12/172095f33b81be23?w=952&h=790&f=png&s=195173) diff --git "a/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/347円274円223円345円255円230,円Redis/Redis344円270円272円344円273円200円344円271円210円350円277円231円344円271円210円345円277円253円.md" "b/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/347円274円223円345円255円230,円Redis/Redis344円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/Java351円235円242円350円257円225円351円242円230円351円233円206円347円273円223円345円217円267円/347円274円223円345円255円230,円Redis/Redis344円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/Java351円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円/TCP345円215円217円350円256円25615円350円277円236円351円227円256円.md" "b/Java351円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円/TCP345円215円217円350円256円25615円350円277円236円351円227円256円.md" new file mode 100644 index 0000000..eae7eec --- /dev/null +++ "b/Java351円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円/TCP345円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/Mysql345円237円272円347円241円200円345円255円246円344円271円240円/README.md" "b/Mysql345円237円272円347円241円200円345円255円246円344円271円240円/README.md" new file mode 100644 index 0000000..f335927 --- /dev/null +++ "b/Mysql345円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/Mysql345円237円272円347円241円200円345円255円246円344円271円240円/order by350円257円246円350円247円243円.md" "b/Mysql345円237円272円347円241円200円345円255円246円344円271円240円/order by350円257円246円350円247円243円.md" new file mode 100644 index 0000000..3aeda16 --- /dev/null +++ "b/Mysql345円237円272円347円241円200円345円255円246円344円271円240円/order by350円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 d5087b2..7018310 100644 --- a/README.md +++ b/README.md @@ -1,8 +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) + +## 工作总结 + +- [盘点数据库主从延迟的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) + ## 个人公众号 -![image](https://user-gold-cdn.xitu.io/2019/7/28/16c381c89b127bbb?w=344&h=344&f=jpeg&s=8943) +微信搜公众号:**捡田螺的小男孩** -- 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论哈~~ +- 小伙伴可以关注我的公众号(扫描下面二维码,还有**很多很多干货文章**),一起学习讨论哈~~ +![扫一扫](https://user-images.githubusercontent.com/20244922/179399354-8a9fd2a8-42ba-4303-9ce5-04891e899e6d.png) diff --git "a/image/350円265円236円350円265円217円347円240円201円.jpg" "b/image/350円265円236円350円265円217円347円240円201円.jpg" new file mode 100644 index 0000000..4599217 Binary files /dev/null and "b/image/350円265円236円350円265円217円347円240円201円.jpg" differ diff --git "a/letecode350円247円243円351円242円230円347円256円227円346円263円225円344円273円213円347円273円215円/README.MD" "b/letecode350円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/letecode350円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/letecode350円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/letecode350円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/letecode350円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/letecode350円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/letecode350円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/letecode350円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 @@ +## ǰ�� + +���Һã����Ǽ����ݵ�С�к�����Ϊ���˿�����������jô���ԣ�```Java```��```Go```����```C++```���䱳���ĺ���˼�붼�����Ƶġ�����������һ������˼���ļ���ר������Ҫ�������˵�һЩ���ơ����ߺ��˹淶���صģ�ϣ���Դ����ճ������а������� + +���������˿�������ʦ����Ҫ�������ǣ�**���ΰ�һ���ӿ����ƺ�**�����ԣ������͸����ҽ��ܣ����ƺýӿڵ�36�����ҡ����ľ��Ǻ���˼��ר���ĵ�һƪ���� + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1bfc8123cee34de2ad82b736121165d2~tplv-k3u1fbpfcp-zoom-1.image) + +- ���ںţ������ݵ�С�к� + + +## 1. �ӿڲ���У�� + +���γ���У����ÿ������Ա�ر��Ļ��������������ƵĽӿڣ�������У�����������������Ƿ�����Ϊ�գ����γ����Ƿ���������Ԥ�ڳ��ȡ�����Ҫ����κ�߹����ճ������У��ܶ��ͼ�bug���Dz�У���������μġ� + +> �����������ݿ����ֶ�����Ϊ```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�ӿڣ�ԭ����ֻ����A��B����������������һ������C���Ϳ��Կ����������� + +``` +//�Ͻӿ� +void oldService(A,B){ + //�����1⁄2ӿڣ�����null����C + newService(A,B,null); +} + +//�1⁄2ӿڣ���ɦ����ɾ���Ͻӿڣ���Ҫ�����ݡ� +void newService(A,B,C){ + ... +} +``` + +## 3. ���ƽӿ�ɦ�����ֿ��ǽӿڵL·���չ�� + +Ҫ����ɻ��ҵ�񳡾����ƽӿڣ����ֿ��ǽӿڵL·���չ�ԡ� + +�������ӵ�һ�����������û����ӻ����޸�Ա��ɦ����Ҫs���������Ƿ����ṩһ��Ա���������ύs����Ϣ�ӿڣ�������˼�����ύs���Dz���ͨ�������أ�����ת�˻���һ��������Ҫ����s���Ļ������Ƿ���Ҫ����ɻ��һ���ӿ��أ����ǵ�ǰ��ҵ�����ͻ���ģ�飬���������ӿھ ͅã������ӿڵL·���չ�ԡ� + +������ģ�黮�ֵĻ���δ��������������һ�����ֽ���s���Ļ��������يٴ�һ���μĽӿڣ�ֻ��Ҫ����ö�٣�Ȼ������s��ͨ�����̽ӿڣ�ɻ��һ������s���IJ��컯���ɡ� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cd3ee52ecaa34de384bb529cbb358889~tplv-k3u1fbpfcp-zoom-1.image) + + +## 4.�ӿڿ����Ƿ���Ҫ���ش��� + +����ǰ���ظ������������߼����δ����Dz��ǿ��ǽӿ�ȥ�ش��� + +��Ȼ�������Dz�ѯ������������ɻ���÷��ء������Ǹ����޸����Ļ�����������ת�����ģ���Ҫ�����ظ������l��򵥵㣬������ʹ��Redis���ظ�������ͬ�������󷽣�һ��ɦ�������ڵ���ͬ���󣬿����Ƿ����l���Ȼ��ת�����ӿڣ��������ߵĻ���**�Ƽ�ʹ�����ݿ����ر�**����**Ψһ��ˮ����Ϊ��������Ψһ����**�� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/059928f565ba4d27a17c54f451b0235d~tplv-k3u1fbpfcp-zoom-1.image) + + +## 5. �ص��ӿڣ������̳߳ظ��롣 + +һЩ��1⁄2��ת�˽��ס��μ�����Ҫ�ӿڣ������̳߳ظ�����������������ҵ�񶼹���һ���̳߳أ���Щҵ����bug�����̳߳����������Ļ����Ǿͱ����x�**����ҵ����Ӱ����**�����˽����̳߳ظ��룬��Ҫҵ��������һ���ĺ����̣߳��͸��ñ�����Ҫҵ���� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d30804afc044026b4eb7bad23689c42~tplv-k3u1fbpfcp-zoom-1.image) + + +## 6. ���õ������ӿ�Ҫ�����쳣�ͳ�ɦ���� + +���������õ������ӿڣ����߷ֲ�ʽԶ�̷����ĵĻ�����Ҫ���ǣ� + +- �쳣���� + +> ���磬�������˵Ľӿڣ������쳣�x���ô���������Ի��ǵ���ʧ�ܻ��Ǹ澯���� + +- �ӿڳ�ɦ + +> û��Ԥ���Է��ӿ�һ�����÷��أ�һ�����ø���ɦ�Ͽ�ɦ�䣬�Ա������Ľӿڡ�**֮ǰ����һ����������**������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. ��־��ӡ�ã��ӿڵĹؼ����룬Ҫ����־���ݻ����� + +�ؼ�ҵ�������������εأ���Ӧ�����㹻����־���ݻ����� +���磺��ɻ��ת��ҵ����ת�������Ȼ��תʧ���x����sͻ�Ͷ�ߣ�Ȼ���㻹û�д�ӡ����־����������ˮ�����ȵ������£���ȴ���ް취������ + +��ô������ת��ҵ������Ҫ��Щ��־��Ϣ�أ����٣���������ǰ��������Ҫ��ӡ��Ҫ�ɣ��ӿڵ��ú�����Ҫ����һ���쳣�ɣ�ͬɦ��ӡ�쳣������־�ɣ����£� +``` +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. �ӿڵĹ��ܶ���Ҫ�߱���һ�� + +��һ����ָ�ӿ����������Ƚε�һ��רһ������һ����1⁄2�ӿڣ�������������ֻ��У���˻������룬Ȼ�󷵻ص�1⁄2�ɹ��Լ�```userId```���ɡ�**����������Ϊ�˼��ٽӿڽ�������һЩע�ᡢһЩ���ò�ѯ��ȫ�ŵ���1⁄2�ӿڣ��Ͳ�̫�ס�** + +��ɻ��Ҳ��΢����һЩ˼�룬�ӿڵĹ��ܵ�һ����ȷ�����綩�����񡢻��֡���Ʒ��Ϣ���صĽӿڶ��ǻ��ֿ��ġ���������΢�����Ļ����Dz��ǾͱȽϼ������� + + +## 10.�ӿ���Щ������ʹ���첽������ + +�يٴ��򵥵����ӣ�������ɻ��һ���û�ע���Ľӿڡ��û�ע���ɹ�ɦ�������ʼ����߶���ȥ֪ͨ�û��������ʼ����߷����ţ��͸��ʺ��첽������Ϊ�ܲ���һ��֪ͨ����ʧ�ܣ�����ע��ʧ�ܰɡ� + +�������첽�ķ�ʽ���򵥵ľ���**���̳߳�**��������ʹ����Ϣ���У������û�ע���ɹ����������߲���һ��ע���ɹ�����Ϣ������������ע���ɹ�����Ϣ���ͷ���֪ͨ�� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/585b098a67b349d495e6e8579ea85e4c~tplv-k3u1fbpfcp-zoom-1.image) + + +�������еĽӿڶ��ʺ�����Ϊͬ���ӿڡ�������Ҫ��һ��ת�˵Ĺ��ܣ��������ǵ��ɻ�ת�x����ǿ��԰ѽӿ�����ͬ�����û�����ת��ɦ���ͻ����ھ����ȴ�ת�˽���� ͅá�������������ת�x�һ������һǧ�ʣ�����һ���ɻģ��������԰ѽӿ�����Ϊ�첽�������û���������ת��ɦ���־û��ɹ����ȷ��������ɹ���Ȼ���û���ʮ���ӻ���ʮ�����ӵ�������ת�˽���� ͅá��ֻ��ߣ�����ת�˳ɹ������ٻص�����εͳ�� + + +![](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) + +�����Ǵ���һ��һ���飬�������û���Ϣ200ms����banner��Ϣ100ms���鵯����Ϣ50ms����һ��� ͅ�ɦ```350ms```�x���������������Ϣ���Ǻ�ɦ�͸����l����ֳ����ǿ��Ը�Ϊ���е��õġ�Ҳ����˵���û���Ϣ����banner��Ϣ���鵯����Ϣ������ͬɦ������ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/83561366219b48a2a85a6bb0419f82a3~tplv-k3u1fbpfcp-zoom-1.image) + +��Java���и��첽����������```CompletableFuture```���Ϳ��Ժܺ�ɻ���������ܡ�����Ȥ��С�������Կ���֮ǰ�������1���[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 Cache��Caffeine```�ȡ�ʹ�ñ��ػ�����Щȱ�㣬�����޷����д����ݴ洢������Ӧ�ý��̵����������ʧЧ�� + +## 14. �ӿڿ����ȵ����ݸ����� + +˲ɦ���ĸ߲��������ܻ���������εͳ��������һЩ�ȵ����ݵĸ��롣����**ҵ�����롢εͳ���롢�û����롢���ݸ���**�ȡ� + +- ҵ�������ԣ�����12306�ķ�ɦ����Ʊ�����ȵ����ݷ�ɢ��������εͳ����ѹ���� +- εͳ���룺������εͳ�ֳ����û�����Ʒ�������������顣���������ֱ�ʹ�ò�ͬ�������������������ݿ⣬��ӽ����㵽Ӧ�ò��اٴ����ݲ�������ȫ���롣 +- �û����룺�ص��û����������ø��õĻ����� +- ���ݸ��룺ʹ�õ����Ļ��漯Ⱥ�������ݿ������ȵ����ݡ� + +## 15. �ɱ��������û�����������Ƥ���л��� + +������Ʒ�������˸�����������ʥ���ڵ�ɦ�򣬺���Ƥ��Ϊʥ�������صģ����ڵ�ɦ����Ϊ���ں���Ƥ���ȡ� + +�����ڴ���д�����ƣ������������ ́��룺 +``` +if(duringChristmas){ + img = redPacketChristmasSkin; +}else if(duringSpringFestival){ + img = redSpringFestivalSkin; +} +``` +��������Ԫ���ڵ�ɦ������ӪС����ͻȻ�����뷨������Ƥ��ɵ������صģ���ɦ�����Dz���Ҫȥ�޸Ĵ����x����·����x� + +��һ��ʼ�ӿ�����ɦ������ɻ��**һ�ź���Ƥ�������ñ�**��������Ƥ���������û��أ���������Ƥ����ֻ���޸�һ�±����ݾ ͅ��l� + +��Ȼ������һЩ�����ʺ�һЩ���û��IJ�����һ����ҳ�����������ơ�ij������������ɦ��������Щ�������Ը㵽�������û������档**��Ҳ����չ��˼����һ�����֡�** + +## 16.�ӿڿ����ݵ��� + +�ӿ�����Ҫ�����ݵ��Եģ�������������ת����Щ��Ҫ�ӿڡ���ֱ�۵�ҵ�񳡾�������**�û����ŵ�������**�����Ľӿ���û��**holdס**��������Ϣ���г����ظ����ѵ�����������ҵ���߼���ô���ƣ� + +�����£�**jô���ݵȣ�** + +> ��������ѧ�У��ݵȱ�ʾһ�κͶ�������ijһ����ԴӦ�þ���ͬ���ĸ����ã�����˵������������������Ӱ����һ������ִ�е�Ӱ��Ч����ͬ�� + +���ұ���������**���غ��ݵ�������ɻ����������**��������ҪΪ�˱��������ظ����ݣ����ظ����������������ɡ����ݵ����Ƴ��������Ѿ����������󣬻�Ҫ��ÿ����ͬ�����󶼷���һ����Ч�����أ��ܶ�ɦ�������ǵĴ������̡����������ƵĹ��� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f8c3d0d5a653455198ba3259ef221387~tplv-k3u1fbpfcp-zoom-1.image) + + +�ӿ��ݵ�ɻ�ַ�����Ҫ��8�֣� + +- select+insert+����/Ψһ������ͻ +- ֱ��insert + ����/Ψһ������ͻ +- ״̬���ݵ� +- ��ȡ���ر� +- token���� +- ������ +- �ֹ��� +- �ֲ�ʽ�� + +���ҿ��Կ�����ƪ���1���[�����ݵ�����](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��ѯ�ƻ����ص���עtype��extra��filtered�ֶΣ� +- show profile�������˽�SQLִ�е��̵߳�״̬�Լ����ĵ�ɦ�� +- �����Ż� ����������������ǰ׺ԭ������ʽת����order by�Լ�group by���Ż���join�Ż��� +- ����ҳ�����Ż����ӳٹ�������1⁄4��һҳ����ID�� +- ������̫����**�ֿ��ֱ�**��ͬ����es����es��ѯ�� + +## 20.�����������ȿ��ƺ� + +jô�Ǽ��������أ� + +> ��ɻ���Ǿ�����Ҫ��ס�ķ�Χ�Ƕ��󡣱������ڼ��������䣬��ֻҪ��ס�������Ϳ����˰ɣ�����Ҫ�������Ҷ����������ü��˽��Űɣ��������������ļ������ȡ� + +����д����ɦ���������漰��������Դ����û�б�Ҫ��ס�ġ���� ͅ������������䣬���ð������Ҷ���ס����ס�������žͿ����l� + +���磬��ҵ�������У���һ��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 -> { + //��������̫���x�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ɹ���ʧ�ܡ������л��������ɹ��ȣ���Ҫ��ȷ���߿ͻ��l������ӿ�ʧ�ܣ���ô����ʧ�ܵ�ԭ����jô����Щ��Ҫ����Ϣ������Ҫ���߸��ͻ��x�������Ҫ������ȷ�Ĵ������Ͷ�Ӧ��������ͬɦ�������Ա�����Ϣ��װһ�£���Ҫ�Ѻ��˵��쳣��Ϣ��ȫ�׳�ͻ��l� + +![](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```���� +- ע���쳣ƥ����˳�������Ȳ����������쳣 + +С����������Ȥ���Կ�����֮ǰд����ƪ���1���[Java �쳣������ʮ������](https://mp.weixin.qq.com/s/3mqY77c8iXWvJFzkVQi9Og) + +## 23. �Ż������߼� + +�Ż������߼����黹��ͦ��Ҫ�ģ�Ҳ����˵����ɻ�ֵ�ҵ�����룬**�����DZȽϸ��ӵĻ���������ע��д����**�����У������߼��������������뾡����Ч�� + +> ���磬��Ҫʹ���û���Ϣ�����ԣ�������session�Ѿ���ȡ��```userId```�x�Ȼ���Ͱ��û���Ϣ�����ݿ���ѯ������ʹ�����󣬺���������Ҫ�õ��û���Ϣ�����ԣ���ЩС����û��̫�࣬���־Ͱ�```userId```�ٴ���ȥ���ٲ�һ�����ݿ⡣����������L·�У��������ִ��롣����ֱ�Ӱ��û������������������ + +����α���룺 + +``` +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������һ��������ʮ�������أ������ǶȾ���˵���߲�����ɦ���������������x�����εͳ�ij�����������ô���أ� + +��������ȡ��ʩ�����е�������������εͳCPU���ڴ桢Load����쮵ĺܸߣ����������������������е������޷�������Ӧ�� + +�������ֳ��������ǿ��Բ��������������Ϊ�˱���εͳ��������������ֱ�Ӷ����� + +�������壺 +> �ڼ����������У��������ǿ��������ӿڷ��ͻ��������������ʣ����ɷ�ֹDoS����������Web���档������Ҳ���������ơ���ָεͳ�����يٴ߲��������ߴ����������������£������μ�������εͳ�ķ��ʣ��Ӷ���֤εͳ���ȶ��ԡ� + +����ʹ��Guava��```RateLimiter```������������Ҳ����ʹ��```Redis```�ֲ�ʽ���������ʹ�ð��↑Դ����```sentinel```���� + +���ҿ��Կ�����֮ǰ��ƪ���1���[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�У�һ��������һ������ɦ�䣩��Ȼ�󷵻ظ��ͻ��l� +3. �ͻ��˴���token������������ +4. ������ȥredisȷ��token�Ƿ����ڣ�һ���� redis.del(token)�ķ�ʽ���������ڻ�ɾ���ɹ���������ҵ���߼�������ɾ��ʧ�ܲ�����ҵ���߼���ֱ�ӷ��ؽ����� + +**�ӿ�ǩ��**�ķ�ʽ�����ǰѽӿ�����������Ϣ���������ģ���������ɦ�������汾�š�appid�ȣ����ͻ���˽Կ��ǩ��Ȼ���������ù�Կ��ǩ����֤ͨ������Ϊ�ǺϷ��ġ�û�б��۸Ĺ��������� + +�й��ڼ�ǩ��ǩ�ģ����ҿ��Կ�������ƪ���1���[����Ա�ر�����ǩ��ǩ](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��ԭ�����Ƿ����˰ѹ�Կ���ͻ��x�Ȼ���ͻ������ɶԳ���Կ�����sͻ����÷����˵Ĺ�Կ���ܶԳ���Կ���ۇٴ��������x����������Լ���˽Կ���ܣ��õ��ͻ��˵ĶԳ���Կ����ɦ���Ϳ������촫�䱨�������ͻ�����**�Գ���Կ������������**��**�������ö�Ӧ�ĶԳ���Կ���ܱ���**�� + +��ɦ�򣬽ӿڵİ�ȫ�ԣ�������**�ֻ��š�����֤����Ϣ������**������˵��**�û�����˽���ݣ��������㱩¶**�� + +## 28.�ֲ�ʽ���������α�֤ + +> �ֲ�ʽ���񣺾���ָ�����IJ����ߡ�֧�������ķ���������Դ�������Լ������������ֱ�λ�ڲ�ͬ�ķֲ�ʽεͳ�IJ�ͬ�ڵ�֮�ϡ�������˵���ֲ�ʽ����ָ�ľ��Ƿֲ�ʽεͳ�е����������Ĵ��ھ���Ϊ�˱�֤��ͬ���ݿ��ڵ�������һ���ԡ� + +�ֲ�ʽ�����ļ��ֽ������ +- 2PC(���׶��ύ)������3PC +- TCC��Try��Confirm��Cancel�� +- ������Ϣ�� +- ����Ŭ��֪ͨ +- seata + +���ҿ��Կ�����ƪ���1���[��һ�������⣺�ֲ�ʽ��������](https://mp.weixin.qq.com/s/3r9MfIz2RAtdFhYzwwZxjA) + +## 29. ����ʧЧ��һЩ���䳡�� + +���ǵĽӿڿ��������У�������Ҫʹ�õ�������������Ҫ�ܿ�����ʧЧ��һЩ���䳡���� + +- �����ķ���Ȩ�ޱ�����public������private��Ȩ�ޣ�����ʧЧ +- ��������������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()```; + +> - Hashmap��Arraylist��LinkedList��TreeMap�ȶ������Բ���ȫ�ģ� +> - Vector��Hashtable��ConcurrentHashMap�ȶ������԰�ȫ�� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1ba0cab945874264a8d8e87b7d7c4a1b~tplv-k3u1fbpfcp-zoom-1.image) + + +## 32.�ӿڶ��������׶��������淶�� + +����д���룬��������Ϊ��ɻ�ֵ�ǰ�Ĺ��ܣ�ҲҪ�����ں�����ά����˵��ά�������벻������д���Լ����ģ�Ҳ�Ǹ����˿��ġ����Խӿڶ���Ҫ�����׶��������淶�� + +## 33. �ӿڵİ汾���� + +�ӿ�Ҫ���ð汾���ơ�����˵���������ģ�Ӧ�ð���```version```�ӿڰ汾���ֶΣ�����δ�����ӿڼ��ݡ���ɻ������Ҳ���ӿ���չ�Ե�һ�����ֵ��ɡ� + +�����ͻ���APPij�������Ż��x����κ汾�Ṳ�棬��ɦ�����ǵ�```version```�汾�ž������ó��x���```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�ֲ�ʽ�����м���ע��Ҫ�㣬���ҿ��Կ�����֮ǰ��ƪ���1���[���ַ�����̽��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.ѧ�ṵͨ����ǰ�˹�ͨ������Ʒ��ͨ + +�Ұ���һ���ŵ�������ѧ�ṵͨ�Ƿdz��dz���Ҫ�ġ������㿪�������ӿ�ɦ��**һ�������������Լ���ͷ�ѽӿڶ�������**��**��Ҫ���ͻ����ȶ����ӿ�**������һЩ�ѵ�ɦ��������leader���뷽����ɻ�������Ĺ����У���jô���⣬��ɦ����Ʒ��ͨ�� + +��֮���ǣ������ӿڹ����У�һ��Ҫ��ͨ��~ + + +## ����(����ע����������) + +������ƪ���¶�����������������������Ļ�����ӭ��ע�ҵĹ��ںţ������ݵ�С�к� + + 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); +} +``` + +���δ�������jô����� ��ɻ����һ��ͦ�����Ĵ��룬���������ɻ���У���ѯ�û���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��е��á������̳߳�ִ����������ɦ������ֵ��```Future��get()```��ȡ�������ģ�����ǰһ������ִ�бȽϺ�ɦ�Ļ���```get����```�������������γ��Ŷӵȴ��������� + +��```CompletionService```�ǶԶ���```ExecutorService```�����˰�װ������һ����������,һ�߻�ȡ�����ķ���ֵ�����������·ֿ�ִ��,����֮�䲻�ụ�����������Ի�ȡ�������ɵ����������� + + +> ```CompletionService```��ɻ��ԭ���Ƚϼ򵥣��ײ�ͨ��FutureTask+�������У�ɻ�������������ɵĻ��������Ȼ�ȡ����Ҳ����˵����ִ�н����������ɵ��Ⱥ�˳���������������ɿ����Ż���ȡ�����ڲ���һ���Ƚ��ȳ����������У����ڱ����Ѿ�ִ�����ɵ�Future��������```CompletionService```��poll��take�������ɻ�ȡ��һ���Ѿ�ִ�����ɵ�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```��Ϣ��������Ҳ�ŵ������̳߳صĻ������ֲ��÷��x���Ϊ�������Ͳ�һ����һ����```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�и�������UserDTO����BannerDTO��**Ψһ��������key**���������£� + +``` +public class BaseRspDTO { + + //������DTO���ص�Ψһ���ǣ�������UserInfoDTO����BannerDTO + 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```ɻ���˲��е��á�������û��jô�����뷨�أ�����,��������ҵ�񳡾���Ҳ��ͨ�����е����Ż������Dz���Ҳ�ø�һ�����Ƶ�2С�ڵĴ��롣���ԣ�**�����Dz��ǿ��Գ�ȡһ��ͨ�õIJ��з������ñ��ij���Ҳ�����ã��԰ɣ������Ǻ���˼ά��**�� + +���ڵ�2С�ڵĴ��룬�������γ�ȡͨ�ò��е��÷����ء� + +���ȣ�����ͨ�õIJ��е��÷�����**���ܸ�ҵ�����ص����Թҹ�**���԰ɣ����Է���������Ӧ������Щ�أ� + +> ���������Σ�������```Callable```�԰ɡ���Ϊ���У��϶��Ƕ���Callable�����ġ����ԣ�����Ӧ����һ��```Callable```�����顣��Ȼ�󣬻���������APP��ҳ��ѯ�����ӣ�```Callable```�����ô�```BaseRspDTO```���ͣ��԰ɣ��������ξ���```List> list```�� + +�Dz��е��õij����أ� ���ж���```Callable```���������Dz��ǵ��ж�����Ӧ�ķ��أ����x����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```�������޸��x�����**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...```�ж��x��ò��Թ�����```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円241円SQL357円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円241円SQL357円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円241円SQL357円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 + +- ���Թٿ���֮������jô�� +- ���Թٿ���֮�������� +- ���Թٿ���֮Ϊjôѡ��B+����Ϊ�����ṹ +- ���Թٿ���֮һ�������������� +- ���Թٿ���֮�������� +- ���Թٿ���֮����ʧЧ���� +- ���Թٿ���֮����ǰ׺ +- ���Թٿ���֮�������� +- ���Թٿ���֮������������ + +### һ�����Թٿ���֮������jô�� + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/21eed26e34534b51aadf28d5defaed7e~tplv-k3u1fbpfcp-watermark.image) + +- ������һ�����������ݿ���ѯЧ�ɻ����ݽṹ�������Ա���һ���ֵ���L·1⁄4�����԰��������ҵ���Ӧ�ļ�1⁄4�� +- ����һ���洢�ڴ��̵��ļ��У�����ռ�������ռ��ġ� +- ����νˮ�����ۣ�Ҳ�ܸ��ۡ��ɻ������������߲�ѯЧ�ʣ�������������Ӱ�����ݿ����IJ����͸��1��ܡ� + +### ������������Щ�������� + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf6725e1a8cb44b496cca8fa45e15c2f~tplv-k3u1fbpfcp-watermark.image) + +#### ���ݽṹά�� + +- B+���������������ݴ洢��Ҷ�ӽڵ㣬���Ӷ�ΪO(logn)���ʺϷ�Χ��ѯ�� +- ��ϣ����: �ʺε�ֵ��ѯ������Ч�yߣ�һ�ε�λ�� +- ȫ��������MyISAM��InnoDB�ж�֧��ʹ��ȫ��������һ�����ı�����char,text,varchar�����Θ����� +- R-Tree����: ������GIS�������ʹ���SPATIAL���� + +#### �����洢ά�� + +- �ۼ��������ۼ������������������������Ҷ�ӽڵ��洢���DZ��е����ݡ� +- �Ǿۼ��������Ǿۼ����������Է��������������Ҷ�ӽڵ��洢���������������С� + +#### �߼�ά�� + +- ����������һ��������Ψһ�������������п�ֵ�� +- ��ͨ������MySQL�л����������ͣ�������ֵ���ظ�ֵ�� +- ���������������ֶδ�����������ʹ��ɦ��ѭ����ǰ׺ԭ���� +- Ψһ�������������е�ֵ������Ψһ�ģ���������Ϊ��ֵ�� +- �ռ�������MySQL5.7֮��֧�ֿռ��������ڿռ������ⷽ����ѭOpenGIS��������ģ�͹����� + +### �������Թٿ���֮Ϊjôѡ��B+����Ϊ�����ṹ + +���ԴӼ���ά��ȥ���������⣬��ѯ�Ƿ񹻿죬Ч���Ƿ��ȶ����洢���ݶ��٣��Լ����Ҵ��̴����ȵȡ�Ϊjô���ǹ�ϣ�ṹ��Ϊjô���Ƕ�������Ϊjô����ƽ����������Ϊjô����B���ƫƫ��B+���أ� + +����дҵ��SQL��ѯɦ�������������£����Ƿ�Χ��ѯ�ģ���һ��SQL +``` +select * from employee where age between 18 and 28; +``` +#### Ϊjô��ʹ�ù�ϣ�ṹ�� +����֪����ϣ�ṹ������k-v�ṹ��Ҳ���ǣ�key��value��һ��һ��ε��������**��ֵ��ѯ**�����ԣ����Ƿ�Χ��ѯ��������Ϊ����Ŷ�� + +#### Ϊjô��ʹ�ö������أ� + +�Ȼ����¶���������֪ʁ��~ ��ν**���������ص����£�** +- ÿ������������������ֱ���Ϊ���������������� +- ���ӽڵ���ֵС�ڵ�ǰ�ڵ���ֵ����ǰ�ڵ�ֵС�����ӽڵ�ֵ +- ���˵Ľڵ���Ϊ���ڵ㣬û���ӽڵ��Ľڵ�ֵ��ΪҶ�ӽڵ㡣 + +�����Ժ��У������׾͸��ֳ����ֶ������ṹͼ�� +![](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) + +�������������⻯Ϊһ�������൱��ȫ��ɨ�衣��ô��Ҫ��������ѽ�����x�һ�����������ʺ���Ϊ�����ṹ�� + +#### Ϊjô��ʹ��ƽ���������أ� + +ƽ���������ص㣺��Ҳ��һ�Ŷ������������κνڵ������������߶�������Ϊ1�����ԾͲ����������⻯һ���������������� + +![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74afa9b54e7f4c0996ff83e66016c09a~tplv-k3u1fbpfcp-watermark.image) + +�����أ� +- ƽ���������������߸����ǣ���Ҫ��������ά��ƽ�⣬ά�����۴� +- �����������Ļ������ĸ߶Ȼ��ܸߡ���Ϊ�����Ǵ��ڴ��̵ģ�������Ϊ�����ṹ��ÿ�δӴ��̶�ȡһ���ڵ㣬����IO�Ĵ����Ͷ����� + + +#### Ϊjô��ʹ��B���أ� + + +���������Ļ���ƽ���������ĸ߶Ȼ��ܸߣ�������IO���Ϊjô��ѡ��ͬ����������**�߶ȸ�����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) + + +���x����� SQL ��ѯ����ִ�д������̾��ǽ��ϣ� +- 1. ����`idx_age`����������̿�1���ص��ڴ棬����32<37,������·��֧��������Ѱַ���̿�2�� +- 2. �����̿�2���ص��ڴ��У����ڴ������������ҵ�age=32�ļ�1⁄4��ȡ��id = 400. +- 3. �õ�id=400�󣬻ص�id������������ +- 4. ����`id����`����������̿�1�����ڴ棬���ڴ��������ҵ���400������B+��������Ҷ�ӽڵ��Dz��������ݵġ���������������400���ҷ�֧��������Ѱַ���̿�3. +- 5. �����̿�3�����ڴ棬���ڴ��������ҵ�id=400�ļ�1⁄4���õ�R4��һ�е����ݣ��õģ��󹦸��ɡ� + +���x�����SQL��ѯ��ִ���˼������������������Dz���һ����Ȼ��ѽ��**�ر���**����`idx_age`�����������ҵ�����`id`�󣬻ص�id�������������Ĺ���,�ͳ�Ϊ�ر� +> jô�ǻر��õ������ٻص�����������ѯ�Ĺ��̣��ͽ���**�ر�** + +### �塢���Թٿ���֮�������� + +**���Թ٣�** ��������`select *`, ����ʹ��`select id,age`�����ε���L·ִ���˼��������������أ� + +**������** �������⣬��Ҫ������ѡ�˵ĸ�������֪ʁ�㡣�ص�`idx_age`�������������Է��ֲ�ѯѡ��id��age����Ҷ�ӽڵ����l����x�����ֱ���ṩ��ѯ�������������Ͳ���Ҫ�ٻر���~ + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/629b6cfd08614adcbb20154707c0c8e0~tplv-k3u1fbpfcp-watermark.image) + +> �����������ڲ�ѯ�����������棬����Ҫ�ر�ȥ�飬ֱ�Ӵ������о���ȡ����Ҫ�Ľ����仰˵����SQL�õ������������ݣ������˲�ѯ�������У������ϸ��������l� + +���ԣ��������ϸ����⣬����ʡȥ�˻ر��������������� + +### �������Թٿ���֮����ʧЧ + +**���Թ٣�** ���������ڸ�`name`�ֶμ�����ͨ������Ȼ���ø�likeģ���������ǻ�ִ�ж��ٴβ�ѯ�أ�SQL���£� +``` +select * from employee where name like '%����%'; +``` +**������** �������֪ʁ�����ǣ�like�Ƿ��ᵼ�2������������ȸ�SQL��explainִ�мƻ��ɡ���ɻlike ģ������ᵼ�2��������ģ����£� + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b01bb44ff5744729e55939cc88bd424~tplv-k3u1fbpfcp-watermark.image) + +���x�����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��С�������������Թ�Ҫ�����������ֵ�һ�����ǡ�С�����x�SQL��like 'С%'�ǿ�������```idx_name_age```���������ġ� + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58b49f88f75f48be873deb395431c2fa~tplv-k3u1fbpfcp-watermark.image) + +�ò�ѯ������idx_name_age���������ҵ���һ������С������ֵ�����������ҵ�```С����С�ס�С�ࡢ```���ֱ��õ�Id=```600��100��700```��Ȼ�������α�ȥ�Ҷ�Ӧ�ļ�1⁄4�� ������������ǰ׺```С```�������ַ�������������M���ַ���ɻ���ϣ� +- ��������ǰ׺��������������������N���ֶΡ���������������a,b,c�������൱�ڽ��x�a������a,b��,(a,b,c)���������������������������������� +- ����ǰ׺Ҳ�������ַ�������������M���ַ��� + +### �l����Թٿ���֮�������� + +**���Թ٣�** ���ǻ��Ǿ����������� idx_name_age����������SQLִ�м����������أ� + +``` +select * from employee where name like 'С%' and age=28 and sex='0'; +``` + +**������** ������������Ƶ�֪ʁ�㣬������**Mysql5.6֮ǰ**����idx_name_age���������ҳ��������ֵ�һ�����ǡ�С�����x��õ����ǵ�����id��Ȼ���ر��ҳ������У���ȥ�Ա��������Ա��������ֶΡ���ͼ�� + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c5729b1a84fd4ee9b872717903ca6f75~tplv-k3u1fbpfcp-watermark.image) + +��Щ���ѿ��ܾ������֣���name,age)�������������Ϊjôѡ��������С���ֺ󣬲���˳�㿴������age�ٻر��أ����Ǹ���Ч�����ѽ��MySQL 5.6 ��������**���������Ż�**���������������������У��������а������ֶ������жϣ�ֱ�ӹ��˵������������ļ�1⁄4�����ٻر������� + + +���x�MySQL5.6�汾֮����ѡ��������С���ֺ���˳������age=28��,���Ծ�ֻ��һ�λر� + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/168aecb4709d4c30bc93c489ad76d712~tplv-k3u1fbpfcp-watermark.image) + +### �š� ���Թٿ���֮������������ + +**���Թ٣�** ����һ�ű�����������ǧ�򼶱����εģ���ô�������ű���������������Ҫ��ô���أ� + +**������** ������Ҫ֪��һ�㣬��������������ɦ�����ǻ��Ա������ġ������������������п��ܳ��������1ɻġ����Բο����·����� + +- 1.�ȴ���һ�Ÿ�ԭ��A���ݽṹ��ͬ���±�B�� +- 2.���±�B������Ҫ���ε��������� +- 3.��ԭ��A���ݵ����±�B +- 4.rename�±�BΪԭ���ı���A��ԭ��A�����ı����� + +### �ܽ�����κ + +������Ҫ������������9���ؼ�֪ʁ�㣬ϣ���Դ����а������������أ������ҳ�һ�����й���������ҵ�񿪷���ļ�����SQL����� ́�������ô�ش��ģ�����Ȥ������ε�ҹ�~��L·���£� +``` + +select * from A where type ='1' and status ='s' order by create_time desc; +``` +����type��9�����ͣ����ֶ��Ի������ԣ�status�����ֶȲ��ߣ���3�����ͣ�����ô�������μ������أ� +- �Ǹ�type�ӵ����� +- ���ǣ�type��status��create_time���������� +- ���ǣ�type��create_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円/Java346円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円/Java346円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円/Java346円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円/Mysql344円270円255円357円274円21421円344円270円252円345円206円231円SQL347円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円/Mysql344円270円255円357円274円21421円344円270円252円345円206円231円SQL347円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円/Mysql344円270円255円357円274円21421円344円270円252円345円206円231円SQL347円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円204円bug.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円204円bug.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円204円bug.md" @@ -0,0 +1 @@ +### ǰ�� ÿһ����κ�߶���һ�jƸ�������������д������16����κ�ߣ�ÿ��ܾ��䣬������Щκ�ߣ����Թ��ܶ�����ҵ����bug��ϣ���Դ����а�������лл�Ķ�������Ŷ~ github��ַ����лÿ��star> https://github.com/whx123/JavaHome ���ںţ�**�����ݵ�С�к�** ### 1. �޸������룬�ǵ��Բ�һ�� **�������룬�Բ�һ��** ��ÿλ����Ա�ر��Ļ������������䲻Ҫ�������ֽ���**������ֻ�Ǹ���һ������������ֻ����һ�����ô��룬�����Բ���**���������룬����Ҫ���Լ���ȥ����һ�1������Թ��ܺܶ಻��Ҫbug�ġ� ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c730a4fe0ebb47258af9fd31f8d9c890~tplv-k3u1fbpfcp-watermark.image) ### 2. �������ξ��������� ����У��Ҳ��ÿ������Ա�ر��Ļ������������ķ�������**������У������**�����������Ƿ�����Ϊ�գ����γ����Ƿ���������Ԥ�ڳ��ȡ�������������κ�߰ɣ��ܶ�**�ͼ�bug**����**��У������**���μġ�> �����������ݿ��ֶ�����Ϊvarchar(16),�Է�����һ��32λ���ַ����������㲻У��������**�������ݿ�ֱ���쳣**�l� ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1e3375dcfad46ca9ad88dafe7574de0~tplv-k3u1fbpfcp-watermark.image) ### 3. �޸��Ͻӿڵ�ɦ����˼���ӿڵļ����ԡ� �ܶ�bug������Ϊ�޸��˶����Ͻӿڣ�����ȴ**�������ݵ���**�ġ��ؼ��������������DZȽ����صģ�����ֱ�ӵ���εͳ����ʧ�ܵġ����ֳ���Ա�����׾ͷ�����������Ŷ~ ���ԣ�����������������ԭ���ӿ����޸ģ������������ӿ��Ƕ����ṩ�����Ļ���һ��Ҫ���ǽӿڼ��ݡ��يٴ����Ӱɣ�����dubbo�ӿڣ�ԭ����ֻ����A��B����������������һ������C���Ϳ��Կ����������� ``` //�Ͻӿ� void oldService(A,B);{ //�����1⁄2ӿڣ�����null����C newService(A,B,null); } //�1⁄2ӿڣ���ɦ����ɾ���Ͻӿڣ���Ҫ�����ݡ� 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��Դ�ͻᱻ��ռ�ţ��������˾�û�а취���x���������**��Դ�˷�**�� ![](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-time����retry������ ������ת�˵���Ҫ�ĵ��������񣬻���Ҫ����**ǩ����ǩ**��**����**�ȡ�֮ǰд��һƪ��ǩ��ǩ�ģ�����Ȥ�����ѿ��Կ�һ�1� [����Ա�ر�����ǩ��ǩ](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ס��> - �ݵȣ�idempotent��idempotence����һ����ѧ��������ѧ��������ڳ��������С�> - �ڱ�����.һ���ݵȲ������ص�������������ִ����������Ӱ������һ��ִ�е�Ӱ����ͬ���ݵȺ��������ݵȷ�������ָ����ʹ����ͬ�����ظ�ִ�У����ܻ�����ͬ�����ĺ����� һ��**�ݵȼ����**���弓��: - ��ѯ���� - Ψһ���� - token���ƣ���ֹ�ظ��ύ - ���ݿ���deleteɾ������ - �ֹ��� - ������ - Redis��zookeeper �ֲ�ʽ������ǰ����������������Redis�ֲ�ʽ���� - ״̬���ݵ� ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a65c2e6c0c0840a982704a0982467771~tplv-k3u1fbpfcp-watermark.image) ### 14. ���߳������£��������԰�ȫ���� ��**�߲���**�����£�HashMap���ܻ�������ѭ������Ϊ���Ƿ����԰�ȫ�ģ����Կ���ʹ��ConcurrentHashMap�� ��������Ҳ��������κ�ߣ���Ҫ�������־���һ��new HashMap();> - Hashmap��Arraylist��LinkedList��TreeMap�ȶ������Բ���ȫ�ģ�> - Vector��Hashtable��ConcurrentHashMap�ȶ������԰�ȫ�� ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2477f57cad1d4b8ab49a5d4d9ddce6cb~tplv-k3u1fbpfcp-watermark.image) ### 15.�����ӳ����⿼�� �Ȳ��룬���ž�ȥ��ѯ,���������߼��Ƚϳ�������**����**���������ġ�һ�����ݿⶼ�������⣬�ӿ��ġ�д���Ļ���д���⣬��һ���Ƕ��ӿ⡣�������������ӳ٣����ܿ��ܳ����������ɹ��x���������ѯ������������ ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/72f32d1f00b94498adf139620f2decab~tplv-k3u1fbpfcp-watermark.image) - ��������Ҫҵ������Ҫ�����Ƿ�ǿ�ƶ����⣬�������޸����Ʒ����� - �����أ���Щҵ�񳡾��ǿ��Խ���������΢�ӳ�һ���ģ���������κ�߻���Ҫ�аɡ� - д���������ݿ��Ĵ��룬�����Ƿ����������ӳ����⡣ ### 16.ʹ�û�����ɦ�򣬿��Ǹ�DB��һ���ԣ����У����洩͸������ѩ���ͻ������ ͨ�׵�˵������ʹ�û�������Ϊ��**���ÿ죬�ӿں�ɦС**�������أ��õ����棬����Ҫ**ע�⻺�������ݿ���һ����**���⡣ͬɦ������Ҫ���ܻ��洩͸������ѩ���ͻ��������������⡣> - ����ѩ����ָ���������ݴ�����������ɦ�䣬����ѯ�������޴����������ݿ�ѹ����������down����> - ���洩͸��ָ��ѯһ��һ�������ڵ����ݣ����ڻ����Dz�����ɦ��Ҫ�����ݿ���ѯ���鲻����������д�뻺�棬�皿����������ڵ�����ÿ��������Ҫ�����ݿ�ȥ��ѯ�����������ݿ�����ѹ����> - �������ָ�ȵ�key��ij��ɦ�������ڵ�ɦ�򣬶�ǡ��������ɦ����������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円246円202円344円275円225円350円256円276円350円256円241円344円270円200円344円270円252円345円256円211円345円205円250円345円257円271円345円244円226円347円232円204円346円216円245円345円217円243円357円274円237円345円212円240円347円255円276円351円252円214円347円255円276円344円272円206円350円247円243円344円270円200円344円270円213円.md" "b/345円267円245円344円275円234円346円200円273円347円273円223円/345円246円202円344円275円225円350円256円276円350円256円241円344円270円200円344円270円252円345円256円211円345円205円250円345円257円271円345円244円226円347円232円204円346円216円245円345円217円243円357円274円237円345円212円240円347円255円276円351円252円214円347円255円276円344円272円206円350円247円243円344円270円200円344円270円213円.md" new file mode 100644 index 0000000..fe450a6 --- /dev/null +++ "b/345円267円245円344円275円234円346円200円273円347円273円223円/345円246円202円344円275円225円350円256円276円350円256円241円344円270円200円344円270円252円345円256円211円345円205円250円345円257円271円345円244円226円347円232円204円346円216円245円345円217円243円357円274円237円345円212円240円347円255円276円351円252円214円347円255円276円344円272円206円350円247円243円344円270円200円344円270円213円.md" @@ -0,0 +1,442 @@ + +## 前言 +我们在求职面试中,经常会被问到,如何设计一个安全对外的接口呢? 其实可以回答这一点,加签和验签,这将让你的接口更加有安全。接下来,本文将和大家一起来学习加签和验签。从理论到实战,加油哦~ + +- 密码学相关概念 +- 加签验签概念 +- 为什么需要加签、验签 +- 加密算法简介 +- 加签验签相关API +- 加签验签代码实现 +- 公众号:捡田螺的小男孩 + +本文已经收录到个人github,文章有用的话,可以给个star呀: +> https://github.com/whx123/JavaHome + +## 密码学相关概念 + +### 明文、密文、密钥、加密、解密 +- 明文:指没有经过加密的信息/数据。 +- 密文:明文被加密算法加密之后,会变成密文,以确保数据安全。 +- 密钥:是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥。 +- 加密:将明文变成密文的过程。 +- 解密:将密文还原为明文的过程。 + +### 对称加密、非对称加密 + +- 对称加密:加密和解密使用相同密钥的加密算法。 +![image](https://upload-images.jianshu.io/upload_images/7175534-2bdf41502e576262?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 非对称加密:非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。 + +![image](https://upload-images.jianshu.io/upload_images/7175534-a18286469fc802bf?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +### 什么是公钥私钥? +- 公钥与私钥是成对存在的密钥,如果用公钥对数据进行加密,只有用对应的私钥才能解密。 +- 其实,公钥就是公开的秘钥,私钥就是要你私自保存好的秘钥。 +- 非对称加密算法需要有一对公私钥~ + +> 假设你有一个文件,你用字母a加密,只有字母b才能解密;或者你用b加密,只有a才能解密,那么a和b就是一对公私钥。如果密钥a公开,密钥b你就要私自保存好啦,这时候密钥a就是公钥,密钥b就是私钥。相反,如果b公开,a就要保存好,这时候呢,秘钥b就是公钥,秘钥a就是私钥。 + + +## 加签验签概念 + +- **加签**:用Hash函数把原始报文生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名。通常来说呢,请求方会把**数字签名和报文原文**一并发送给接收方。 + ![image](https://upload-images.jianshu.io/upload_images/7175534-bd416aa92697ed8e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- **验签**:接收方拿到原始报文和数字签名后,用**同一个Hash函数**从报文中生成摘要A。另外,用对方提供的公钥对数字签名进行解密,得到摘要B,对比A和B是否相同,就可以得知报文有没有被篡改过。 + +![image](https://upload-images.jianshu.io/upload_images/7175534-450a46c08e2fb43f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +## 为什么需要加签验签 + +上小节中,加签和验签我们已经知道概念啦,那么,为什么需要加签和验签呢?有些朋友可能觉得,我们不是用**公钥加密,私钥解密**就好了嘛? + +接下来呢,举个demo吧。 + +> 假设现在有A公司,要接入C公司的转账系统。在一开始呢,C公司把自己的公钥寄给A公司,自己收藏好私钥。A公司这边的商户,发起转账时,A公司先用C公司的公钥,对请求报文加密,加密报文到达C公司的转账系统时,C公司就用自己的私钥把报文揭开。假设在加密的报文在传输过程中,被中间人Actor获取了,他也郁闷,因为他没有私钥,看着天鹅肉,又吃不了。本来想修改报文,给自己账号转一个亿的,哈哈。这个实现方式看起来是天衣无缝,稳得一匹的。 + +![image](https://upload-images.jianshu.io/upload_images/7175534-2032c2ce1dd4b55a?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +但是呢,如果一开始,C公司把公钥发给公司A的时候,就被中间人Actor获取到呢,酱紫就出问题了。 +> 中间人Actor截取了C的公钥,他把自己的公钥发给了A公司,A误以为这就是C公司的公钥。A在发起转账时,用Actor的公钥,对请求报文加密,加密报文到在传输过程,Actor又截取了,这时候,他用自己的私钥解密,然后修改了报文(给自己转一个亿),再用C的公钥加密,发给C公司,C公司收到报文后,继续用自己的私钥解密。最后是不是A公司的转账账户损失了一个亿呢~ + +![image](https://upload-images.jianshu.io/upload_images/7175534-d405e869531553e0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +C公司是怎么区分报文是不是来自A呢,还是被中间人修改过呢?为了表明身份和报文真实性,这就需要**加签验签**啦! +> A公司把自己的公钥也发送给C公司,私钥自己保留着。在发起转账时,先用自己的私钥对请求报文加签,于是得到自己的数字签名。再把数字签名和请求报文一起发送给C公司。C公司收到报文后,拿A的公钥进行验签,如果原始报文和数字签名的摘要内容不一致,那就是报文被篡改啦~ + + +![image](https://upload-images.jianshu.io/upload_images/7175534-6cc81df7a641afd7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +有些朋友可能有疑问,假设A在发自己的公钥给C公司的时候,也被中间人Actor截取了呢。嗯嗯,我们来模拟一波Actor又截取了公钥,看看Actor能干出什么事情来~哈哈 + +> 假设Actor截取到A的公钥后,随后也截取了到A发往C的报文。他截取到报文后,第一件想做的事肯定是修改报文内容。但是如果单单修改原始报文是不可以的,因为发过去C公司肯定验签不过啦。但是呢,数字签名似乎解不开,因为消息摘要算法(hash算法)无法逆向解开的,只起验证的作用呢.... + +所以呢,公钥与私钥是用来加密与加密的,**加签与验签是用来证明身份**,以免被篡改的。 + +## 常见加密相关算法简介 + +- 消息摘要算法 +- 对称加密算法 +- 非对称加密算法 +- 国密算法 + +### 消息摘要算法: +- 相同的明文数据经过相同的消息摘要算法会得到相同的密文结果值。 +- 数据经过消息摘要算法处理,得到的摘要结果值,是无法还原为处理前的数据的。 +- 数据摘要算法也被称为哈希(Hash)算法或散列算法。 +- 消息摘要算法一般用于签名验签。 + +消息摘要算法主要分三类:MD(Message Digest,消息摘要算法)、SHA(Secure Hash Algorithm,安全散列算法)和MAC(Message Authentication Code,消息认证码算法)。 + + + +![image](https://upload-images.jianshu.io/upload_images/7175534-6f182b2b27d1527b?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +#### MD家族算法 +MD(Message Digest,消息摘要算法)家族,包括MD2,MD4,MD5。 +- MD2,MD4,MD5 计算的结果都是是一个128位(即16字节)的散列值,用于确保信息传输完整一致。 +- MD2的算法较慢但相对安全,MD4速度很快,但安全性下降,MD5则比MD4更安全、速度更快。 +- MD5被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等。 +- MD5,可以被破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞攻击,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。 + +举个例子,看看如何获取字符串的MD5值吧: +``` +public class MD5Test { + + public static void main(String[] args) throws UnsupportedEncodingException { + String s = "123"; + byte[] result = getMD5Bytes(s.getBytes()); + StringBuilder stringBuilder = new StringBuilder(); + for (byte temp : result) { + if (temp>= 0 && temp < 16) { + stringBuilder.append("0"); + } + stringBuilder.append(Integer.toHexString(temp & 0xff)); + } + System.out.println(s + ",MD5加密后:" + stringBuilder.toString()); + } + + private static byte[] getMD5Bytes(byte[] content) { + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + return md5.digest(content); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} + + +``` +运行结果: + +``` +123,MD5加密后:202cb962ac59075b964b07152d234b70 +``` +#### ShA家族算法 + +SHA(Secure Hash Algorithm,安全散列算法),包括SHA-0、SHA-1、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384等)、SHA-3。它是在MD算法基础上实现的,与MD算法区别在于**摘要长度**,SHA 算法的摘要**长度更长,安全性更高**。 +>- SHA-0发布之后很快就被NSA撤回,因为含有会降低密码安全性的错误,它是SHA-1的前身。 +>- SHA-1在许多安全协议中广为使用,包括TLS、GnuPG、SSH、S/MIME和IPsec,是MD5的后继者。 +>- SHA-2包括SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。它的算法跟SHA-1基本上相似,目前还没有出现明显弱点。 +>- SHA-3是2015年正式发布,由于对**MD5出现成功的破解**,以及对SHA-0和SHA-1出现理论上破解的方法,SHA-3应运而生。它与之前算法不同的是,它是可替换的加密散列算法。 + + +SHA-1、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384)等算法是比较常用的,我们来看看跟MD5的对比吧 + +![image.png](https://upload-images.jianshu.io/upload_images/7175534-f6d56b58adaab502.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + + +#### MAC算法家族 +MAC算法 MAC(Message Authentication Code,消息认证码算法),是带密钥的Hash函数。输入密钥和消息,输出一个消息摘要。 +它集合了MD和SHA两大系列消息摘要算法。 +- MD 系列算法: HmacMD2、HmacMD4 和 HmacMD5 ; +- SHA 系列算法:HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384 和 HmacSHA512 。 + +### 对称加密算法 +加密和解密使用**相同密钥**的加密算法就是对称加密算法。常见的对称加密算法有AES、3DES、DES、RC5、RC6等。 + +![image](https://upload-images.jianshu.io/upload_images/7175534-615dd9ef60448863?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +#### DES + +数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法。 +DES算法的入口参数有三个:Key、Data、Mode。 +- Key: 7个字节共56位,是DES算法的工作密钥; +- Data: 8个字节64位,是要被加密或被解密的数据; +- Mode: 加密或解密。 + +#### 3DES +三重数据加密算法(英语:Triple Data Encryption Algorithm,又称3DES(Triple DES),是一种对称密钥加密块密码,相当于是对每个数据块应用三次数据加密标准(DES)算法。 + + +#### AES +AES,高级加密标准(英语:Advanced Encryption Standard),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。 +- 采用对称分组密码体制,密钥长度为 128 位、 192 位、256 位,分组长度128位 +- 相对于DES ,AES具有更好的 安全性、效率 和 灵活性。 + +### 非对称加密算法 +非对称加密算法需要两个密钥:公钥和私钥。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有用对应的私钥才能解密。主要的非对称加密算法有:RSA、Elgamal、DSA、D-H、ECC。 + +![image](https://upload-images.jianshu.io/upload_images/7175534-4f93828d0d31ad52?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +#### RSA算法 +- RSA加密算法是一种非对称加密算法,广泛应用于加密和数字签名 +- RSA算法原理:两个大素数的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。 +- RSA是被研究得最广泛的公钥算法,从提出到现在,经历了各种攻击的考验,普遍认为是目前最优秀的公钥方案之一。 + +#### DSA +- DSA(Digital Signature Algorithm,数字签名算法),也是一种非对称加密算法。 +- DSA和RSA区别在,DSA仅用于数字签名,不能用于数据加密解密。其安全性和RSA相当,但其性能要比RSA好。 + +#### ECC 算法 +- ECC(Elliptic Curves Cryptography,椭圆曲线密码编码学),基于椭圆曲线加密。 +- Ecc主要优势是,在某些情况下,它比其他的方法使用更小的密钥,比如RSA加密算法,提供相当的或更高等级的安全级别。 +- 它的一个缺点是,加密和解密操作的实现比其他机制时间长 (相比RSA算法,该算法对CPU 消耗严重)。 + + +### 国密算法 +国密即国家密码局认定的国产密码算法。为了保障商用密码的安全性,国家商用密码管理办公室制定了一系列密码标准,即SM1,SM2,SM3,SM4等国密算法。 + +![image](https://upload-images.jianshu.io/upload_images/7175534-c6c88adeddfdeedb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +#### SM1 +- SM1,为对称加密算法,加密强度为128位,基于硬件实现。 +- SM1的加密强度和性能,与AES相当。 + +#### SM2 +- SM2主要包括三部分:签名算法、密钥交换算法、加密算法 +- SM2用于替换RSA加密算法,基于ECC,效率较低。 + +#### SM3 +- SM3,即国产消息摘要算法。 +- 适用于商用密码应用中的数字签名和验证,消息认证码的生成与验证以及随机数的生成。 + +#### SM4 +- SM4是一个分组算法,用于无线局域网产品。 +- 该算法的分组长度为128比特,密钥长度为128比特。 +- 加密算法与密钥扩展算法都采用32轮非线性迭代结构。 +- 解密算法与加密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。 +- 它的功能类似国际算法的DES。 + + +## 加签验签相关Java的API +这个小节先介绍一下加签验签需要用到的API吧~ + +![image](https://upload-images.jianshu.io/upload_images/7175534-3406a3ec99c0c97d?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +### 加签相关API +``` +- java.security.Signature.getInstance(String algorithm); //根据对应算法,初始化签名对象 +- KeyFactory.getInstance(String algorithm);// 根据对应算法,生成KeyFactory对象 +- KeyFactory.generatePrivate(KeySpec keySpec); //生成私钥 +- java.security.Signature.initSign(PrivateKey privateKey) //由私钥,初始化加签对象 +- java.security.Signature.update(byte[] data) //把原始报文更新到加签对象 +- java.security.Signature.sign();//加签 +``` + +**Signature.getInstance(String algorithm);** +- 根据对应算法,初始化签名对象 +- algorithm参数可以取SHA256WithRSA或者MD5WithRSA等参数,SHA256WithRSA表示生成摘要用的是SHA256算法,签名加签用的是RSA算法 + +**KeyFactory.getInstance(String algorithm);** +- 根据对应算法,生成KeyFactory对象,比如你的公私钥用的是RSA算法,那么就传入RSA + +**KeyFactory.generatePrivate(KeySpec keySpec)** +- 生成私钥,加签用的是私钥哈,所以需要通过KeyFactory先构造一个私钥对象。 + +**Signature.initSign(PrivateKey privateKey)** +- 加签用的是私钥,所以传入私钥,初始化加签对象 + +**Signature.update(byte[] data)** +- 把原始报文更新到加签对象 + +**java.security.Signature.sign();** +- 进行加签操作 + +### 验签相关API +``` +- java.security.Signature.getInstance(String algorithm); //根据对应算法,初始化签名对象 +- KeyFactory.getInstance(String algorithm);// 根据对应算法,生成KeyFactory对象 +- KeyFactory.generatePublic(KeySpec keySpec); //生成公钥 +- java.security.Signature.initVerify(publicKey); //由公钥,初始化验签对象 +- java.security.Signature.update(byte[] data) //把原始报文更新到验签对象 +- java.security.Signature.verify(byte[] signature);//验签 +``` + +**Signature.getInstance(String algorithm)** +- 根据对应算法,初始化签名对象,注意验签和加签是需要用相同的algorithm算法参数哦~ + +**KeyFactory.getInstance(String algorithm);** +- 根据对应算法,生成KeyFactory对象 + +**KeyFactory.generatePublic(KeySpec keySpec);** +- 生成公钥,验签用的是公钥,通过KeyFactory先构造一个公钥对象 + +**Signature.initVerify(publicKey); ** +- 公钥验签,所以传入公钥对象参数,初始化验签对象 + + +**Signature.update(byte[] data)** +- 把原始报文更新到加签对象 + +**Signature.verify(byte[] signature);** +- 进行验签操作 + + +## 加签验签代码实现 + +前几个小节讨论完概念,是时候上代码实战了,如下: +``` +package pattern; + +import sun.misc.BASE64Decoder; +import sun.misc.BASE64Encoder; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +/** + * 加签验签demo + * @Author 捡田螺的小男孩 + */ +public class SignatureTest { + //公钥字符串 + private static final String PUBLIC_KEY_STR = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaJzVjC5K6kbS2YE2fiDs6H8pB\n" + + "JFDGEYqqJJC9I3E0Ebr5FsofdImV5eWdBSeADwcR9ppNbpORdZmcX6SipogKx9PX\n" + + "5aAO4GPesroVeOs91xrLEGt/arteW8iSD+ZaGDUVV3+wcEdci/eCvFlc5PUuZJou\n" + + "M2XZaDK4Fg2IRTfDXQIDAQAB"; + //私钥字符串 + private static final String PRIVATE_KEY_STR = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANonNWMLkrqRtLZg\n" + + "TZ+IOzofykEkUMYRiqokkL0jcTQRuvkWyh90iZXl5Z0FJ4APBxH2mk1uk5F1mZxf\n" + + "pKKmiArH09floA7gY96yuhV46z3XGssQa39qu15byJIP5loYNRVXf7BwR1yL94K8\n" + + "WVzk9S5kmi4zZdloMrgWDYhFN8NdAgMBAAECgYA9bz1Bn0i68b2KfqRdgOfs/nbe\n" + + "0XNN1DLQp2t7WDfRCg01iI1zPkZgyFVZWtI85f5/uIrLs5ArLosL1oNuqqc0nNne\n" + + "CvJK+ZxvA98Hx3ZqYTzDnleR054YhofL5awbhSciYVic204DOG1rhSsYWMqtX7J7\n" + + "3geoWL7TYdMfYXcCAQJBAPMMKsz6ZJh98EeQ1tDG5gpAGWFQkYNrxZDelP/LjeO0\n" + + "TP3XkQnIpcaZoCs7V/rRGRGMWwQ2BUdc/01in89ZZ5ECQQDlx2oBc1CtOAm2UAhN\n" + + "1xWrPkZWENQ53wTrwXO4qbTGDfBKon0AehLlGCSqxQ71aufLkNO7ZlX0IHTAlnk1\n" + + "TvENAkAGSEQ69CXxgx/Y2beTwfBkR2/gghKg0QJUUkyLqBlMz3ZGAXJwTE1sqr/n\n" + + "HiuSAiGhwH0ByNuuEotO1sPGukrhAkAMK26a2w+nzPL+u+hkrwKPykGRZ1zGH+Cz\n" + + "19AYNKzFXJGgclCqiMydY5T1knBDYUEbj/UW1Mmyn1FvrciHoUG1AkAEMEIuDauz\n" + + "JabEAU08YmZw6OoDGsukRWaPfjOEiVhH88p00veM1R37nwhoDMGyEGXVeVzNPvk7\n" + + "cELg28MSRzCK"; + + + public static void main(String[] args) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidKeySpecException { + //原始报文 + String plain = "欢迎大家关注我的公众号,捡田螺的小男孩"; + //加签 + byte[] signatureByte = sign(plain); + System.out.println("原始报文是:" + plain); + System.out.println("加签结果:"); + System.out.println(new BASE64Encoder().encode(signatureByte)); + //验签 + boolean verifyResult = verify(plain, signatureByte); + System.out.println("验签结果:" + verifyResult); + } + + /** + * 加签方法 + * @param plain + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws UnsupportedEncodingException + * @throws SignatureException + */ + private static byte[] sign(String plain) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, SignatureException { + //根据对应算法,获取签名对象实例 + Signature signature = Signature.getInstance("SHA256WithRSA"); + //获取私钥,加签用的是私钥,私钥一般是在配置文件里面读的,这里为了演示方便,根据私钥字符串生成私钥对象 + PrivateKey privateKey = getPriveteKey(PRIVATE_KEY_STR); + //初始化签名对象 + signature.initSign(privateKey); + //把原始报文更新到对象 + signature.update(plain.getBytes("UTF-8")); + //加签 + return signature.sign(); + } + + /** + * 验签方法 + * @param plain + * @param signatureByte + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws IOException + * @throws SignatureException + * @throws InvalidKeySpecException + */ + private static boolean verify(String plain, byte[] signatureByte) throws NoSuchAlgorithmException, InvalidKeyException, IOException, SignatureException, InvalidKeySpecException { + //获取公钥 + PublicKey publicKey = getPublicKey(PUBLIC_KEY_STR); + //根据对应算法,获取签名对象实例 + Signature signature = Signature.getInstance("SHA256WithRSA"); + //初始化签名对象 + signature.initVerify(publicKey); + //把原始报文更新到签名对象 + signature.update(plain.getBytes("UTF-8")); + //进行验签 + return signature.verify(signatureByte); + } + + private static PublicKey getPublicKey(String publicKeyStr) throws InvalidKeySpecException, IOException { + PublicKey publicKey = null; + try { + java.security.spec.X509EncodedKeySpec bobPubKeySpec = new java.security.spec.X509EncodedKeySpec( + new BASE64Decoder().decodeBuffer(publicKeyStr)); + // RSA对称加密算法 + java.security.KeyFactory keyFactory; + keyFactory = java.security.KeyFactory.getInstance("RSA"); + // 生成公钥对象 + publicKey = keyFactory.generatePublic(bobPubKeySpec); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return publicKey; + } + + private static PrivateKey getPriveteKey(String privateKeyStr) { + PrivateKey privateKey = null; + PKCS8EncodedKeySpec priPKCS8; + try { + priPKCS8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(privateKeyStr)); + KeyFactory keyf = KeyFactory.getInstance("RSA"); + privateKey = keyf.generatePrivate(priPKCS8); + } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { + e.printStackTrace(); + } + return privateKey; + } +} +``` +**运行结果:** + +``` +原始报文是:欢迎大家关注我的公众号,捡田螺的小男孩 +加签结果: +Oz15/aybGe42eGHbc+iMoSYHSCc8tfRskTVjjGSTPD4HjadL0CC5JUWNUW0WxHjUb4MvxWo2oeWE +Qw0+m61d+JgBMto/TWcVDcgwL/AbObsbWdQ6E/fVRqG13clkE8MyKsjt9Z7tcbwpycYTv0rUR4co +rndAVfBdtv5KeV+OXqM= +验签结果:true +``` + +## 参考与感谢 +- [维基百科](https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5 "维基百科") +- [百度百科](https://baike.baidu.com/ "百度百科") +- [常用消息摘要算法简介](https://cloud.tencent.com/developer/article/1584742 "常用消息摘要算法简介") +- [浅谈常见的七种加密算法及实现](https://juejin.im/post/5b48b0d7e51d4519962ea383#heading-12 "浅谈常见的七种加密算法及实现") +- [【易错概念】国密算法SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、ZUC](https://www.jianshu.com/p/8c3657a1769f "【易错概念】国密算法SM1(SCB2)、SM2、SM3、SM4、SM7、SM9、ZUC") + +## 微信公众号 + +![image](https://upload-images.jianshu.io/upload_images/7175534-3a420deafc86dfbd?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 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```�Ƿ��ǻ�Ա�� + +**����������** һЩС����������ɻ�֣��Ȳ����û���Ϣ�������û���1⁄4��Ȼ����ȥ�ж��Ƿ��ǻ�Ա: +``` + + +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�߼����������Ե���˳�����ó�������Ч + + +����ҵ�������������������û��ǻ�Ա�����ҵ�һ�ε�1⁄2ɦ����Ҫ��һ��֪ͨ�Ķ��š�����û�о���˼���������ܿ���ֱ������д�l� + +``` +if(isUserVip && isFirstLogin){ + sendMsgNotify(); +} +``` + +�����ܹ���5������������isUserVipͨ������3��������isFirstLoginͨ������1�������� ��ô���Θ��룬isUserVipִ�еĴ���Ϊ5�Σ�isFirstLoginִ�еĴ���Ҳ��3�Σ����£� + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b30c2899d10f4258bf1fc94a9ebf324c~tplv-k3u1fbpfcp-watermark.image) + + +��������һ��isUserVip��isFirstLogin��˳���أ� +``` +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��򣬾ܾ�����Ҫ�Ķ��� + + +�������ı������������߼��жϣ�һ���ᱻ��ֵ������˵��ֻ��һ���ַ���������ֱ�ӳ�ʼ���ַ����Ϳ����x�û�б�Ҫ����Ҫnew String(). + + +������ +``` +String s = new String ("��ӭ��ע���ںţ������ݵ�С�к�"); +``` + +������ + +``` +String s= "��ӭ��ע���ںţ������ݵ�С�к� ��; +``` + + +### 5. ��ʼ������ɦ��ָ������ + + +�����L·����ֲᣬҲ��ȷ�ᵽ�����㣺 +![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); + ����ΪhashMap��������2�����йأ����Կ���ȡ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. ��ӡ��־��ɦ�򣬶���û�и���Object��toString�ķ�����ֱ�Ӱ�������ӡ�����l� + + +�����ڴ�ӡ��־��ɦ�򣬾����뿴��һ��������������request��jô�����Ǻ�����������������Щ���룺 + + +``` +publick Response dealWithRequest(Request request){ + log.info("���������ǣ�".request.toString) +} +``` +��ӡ�������£� + +``` +���������ǣ�local.Request@49476842 +``` + +������Ϊ������toString������Ĭ�ε�ɻ���ǡ�����@ɢ�������޷���ʮ�����ơ��������㿴�ɣ������Ӵ�ӡ��־��ûɶ��˼�����㶼��֪����ӡ����jô���ݡ� + +����һ������(������Ϊ���εĶ��󣩣�**��������д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 getUserInfo��String name,String age,String sex,String mobile){ + // do something ... +} +``` + +����������Ҫ�ഫһ��version�����������������Ĺ��з���������dubbo���ֶ����ṩ�ĽӿڵĻ�����ô���Ľӿ��Dz�����Ҫ�����κ汾���� + +``` +public void getUserInfo��String name,String age,String sex,String mobile){ + // do something ... +} + +/** + * �1⁄2ӿڵ����� + */ +public void getNewUserInfo��String name,String age,String sex,String mobile��String version){ + // do something ... +} +``` + +�����أ�һ��һ����IJ�����һ�㲻�˹�����IJ����б����������������ţ����ҽӿ�����ɦ�����ܻ�Ҫ�������κ汾���ݡ���������ɻ���Ƕ���ô���أ������ø�DTO������װһ����Щ������~���£� + +``` +public void getUserInfo��UserInfoParamDTO userInfoParamDTO){ + // do something ... +} + +class UserInfoParamDTO{ + private String name; + private String age; + private String sex; + private String mobile; +} +``` +�ø�DTO������װһ�£���ʹ�����в����䶯��Ҳ���Բ��ö������ӿ��x��ô��ܸܵġ� + +### 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```ɻ���ļ���д���ܣ���û��jô�����ġ������أ�����ʹ�û�����```BufferedReader```��```BufferedWriter```��```BufferedInputStream```��```BufferedOutputStream```�ȣ�����IO���������߶�дЧ�ʡ� +> �����Dz������������ȡ��һ���ֽڻ����ַ��ģ��ͻ�ֱ�����������l��������������ȡ��һ���ֽڻ����ַ�ɦ���Ȳ�������ǵȴفخم��������������������һ���������� + +**������** + + +``` +/** + * ���ںţ������ݵ�С�к� + * @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 insertUserVip��String 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 insertUserVip��UserInfo userInfo��{ + //�����û�vip��ˮ + insertUserVipFlow(userInfo); + .... +} +``` + + +### 11. ��ҪΪ�˷��㣬ֱ���ڴ�����ʹ��0,1��ħ��ֵ��Ӧ��Ҫ��enumö�ٴ��档 + + +**������** + +``` +if("0".equals(userInfo.getVipFlag)){ + //�ǻ�Ա����ʾȥ��ͨ��Ա + tipOpenVip(userInfo); +}else if("1".equals(userInfo.getVipFlag)){ + //��Ա����ѫ�·��� + addMedal��userInfo��; +} +``` + +**������** + + +``` +if(UserVipEnum.NOT_VIP.getCode.equals(userInfo.getVipFlag)){ + //�ǻ�Ա����ʾȥ��ͨ��Ա + tipOpenVip(userInfo); +}else if(UserVipEnum.VIP.getCode.equals(userInfo.getVipFlag)){ + //��Ա����ѫ�·��� + addMedal��userInfo��; +} + +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����������Ǵ��뿪�������У�һ��Ҫ�Կ�ָ�뱣�������������� + +��Ҫ���弓����ָ�����⣺ + +- ��װ���͵L·�ָ������ +- �������õL·�ָ������ +- Equals�������ߵL·�ָ������ +- 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("�쳣�x���ε����С���翴����",e); + } +} +``` + +### 15. ����Lambda����ʽ�滻�ڲ������࣬ʹ���������� + +JDK8������������-Lambda����ʽ��Lambda����ʽ�����������ڲ����������ţ������ڴ������������У����Dz���invokeDynamicָ��ɻ�֣������������ڲ��࣬Ч��Ҳ���� + + +**������** +``` + 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. ֪ͨ�ࣨ�緢�ʼ����ж��ţ��Ĵ��룬�����첽���� + +����ҵ��������������Ҫ���û���1⁄2ɦ�����Ӹ�����֪ͨ���ķ�˿�� �������뵽��ɻ���������£� + + +![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ed6daf4932b34452b1aacdb98aa2dcc8~tplv-k3u1fbpfcp-watermark.image) + +�����ṩsendMsgNotify������εͳ���x����ߵ���sendMsgNotifyʧ���x���ô�û���1⁄2��ʧ���l����� +һ��֪ͨ���ܵ����˵�1⁄2�����̲����ã����Եļ���֥�鶪���ϡ���ô��û���������Ƽ��õķ����أ��еģ��������Žӿڲ����쳣�����������߳��첽�������£� + + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58a956dc04c6456395b39d02afe3187e~tplv-k3u1fbpfcp-watermark.image) + +���x�����֪ͨ���Ȳ��Ƿ���Ҫ���ɽ����Ľӿ�ɦ��Ӧ�þ������������Ƿ���Ӱ����Ҫ���̣�˼����ô�������á� + +### 17. ����Java����ɦ������YYYY��ʽ���õ����⡣ + +�ճ������У����Ǿ�����Ҫ�������ڡ�����Ҫ��ɦ���ڸ�ʽ����ɦ���������Ǵ�д```YYYY```�L·ӡ� + + +``` +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日 +``` + +> Ϊjô������2019��12��31�ţ���ת��һ� ̧�ʽ���ͱ�����2020��12��31���x���ΪYYYY�ǻ��������������ģ���ָ���������������ڵ����ݣ�һ�ܴ����տ�ʼ����������������ֻҪ���ܿ��꣬��ô��һ�ܾ�����һ�����l���ȷ������ʹ��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���η��������ᱻ�̳��x����������з�������final���l�Java���������һ����������е�final������������Java����Ч�ʡ� + + +### 19. static��̬������Ҫ����springɻ�������������ܻᵼ�3�ʼ������ + +֮ǰ������L·�����ƵĴ��롣��̬����������spring������bean�� +``` + private static SmsService smsService = SpringContextUtils.getBean(SmsService.class); +``` + +������̬��smsService�п��ܻ�ȡ�����ģ���Ϊ������˳������ȷ���ģ������εĴ��룬��̬��smsService��ʼ��ǿ������spring������ɻ���l���ȷ��д���������������£� + + +``` + 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�޷���ȷ���������Ϊjô�������ᵼ�3⁄4�ȷ��ȱʧ�ġ����x��������㣬һ�㶼����BigDecimal ���� + + +``` +System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2))); +//output: +0.3000000000000000166533453693773481063544750213623046875 +``` +��ɻ��ʹ�� BigDecimal ��ʾ�ͼ��㸡����������ʹ���ַ����Ĺ��췽������ʼ�� BigDecimal�����ң���Ҫ��עBigDecimal�ļ�λС���㣬���а�������ģʽ�� + +### 25. ע��Arrays.asList�ļ����� + +- **�������Ͳ�����Ϊ Arrays.asList�����IJ����������ᱻ����һ��������** + +``` +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��Դ�ͻᱻ��ռ�ţ��������˾�û�а취���x�����������Դ�˷ѡ� + + +����ʹ����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}; +``` + +����ƫ�����ر�����ɦ�򣬲�ѯЧ�ʾͱ��õ��¡����������ַ�ʽ�Ż��� + +``` +//����һ �������Θβ�ѯ��������1⁄4(ƫ����) +select id��name from user_info where id>10000 limit #{pageSize}. + +//��������order by + ���� +select id��name 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�ӿڣ�ԭ����ֻ����A��B����������������һ������C���Ϳ��Կ����������� + +``` +//�Ͻӿ� +void oldService(A,B);{ + //�����1⁄2ӿڣ�����null����C + newService(A,B,null); +} + +//�1⁄2ӿڣ���ɦ����ɾ���Ͻӿڣ���Ҫ�����ݡ� +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() ǿת�L·� + +``` +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�ġ����ԣ�Ϊjô��ѯDB���ݿ⣬һ�㶼���������� + +��ȡ�ļ��Ļ���һ���ļ�����̫�󣬲�ʹ��Files.readAllLines()��Ϊjô�أ���Ϊ����ֱ�Ӱ��ļ��������ڴ��ģ�Ԥ���2���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. ���õ������ӿڣ���Ҫ�����쳣������ȫ�ԣ���ɦ�����弓���㡣 + +�ճ������У�������Ҫ���õ��������񣬻��߷ֲ�ʽԶ�̷����ĵĻ�����Ҫ���ǣ� + + +- �쳣�������磬�������˵Ľӿڣ������쳣�x���ô���������Ի��ǵ���ʧ�ܣ� +- ��ɦ��û��Ԥ���Է��ӿ�һ�����÷��أ�һ�����ø���ɦ�Ͽ�ɦ�䣬�Ա������Ľӿڣ� +- ���Դ��������Ľӿڵ�ʧ�ܣ��費��Ҫ���ԣ���Ҫվ��ҵ���ϽǶ�˼���������⣩ + +> ����һ�����ӣ���һ��http���������˵ķ�������Ҫ��������connect-time����retry������ + + +### 39 ��Ҫʹ��ѭ���������ϣ�����ʹ��JDK�ṩ�ķ����������� + +> - JDK�ṩԭ��API����������ֱ��ָ�����ε��������������������������ܡ� +> - ��Щ�����ĵײ�����System.arraycopy����ɻ�֣��������ݵ���������Ч�y��ߡ� + +**������** + +``` +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(); + +- Hashmap��Arraylist��LinkedList��TreeMap�ȶ������Բ���ȫ�ģ� +- Vector��Hashtable��ConcurrentHashMap�ȶ������԰�ȫ�� + +### 42. ʹ��spring��������ɦ��ע���弓������δ��Ч�L·� + +�ճ�ҵ�񿪷��У����Ǿ����������򽻵�������ʧЧ��Ҫ�����1⁄4������ + +- �ײ����ݿ����治֧������ +- �ڷ�public���εķ���ʹ�� +- rollbackFor�������ô��� +- ���෽��ֱ�ӵ��� +- �쳣��try...catch���x���������ʧЧ�� + +**������** + + +``` +public class TransactionTest{ + public void A(){ + //����һ������ + //���÷���B (���ص������ã�����ʧЧ��) + B(); + } + + @Transactional + public void B(){ + //�������� + } +} +``` + +**ע�������񷽷������෽��ֱ�ӵ��ã�����ʧЧ** + + +### 43. ʹ��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 : + +![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���� +- �ֹ��� +- ������ +- Redis��zookeeper �ֲ�ʽ������ǰ����������������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. ���Ĺؼ�ҵ�����룬һ�㽨��������־���ݻ����� + +�ؼ�ҵ�������������εأ���Ӧ�����㹻����־���ݻ����� + +> ���磺��ɻ��ת��ҵ����ת�������Ȼ��תʧ���x����sͻ�Ͷ�ߣ�Ȼ���㻹û�д�ӡ����־����������ˮ�����ȵ������£���ȴ���ް취������ + +��ô������ת��ҵ������Ҫ��Щ��־��Ϣ�أ����٣���������ǰ��������Ҫ��ӡ��Ҫ�ɣ��ӿڵ��ú�����Ҫ����һ���쳣�ɣ�ͬɦ��ӡ�쳣������־�ɣ����£� + +``` +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���𣬸澯��ҹ�������������Ų������Ͳ����l� + +### 48. ijЩ�ɱ����أ�������Ƥ���ȵȣ��������û��Ƿ��������ء� + +������Ʒ���˸�����������ʥ���ڵ�ɦ�򣬺���Ƥ��Ϊʥ�������صģ����ڵ�ɦ�򣬺���Ƥ���ȡ� + +����: + +``` +if(duringChristmas){ + img = redPacketChristmasSkin; +}else if(duringSpringFestival){ + img = redSpringFestivalSkin; +} +``` + +��������Ԫ���ڵ�ɦ������ӪС����ͻȻ�����뷨������Ƥ��ɵ������صģ���ɦ�����Dz���Ҫȥ�޸Ĵ����x����·����x���һ��ʼ��ɻ��һ�ź���Ƥ�������ñ�������Ƥ���������û��أ���������Ƥ����ֻ���޸�һ�±����ݾ ͅ��l� + +### 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���ɻ���࣬�ɵ���� ́��룺 + + +``` +//ѫ�1⁄2ӿ� +public interface IMedalService { + void showMedal(); +} + +//�ػ�ѫ�2���ɻ���� +public class GuardMedalServiceImpl implements IMedalService { + @Override + public void showMedal() { + System.out.println("չʾ�ػ�ѫ��"); + } +} +//�α�ѫ�2���ɻ���� +public class GuestMedalServiceImpl implements IMedalService { + @Override + public void showMedal() { + System.out.println("�α�ѫ��"); + } +} +//VIPѫ�2���ɻ���� +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�֣��ֱ���error��warn��info��debug��trace���ճ������У�������Ҫѡ��ǡ������־���𣬲�Ҫ���־��Ǵ�ӡinfo��~ + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c4502943568747e79ac4091b4d2868ea~tplv-k3u1fbpfcp-zoom-1.image) + +- error��������־��ָ�Ƚ����صĴ��󣬶�����ҵ����Ӱ�죬��Ҫ**��ά���ü��ص�**�� +- warn��������־��һ���Ĵ��󣬶�ҵ��Ӱ�첻�󣬵�����Ҫ**������ע**�� +- info����Ϣ��־����1⁄4�Ų������Ĺؼ���Ϣ��������ɦ�䡢�������εȵȣ� +- debug�����ڿ���DEBUG�ģ��ؼ��߼�����������ɦ���ݣ� +- trace������ϸ����Ϣ��һ����Щ��Ϣֻ��1⁄4����־�ļ��С� + + +## 2. ��־Ҫ��ӡ��������Ρ����� + +���Dz�����Ҫ��ӡ�ܶ��ܶ���־��ֻ��Ҫ��ӡ����**���وٴ�λ��������Ч��־**����Ч����־����˦���������� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c59f66bfc09d42ffa8528c16145952b8~tplv-k3u1fbpfcp-zoom-1.image) + +��Щ���õ���**��Ч�ؼ�**����־�أ�����˵������������ɦ�򣬴�ӡ**����**����Ȼ���أ��ڷ������ص�ɦ�򣬾���**��ӡ���Σ�����ֵ**�����εĻ���һ������**userId����bizSeq��Щ�ؼ�**��Ϣ���������£� + +``` +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 + + +``` + +�������ǵ���־��ʽ������ǰɦ�䶼�]�м�1⁄4����**��������ɦ���㶼��֪����**�� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ecfebbe1a0b411e8186d46edaddbd38~tplv-k3u1fbpfcp-zoom-1.image) + + +## 4. ����if...else...������ɦ��ÿ����֧���ж�������ӡ��־ + +��������**if...else...����switch**����������ɦ�������ڷ�֧�����оʹ�ӡ��־�������Ų�����ɦ���Ϳ���ͨ����־��ȷ���������ĸ���֧�������߼���������Ҳ�������Ų������l� + +![](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��Щ�Ƚε͵���־���𣬱���������־�����L·����жϡ� + +������ +``` +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. ����ֱ��ʹ����־εͳ��Log4j��Logback���е� 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()�����������ַ�����1⁄4���Ƕ�ջ��Ϣ��������Ϣ̫��̫�࣬�ַ��������ڵ��ڴ���û�пռ���,���ڴ����x���ô���û��������Ϳ�ס��~ + +## 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��û�д�ӡ����������ѹ����֪������jô���͵��쳣�� + +����2�� +``` +try { + //ҵ�����봦�� +} catch (Exception e) { + // ���� + LOG.error('���ij������쳣��', e.getMessage()); +} +``` + +- ```e.getMessage()```������1⁄4��ϸ�Ķ�ջ�쳣��Ϣ��ֻ����1⁄4��������������Ϣ���������Ų����⡣ + +������ + +``` +try { + //ҵ�����봦�� +} catch (Exception e) { + // ���� + LOG.error('���ij������쳣��', e); +} +``` + +## 11. ��ֹ�����ϻ������� debug + +��ֹ�����ϻ�������debug����һ���dz���Ҫ�� + + +��Ϊһ��εͳ��debug��־���ܶ࣬���Ҹ��ֿ�����Ҳ����ʹ�� debug����־�����Ͽ���debug���ÿ��ܻ��������̣�Ӱ��ҵ��εͳ���������С� + +## 12.��Ҫ��1⁄4���쳣�����׳��쳣 + + +![](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�쳣�ĵط��������ٴ�ӡһ�Ρ� +- ��������־��1⁄4�����߰�װ�����׳�ȥ����Ҫͬɦʹ�ã�����������־�����������˺��Ի��� + + +## 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.log������error����error.log�������Ե�����ӡ��һ���ļ����档 +- ��Ȼ��Ҳ���Ը��ݲ�ͬ��ҵ��ģ�飬��ӡ����ͬ����־�ļ�����������Ų�������������ͳ�Ƶ�ɦ�򣬶����ȽϷ������� + + +## 15. ���Ĺ���ģ�飬������ӡ����������־ + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6f26259cda042edb98400145d208d12~tplv-k3u1fbpfcp-zoom-1.image) + + +- �����ճ������У��������Ļ����߼����ӵĴ��룬����������ϸ��ע�ͣ��Լ�����ϸ����־�� +- ��־Ҫ����ϸ�أ��Զ�һ�£��������ĺ��ij�����һ�������x�ͨ����־���Զ�λ�����ǾͿ������� + + + + + + 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円221円bug345円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円221円bug345円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円221円bug345円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+����һ�ø߶�Ϊ3��B+����s��Դ洢��ǧ�������ݡ������������Ļ���B+��Ҫ���ߣ���ѯ���ܻ��1⁄2��� + +���x�����������ɦ�򣬽����ֿ��ֱ��ֿ��ֱ����м�����**mycat��sharding-jdbc** + + +#### 1.1.3 ��������SQL + +�ճ������У����߼����ܶ಻������SQL������һ��SQL��Ȼ����**6��������**,����̫����Ӱ����ѯ���ܣ��ٱ���һ������Ȼ����**10������**�ȵȡ������ǻή���˲����͸���SQL���ܣ���������һ�㲻����̫�࣬һ�㲻�ܳ��������� + + +### 1.2 ���ݿ��ֶ�ע���� + +���ݿ��ֶ��������ݣ������׳�bug�����磬�����Ի����޸��˱��ṹ������ij���ֶΣ����ǰѽű������������Ƿ����϶��������l� + +#### 1.2.1 �ֶ��Ƿ��ᳬ�� + +�����������ݿ��ֶ��ǣ� + +``` +`name` varchar(255) DEFAULT NOT NULL +``` + +���������������˱���name���ֶγ�����300���Dz�������ɦ����**����**�l�������ҪУ����������ֹ�ֶγ����� + +#### 1.2.2 �ֶ�Ϊ�գ��Ƿ��ᵼ�¿�ָ���� + +�����������ݿ����ֶε�ɦ��,�������ֶ�����Ϊ**not null**�� + +- ���������Σ�����һ��ʹ��0����-1��ΪĬ��ֵ�� +- �����ַ�Ĭ�Ͽ��ַ��� + +�������ݿ��ֶ�����Ϊ```NULL```ֵ�����׵��3�����ָ�룻�������ݿ��ֶ�����Ϊ```NULL```ֵ����Ҫע��**count(������)** ��ʹ�ã����пӡ� + +#### 1.2.3 �ֶ�ȱʧ + +���ǵ��ճ����������������ڲ��Ի������Ա������޸ģ�����������һ�����ֶΣ�����Ҫ��SQL�ű����������������ֶ�ȱʧ������������������ + + +#### 1.2.4 �ֶ������Ƿ�֧�ֱ��� + +����һ�����ֶ���Ҫ֧�ֱ����洢��ʹ��**utf8mb4**�� + +#### 1.2.5 ����ʹ��text��blob�ֶ� + +������Ҫ��һ���ֶδ洢�ļ�������**�洢�ļ���·��**�����������ļ���ȥ��ʹ��textɦ���漰��ѯ����ɦ��ע�ⴴ��**ǰ׺����**�� + +### 1.3 ����ʧЧ�ij��� + +#### 1.3.1 @Transactional �ڷ�public���εķ�����ʧЧ + + +@Transactionalע�⣬���ڷ�public���εķ����ϣ������Dz�����Ч�ġ�spring�����ǽ�����AOP��˼�룬Ҳ��ͨ����̬������ɻ�ֵġ�spring�����Լ��ڵ��ö�̬����֮ǰ���Ѿ��Է�public���������x����Է�public��������������Ч�� + +#### 1.3.2 ���ط���ֱ�ӵ��� +����������� @Transactional����Ҳ����Ч�� +``` +public class TransactionTest{ + public void A(){ + //����һ������ + //���÷���B (���ص������ã�����ʧЧ��) + B(); + } + + @Transactional + public void B(){ + //�������� + } +} +``` + +#### 1.3.3 �쳣��try...catch���x���������ʧЧ�� + + +``` +@Transactional +public void method(){ + try{ + //����һ������ + insertA(); + //����һ������ + updateB(); + }catch(Exception e){ + logger.error("�쳣�������x�������������ʧЧ��",e); + } +} + +``` + +#### 1.3.4 rollbackFor�������ô��� + +SpringĬ���׳���δ����```unchecked```�쳣���̳���RuntimeException ���쳣������Error�Żع������������쳣���ᴥ���ع��������������������׳��������͵��쳣������Ҫָ��```rollbackFor```���ԡ� + +#### 1.3.5 �ײ����ݿ����治֧������ + +MyISAM�洢���治֧��������InnoDb��֧������ + +#### 1.3.6 spring������ҵ���߼�����������һ���߳��� + +ҵ������Ҫ��spring������Դ����ͬһ���߳��У��Ż���spring�����L·��ơ������������룬����mothed�����̣߳��ڲ�ִ�е��������������mothed������spring�����L·��ƣ���һ������Ҫע�⡣������Ϊ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 9��������SQL�������� + +Ҫ������������Ҫѧ��������һ��SQL�ļ��������ν��е�?һ��SQL���������Է�9����������̽�֣� + +- ����һ��id����������RC���뼶�� +- ���϶���id���Ƕ���Ψһ������RC���뼶�� +- ��������id���Ƕ�����Ψһ������RC���뼶�� +- �����ģ�id����û��������RC���뼶�� +- �����壺id����������RR���뼶�� +- ��������id���Ƕ���Ψһ������RR���뼶�� +- �����ߣ�id���Ƕ�����Ψһ������RR���뼶�� +- ���κx�id����û��������RR���뼶�� +- ���Ͼţ�Serializable���뼶�� + + +#### 1.4.2 ���η������������� + +�������������IJ������£� + +- ģ������ +- show engine innodb status;�鿴������־ +- �ҳ�����SQL +- SQL������������������ȥ�������� +- ����������־������jô�����ȴ�jô���� +- ��Ϥ��ģʽ���ݾ�����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 �����ӳ����⿼�� + +�Ȳ��룬���ž�ȥ��ѯ,���������߼��Ƚϳ����������ܻ��������ġ�һ�����ݿⶼ�������⣬�ӿ��ġ�д���Ļ���д���⣬��һ���Ƕ��ӿ⡣�������������ӳ٣����ܿ��ܳ����������ɹ��x���������ѯ������������ + +![](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 �1⁄4ӵ��ֶΣ����Ǵ������ݵ�Ĭ��ֵ + +�����ճ������У�����ҵ�������������Ҫ��ij�����ݿ������Ӹ��ֶΡ�������ij��APP���ñ���Ҫ���Ӹ�����ֶΣ���```scene_type```,����ö��ֵ�� ```01��02��03```�������Ǿ�Ҫ��ҵ�����룬�����ӵ��ֶΣ���������jôĬ��ֵ����Ϊ�ջ���Ĭ��01��������Ϊ```NULL```�Ļ�������������Ҫ���ÿ�ָ�봦�� + +#### 1.6.2 ������ҵ�����ε��ֶΣ����������ݵ�ֵ�Ƿ��п� + +�������ǿ����У���Ҫ�������ݿ��������ֶΣ������д������ݣ��Ǿ���Ҫ�����Θ������ݿ���ֵ�Ƿ��пӡ��������DZ��и�user_role_code ���ֶΣ��ε������У���ö��ֵ�� ``` 01����������Ա 02������Ա 03��һ���û�```������ҵ��������**һ���û�**����Ϊ**03��ѯ�û���04�����û�**���������ڿ����У���Ҫ���������ݵ��������� + +### 1.7 һЩSQL�ľ���ע���� + +#### 1.7.1 limit����ҳ���� + +limit����ҳ��һ���dz�������SQL���⣬����һ������3�ֶ�Ӧ�Ľ���� + +**����һ��** ����id�������ģ������������Θβ�ѯ��������1⁄4(ƫ����)��������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�����ɦ������Ҫע�����������͵L·�ָ������ + +- ��װ���͵L·�ָ������ +- �������õL·�ָ������ +- Equals�������ߵL·�ָ������ +- ConcurrentHashMap ����������֧�� k-vΪ null�� +- ���ϣ�����ֱ�ӻ�ȡԪ�� +- ����ֱ�ӻ�ȡ���� + + +``` +if(object!=null){ + String name = object.getName(); +} +``` + +#### 2.1.2 �̳߳�ʹ��ע���� + +- ʹ�� Executors.newFixedThreadPool�����ܻ�����OOM���⣬��Ϊ��ʹ�õ����޽��������� +- ����ʹ���Զ������̳߳أ����ø��̳߳�һ���������������Ų����� +- ��ͬ��ҵ�����������̳߳ظ��룬�������е�ҵ������һ���̳߳ء� +- �̳߳��쳣����Ҫ���Ǻ� + +#### 2.1.3 ���԰�ȫ�ļ��ϡ��� + +�ڸ߲��������£�```HashMap```���ܻ�������ѭ������Ϊ���Ƿ����԰�ȫ�ģ����Կ���ʹ��```ConcurrentHashMap```����������ʹ����Щ���ε�ɦ������Ҫע���Dz������԰�ȫ�ġ� + +- Hashmap��Arraylist��LinkedList��TreeMap�ȶ������Բ���ȫ�ģ� +- Vector��Hashtable��ConcurrentHashMap�ȶ������԰�ȫ�� + +#### 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){ + 1��deleteTicketById(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 У�������Ϸ��� + +�����ṩ�����Ľӿڣ��������ṩ���ͻ��l�����ǰ�x��ֻ��DZ���εͳ���ã�����ҪУ��һ�����εĺϷ��ԡ� + +> �����������ݿ��ֶ�����Ϊvarchar(16),�Է�����һ��32λ���ַ����������㲻У���������ȣ��������ݿ�ֱ���쳣�l� + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f4649f1bb3b43418ef808b8c5599c76~tplv-k3u1fbpfcp-watermark.image) + +#### 2.2.2 ���Ͻӿڼ��� + +�ܶ�bug������Ϊ�޸��˶����Ͻӿڣ�����ȴ�������ݵ��μġ��ؼ��������������DZȽ����صģ�����ֱ�ӵ���εͳ����ʧ�ܵġ����ֳ���Ա�����׷���������Ŷ~ + +���������и�dubbo�ķֲ�ʽ�ӿڣ��������޸������Σ�����Ҫ�������Ͻӿڼ��ݡ�ԭ����ֻ����A��B����������������һ������C���Ϳ��Կ����������� + + +``` +//�Ͻӿ� +void oldService(A,B){ + //�����1⁄2ӿڣ�����null����C + newService(A,B,null); +} + +//�1⁄2ӿڣ���ɦ����ɾ���Ͻӿڣ���Ҫ�����ݡ� +void newService(A,B,C); +``` + + + +#### 2.2.3 �����ֹ������ѹ��εͳ + +����˲���Ĵ�������������������ѹ��εͳ������Ϊ�˱������ǵ�εͳ��һ��Ҫ��������������ʹ��**guava ratelimiter** ������������Ҳ�����ð��↑Դ��**Sentinel** + +#### 2.2.4 �ӿڰ�ȫ�ԣ���ǩ��ǩ����Ȩ + + +����ת�˵����͵Ľӿڣ�һ��Ҫע�ⰲȫ�ԡ�һ��Ҫ��Ȩ��**��ǩ��ǩ**��Ϊ�û����ױ��ݻ����� + + +#### 2.2.5 ���ǽӿ��ݵ��� + +�ӿ�����Ҫ�����ݵ��Եģ�������������ת����Щ��Ҫ�ӿڡ���ֱ�۵�ҵ�񳡾�������**�û����ŵ�������**�����Ľӿ���û��holdס�� + +> 1. �ݵȣ�idempotent��idempotence����һ����ѧ��������ѧ��������ڳ��������С� +> 2. �ڱ�����.һ���ݵȲ������ص�������������ִ����������Ӱ������һ��ִ�е�Ӱ����ͬ���ݵȺ��������ݵȷ�������ָ����ʹ����ͬ�����ظ�ִ�У����ܻ�����ͬ�����ĺ����� + + +һ�㡸�ݵȼ���������弓��: + +1. ��ѯ���� +2. Ψһ���� +3. token���ƣ���ֹ�ظ��ύ +4. ���ݿ���deleteɾ������ +5. �ֹ��� +6. ������ +7. Redis��zookeeper �ֲ�ʽ������ǰ����������������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�򣬳ɹ���ɦ���Ͱѱ���ת����ˮ��Ϊ�ɹ���ʧ�ܵ�ɦ���Ͱѱ�����ˮ��Ϊʧ�ܡ���������ת��εͳ��ɦ���أ�������ô�����أ���Ϊ�ɹ�����ʧ���أ�����**��ɦ������Ҫ���Ǻ�**��Ҫ��Ȼ���ʽ���ʧ�l����ֳ����£����ӿڳ�ɦ�����ǾͿ�����**�����±���ת����ˮ**״̬���������·�����ѯԶ��ת�����󣬲�ѯ��ת�˳ɹ��ļ�1⁄4���يٴ��±���״̬״̬ + + + +#### 2.3.2 �������Ի��� + +�������ǵ���һ��Զ��http����dubbo�ӿڣ�����ʧ���x����ǿ��Կ����������Ի��ơ���ɦ����·����һ�£��ӿھ͵�ʧ���x��������Ի��ƿ��������û����顣�����������Ի�����Ҫ����������������Щ�ӿڲ�֧���ݵȣ��Ͳ��ʺ����Եġ� + +#### 2.3.3 �����Ƿ񽵼����� + + +��������εͳ��һ���ṩע���ķ������û�ע���ɹ�֮�󣬵�Զ��A�ӿڷ����ţ���Զ��B�ӿڷ��ʼ�����������ע��״̬Ϊ�ɹ��� + + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2a2180e624d4f8fadcf8fc8f63651bf~tplv-k3u1fbpfcp-zoom-1.image) + + +�������ýӿ�B���ʼ�ʧ�ܣ����û���ע��ʧ�ܣ�ҵ�����ܾͲ���ͬ���l���ɦ�����ǿ��Կ��Ǹ�B�ӿ�**��������**���ṩ**��������**��Ҳ����˵����������B�ӿ�ʧ�ܣ����Ȳ����ʼ������������û�ע���ɹ�������������ɦ�����ʼ�� ͅ����� + +#### 2.3.4 �����Ƿ��첽���� + +�һ���ʹ���ϸ�С�ڵ�**�û�ע��**�����ӡ����ǿ��Կ����첽�߳�ȥ��A�ӿڷ����ţ��첽��B�ӿڷ��ʼ����Ǽ�ʹA����B�ӿڵ�ʧ�ܣ����ǻ��ǿ��Ա�֤�û���ע���ɹ��� + +�ѷ�������Щ֪ͨ���ӿڣ��ŵ��첽�̴߳������Խ��ͽӿں�ɦ�������û�����Ŷ�� + + +![](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�����ݣ������ݣ������ݲ�һ���x������ݳ�������������ɾ������ȡ�����»����򲻻������������������⡣ + + +#### 3.1.3 �Ȳ������ݿ⻹���Ȳ���� + +˫д�������£��Ȳ������ݿ⻹���Ȳ���棿����������һ�����ӣ�������A��B��������������A�����2���������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. �߳�B������DB��������һ�������� +4. Ȼ���߳�B��������������cache +5. �߳�Aд��DB���μ����� + +���Ͼ��������������������ݿ������ݲ�һ���l����汣�����������ݣ����ݿⱣ�����������ݡ����x�Cache-Aside����ģʽ��ѡ�����Ȳ������ݿ��������Ȳ���档 + + +#### 3.1.4 ���α�֤����һ���� + +- ������ɦ˫ɾ +- ɾ���������Ի��� +- ��ȡbiglog�첽ɾ������ + + + +### 3.2 ���洩͸ + +> ���洩͸��ָ��ѯһ��һ�������ڵ����ݣ����ڻ��治����ɦ����Ҫ�����ݿ���ѯ���鲻����������д�뻺�棬�皿����������ڵ�����ÿ��������Ҫ�����ݿ�ȥ��ѯ�����������ݿ�����ѹ���� + +���洩͸һ�㶼���弓�����������ģ�**ҵ�񲻺��������ơ�ҵ��/��ά/����ʧ���IJ���ڿͷǷ����󹥻�**�� +���α��⻺�洩͸�أ� һ�������ַ����� + +- �����ǷǷ�������������API���ڣ�**�Բ�������У��**�����˷Ƿ�ֵ�� +- ������ѯ���ݿ�Ϊ�գ����ǿ���**���������ø���ֵ������Ĭ��ֵ**������������д���������Ļ�����Ҫ���»��������Ա�֤����һ���ԣ�ͬɦ�����������������ɻ��Ĺ���ɦ�䡣��ҵ���ρȽϳ��ã�������Ч�� +- ʹ��**��¡������**�����ж������Ƿ����ڡ���һ����ѯ��������ɦ����ͨ����¡�������ж�ֵ�Ƿ����ڣ����ڲż������2顣 + +### 3.3 ����ѩ�� + +> ����ѩ����ָ���������ݴ�����������ɦ�䣬����ѯ�������޴����������ݿ�ѹ����������down���� + +- ����ѩ��һ�������ڴ�������ͬɦ�������ɵģ���������ԭ�򣬿�ͨ��**�������ù���ɦ��������ù���ɦ��������ɢһ��**��������һ���Θ��̶�ֵ+һ����С������ֵ��5Сɦ+0��1800�뽴�ϡ� +- **Redis ����崻�Ҳ�������𻺴�ѩ��**��������Ҫ����Redis�߿��ü�Ⱥ���� + + +### 3.4 ���������� + +> ������� ָ�ȵ�key��ij��ɦ�������ڵ�ɦ�򣬶�ǡ��������ɦ����������Key�д����IJ��������������Ӷ���������������db�� + +����������е��񻺴�ѩ������ɻ���������ǣ�����ѩ����ָ���ݿ�ѹ����������down��������ֻ�Ǵ���������������DB���ݿ����档������Ϊ�����ǻ���ѩ����һ���Ӽ��ɡ���Щ������Ϊ���������������ڻ�������ijһ�ȵ�key���棬ѩ�����Ǻܶ�key�� + + +������������֣� + +1. **ʹ�û�����**������ʧЧɦ����������ȥ����db���ݣ�������ʹ��ijЩ���ɹ����ص�ԭ�Ӳ��������(Redis��setnx��ȥ����ɹ���ɦ������ȥ����db���ݿ����ݺ����û��档������ȥ���Ի�ȡ���档 +2. **���������ڡ�**����ָû�����ù���ɦ�䣬�����ȵ����ݿ�Ҫ����ɦ���첽�߳�ȥ���o����ù���ɦ�䡣 + + +### 3.5 ������Key + +��Redis�У����ǰѷ���Ƶ�yߵ�key����Ϊ�ȵ�key������ijһ�ȵ�key�����󵽷���������ɦ�������������ر��󣬿��ܻᵼ��������Դ���㣬����崻����Ӷ�Ӱ�������ķ����� + +���ν�����key���⣿ + +- **Redis��Ⱥ����**�����ӷ�Ƭ������������������ +- **����key����hashɢ��**�����罫һ��key����Ϊkey1,key2����keyN��ͬ��������N��ݣ�N��ݷֲ�����ͬ��Ƭ������ɦ����������N����е�һ���һ���ֵ��������� +- **ʹ�ö�������**����JVM���ػ���,����Redis�Ķ������� + +### 3.6 ���������ڴ濼�� + +#### 3.6.1 ������������������ + +��������ʹ�õ���Redis����Redis���ڴ��DZȽκ����ģ����Dz�Ҫjô���ݶ���Redis��������һ��Redisֻ������ѯ�Ƚ�Ƶ�������ݡ�ͬɦ������Ҫ��������Redis��������Ҳ����Ƶ��set���ǣ����������˹���ɦ����keyʧЧ�� + +��������ʹ�õ��DZ��ػ��棬��guava�ı��ػ��棬ҲҪ�������������������������� + + +#### 3.6.2 Redis�İ����ڴ���̭���� + +Ϊ�˱���Redis�ڴ治���ã�Redis��8���ڴ���̭���Ա����Լ�~ + +> - volatile-lru�����ڴ治����������д������ɦ���������˹���ɦ����key��ʹ��LRU����������ʹ�ã��㷨������̭�� +> - allkeys-lru�����ڴ治����������д������ɦ��������key��ʹ��LRU����������ʹ�ã��㷨������̭�� +> - volatile-lfu��4.0�汾������ڴ治����������д������ɦ���ڹ��ڵ�key�У�ʹ��LFU�㷨����ɾ��key�� +> - allkeys-lfu��4.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. ����Redis��monitor���� +4. ��ֹʹ��flushall��flushdb +5. ע��ʹ��del���� + +## ���� + +�����ܽ���60��������bug�ı���ע���㣬�����ճ����������ķ�����ϣ���Դ����а����������⣬ +��ע���ںţ�**�����ݵ�С�к�**���ظ�**˼ά��ͼ**������**��ȡ���ĵĸ���˼ά��ͼ**�� + + 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円221円bug345円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円221円bug345円221円242円357円274円237円.md.bak" new file mode 100644 index 0000000..e69de29 diff --git "a/345円267円245円344円275円234円346円200円273円347円273円223円/350円256円260円344円270円200円346円254円241円346円216円245円345円217円243円346円200円247円350円203円275円344円274円230円345円214円226円345円256円236円350円267円265円346円200円273円347円273円223円357円274円232円344円274円230円345円214円226円346円216円245円345円217円243円346円200円247円350円203円275円347円232円204円345円205円253円344円270円252円345円273円272円350円256円256円.md" "b/345円267円245円344円275円234円346円200円273円347円273円223円/350円256円260円344円270円200円346円254円241円346円216円245円345円217円243円346円200円247円350円203円275円344円274円230円345円214円226円345円256円236円350円267円265円346円200円273円347円273円223円357円274円232円344円274円230円345円214円226円346円216円245円345円217円243円346円200円247円350円203円275円347円232円204円345円205円253円344円270円252円345円273円272円350円256円256円.md" new file mode 100644 index 0000000..a795f83 --- /dev/null +++ "b/345円267円245円344円275円234円346円200円273円347円273円223円/350円256円260円344円270円200円346円254円241円346円216円245円345円217円243円346円200円247円350円203円275円344円274円230円345円214円226円345円256円236円350円267円265円346円200円273円347円273円223円357円274円232円344円274円230円345円214円226円346円216円245円345円217円243円346円200円247円350円203円275円347円232円204円345円205円253円344円270円252円345円273円272円350円256円256円.md" @@ -0,0 +1,226 @@ +## 前言 +最近对外接口偶现504超时问题,真枪实弹搞了一次接口性能优化。在这里结合优化过程,总结了接口优化的八个要点,希望对大家有帮助呀~ +- 数据量比较大,批量操作数据入库 +- 耗时操作考虑异步处理 +- 恰当使用缓存 +- 优化程序逻辑、代码 +- SQL优化 +- 压缩传输内容 +- 考虑使用文件/MQ等其他方式暂存,异步再落地DB +- 跟产品讨论需求最恰当,最舒服的实现方式 + +嘻嘻,先看一下我们对外转账接口的大概流程吧 +![](https://user-gold-cdn.xitu.io/2020/5/30/1726375c0d0162f3?w=1393&h=586&f=png&s=50348) + +### 1. 数据量比较大,批量操作数据入库 +批量插入优化方法应该是我们最容易想到的啦~ + +**优化前** + +``` +//for循环单笔入库 +for(TransDetail detail:list){ + insert(detail); +} +``` +**优化后** + +``` +// 批量入库,mybatis demo实现 + + insert into trans_detail( id,amount,payer,payee) values + ( + #{item.id}, #{item.amount}, + #{item.payer},#{item.payee} + ) + + +``` +**性能对比** +| 单位(ms) | for循环单笔入库 | 批量入库 | +|-----|-----|------| +| 500条 | 611 | 449 | +| 1000条 | 1420 | 1128 | + +**解析** +- 批量插入性能更好,更加省时间,为什么呢? + +``` +打个比喻:假如你需要搬一万块砖到楼顶,你有一个电梯,电梯一次可以放适量的砖(最多放500), +你可以选择一次运送一块砖,也可以一次运送500,你觉得哪种方式发方便,时间消耗小? +``` + + +### 2.耗时操作考虑异步处理 + +**优化前:** + + + + +**优化后:** + +![](https://user-gold-cdn.xitu.io/2020/5/30/172636b9f5238802?w=999&h=644&f=png&s=67707) + +**性能对比:** + +假设一个联行号匹配6ms +| |同步| 异步| +|-----|-----|------| +| 500条 | 3000ms | 0 | +| 1000条| 6000ms | 0| + + +**解析:** +- 因为联行号匹配比较耗时,放在异步处理的话,可以同步联机返回可以省掉这部分时间,大大提升接口性能,并且不会影响到转账主流程功能。 +- 除了这个例子,平时我们类似功能,如用户注册成功后,短信邮件通知,也是可以异步处理的,这个优化点香饽饽的~ +- 所以,太耗时的操作,在不影响主流程功能的情况下,可以考虑开子线程异步处理的啦。 + +### 3.恰当使用缓存 +在适当的业务场景,恰当地使用缓存,是可以大大提高接口性能的。这里的缓存包括:Redis,JVM本地缓存,memcached,或者Map等。 + +这次转账接口,使用到缓存啦,举个简单例子吧~ + +**优化前:** + + +![](https://user-gold-cdn.xitu.io/2020/5/30/17263c548352f480?w=396&h=719&f=png&s=36528) + +**优化后:** + + +![](https://user-gold-cdn.xitu.io/2020/5/30/17263cc73ccd1e76?w=723&h=785&f=png&s=70764) + +**解析:** +- 把热点数据放到缓存,不用每次查询都去DB拉取,节省了这部分查SQL的耗时,美滋滋呀~ +- 当然,不是什么数据都适合放到缓存的哦,访问比较频繁的热点数据才考虑缓存起来呢~ + +### 4.优化程序逻辑、代码 +优化程序逻辑,优化程序代码,是可以节省耗时的。 + +我这里也就本次的转账接口优化,举个例子吧~(优化前,联行号查询了两次) + +**优化前:** + +``` + +punlic void process(Req req){ + //检验参数,包括联行号(前端传来的payeeBankNo可以为空,但是如果后端没匹配到,会抛异常) + checkTransParams(Req req); + //Save DB + saveTransDetail(req); +} + +void checkTransParams(Req req){ + //check Amount,and so on. + checkAmount(req.getamount); + //check payeebankNo + if(Utils.isEmpty(req.getPayeeBankNo())){ + String payeebankNo = getPayeebankNo(req.getPayeeAccountNo); + if(Utils.isEmpty(payeebankNo){ + throws Exception(); + } + } +} + +int saveTransDetail(req){ + String payeebankNo = getPayeebankNo(req.getPayeeAccountNo); + req.setPayeeBankNo(payeebankNo); + insert(req); + ... +} + +``` +**优化后:** + +``` +void checkTransParams(Req req){ + //check Amount,and so on. + checkAmount(req.getamount); + //check payeebankNo + if(Utils.isEmpty(req.getPayeeBankNo())){ + String payeebankNo = getPayeebankNo(req.getPayeeAccountNo); + if(Utils.isEmpty(payeebankNo){ + throws Exception(); + } + } + //查询到有联行号,直接设置进去啦,这样等下入库不用再差多一次 + req.setPayeeBankNo(payeebankNo); +} + +int saveTransDetail(req){ + insert(req); + ... +} +``` + +**解析:** +- 对于优化程序逻辑、代码,是可以降低接口耗时的。以上demo只是一个很简单的例子,就是优化前payeeBankNo查询了两次,但是其实只查一次就可以了。很多时候,我们都知道这个点,但就是到写代码自己又忘记了呀~所以,写代码的时候,留点心吧,优化你的程序逻辑、代码哦。 +- 除了以上demo这点,还有其它的,如优化if复杂的逻辑条件,考虑是否可以调整顺序,或者for循环,是否重复实例化对象等等,这些都是我们需要关注的优化点。 + +之前我这篇文章,也提了几个优化点啦,有兴趣的朋友可以看一下哈~ + +[写代码有这些想法,同事才不会认为你是复制粘贴程序员](https://juejin.im/post/5dfe2e72518825125f39a2de#heading-1) + +### 5.优化SQL + +很多时候,你的接口性能瓶颈就在SQL这里,慢查询需要我们重点关注的呢。 + +我们可以通过这些方式优化我们的SQL: +- 加索引 +- 避免返回不必要的数据 +- 优化sql结构 +- 分库分表 +- 读写分离 + +有兴趣的朋友可以看一下我这篇文章呢,很详细的SQL优化点: + +[后端程序员必备:书写高质量SQL的30条建议](https://juejin.im/post/5e624d156fb9a07ca80ab6f2) + + +### 6.压缩传输内容 + +压缩传输内容,让文件更小,因此传输会更快啦。10M带宽,传输10k的报文,一般比传输1M的会快呀;打个比喻,一匹千里马,它驮着一百斤的货跑得快,还是驮着10斤的货物跑得快? + + +传输100个转账对账对象耗时: + +传输500个转账对象的耗时: + + +**解析:** +- 如果你的接口性能不好,然后传输报文很大,这时候是可以考虑压缩文件内容传输的,最后优化效果可能很不错哦~ + + +### 7.考虑使用文件/MQ等其他方式暂存,异步再落地DB +如果数据太大,落地数据库实在是慢,可以考虑先用文件的方式保存,或者考虑MQ,先落地,再异步保存到数据库~ +> 本次转账接口,如果是并发开启,10个并发度,每个批次1000笔数据,数据库插入会特别耗时,大概10秒左右,这个跟我们的数据库机制有关,并发情况下,因为优先保证同步,所以并行的插入变成串行啦,就很耗时。 + +**优化前:** + +![](https://user-gold-cdn.xitu.io/2020/5/30/172649d6bd1017bf?w=704&h=490&f=png&s=43540) + +**优化后:** + +![](https://user-gold-cdn.xitu.io/2020/5/30/17264a156b8b9691?w=778&h=518&f=png&s=50459) +**解析:** +- 如果你的耗时瓶颈就在数据库插入操作这里了,那就考虑文件保存或者MQ或者其他方式暂存吧,文件保存数据,对比一下耗时,有时候会有意想不到的效果哦。 + + +### 8.跟产品讨论需求最恰当,最舒服的实现方式 +这点个人觉得还是很重要的,有些需求需要好好跟产品沟通的。 + +> 比如有个用户连麦列表展示的需求,产品说要展示所有的连麦信息,如果一个用户的连麦列表信息好大,你拉取所有连麦数据回来,接口性能就降下来啦。如果产品打桩分析,会发现,一般用户看连麦列表,也就看前几页~因此,奸笑,哈哈~ 其实,那个超大分页加载问题也是类似的。即limit +一个超大的数,一般会很慢的~~ + +### 总结 + +本文呢,基于一次对外接口耗时优化的实践,15秒降到4秒左右,总结了优化接口性能的八个点,希望对大家日常开发有帮助哦~嘻嘻,有兴趣可以逛逛我的github哈,本文会收藏到github里滴 +> https://github.com/whx123/JavaHome + + +### 公众号 +![](https://user-gold-cdn.xitu.io/2020/5/16/1721b50d00331393?w=900&h=500&f=png&s=389569) +- 欢迎关注我个人公众号,交个朋友,一起学习哈~ + + + 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円250円213円345円272円217円345円221円230円345円277円205円345円244円207円345円267円245円345円205円267円347円257円207円/347円250円213円345円272円217円345円221円230円345円277円205円345円244円207円345円237円272円347円241円200円357円274円232円Git 345円221円275円344円273円244円345円205円250円346円226円271円344円275円215円345円255円246円344円271円240円.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円/347円250円213円345円272円217円345円221円230円345円277円205円345円244円207円345円237円272円347円241円200円357円274円232円Git 345円221円275円344円273円244円345円205円250円346円226円271円344円275円215円345円255円246円344円271円240円.md" new file mode 100644 index 0000000..6495e59 --- /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円/347円250円213円345円272円217円345円221円230円345円277円205円345円244円207円345円237円272円347円241円200円357円274円232円Git 345円221円275円344円273円244円345円205円250円346円226円271円344円275円215円345円255円246円344円271円240円.md" @@ -0,0 +1,471 @@ +## 前言 +掌握Git命令是每位程序员必备的基础,之前一直是用smartGit工具,直到看到大佬们都是在用Git命令操作的,回想一下,发现有些Git命令我都忘记了,于是写了这篇博文,复习一下~ + +> https://github.com/whx123/JavaHome + +**公众号:捡田螺的小男孩** + +**文章目录** +- Git是什么? +- Git的相关理论基础 +- 日常开发中,Git的基本常用命令 +- Git进阶之分支处理 +- Git进阶之处理冲突 +- Git进阶之撤销与回退 +- Git进阶之标签tag +- Git其他一些经典命令 + +## Git是什么 +在回忆Git是什么的话,我们先来复习这几个概念哈~ + +### 什么是版本控制? +百度百科定义是酱紫的~ +> 版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。 + +那些年,我们的毕业论文,其实就是版本变更的真实写照...脑洞一下,版本控制就是这些论文变更的管理~ +![](https://user-gold-cdn.xitu.io/2020/6/20/172d07667ab32c55?w=783&h=222&f=png&s=42433) + +### 什么是集中化的版本控制系统? +那么,集中化的版本控制系统又是什么呢,说白了,就是有一个集中管理的中央服务器,保存着所有文件的修改历史版本,而协同开发者通过客户端连接到这台服务器,从服务器上同步更新或上传自己的修改。 + +![](https://user-gold-cdn.xitu.io/2020/6/20/172d0caa0af7ca9e?w=1247&h=825&f=png&s=77377) + +### 什么是分布式版本控制系统? +分布式版本控制系统,就是远程仓库同步所有版本信息到本地的每个用户。嘻嘻,这里分三点阐述吧: +- 用户在本地就可以查看所有的历史版本信息,但是偶尔要从远程更新一下,因为可能别的用户有文件修改提交到远程哦。 +- 用户即使离线也可以本地提交,push推送到远程服务器才需要联网。 +- 每个用户都保存了历史版本,所以只要有一个用户设备没问题,就可以恢复数据啦~ + +![](https://user-gold-cdn.xitu.io/2020/6/21/172d47e8db158f67?w=1453&h=764&f=png&s=95399) + +### 什么是Git? +Git是免费、开源的**分布式版本控制**系统,可以有效、高速地处理从很小到非常大的项目版本管理。 + +![](https://user-gold-cdn.xitu.io/2020/6/26/172efd2d7e5a8b30?w=220&h=92&f=png&s=9300) + +## Git的相关理论基础 +- Git的四大工作区域 +- Git的工作流程 +- Git文件的四种状态 +- 一张图解释Git的工作原理 + +### Git的四大工作区域 +先复习Git的几个工作区域哈: +![](https://user-gold-cdn.xitu.io/2020/6/25/172ea283b8925e39?w=1314&h=1027&f=png&s=75979) + +- **Workspace**:你电脑本地看到的文件和目录,在Git的版本控制下,构成了工作区。 +- **Index/Stage**:暂存区,一般存放在 .git目录下,即.git/index,它又叫待提交更新区,用于临时存放你未提交的改动。比如,你执行git add,这些改动就添加到这个区域啦。 +- **Repository**:本地仓库,你执行git clone 地址,就是把远程仓库克隆到本地仓库。它是一个存放在本地的版本库,其中**HEAD指向最新放入仓库的版本**。当你执行git commit,文件改动就到本地仓库来了~ +- **Remote**:远程仓库,就是类似github,码云等网站所提供的仓库,可以理解为远程数据交换的仓库~ + +### Git的工作流程 +上一小节介绍完Git的四大工作区域,这一小节呢,介绍Git的工作流程咯,把git的操作命令和几个工作区域结合起来,个人觉得更容易理解一些吧,哈哈,看图: + +![](https://user-gold-cdn.xitu.io/2020/6/26/172f02fc758ed96d?w=1254&h=1010&f=png&s=113385) + git 的正向工作流程一般就这样: + - 从远程仓库拉取文件代码回来; + - 在工作目录,增删改查文件; + - 把改动的文件放入暂存区; + - 将暂存区的文件提交本地仓库; + - 将本地仓库的文件推送到远程仓库; + +### Git文件的四种状态 +根据一个文件是否已加入版本控制,可以把文件状态分为:Tracked(已跟踪)和Untracked(未跟踪),而tracked(已跟踪)又包括三种工作状态:Unmodified,Modified,Staged + +![](https://user-gold-cdn.xitu.io/2020/6/21/172d5ecd2d130275?w=1557&h=820&f=png&s=106243) +- **Untracked**: 文件还没有加入到git库,还没参与版本控制,即未跟踪状态。这时候的文件,通过git add 状态,可以变为Staged状态 +- **Unmodified**:文件已经加入git库, 但是呢,还没修改, 就是说版本库中的文件快照内容与文件夹中还完全一致。 Unmodified的文件如果被修改, 就会变为Modified. 如果使用git remove移出版本库, 则成为Untracked文件。 +- **Modified**:文件被修改了,就进入modified状态啦,文件这个状态通过stage命令可以进入staged状态 +- **staged**:暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodified状态. + +### 一张图解释Git的工作原理 + +![](https://user-gold-cdn.xitu.io/2020/6/22/172dc85dbffd9ee9?w=1538&h=828&f=png&s=252469) + +## 日常开发中,Git的基本常用命令 +- git clone +- git checkout -b dev +- git add +- git commit +- git log +- git diff +- git status +- git pull/git fetch +- git push + +这个图只是模拟一下git基本命令使用的大概流程哈~ +![](https://user-gold-cdn.xitu.io/2020/6/25/172ec08ede51d056?w=1562&h=692&f=png&s=136113) + +### git clone +当我们要进行开发,第一步就是克隆远程版本库到本地呢 +``` +git clone url 克隆远程版本库 +``` + +![](https://user-gold-cdn.xitu.io/2020/6/25/172eac70ddf0000f?w=606&h=119&f=png&s=16710) + +### git checkout -b dev +克隆完之后呢,开发新需求的话,我们需要新建一个开发分支,比如新建开发分支dev + +创建分支: +``` +git checkout -b dev 创建开发分支dev,并切换到该分支下 +``` +![](https://user-gold-cdn.xitu.io/2020/6/25/172eac94cbf03f92?w=585&h=118&f=png&s=14677) + +### git add +git add的使用格式: +``` +git add . 添加当前目录的所有文件到暂存区 +git add [dir] 添加指定目录到暂存区,包括子目录 +git add [file1] 添加指定文件到暂存区 +``` + +有了开发分支dev之后,我们就可以开始开发啦,假设我们开发完HelloWorld.java,可以把它加到暂存区,命令如下 +``` +git add Hello.java 把HelloWorld.java文件添加到暂存区去 +``` + +![](https://user-gold-cdn.xitu.io/2020/6/25/172ead69113ecab6?w=536&h=57&f=png&s=7135) + + +### git commit +git commit的使用格式: +``` +git commit -m [message] 提交暂存区到仓库区,message为说明信息 +git commit [file1] -m [message] 提交暂存区的指定文件到本地仓库 +git commit --amend -m [message] 使用一次新的commit,替代上一次提交 +``` +把HelloWorld.java文件加到暂存区后,我们接着可以提交到本地仓库啦~ +``` +git commit -m 'helloworld开发' +``` +![](https://user-gold-cdn.xitu.io/2020/6/25/172eae180b28d9e7?w=548&h=90&f=png&s=14447) + +### git status +git status,表示查看工作区状态,使用命令格式: +``` +git status 查看当前工作区暂存区变动 +git status -s 查看当前工作区暂存区变动,概要信息 +git status --show-stash 查询工作区中是否有stash(暂存的文件) +``` +当你忘记是否已把代码文件添加到暂存区或者是否提交到本地仓库,都可以用git status看看哦~ +![](https://user-gold-cdn.xitu.io/2020/6/25/172eb5ea0d5fde25?w=724&h=195&f=png&s=22238) + +### git log +git log,这个命令用得应该比较多,表示查看提交历史/提交日志~ + +``` +git log 查看提交历史 +git log --oneline 以精简模式显示查看提交历史 +git log -p 查看指定文件的提交历史 +git blame 一列表方式查看指定文件的提交历史 +``` +嘻嘻,看看dev分支上的提交历史吧~要回滚代码就经常用它喵喵提交历史~ +![](https://user-gold-cdn.xitu.io/2020/6/25/172eb79224fae0eb?w=593&h=214&f=png&s=28063) + +### git diff +``` +git diff 显示暂存区和工作区的差异 +git diff filepath filepath路径文件中,工作区与暂存区的比较差异 +git diff HEAD filepath 工作区与HEAD ( 当前工作分支)的比较差异 +git diff branchName filepath 当前分支的文件与branchName分支的文件的比较差异 +git diff commitId filepath 与某一次提交的比较差异 +``` +如果你想对比一下你改了哪些内容,可以用git diff对比一下文件修改差异哦 +![](https://user-gold-cdn.xitu.io/2020/6/25/172eb8d4ff0cb241?w=747&h=247&f=png&s=29019) + +### git pull/git fetch +``` +git pull 拉取远程仓库所有分支更新并合并到本地分支。 +git pull origin master 将远程master分支合并到当前本地master分支 +git pull origin master:master 将远程master分支合并到当前本地master分支,冒号后面表示本地分支 + +git fetch --all 拉取所有远端的最新代码 +git fetch origin master 拉取远程最新master分支代码 +``` + +我们一般都会用git pull拉取最新代码看看的,解决一下冲突,再推送代码到远程仓库的。 + +![](https://user-gold-cdn.xitu.io/2020/6/25/172ebdbf926f16b6?w=566&h=67&f=png&s=7840) + +> 有些伙伴可能对使用git pull还是git fetch有点疑惑,其实 +git pull = git fetch+ git merge。pull的话,拉取远程分支并与本地分支合并,fetch只是拉远程分支,怎么合并,可以自己再做选择。 + +### git push +git push 可以推送本地分支、标签到远程仓库,也可以删除远程分支哦。 +``` +git push origin master 将本地分支的更新全部推送到远程仓库master分支。 +git push origin -d 删除远程branchname分支 +git push --tags 推送所有标签 +``` +如果我们在dev开发完,或者就想把文件推送到远程仓库,给别的伙伴看看,就可以使用git push origin dev~ +![](https://user-gold-cdn.xitu.io/2020/6/25/172ebe5bab790e17?w=701&h=321&f=png&s=43043) + +## Git进阶之分支处理 +Git一般都是存在多个分支的,开发分支,回归测试分支以及主干分支等,所以Git分支处理的命令也需要很熟悉的呀~ +- git branch +- git checkout +- git merge + +### git branch +git branch用处多多呢,比如新建分支、查看分支、删除分支等等 + +**新建分支:** +``` +git checkout -b dev2 新建一个分支,并且切换到新的分支dev2 +git branch dev2 新建一个分支,但是仍停留在原来分支 +``` +![](https://user-gold-cdn.xitu.io/2020/6/25/172ec19a8c138ab3?w=530&h=64&f=png&s=8940) + +**查看分支:** +``` +git branch 查看本地所有的分支 +git branch -r 查看所有远程的分支 +git branch -a 查看所有远程分支和本地分支 +``` +![](https://user-gold-cdn.xitu.io/2020/6/25/172ec1f4c26f2847?w=566&h=357&f=png&s=34314) + +**删除分支:** +``` +git branch -D 删除本地branchname分支 +``` + +![](https://user-gold-cdn.xitu.io/2020/6/25/172ec2607fabe1c2?w=635&h=294&f=png&s=32151) + +### git checkout +**切换分支:** +``` +git checkout master 切换到master分支 +``` + +![](https://user-gold-cdn.xitu.io/2020/6/25/172ec20cf6223d5c?w=540&h=83&f=png&s=10798) + +### git merge +我们在开发分支dev开发、测试完成在发布之前,我们一般需要把开发分支dev代码合并到master,所以git merge也是程序员必备的一个命令。 + +``` +git merge master 在当前分支上合并master分支过来 +git merge --no-ff origin/dev 在当前分支上合并远程分支dev +git merge --abort 终止本次merge,并回到merge前的状态 +``` +比如,你开发完需求后,发版全需要把代码合到主干master分支,如下: +![](https://user-gold-cdn.xitu.io/2020/6/26/172ed9c119765d70?w=566&h=135&f=png&s=15550) + +## Git进阶之处理冲突 +Git版本控制,还是多个人一起搞的,多个分支并存的,这就难免会有冲突出现~ + +### Git合并分支,冲突出现 +同一个文件,在合并分支的时候,如果同一行被多个分支或者不同人都修改了,合并的时候就会出现冲突。 + +举个粟子吧,我们现在在dev分支,修改HelloWorld.java文件,假设修改了第三行,并且commit提交到本地仓库,修改内容如下: + +``` +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello,捡田螺的小男孩!"); + } +} +``` + +我们切回到master分支,也修改HelloWorld.java同一位置内容,如下: + +``` +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello,jay!!"); + } +} +``` +再然后呢,我们提交一下master分支的这个改动,并把dev分支合并过下,就出现冲突啦,如图所示: + +![](https://user-gold-cdn.xitu.io/2020/6/26/172f101fc6cf63c9?w=934&h=591&f=png&s=78508) + +### Git解决冲突 +Git 解决冲突步骤如下: +- 查看冲突文件内容 +- 确定冲突内容保留哪些部分,修改文件 +- 重新提交,done + +#### 1.查看冲突文件内容 + +git merge提示冲突后,我们切换到对应文件,看看冲突内容哈,,如下: +![](https://user-gold-cdn.xitu.io/2020/6/26/172f0fccbe271602?w=661&h=194&f=png&s=19547) + +#### 2.确定冲突内容保留哪些部分,修改文件 +- Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容, +- <<<<<< >>>>>> dev是指dev分支上修改的内容 + +所以呢,我们确定到底保留哪个分支内容,还是两个分支内容都保留呢,然后再去修改文件冲突内容~ + +#### 3.修改完冲突文件内容,我们重新提交,冲突done + +![](https://user-gold-cdn.xitu.io/2020/6/26/172f159fc458e088?w=760&h=419&f=png&s=38630) + + +## Git进阶之撤销与回退 +Git的撤销与回退,在日常工作中使用的比较频繁。比如我们想将某个修改后的文件撤销到上一个版本,或者想撤销某次多余的提交,都要用到git的撤销和回退操作。 + +代码在Git的每个工作区域都是用哪些命令撤销或者回退的呢,如下图所示: +![](https://user-gold-cdn.xitu.io/2020/6/27/172f33c532a96e98?w=1595&h=958&f=png&s=181268) + +有关于Git的撤销与回退,一般就以下几个核心命令 +- git checkout +- git reset +- git revert + +### git checkout +如果文件还在**工作区**,还没添加到暂存区,可以使用git checkout撤销 +``` +git checkout [file] 丢弃某个文件file +git checkout . 丢弃所有文件 +``` +以下demo,使用git checkout -- test.txt 撤销了暂存区test.txt的修改 +![](https://user-gold-cdn.xitu.io/2020/6/26/172edffb19594acd?w=748&h=600&f=png&s=56693) + +### git reset + +#### git reset的理解 +> git reset的作用是修改HEAD的位置,即将HEAD指向的位置改变为之前存在的某个版本. + +为了更好地理解git reset,我们来回顾一下,Git的版本管理及HEAD的理解 +> Git的所有提交,会连成一条时间轴线,这就是分支。如果当前分支是master,HEAD指针一般指向当前分支,如下: + +![](https://user-gold-cdn.xitu.io/2020/6/26/172eea4e91126bf2?w=1264&h=580&f=png&s=43485) + +假设执行git reset,回退到版本二之后,版本三不见了哦,如下: + +![](https://user-gold-cdn.xitu.io/2020/6/26/172eeac7913d8e0e?w=798&h=441&f=png&s=26783) + +#### git reset的使用 +Git Reset的几种使用模式 +![](https://user-gold-cdn.xitu.io/2020/6/26/172ee65cd631720e?w=1185&h=536&f=png&s=53298) +``` +git reset HEAD --file +回退暂存区里的某个文件,回退到当前版本工作区状态 +git reset –-soft 目标版本号 可以把版本库上的提交回退到暂存区,修改记录保留 +git reset –-mixed 目标版本号 可以把版本库上的提交回退到工作区,修改记录保留 +git reset –-hard 可以把版本库上的提交彻底回退,修改的记录全部revert。 +``` + +先看一个粟子demo吧,代码**git add到暂存区,并未commit提交**,就以下酱紫回退,如下: + +``` +git reset HEAD file 取消暂存 +git checkout file 撤销修改 +``` +![](https://user-gold-cdn.xitu.io/2020/6/26/172ee361984a63b7?w=782&h=376&f=png&s=38651) + +再看另外一个粟子吧,代码已经git commit了,但是还没有push: + +``` +git log 获取到想要回退的commit_id +git reset --hard commit_id 想回到过去,回到过去的commit_id +``` +![](https://user-gold-cdn.xitu.io/2020/6/27/172f354c0a89d53b?w=808&h=830&f=png&s=107514) + +如果代码已经push到远程仓库了呢,也可以使用reset回滚哦(这里大家可以自己操作实践一下哦)~ +``` +git log +git reset --hard commit_id +git push origin HEAD --force +``` + +### git revert +> 与git reset不同的是,revert复制了那个想要回退到的历史版本,将它加在当前分支的最前端。 + +**revert之前:** +![](https://user-gold-cdn.xitu.io/2020/6/27/172f3d1e923ec80f?w=1264&h=580&f=png&s=43485) +**revert 之后:** +![](https://user-gold-cdn.xitu.io/2020/6/26/172eeaf41ad2494a?w=1557&h=547&f=png&s=49240) + +当然,如果代码已经推送到远程的话,还可以考虑revert回滚呢 +``` +git log 得到你需要回退一次提交的commit id +git revert -n 撤销指定的版本,撤销也会作为一次提交进行保存 +``` + +![](https://user-gold-cdn.xitu.io/2020/6/27/172f3d0024e37637?w=1099&h=850&f=png&s=157867) + + +## Git进阶之标签tag +打tag就是对发布的版本标注一个版本号,如果版本发布有问题,就把该版本拉取出来,修复bug,再合回去。 + +``` +git tag 列出所有tag +git tag [tag] 新建一个tag在当前commit +git tag [tag] [commit] 新建一个tag在指定commit +git tag -d [tag] 删除本地tag +git push origin [tag] 推送tag到远程 +git show [tag] 查看tag +git checkout -b [branch] [tag] 新建一个分支,指向某个tag +``` + +![](https://user-gold-cdn.xitu.io/2020/6/27/172f41c4b0de5804?w=896&h=593&f=png&s=63528) + +## Git其他一些经典命令 +### git rebase +rebase又称为衍合,是合并的另外一种选择。 + +假设有两个分支master和test +``` + D---E test + / + A---B---C---F--- master +``` +执行 git merge test得到的结果 +``` + D--------E + / \ + A---B---C---F----G--- test, master +``` +执行git rebase test,得到的结果 + +``` +A---B---D---E---C‘---F‘--- test, master +``` +**rebase好处是:** 获得更优雅的提交树,可以线性的看到每一次提交,并且没有增加提交节点。所以很多时候,看到有些伙伴都是这个命令拉代码:git pull --rebase + +### git stash +stash命令可用于临时保存和恢复修改 +``` +git stash 把当前的工作隐藏起来 等以后恢复现场后继续工作 +git stash list 显示保存的工作进度列表 +git stash pop stash@{num} 恢复工作进度到工作区 +git stash show :显示做了哪些改动 +git stash drop stash@{num} :删除一条保存的工作进度 +git stash clear 删除所有缓存的stash。 +``` + +![](https://user-gold-cdn.xitu.io/2020/6/27/172f438c7afbdb8e?w=658&h=616&f=png&s=70671) + +### git reflog +显示当前分支的最近几次提交 + +![](https://user-gold-cdn.xitu.io/2020/6/27/172f4282186ed3fd?w=827&h=556&f=png&s=124384) + +### git blame filepath +git blame 记录了某个文件的更改历史和更改人,可以查看背锅人,哈哈 +![](https://user-gold-cdn.xitu.io/2020/6/27/172f429391270576?w=825&h=184&f=png&s=38888) + +### git remote +``` +git remote 查看关联的远程仓库的名称 +git remote add url 添加一个远程仓库 +git remote show [remote] 显示某个远程仓库的信息 +``` + +## 参考与感谢 +感谢各位前辈的文章: +- [一个小时学会Git](https://www.cnblogs.com/best/p/7474442.html#_label3_4_0_4) +- [【Git】(1)---工作区、暂存区、版本库、远程仓库](https://www.cnblogs.com/qdhxhz/p/9757390.html) +- [Git Reset 三种模式](https://www.jianshu.com/p/c2ec5f06cf1a) +- [Git恢复之前版本的两种方法reset、revert(图文详解)](https://blog.csdn.net/yxlshk/article/details/79944535) +- [Git撤销&回滚操作(git reset 和 get revert)](https://blog.csdn.net/asoar/article/details/84111841) +- [为什么要使用git pull --rebase?](https://www.jianshu.com/p/dc367c8dca8e) + +## 公众号 +![](https://user-gold-cdn.xitu.io/2020/5/16/1721b50d00331393?w=900&h=500&f=png&s=389569) +- 欢迎关注我个人公众号,交个朋友,一起学习哈~ +- 如果文章有错误,欢迎指出哈,感激不尽~ + diff --git "a/347円274円223円345円255円230円Redis346円200円273円347円273円223円/README.MD" "b/347円274円223円345円255円230円Redis346円200円273円347円273円223円/README.MD" new file mode 100644 index 0000000..996dc81 --- /dev/null +++ "b/347円274円223円345円255円230円Redis346円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円230円Redis346円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円230円Redis346円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円230円Redis346円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円230円Redis346円200円273円347円273円223円/344円275円277円347円224円250円Redis347円232円20421円344円270円252円346円263円250円346円204円217円347円202円271円.md" "b/347円274円223円345円255円230円Redis346円200円273円347円273円223円/344円275円277円347円224円250円Redis347円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円230円Redis346円200円273円347円273円223円/344円275円277円347円224円250円Redis347円232円20421円344円270円252円346円263円250円346円204円217円347円202円271円.md" @@ -0,0 +1,386 @@ +### ǰ�� + +������ѧκRedis����֪ʁ�����˰�����redis�����淶���Լ�Redis��������ά�Ȿ�顣��ʹ�ù淶���пӵ������L·ɻս��������ά�����ĸ�������������ʹ��Redis��21��ע���㣬ϣ���Դ����а�����һ��ѧκ�� + +���ںţ�**�����ݵ�С�к�** +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c59661f74a014a63a4527939096f11aa~tplv-k3u1fbpfcp-watermark.image) + +## 1��Redis��ʹ�ù淶 + +### 1.1�� key�Ĺ淶Ҫ�� + +��������Redis��key��ɦ����Ҫע�������弓���㣺 + +> - ��ҵ����Ϊkeyǰ׺����ð�Ÿ���Է�ֹkey��ͻ���ǡ��磬live:rank:1 +> - ȷ��key�����������������£�key�ij��Ⱦ���С��30���ַ��� +> - key��ֹ���������ַ������ո񡢻��С���˫�����Լ�����ת���ַ��� +> - Redis��key��������ttl���Ա�֤��ʹ�õ�Key�ܱ���ɦ��������̭�� + +### 1.2��value�Ĺ淶Ҫ�� + +Redis��valueֵ�������������õ�Ŷ�� + +**��һ��**�����������洢bigKey�ǻ��������ģ��ᵼ������ѯ���ڴ�������ȵȡ� +> - ������String���ͣ�����value��С����10k���ڡ� +> - ������hash��list��set��zset���ͣ�Ԫ�ظ���һ�㲻����5000�� + + +**�ڶ���**��Ҫѡ���ʺε��������͡�����С����ֻ��Redis��String���ͣ���������set��get��ɻ���ϣ�Redis �ṩ��**�ḻ�����ݽṹ����**����Щҵ�񳡾������ʺ�```hash��zset```���������ݽ����� + +![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���������ھͲ���̫�������� +- ���x�����key��һ�㽨����**expire���ù���ɦ��**�� + + +����������key��ij��ɦ���㼯�й��ڣ������ڵ��Ǹ�ɦ���㣬Redis���ܻ����ڿ��٣���������**����ѩ��**����������һ�㲻ͬҵ����key������ɦ��Ӧ�÷�ɢһЩ����ɦ����ͬҵ���ģ�Ҳ������ɦ���ϼ�һ������ֵ���ù���ɦ����ɢһЩ�� + + +### 1.4.����ʹ��������������Ч�� + +�����ճ�дSQL��ɦ�򣬶�֪������������Ч�ʻ����ߣ�һ�θ���50���ѭ��50�Σ�ÿ�θ���һ��Ч�y��ߡ���ɻRedis��������Ҳ���������� + +Redis�ͻ���ִ��һ�������ɷ�Ϊ4�����̣�1.��������-> 2.�����Ŷ�-> 3.����ִ��-> 4. ���ؽ�����1��4 ��ΪRRT������ִ������ɦ�䣩�� Redis�ṩ��**�������������mget��mset**�ȣ�����Ч��ԼRRT�������أ��󲿷ֵ�����Dz�֧�����������ģ�����hgetall����û��mhgetall���ڡ�**Pipeline** �����Խ����������⡣ + +> Pipeline��jô��?���ܽ�һ��Redis����������װ��ͨ��һ��RTT������Redis���ٽ�����Redis������ִ�н�����˳�򷵻ظ��ͻ���. + +������������û��ʹ��Pipelineִ����n��������ģ�ͣ� + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b71283614a3344c4afac8ac82438fa44~tplv-k3u1fbpfcp-watermark.image) + +ʹ��Pipelineִ����n���������������Ҫ1��RTT��ģ�����£� + +![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b865d241c02d45e7a88540ab7f70280a~tplv-k3u1fbpfcp-watermark.image) + +## 2��Redis �пӵ���Щ���� + + +### 2.1. ����```O(n)```���Ӷ������```hgetall```��```smember```��```lrange```�� + +��ΪRedis�ǵ��߳�ִ�������ġ�hgetall��smember������ɦ�临�Ӷ�ΪO(n)����n��������ɦ���ᵼ�� Redis CPU ����쭸ߣ���������������ִ�С� + +> hgetall��smember��lrange����Щ�����һ������ʹ�ã���Ҫ�ۺ���������������ȷn��ֵ����ȥ��� +> ����hgetall��������ϣԪ��n�Ƚ϶��Ļ����������ȿ���ʹ��**hscan**�� + + +### 2.2 ����Redis��monitor���� + +Redis Monitor ��������ɻɦ��ӡ��Redis���������յ����������������֪���ͻ��˶�redis������������Щ��������Ϳ�����Monitor �����鿴��������һ��**����**�ö��ѣ�������Ҫ���������ã���Ϊ**monitor�������ܵ���redis���ڴ�����������** + +> monitor��ģ���ǽ��εģ����Ὣ������Redis������ִ�е���������������һ������Redis��������QPS�Ǻܸߵģ�Ҳ��������ִ����monitor���Redis��������Monitor�����ͻ��˵���������ֻ��д����������Ҳ��ռ���˴���Redis�ڴ档 + + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3610a4a95fe4995862d9ddd878269fe~tplv-k3u1fbpfcp-watermark.image) + + +### 2.3����������ʹ�� keysָ�� + +Redis Keys �������ڲ������з��ϸ���ģʽpattern��key���������鿴Redis ij���͵�key�ж��يٴ����С�����뵽��keys������£� + +``` +keys keyǰ׺* +``` + +���ǣ�redis��```keys```�DZ���ƥ���ģ����Ӷ���```O��n��```�����ݿ�����Խ����Խ��������֪����redis�ǵ��̵߳ģ��������ݱȽ϶��Ļ���keysָ���ͻᵼ��redis�߳����������Ϸ���Ҳ��ͣ���x�ֱ��ָ��ִ���꣬�����Ż��ָ������x�**һ������������Ҫʹ��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 ��ֹʹ��flushall��flushdb + +> - Flushall ���������������� Redis ������������(ɾ���������ݿ������� key )�� +> - Flushdb �����������յ�ǰ���ݿ��е����� key�� + +����������ԭ���Եģ�������ִֹ�С�һ����ʼִ�У�����ִ��ʧ�ܵġ� + +### 2.5 ע��ʹ��del���� + +ɾ��key��һ��ʹ��jô�����ֱ��del������ɾ��һ��key��ֱ��ʹ��del���Ȼû���⡣���ǣ�������del��ɦ�临�Ӷ��Ƕ�������Ƿ�����̽��һ�£� +- ����ɾ��һ��String���͵�key��ɦ�临�ӶȾ���```O��1��```��**����ֱ��del**�� +- ����ɾ��һ��List/Hash/Set/ZSet����ɦ�����ĸ��Ӷ���```O(n)```, n��ʾԪ�ظ����� + +���x�������ɾ��һ��List/Hash/Set/ZSet���͵�keyɦ��Ԫ��Խ�࣬��Խ����**��n�ܴ�ɦ��Ҫ����ע��**�����������̵߳ġ���ô����������del������Ӧ����ôɾ���أ� + +> - ������List���ͣ�������ִ��```lpop����rpop```��ֱ������Ԫ��ɾ�����ɡ� +> - ������Hash/Set/ZSet���ͣ���������ִ��```hscan/sscan/scan```��ѯ����ִ��```hdel/srem/zrem```����ɾ��ÿ��Ԫ�ء� + +### 2.6 ����ʹ��SORT��SINTER�ȸ��Ӷȹ��ߵ���� + +ִ�и��ӶȽϸߵ���������ĸ����� CPU ��Դ�����������̡߳�������Ҫ����ִ����```SORT��SINTER��SINTERSTORE��ZUNIONSTORE��ZINTERSTORE```�Ⱦۺ����һ�㽨�������ŵ��ͻ�����ִ�С� + +## 3����L·ɻս�ܿӲ��� + +### 3.1 �ֲ�ʽ��ʹ�õ�ע���� + +�ֲ�ʽ����ɻ���ǣ����Ʒֲ�ʽεͳ��ͬ���̹�ͬ���ʹ�����Դ��һ������ɻ�֡���ɱ�μ����������ȵ�ҵ�񳡾�������Ҫ�õ��ֲ�ʽ�������Ǿ���ʹ��Redis��Ϊ�ֲ�ʽ������Ҫ����Щע���㣺 + +#### 3.1.1 ��������SETNX + EXPIRE�ֿ�д�����ʹ���ɻ�ַ����� +``` +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���ù���ɦ��ɦ������crash����Ҫ����ά���x���ô�������͡��������ϡ��x�**�����߳���Զ��ȡ������**��������һ���ֲ�ʽ��������ôɻ�֡� + +#### 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()) { + + // ���ѹ��ڣ���ȡ��һ�����Ĺ���ɦ�䣬�������������Ĺ���ɦ�䣨���˽�redis��getSet������С���飬����ȥ�������1��� + String oldValueStr = jedis.getSet(key_resource_id, expiresStr); + + if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { + // ���Ƕ��̲߳�����������ֻ��һ���̵߳�����ֵ�͵�ǰֵ��ͬ�����s��Լ��� + return true; + } +} + +//������������ؼ���ʧ�� +return false; +} +``` +���ַ�����**ȱ��**�� +> - ����ɦ���ǿͻ����Լ����ɵģ��ֲ�ʽ�����£�ÿ���ͻ��˵�ɦ������ͬ�� +> - û�б��������ߵ�Ψһ��ʁ�����ܱ����L·ͻ����ͷ�/������ +> - �����ڵ�ɦ�򣬲��������ͻ���ͬɦ������������ִ����```jedis.getSet()```������ֻ����һ���ͻ��˼����ɹ������Ǹÿͻ������Ĺ���ɦ�䣬���ܱ����L·ͻ��˸��ǡ� + +#### 3.1.3�� SET����չ���SET EX PX NX����ע�����ܴ��ڵ����⣩ + +``` +if��jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1��{ //���� + try { + do something //ҵ������ + }catch(){ +����} +����finally { + jedis.del(key_resource_id); //�ͷ��� + } +} +``` + +��������ǿ��ܴ������⣺ +- �������ͷ��x�ҵ����ûִ���ꡣ +- ���������߳���ɾ�� + + +#### 3.1.4 SET EX PX NX + У��Ψһ����ֵ,��ɾ������������ɾ���⣬���Ǵ��������ڣ�ҵ��ûִ���������⣩ + +``` +if��jedis.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()�ͷ�����ɦ�򣬿����������Ѿ������ڵ�ǰ�ͻ��x����������˼ӵ����� + + +![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) +> �����߳�һ��Redis��master�ڵ����õ������Ǽ�����key��ûͬ����slave�ڵ㡣ǡ����ɦ��master�ڵ㷢�����ϣ�һ��slave�ڵ��ͻ�����Ϊmaster�ڵ㡣�̶߳��Ϳ��Ի�ȡͬ��key�����������߳�һҲ�Ѿ��õ����x����İ�ȫ�Ծ�û�l� + +���Ե������⣬����ʹ��Redlock�㷨������Ȥ�����ѿ��Կ�������ƪ���1���[���ַ�����̽��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��֤�� + +����Ȥ�����ѣ����Կ�������ƪ���1���[���������£��Ȳ������ݿ⻹���Ȳ���棿](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 ���洩͸���� + +������һ����Ļ���ʹ�÷�ʽ�����������x��Ȳ��»��棬������ֵ���У���ֱ�ӷ��أ�����û���У���ȥ�����ݿ⣬Ȼ�������ݿ���ֵ���μ����棬�ۇٴ��ء� + + +![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59c69359b2c249ad8954b7da7c0e6fb8~tplv-k3u1fbpfcp-watermark.image) + +**���洩͸**��ָ��ѯһ��һ�������ڵ����ݣ����ڻ����Dz�����ɦ��Ҫ�����ݿ���ѯ���鲻����������д�뻺�棬�皿����������ڵ�����ÿ��������Ҫ�����ݿ�ȥ��ѯ�����������ݿ�����ѹ���� + +> ͨ�׵�˵������������ɦ�����������ݿⶼû��ij��ֵ�������ͻᵼ��ÿ�ζ�����ֵ�IJ�ѯ���󶼻ᴩ͸�����ݿ⣬�����ǻ��洩͸�� + +���洩͸һ�㶼���弓�����������ģ� + +- **ҵ�񲻺���������**�������������û���û���ػ�����������ÿ��������ȥ���棬��ѯij��userid��ѯ��û���ػ��� +- **ҵ��/��ά/����ʧ���IJ���**�����绺�������ݿ������ݶ�����ɾ���l� +- **�ڿͷǷ����󹥻�**�������ڿ͹������������Ƿ��������Զ�ȡ�����ڵ�ҵ�����ݡ� + +**���α��⻺�洩͸�أ�** һ�������ַ����� + +- 1. �����ǷǷ�������������API���ڣ��Բ�������У�飬���˷Ƿ�ֵ�� +- 2. ������ѯ���ݿ�Ϊ�գ����ǿ��Ը��������ø���ֵ������Ĭ��ֵ������������д���������Ļ�����Ҫ���»��������Ա�֤����һ���ԣ�ͬɦ�����������������ɻ��Ĺ���ɦ�䡣��ҵ���ρȽϳ��ã�������Ч�� +- 3. ʹ�ò�¡�����������ж������Ƿ����ڡ���һ����ѯ��������ɦ����ͨ����¡�������ж�ֵ�Ƿ����ڣ����ڲż������2顣 +> ��¡������ԭ�����ɳ�ʼֵΪ0��λͼ������N����ϣ�������ɡ�һ����һ��key����N��hash�㷨��ȡN��ֵ���ڱ��������н���N��ֵɢ�к��趨Ϊ1��Ȼ������ɦ�������ض����弓��λ�ö�Ϊ1����ô��¡�������жϸ�key���ڡ� + + +### 3.5 ����ѩ������ + +**����ѩ����** ָ���������ݴ�����������ɦ�䣬����ѯ�������޴���������ֱ�ӷ������ݿ⣬�������ݿ�ѹ����������down���� + +- ����ѩ��һ�������ڴ�������ͬɦ�������ɵģ���������ԭ�򣬿�ͨ���������ù���ɦ��������ù���ɦ��������ɢһ�㡣������һ���Θ��̶�ֵ+һ����С������ֵ��5Сɦ+0��1800�뽴�ϡ� +- Redis ����崻�Ҳ�������𻺴�ѩ����������Ҫ����Redis�߿��ü�Ⱥ���� + + +### 3.6 ������������ + +**�������** ָ�ȵ�key��ij��ɦ�������ڵ�ɦ�򣬶�ǡ��������ɦ����������Key�д����IJ��������������Ӷ���������������db�� + +����������е�������ɻ���������ǣ�����ѩ����ָ���ݿ�ѹ����������down��������ֻ�Ǵ���������������DB���ݿ����档������Ϊ�����ǻ���ѩ����һ���Ӽ��ɡ���Щ������Ϊ�������������������ڻ�������ijһ�ȵ�key���棬ѩ�����Ǻܶ�key�� + +������������֣� + +- **1.ʹ�û�����**������ʧЧɦ����������ȥ����db���ݣ�������ʹ��ijЩ���ɹ����ص�ԭ�Ӳ��������(Redis��setnx��ȥ����ɹ���ɦ������ȥ����db���ݿ����ݺ����û��档������ȥ���Ի�ȡ���档 +- **2. ���������ڡ�**����ָû�����ù���ɦ�䣬�����ȵ����ݿ�Ҫ����ɦ���첽�߳�ȥ���o����ù���ɦ�䡣 + +### 3.7��������key���� + +��Redis�У����ǰѷ���Ƶ�yߵ�key����Ϊ�ȵ�key������ijһ�ȵ�key�����󵽷���������ɦ�������������ر��󣬿��ܻᵼ��������Դ���㣬����崻����Ӷ�Ӱ�������ķ����� + +���ȵ�Key����ô�������أ���Ҫԭ���������� +> - �û����ѵ�����Զ�������������ݣ�����ɱ���ȵ����ŵȶ���д�اٴij����� +> - ������Ƭ���У�������Redi�����������ܣ������̶�����key��Hash����ͬһ̨��������˲�����������󣬳�������ƿ���������ȵ�Key���⡣ + +��ô���ճ������У�����ʁ�����ȵ�key�أ� +> - ƾ�����ж���Щ����Key�� +> - �ͻ���ͳ���ρ��� +> - �����������ρ� + +���ν�����key���⣿ + +> - Redis��Ⱥ���ݣ����ӷ�Ƭ������������������ +> - ����key����hashɢ�У����罫һ��key����Ϊkey1,key2����keyN��ͬ��������N��ݣ�N��ݷֲ�����ͬ��Ƭ������ɦ����������N����е�һ���һ���ֵ��������� +> - ʹ�ö������棬��JVM���ػ���,����Redis�Ķ������� + +## 4. Redis������ά + +### 4.1 ʹ�ó����Ӷ����Ƕ����ӣ����Һ������ÿͻ��˵����ӳ� + +- ����ʹ�ö����ӣ�ÿ�ζ���Ҫ�� TCP �������֡��Ĵλ��֣������Ӻ�ɦ��Ȼ�������ӵĻ���������һ�����ӣ�redis����������һֱʹ�ã����Ͽ��Լ��ٽ���redis����ɦ�䡣 +- ���ӳؿ���ɻ���ڿͻ��˽����������Ӳ��Ҳ��ͷţ���Ҫʹ�����ӵ�ɦ�򣬲���ÿ�ζ��������ӣ���ʡ�˺�ɦ��������Ҫ�������ò�������ɦ�䲻���� Redisɦ��Ҳ�輰ɦ�ͷ�������Դ�� + +### 4.2 ֻʹ�� db0 + +Redis-standalone�ܹ���ֹʹ�÷�db0.ԭ�������� + +- һ�����ӣ�Redisִ������select 0��select 1�л������������ܡ� +- Redis Cluster ֻ֧�� db0��ҪǨ�ƵĻ����ɱ��� + +### 4.3 ����maxmemory + ǡ������̭���ԡ� + +Ϊ�˷�ֹ�ڴ���ѹ���͡�������Щɦ����ҵ�����������x�redis��key������ʹ�ã��ڴ�ֱ�Ӳ����x���άС����Ҳ���ǼӴ��ڴ��l��ѵ�redisֱ�������ҵ���������Ҫ����ɻ��ҵ����ѡ��maxmemory-policy(�����ڴ���̭����)�����úù���ɦ�䡣һ����8���ڴ���̭���ԣ� + + - volatile-lru�����ڴ治����������д������ɦ���������˹���ɦ����key��ʹ��LRU����������ʹ�ã��㷨������̭�� +- allkeys-lru�����ڴ治����������д������ɦ��������key��ʹ��LRU����������ʹ�ã��㷨������̭�� +- volatile-lfu��4.0�汾������ڴ治����������д������ɦ���ڹ��ڵ�key�У�ʹ��LFU�㷨����ɾ��key�� +- allkeys-lfu��4.0�汾������ڴ治����������д������ɦ��������key��ʹ��LFU�㷨������̭�� +- volatile-random�����ڴ治����������д������ɦ���������˹���ɦ����key�У�������̭���ݣ��� +- allkeys-random�����ڴ治����������д������ɦ��������key��������̭���ݡ� +- volatile-ttl�����ڴ治����������д������ɦ���������˹���ɦ����key�У����ݹ���ɦ��������̭��Խ�����ڵ����ȱ���̭�� +- noeviction��Ĭ�ς��ԣ����ڴ治����������д������ɦ����д�������ᱨ�� + +### 4.4 ���� lazy-free ���� + +Redis4.0+�汾֧��lazy-free���ƣ���������Redis������bigKey�����������ڣ�������lazy-free���������������Redis ����ɾ��һ�� 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円200円.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円200円.md" new file mode 100644 index 0000000..da7d07f --- /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円200円.md" @@ -0,0 +1,18 @@ +### 问题一:websocket接收客户端的图片,断开问题 + +>当时用websocket接收客户端的图片,总是断开连接,tomcat websocket默认的接收字节消息最大是8192 bytes,由于图片有100k+,所以会断开连接。 + +**解决方法:** + +是客户端建立连接后,用session.setMaxBinaryMessageBufferSize()方法修改默认的字节大小 + +![](https://user-gold-cdn.xitu.io/2020/7/4/1731a4cdd2291bc9?w=800&h=491&f=png&s=201414) + +### 问题二: Redis连接超时怎么排查 +- 1.到最大连接数上限了(看Redis配置) +- 2.项目中连接的从来没释放过(Redis满载情况,看代码逻辑,是否有死循环) +- 3.基本配置,开放的端口配置(看服务端是否临时关闭) +- 4.服务器不稳定(ping一下或者用工具查看网络波动情况) + +### 问题三:多个list的交集有没有优雅的方法 +用谷歌的工具包,guava交并补 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