暂时未有相关云产品技术能力~
背景做过 JavaWeb 开发的对过滤器和拦截器肯定不会陌生,而且也会熟练的使用,但是关于过滤器和拦截器具体的区别和差异可能不是特别的了解,这篇文章就跟大家介绍下过滤器和拦截器的区别。过滤器 Filter首先介绍下什么是过滤器,过滤器英文叫 Filter,是 JavaEE 的标准,依赖于 Servlet 容器,使用的时候是配置在 web.xml 文件中的,可以配置多个,执行的顺序是根据配置顺序从上到下。常用来配置请求编码以及过滤一些非法参数,垃圾信息或者是网站登录验证码。 <!-- filter --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- filter end -->参考实现public class CaptchaFilter implements Filter { public void init(FilterConfig config) throws ServletException { } public void destroy() { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String servletPath = request.getServletPath(); //获取验证码 if(servletPath.matches("/captcha.jpg")) { response.setContentType("image/jpeg"); //禁止图像缓存。 response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); //参数:宽、高、字符数、干扰量 CaptchaProductor vCode = new CaptchaProductor(70,30,4,75); //根据token保存验证码内容 CaptchaBean bean = new CaptchaBean(); bean.setCaptcha(vCode.getCode()); bean.setCreateTime(new Date()); HttpSessionUtils.setSessionValue(request, "sessionCaptcha", bean); vCode.write(response.getOutputStream()); return; } } }过滤器的实现可以通过实现 Filter 接口或者继承 Spring 的org.springframework.web.filter.OncePerRequestFilter 来实现。拦截器 Interceptor拦截器 Interceptor 不依赖 Servlet 容器,依赖 Spring 等 Web 框架,在 SpringMVC 框架中是配置在SpringMVC 的配置文件中,在 SpringBoot 项目中也可以采用注解的形式实现。拦截器是 AOP 的一种应用,底层采用 Java 的反射机制来实现的。与过滤器一个很大的区别是在拦截器中可以注入 Spring 的 Bean,能够获取到各种需要的 Service 来处理业务逻辑,而过滤器则不行。 <!-- 拦截器 --> <mvc:interceptors> <!-- 多个拦截器,顺序执行 --> <bean class="com.test.admin.interceptor.AuthInterceptor"/> </mvc:interceptors>参考实现public class AuthInterceptor extends HandlerInterceptorAdapter { @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //todo super.postHandle(request, response, handler, modelAndView); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //todo return super.preHandle(request, response, handler); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }拦截器的实现可以通过继承org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 来实现。执行顺序因为我们的过滤器和拦截器都可以配置多个,那么关于各自的执行顺序是什么样子的呢?过滤器的执行顺序首先跟在 web.xml 中配置的顺序有关,先配置的先执行,但是并不是说是等上一个过滤器执行结束了再执行下一个,它们之间是通过链来执行的,具体的过滤器和拦截器的执行过程我画了个图,可以看下。小结今天简单的给大家介绍了过滤器和拦截器的区别和使用,希望对大家有帮忙。平时的工作中可能这些东西都是组长或者架构师搭建好的,自己只关注业务逻辑,但是很多时候我们还是要知其然知其所以然,多了解一些对自己是很有帮助的。598)]小结今天简单的给大家介绍了过滤器和拦截器的区别和使用,希望对大家有帮忙。平时的工作中可能这些东西都是组长或者架构师搭建好的,自己只关注业务逻辑,但是很多时候我们还是要知其然知其所以然,多了解一些对自己是很有帮助的。关于过滤器与拦截器,你学废了么?
查询重写插件从MySQL 5.7.6开始,MySQL Server支持查询重写插件,可以在服务器执行之前检查并可能修改服务器接收的语句。以下是官方文档介绍:预解析重写插件具有以下特点:1.该插件允许在服务器处理之前重写到达服务器的SQL语句。2.该插件接收一个语句字符串,并可能返回一个不同的字符串。后解析查询重写插件具有以下特征:1.该插件支持基于解析树的语句重写。2.服务器解析每个语句并将其解析树传递给插件,插件可以遍历树。插件可以将原始树返回到服务器以进行进一步处理,或者构造不同的树并返回该树。通俗来讲,是指该插件支持两种重写方式,一种是在语法解析之前,直接修改SQL字符串,一种是在语法解析之后,通过操控语法解析树来进行重写。这个特性还是非常有用的,例如错误的上线了某个SQL,但由于无法走到索引导致全库查询; 或者你可能使用某个第三方的已编译好的软件,但SQL可能执行错误,你又无法直接修改应用,这个特性将会非常有用,还可以去编写符合用户要求的插件。安装或卸载最简单的安装过程如下:可以发现,在数据库中多增加了一个库query_rewrite,查看该数据库:查看插件当前是否安装:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-szdlBKmr-1660788190224)(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)]实践操作例如为如下语句强制使用主键查询:改写成:要为Rewriter插件添加规则,具体步骤分为两步:1.向rewrite_rules表中添加相应的规则;2.调用flush_rewrite_rules()存储过程以将表中的规则加载到插件中。输出的规则每一列的内容如下:重写器查询重写插件过程将规则添加到 rewrite_rules表中不足以使Rewriter插件使用该规则。还必须调用flush_rewrite_rules()以将表内容加载到插件内存缓存中:Rewriter插件操作使用存储过程将规则表加载到其内存缓存中,在正常操作下,用户仅调用flush_rewrite_rules()从而将rewrite_rules表的内容加载到Rewriter内存高速缓存中。加载表后,它还会清除查询缓存。当修改规则表后,需要重新调用此过程以使插件从新表内容更新其缓存:使用重写插件中定义的语句模式查询相应记录:通过使用explain语句查看,当前SQL已经使用了索引重写插件操作信息该Rewriter插件通过几个状态变量提供有关其操作的信息:有关这些变量的说明:Rewriter_number_loaded_rules:成功从rewrite_rules表中加载到内存中以供Rewriter 插件使用的重写插件重写规则的数量。Rewriter_number_reloads:rewrite_rules被加载到Rewriter插件缓存中的次数。Rewriter_number_rewritten_queries:Rewriter查询重写插件自加载以来重写的查询数 。Rewriter_reload_error:是否在最近将rewrite_rules表加载到Rewriter 插件使用的内存高速缓存中时发生错误 。如果值为OFF,则不会发生错误。如果值为,则ON发生错误;检查表的message列rewriter_rules是否有错误消息。通过调用flush_rewrite_rules()存储过程加载规则表时 ,如果某些规则发生错误,则该CALL 语句会产生错误,并且该插件会将 Rewriter_reload_error状态变量设置为ON:在这种情况下,请检查rewrite_rules表中是否包含非NULL message列值的行,以查看存在的问题。重写器插件使用字符集当rewrite_rules表加载到Rewriter插件中时,插件使用character_set_client系统变量的当前全局值来解释语句 。如果character_set_client随后更改全局 值,则必须重新加载规则表。客户端的会话character_set_client值必须 与加载规则表时的全局值相同,否则规则匹配将不适用于该客户端。变量的当前全局值来解释语句 。如果character_set_client随后更改全局 值,则必须重新加载规则表。客户端的会话character_set_client值必须 与加载规则表时的全局值相同,否则规则匹配将不适用于该客户端。关于MySQL查询重写插件,你学废了么?
Java 中 hashCode() 和 equals() 的关系是面试中的常考点,如果没有深入思考过两者设计的初衷,这个问题将很难回答。除了应付面试,理解二者的关系更有助于我们写出高质量且准确的代码。一.基础:hashCode() 和 equals() 简介在学习 hashCode() 和 equals() 之间的关系之前, 我们有必要先单独地了解他俩的特点.equals()equals() 方法用于比较两个对象是否相等,它与 == 相等比较符有着本质的不同。在万物皆对象的 Java 体系中,系统把判断对象是否相等的权力交给程序员。具体的措施是把 equals() 方法写到 Object 类中,并让所有类继承 Object 类。这样程序员就能在自定义的类中重写 equals() 方法, 从而实现自己的比较逻辑。hashCode()hashCode() 的意思是哈希值, 哈希值是经哈希函数运算后得到的结果,哈希函数能够保证相同的输入能够得到相同的输出(哈希值),但是不能够保证不同的输入总是能得出不同的输出。当输入的样本量足够大时,是会产生哈希冲突的,也就是说不同的输入产生了相同的输出。暂且不谈冲突,就相同的输入能够产生相同的输出这点而言,是及其宝贵的。它使得系统只需要通过简单的运算,在时间复杂度O(1)的情况下就能得出数据的映射关系,根据这种特性,散列表应运而生。一种主流的散列表实现是:用数组作为哈希函数的输出域,输入值经过哈希函数计算后得到哈希值。然后根据哈希值,在数组种找到对应的存储单元。当发生冲突时,对应的存储单元以链表的形式保存冲突的数据。二. 漫谈:初识 hashCode() 与 equals() 之间的关系下面我们从一个宏观的角度讨论 hashCode() 和 equals() 之间的关系。在大多数编程实践中,归根结底会落实到数据的存取问题上。在汇编语言时代,你需要老老实实地对每个数据操作编写存取语句。而随着时代发展到今天,我们都用更方便灵活的高级语言编写代码,比如 Java。Java 以面向对象为核心思想,封装了一系列操作数据的 api,降低了数据操作的复杂度。但在我们对数据进行操作之前,首先要把数据按照一定的数据结构保存到存储单元中,否则操作数据将无从谈起。然而不同的数据结构有各自的特点,我们在存储数据的时候需要选择合适的数据结构进行存储。Java 根据不同的数据结构提供了丰富的容器类,方便程序员选择适合业务的容器类进行开发。通过继承关系图我们看到 Java 的容器类被分为 Collection 和 Map 两大类,Collection 又可以进一步分为 List 和 Set。 其中 Map 和 Set 都是不允许元素重复的,严格来说Map存储的是键值对,它不允许重复的键值。值得注意的是:Map 和 Set 的绝大多数实现类的底层都会用到散列表结构。讲到这里我们提取两个关键字不允许重复和散列表结构,回顾 hashCode() 和 equals() 的特点,你是否想到了些什么东西呢?三. 解密:深入理解 hashCode() 和 equals() 之间的关系equals() 会有力不从心的时候上面提到 Set 和 Map 不存放重复的元素(key),这些容器在存储元素的时必须对元素做出判断:在当前的容器中有没有和新元素相同的元素?你可能会想:这容易呀,直接调用元素对象的 equals() 方法进行比较不就行了吗?如果容器中的存储的对象数量较少,这确实是个好主意,但是如果容器中存放的对象达到了一定的规模,要调用容器中所有对象的 equals() 方法和新元素进行比较,就不是一件容易的事情了。就算 equals() 方法的比较逻辑简单无比,总的来说也是一个时间复杂度为 O(n) 的操作啊。hashCode() 小力出奇迹但在散列表的基础上,判断“新对象是否和已存在对象相同”就容易得多了。由于每个对象都自带有 hashCode(),这个 hashCode 将会用作散列表哈希函数的输入,hashCode 经过哈希函数计算后得到哈希值,新对象会根据哈希值,存储到相应的内存的单元。我们不妨假设两个相同的对象,hashCode() 一定相同,这么一来就体现出哈希函数的威力了。由于相同的输入一定会产生相同的输出,于是如果新对象,和容器中已存在的对象相同,新对象计算出的哈希值就会和已存在的对象的哈希值产生冲突。这时容器就能判断:这个新加入的元素已经存在,需要另作处理:覆盖掉原来的元素(key)或舍弃。按照这个思路,如果这个元素计算出的哈希值所对应的内存单元没有产生冲突,也就是没有重复的元素,那么它就可以直接插入。所以当运用 hashCode() 时,判断是否有相同元素的代价,只是一次哈希计算,时间复杂度为O(1),这极大地提高了数据的存储性能。Java 设计 equals(),hashCode() 时约定的规则前面我们还提到:当输入样本量足够大时,不相同的输入是会产生相同输出的,也就是形成哈希冲突。这么一来就麻烦了,原来我们设定的“如果产生冲突,就意味着两个对象相同”的规则瞬间被打破,因为产生冲突的很有可能是两个不同的对象!而令人欣慰的是我们除了 hashCode() 方法,还有一张王牌:equals() 方法。也就是说当两个不相同的对象产生哈希冲突后,我们可以用 equals() 方法进一步判断两个对象是否相同。这时 equals() 方法就相当重要了,这个情况下它必须要能判定这两个对象是不相同的。讲到这里就引出了 Java 程序设计中一个重要原则:如果两个对象是相等的,它们的 equals() 方法应该要返回 true,它们的 hashCode() 需要返回相同的结果。但有时候面试不会问得这么直接,他会问你:两个对象的 hashCdoe() 相同,它的 equals() 方法一定要返回 true,对吗?那答案肯定不对。因为我们不能保证每个程序设计者,都会遵循编码约定。有可能两个不同对象的hashCode()会返回相同的结果,但是由于他们是不同的对象,他们的 equals() 方法会返回false。如果你理解上面的内容,这个问题就很好解答,我们再回顾一下:如果两个对象的 hashCode() 相同,将来就会在散列表中产生哈希冲突,但是它们不一定是相同的对象呀。当产生哈希冲突时,我们还得通过 equals() 方法进一步判断两个对象是否相同,equals() 方法不一定会返回 true。这也是为什么 Java 官方推荐我们在一个类中,最好同时重写 hashCode() 和 equals() 方法的原因。四. 验证:结合 HashMap 的源码和官方文档,验证两者的关系以上的文字,是我经过思考后得出的,它有一定依据但并非完全可靠。下面我们根据 HashMap 的源码(JDK1.8)和官方文档,来验证这些推论是否正确。通过阅读JDK8的官方文档,我们发现 equals() 方法介绍的最后有这么一段话:Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.官方文档提醒我们当重写 equals() 方法的时候,最好也要重写 hashCode() 方法。也就是说如果我们通过重写 equals() 方法判断两个对象相同时,他们的hash code也应该相同,这样才能让hashCode()方法发挥它的作用。那它究竟能发会怎样的作用呢?我们结合部分较为常用的 HashMap 源码进一步分析。(像 HashSet 底层也是通过 HashMap 实现的)在 HashMap 中用得最多无疑是 put() 方法了,以下是put()的源码:public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } 我们可以看到 put() 方法实际调用的是 putVal() 方法,继续跟进: final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //在我们创建HashMap对象的时候, 内存中并没有为HashMap分配表的空间, 直到往HashMap中put添加元素的时候才调用resize()方法初始化表 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;//同时确定了表的长度 //((n - 1) & hash)确定了要put的元素的位置, 如果要插入的地方是空的, 就可以直接插入. if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {//如果发生了冲突, 就要在冲突位置的链表末尾插入元素 Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //关键!!!当判断新加入的元素是否与已有的元素相同, 首先判断的是hash值, 后面再调用equals()方法. 如果hash值不同是直接跳过的 e = p; else if (p instanceof TreeNode)//如果冲突解决方案已经变成红黑树的话, 按红黑树的策略添加结点. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//解决冲突的方式仍是链表 for (int binCount = 0; ; ++binCount) {//找到链表的末尾, 插入. if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash);//插入之后要判断链表的长度, 如果到达一定的值就可能要转换为红黑树. break; }//在遍历的过程中仍会不停地判定当前key是否与传入的key相同, 判断的第一条件仍然是hash值. if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount;//修改map的次数增加 if (++size > threshold)//如果hashMap的容量到达了一定值就要进行扩容 resize(); afterNodeInsertion(evict); return null; } 我们可以看到每当判断 key 是否相同时,首先会判断 hash 值,如果 hash 值相同(产生了冲突),然后会判断 key 引用所指的对象是否相同,最终会通过 equals() 方法作最后的判定。如果 key 的 hash 值不同,后面的判断将不会执行,直接认定两个对象不相同。if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; 五. 结束讲到这里希望大家对 hashCode() 与 equals() 方法能有更深入的理解,明白背后的设计思想与原理。我之前有一个疑问,可能大家看完这篇文章后也会有:equals() 方法平时我会用到,所以我知道它除了和 hashCode() 方法有密切联系外,还有别的用途。但是hashCode()呢?它除了和equals()方法有密切联系外,还有其他用途吗?经过在互联网上一番搜寻,我目前给出的答案是没有。也就是说 hashCode() 仅在散列表中才有用,在其它情况下没用。当然如果这个答案不正确,或者你还有别的思考,欢迎留言与我交流~关于hashCode() 和 equals(),你学废了么?
我们都知道,MySQL中关于字符,有char和varchar两种常用的类型,可能在平时的使用过程中,大家不会去关心这两种类型的区别,只是会用就可以了,或者说看到过一些它们的区别,但是没有时间去测试,今天有时间了,我将这两种类型的具体情况实验一把,让大家直观感受下,纯属分享,大神请绕道。 先说说理论吧。 char类型为固定长度的字符串,比如说char(10),它定义了指定的字符串长度最大为10个字符,如果你现在输入一个字符串为’12345678’,那么它在char类型中到底会占用多少个字符呢?答案是10个,后面缺少的2个字符,MySQL会自动补充为空值,然后进行存放。在取这个记录的时候,char类型的会使用trim()函数去掉多余的空格,所以我们看到的还是8个字符的记录。当输入的字符长度大于最大的长度时,MySQL会自动报错。 varchar类型是长度可变的字符串,varchar(M)表示最大长度是M个字符,varchar的最大实际长度由最大的行的大小和使用的字符集确定。例如varchar(50)定义了一个最大长度为50的字符串,如果插入的字符串只有20个字符,那么实际存储的字符串具有21个字符,因为varchar会自动包含一个字符串结束字符。varchar在值保存和检索时,尾部的空格仍然保留。 介绍完概念,我们来看具体的实践过程,本文中使用的测试版本为MySQL5.7.22版本。1.测试char的trim()功能 首先创建一个表,这个表里面包含两个字段,d_char和d_varchar,设定初始的字符长度都为4,如下:查看一下,此时,我们插入两条记录,每条记录都是’ab ',注意,ab后面有2个空格,然后我们使用mysql里面的concat函数进行字符连接,给每条记录的左右分别添加小括号,此时我们可以看到,d_char的ab后面的空格被取消掉了,而d_varchar后面的空格还依旧存在。2.测试两种字符类型的最大长度首先看看char的最大长度,我们设置的值为256,结果如下所以,char类型的长度取值范围为0~255个字符上面提到了varchar的最大实际长度由最大的行的大小和使用的字符集确定,这里我们进行实验:可以看到,字符集不一样,最后的max的值也是不一样的,utf8模式下是0~21845,一个字符占三个字节,最多能存 21844 个字符latin1模式下是0~65535,一个字符占一个字节,最多能存放 65532 个字符gbk模式下是0~32767,一个字符占两个字节,最多能存 32766 个字符若定义的时候超过上述限制,则varchar字段会被强行转为text类型,并产生warning。可能这里有人要问了,为什么最大值是32767,而最多只能放32766个字符呢?举两个例说明一下实际长度的计算。a) 若一个表只有一个varchar类型,如定义为create table t4(c varchar(N)) charset=gbk;则此处N的最大值为(65535-1-2)/2= 32766 个字符。减1的原因是实际行存储从第二个字节开始’;减2的原因是varchar头部的2个字节表示长度;除2的原因是字符编码是gbk。b) 若一个表定义为create table t4(c int, c2 char(30), c3 varchar(N)) charset=utf8;则此处N的最大值为 (65535-1-2-4-30*3)/3=21812减1和减2与上例相同;减4的原因是int类型的c占4个字节;减30*3的原因是char(30)占用90个字节,编码是utf8。如果被varchar超过上述的b规则,被强转成text类型,则每个字段占用定义长度为11字节,当然这已经不是“varchar”了。则此处N的最大值为 (65535-1-2-4-30*3)/3=218123.MySQL的字段长度模式 字段长度的模式分为严格模式和不严格模式,在严格模式下,如果我们想给一个字段中插入一个大于规定长度的字符串,MySQL会给出错误提示,例如我们的表:当我们插入一个大于4字符的记录时,如果在非严格模式下,mysql会自动截断超出最大长度的字符,上面的操作是,我们先把字段模式改为非严格模式,然后查询更改,确保更改生效,接着我们插入’abcde’字符串,发现它可以被成功执行,但是包含两个警告,查看警告可以发现,一些数据被截断了,实验部分的内容基本就完成了,这里我们进行几点分析:1.MySQL为什么要设置这两种类型?它们各自有什么优点? char是固定长度的,它的存取速度比varchar快,方便程序的存储于查找,但是它需要浪费一定的空间,可以看做是一种以空间换时间的方法。 而varchar的特点是可变长,当定义一个varchar(10)而只存入了4个字符,此时varchar会直接将字符记录的长度变为4,从而节省空间,它可以看做是一种用时间换取空间的方法。 char的存储方式是,对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节;而varchar的存储方式是,对每个英文字符占用2个字节,汉字也占用2个字节,两者的存储数据都非unicode的字符数据。2.两种类型适应的情况分析。关于char: CHAR适合存储很短的字符串,或者所有值都接近同一个长度。 对于经常变更的数据,CHAR也比VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHAR在存储空间上也更有效率。例如用char(1)来存储只有Y和N的值,只需要一个字节,但是varchar却需要两个字节,因为还一个记录长度的额外字节。关于varchar VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型 更节省空间,因为它仅使用必要的空间(例如,越短的字符串使用越少的空间)。 VARCHAR节省了存储空间,所以对性能也有帮助。但是,由于行是变长的,在UPDATE时可能使行变得比原来更长,这就导致需要做额外的工作。如果一个行占用 的空间增长,并且在页内没有更多的空间可以存储,在这种情况下,不同的存储引擎的处理方式是不一样的。例如,MyISAM会将行拆成不同的片段存储,InnoDB 则需要分裂页来使行可以放进页内。 VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节。假设采用latinl字符集 ,一个varchar(10)的列需要11个字节的存储空间。varchar(1000)的列则需要1002个字节,因为需要2个字节存储长度信息。适用情况: 1、对于MyISAM表,尽量使用Char,对于那些经常需要修改而容易形成碎片的myisam和isam数据表就更是如此,它的缺点就是占用磁盘空间; 2、对于InnoDB表,因为它的数据行内部存储格式对固定长度的数据行和可变长度的数据行不加区分(所有数据行共用一个表头部分,这个标头部分存放着指向各有关数据列的指针),所以使用char类型不见得会比使用varchar类型好。事实上,因为char类型通常要比varchar类型占用更多的空间,所以从减少空间占用量和减少磁盘i/o的角度,使用varchar类型反而更有利; 3、存储很短的信息,比如门牌号码101,201……这样很短的信息应该用char,因为varchar还要占个byte用于存储信息长度,本来打算节约存储的现在得不偿失。 4、固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占个长度信息。 5、十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。关于MySQL之char、varchar,你学废了么?
之前没有仔细研究过my.cnf文件,今天有时间研究了一下my.cnf中的一些概念,这里简单整理如下,如果有什么问题,还请大家指出。按照教程安装好MySQL之后,打开etc目录下的my.cnf文件,大概可看到下面这样的参数列表,可能不同版本的mysql参数多少会有一些不一致,但是并不妨碍我们理解。首先,我们可以看到这个文件里面有mysqld和mysql_safe两类参数,我们知道mysqld和mysql_safe都可以启动mysql服务,那么mysqld和mysql_safe这两个类之间有什么不同呢?要讨论这个问题,我们需要引入第三个类别mysql.server,并同时讨论这三种启动方式的区别。问题1.mysql.server,mysqld,mysqld_safe的区别mysql.server它是一个服务器启动的shell脚本,主要作用就是为了方便启动和关闭mysql服务,它使用mysql_safe来启动mysql服务器,在mysql.server启动服务器之前,它将目录转换到mysql安装目录里面去,然后调用mysqld_safe。mysql.server通过向服务器发送一个信号来停止它,也可以使用mysqladmin shutdown命令来停止服务器,如果你使用源码或者二进制格式安装mysql(没有自动安装mysql.server这个脚本),你可以手动安装; 这个脚本在mysql安装目录下的support-files目录里边或者在源码包里边;为了能使用service mysqld start命令启动mysql服务,此时需要做的是将mysql.server的脚本复制到/etc/init.d目录下,然后重命名为mysqld,最后给予执行权限。mysqld.server会从配置文件的[mysqld] [mysql.server] 区域读取配置选项;可以在全局配置文件/etc/my.cnf中配置mysql.server,mysql.server脚本支持下面这些选项;一旦指定,它们必须放在配置文件中,不能放到命令行中(mysql.server支持的命令行参数只有start和stop);–basedir mysql安装目录;–datadir 数据文件的路径;–pid-file 服务器写自己的进程号的文件;如果这个不指定,mysql使用默认的hostname.pid;The PID file value被传递给mysqld_safe,覆盖了[mysqld_safe]下面指定的值;因为mysql.server读取[mysqld]选项组而不读取[mysqld_safe]选项组,所以为了在使用mysql.server 调用mysqld_safe的时候, mysqld_safe能够获得一样的pid,我们可以让[mysqld]选项组和[mysqld_safe]选项组使用同一个pid-file;mysql_safe这是mysql服务启动脚本,它是mysqld的父进程,它调用mysqld启动数据库服务,并在启动MySQL服务器后继续监控其运行情况,并在其死机时重新启动它,当我们开启mysqld_safe命令的时候,可以防止mysql服务的意外终止,这里做一个小小的测试。首先查看当前的mysql服务:然后发现服务中有一个mysql_safe和一个mysqld,其中mysqld_safe的端口号是1929,mysqld的端口号是2228,这个时候,我们把2228的进程杀掉:我们发现,进程号为2228的mysqld进程已经被杀掉,进程号为1929的mysqld_safe进程还在,又重新生成了一个进程号为2288的mysqld进程,接下来,我们杀掉mysqld_safe的进程,kill -9 1929,得到的结果如下:我们发现杀掉mysqld_safe之后,只剩下进程号为2288的mysqld进程了,并没有生成新的mysqld_safe进程,这个时候,在再次杀掉mysqld进程2288,结果如下:此时,所有的进程都被关闭掉了,综合上述操作,我们可以发现,当mysqld_safe进程存在时,我们无法直接杀掉mysqld进程,当我们杀掉mysqld_safe进程的时候,此时才可以杀掉mysqld进程,这便是mysqld_safe的守护进程作用,它可以防止mysqld进程被意外终止。mysqldmysqld是关于服务器端的程序,要想使用客户端程序,该程序必须运行,因为客户端通过连接服务器来访问数据库。问题2.mysql的三种启动方式:1、mysqld启动mysql服务器:客户端连接:2、mysqld_safe启动mysql服务器:客户端连接:3、mysql.server启动mysql服务器:客户端连接:同1、2问题3.socket文件mysql.sock详解mysql有两种连接方式,一种是TCP/IP的方式,另外一种是socket的方式,mysql.sock主要用户程序与mysqlserver在同一机器上,发起本地连接的时候使用,即无需再连接服务时使用host和IP,mysql.sock是随着每一次mysql server的启动而生成的,当服务重启时,mysql.sock也会重新生成。利用mysql.sock连接服务的样例如下:linux下安装mysql连接的时候经常回提示说找不到mysql.sock文件,解决办法很简单:1.如果是新安装的mysql,提示找不到文件,就搜索下,指定正确的位置。2.如果mysql.sock文件误删的话,就需要重启mysql服务,如果重启成功的话会在datadir目录下面生成mysql.sock 到时候指定即可。问题4.查看mysql的配置文件调用顺序mysql --help|grep “my.cnf”,当启动mysql服务的时候,会从当前目录的my.cnf中去读对应的参数,优先级顺序和输出顺序保持一致。问题5.MySQL的pid文件介绍MySQL pid 文件记录的是当前 mysqld 进程的 pid,pid 亦即 Process ID。1、未指定pid 文件时,pid 文件默认名为 主机名.pid,存放的路径在默认 MySQL 的数据目录。通过 mysqld_safe 启动 MySQL时,mysqld_safe 会检查 pid 文件,如果 pid 文件不存在,不做处理;如果文件存在,且 pid 已占用则报错 “Amysqld process already exists”,如果文件存在,但 pid 未占用,则删除 pid 文件。2、查看 MySQL 的源码可以知道,mysqld 启动后会通过 create_pid_file 函数新建 pid 文件,通过 getpid() 获取当前进程 pid 并将 pid 写入 pid 文件。3、因此,通过 mysqld_safe 启动时, MySQL pid 文件的作用是:在数据文件是同一份,但端口不同的情况下,防止同一个数据库被启动多次。,通过 mysqld_safe 启动时, MySQL pid 文件的作用是:在数据文件是同一份,但端口不同的情况下,防止同一个数据库被启动多次。关于MySQL之my.cnf配置文件,你学废了么?
关于VimVim是Linux系统下一款功能强大的编辑器,在Vi的基础上改进和增加了许多特性。Vim的三种模式编辑模式。输入模式。末行模式。三种模式之间的关系如下图:三种模式之间的转换方式如下:编辑–>输入: i: 在当前光标所在字符的前面,转为输入模式; a: 在当前光标所在字符的后面,转为输入模式; o: 在当前光标所在行的下方,新建一行,并转为输入模式; I:在当前光标所在行的行首,转换为输入模式 A:在当前光标所在行的行尾,转换为输入模式 O:在当前光标所在行的上方,新建一行,并转为输入模式;输入–>编辑:ESC • 1编辑–>末行:: • 1末行–>编辑:ESC, ESCVim常用命令Vim的常用命令如下图所示:具体常用命令可以分为以下几个大类:▼打开/退出▼vim -R file1 只读打开 :qall 退出所有文件 :wq 写入并退出 :q! 强制退出▼插入▼i 在当前位置生前插入 I 在当前行首插入 a 在当前位置后插入 A 在当前行尾插入 o 在当前行之后插入一行 O 在当前行之前插入一行▼移动▼h 左移一个字符 l 右移一个字符 k 上移一个字符 j 下移一个字符▼删除▼dd 删除当前行 dj 删除当前行和上一行 dk 删除当前行和下一行 10dd 删除当前行开始的共10行 D 删除当前字符至行尾x: 删除光标所在处的单个字符 #x: 删除光标所在处及向后的共#个字符▼跳转▼gg 跳转到文件头 G 跳转到文件尾 gg=G自动缩进 (非常有用) Ctrl + d 向下滚动半屏 Ctrl + u 向上滚动半屏 Ctrl + f 向下滚动一屏 Ctrl + b 向上滚动一屏 冒号+行号,跳转到指定行;比如:120,跳转到120行; $ 跳转到行尾0 跳转到行首▼编辑▼u 撤销 Ctrl + r 重做 yy 复制当前行 按v(逐字)或V(逐行)进入可视模式,然后用jklh命令移动即可选择某些行或字符,再按y即可复制任意部分 p 粘贴在当前位置 另外,删除在vim里面就是剪切的意思,所以dd就是剪切当前行,可以用v或V选择特定部分再按d就是任意剪切了▼查找▼/text 查找text,按n健查找下一个,按N健查找前一个?text 查找text,反向查找,:set ignorecase 忽略大小写的查找 :set noignorecase 不忽略大小写的查找▼替换▼:s/old/new/ 用old替换new,替换当前行的第一个匹配 :s/old/new/g 用old替换new,替换当前行的所有匹配 :%s/old/new/ 用old替换new,替换所有行的第一个匹配 :%s/old/new/g 用old替换new,替换整个文件的所有匹配 也可以用v或V选择指定行,然后执行▼多文件操作▼vim file1 file2 file3 ... 同时编辑多个文件 :split 将窗口分成上下两个子窗口,对应两个不同的文件 :vsplit 将窗口分成左右两个子窗口,对应两个不同的文件 :open file4 打开新文件 :bn 切换到下一个文件(当前窗口) :bp 切换到上一个文件(当前窗口) Ctrl-w h 移动到窗口左边 Ctrl-w j 移动到窗口下边 Ctrl-w k 移动到窗口上边 Ctrl-w l 移动到窗口右边▼高级话题▼1、显示或取消显示行号 :set nu :set nonu mu = number2、显示忽略或区分字符大小写 :set ic :set noic ic = ignorecase3、设定自动缩进 :set ai :set noai ai = autoindent4、查找到的文本高亮显示或取消 :set hlsearch :set nohlsearch5、语法高亮 :syntax on :syntax off``:set ic:set noicic = ignorecase 3、设定自动缩进 :set ai :set noai ai = autoindent 4、查找到的文本高亮显示或取消 5、语法高亮:syntax on :syntax off关于Linux工具之Vim编辑器,你学废了么?
一、主从复制的目的 MySQL内建的复制功能是构建基于MySQL的大规模、高性能应用的基础,复制功能的目的是构建高性能的应用,同时也是高可用性、可扩展性、灾难恢复、备份以及数据仓库等工作的基础。比较常见的用途有以下几种:数据分布:备份特定数据库负载均衡:读写分离高可用性和故障切换:从库的存在可以缩短宕机时间MySQL升级测试:使用一个更高版本的MySQL作为备库,保证在升级全部实例前,查询能够在备库按照预期进行二、主从复制的原理和步骤 简单的说就是master将数据库的改变写入binary log二进制日志,这个日志会记录下所有修改了数据库的SQL语句(insert,update,delete,grant等),slave同步这些二进制日志,并根据这些二进制日志进行数据操作,其实就是把主服务器上的binary log复制到从服务器上执行一遍,这样从服务器上的数据就和主服务器上的数据相同了。整体来说,主从复制有以下步骤:1.主节点必须启用二进制日志,记录任何修改数据库数据的事件。2.从节点开启一个线程I/O Thread把自己扮演成mysql的客户端,通过mysql协议,请求主节点的二进制日志文件中的事件3.主节点启动一个线程(dump Thread),检查自己二进制日志中的事件,跟对方请求的位置对比,如果不带请求位置参数,则主节点就会从第一个日志文件中的第一个事件一个一个发送给从节点。4.从节点接收到主节点发送过来的数据把它放置到中继日志(Relay log)文件中。并记录该次请求到主节点的具哪个二进制日志文件的哪个位置。5.从节点启动另外一个线程(sql Thread ),把replaylog中的事件读取出来,并在本地再执行一次。其原理图如下:三、复制中线程的作用从节点:I/O Thread:从Master请求二进制日志事件,并保存于中继日志中。Sql Thread:从中继日志中读取日志事件,在本地完成更新。主节点:Dump Thread:为每个Slave的I/O Thread启动一个dump线程,用于向从节点发送二进制事件。如果从节点需要作为其他节点的主节点时,是需要开启二进制日志文件的。这种情况叫做级联复制。如果只是作为从节点,则不需要创建二进制文件。四、主从复制配置过程主节点:1.启用二进制日志。2.为当前节点设置一个全局唯一的server_id。3.创建有复制权限的用户账号 replication slave。从节点:1.启动中继日志。2.为当前节点设置一个全局唯一的server_id。3.使用有复制权限的用户账号连接至主节点,并启动复制线程。4.1 测试环境4.2 主节点配置过程4.2.1 编辑主节点配置文件在Centos中打开my.cnf文档:· 添加:log-bin = mysql-bin(开启二进制日志)· 添加:server-id =4(设置服务器id,主节点和从节点的id需要设为不同)· 添加:binlog-do-db=DBAs(确定需要同步的数据库)· 添加:binlog-ignore-db=mysql(此处可以实际需求添加需要忽略的数据库)· 添加:expire_logs_days=7(自动清理 7 天前的log文件,可根据需要修改)4.2.2 启动主节点mysql服务,并连接mysql正常情况下,mysql服务启动命令为:为了更方便的启动mysql服务,为mysql创建软连接此时,启动命令变为:4.3.4 在从节点配置访问主节点的参数信息添加 主节点主机,访问主节点的用户名及密码,主节点二进制文件信息。命令:此处的master_log_file和master_log_pos需要和主节点状态保持一致。4.3.5 查看从节点的状态信息因为没有启动 从节点的复制线程,I/O线程和SQL 线程都为NO.使用start slave命令启动从节点的复制线程,再利用show slave status命令查看当前的从节点状态。4.4 功能测试查看主节点的状态2)在从节点查找二进制日志信息,并查看mydb数据库是否复制成功最后在从节点上查看数据是否已经同步,命令:经过验证,证明主从复制同步成功!!!五、错误排查总结5.1 Connecting错误操作过程中有时候出现如下所述错误:排错思路如下:1.二进制日志没有开启2.IPTABLES 没有放开端口3.对应的主机 IP地址写错了实际操作:关闭主节点防火墙,重新开启从节点,即可连接上。5.2 Slave_SQL_Running:NO 操作过程中还出现了如下所示的从节点的SQL线程运行错误排错思路如下:查看是否使用了nat的网络结构到导致网络有问题,连接不上。有可能my.cnf有问题,配置文件授权的问题,replication slave和file权限是必须的。实际情况,换工位后网络IP出现问题,连接不上,重新配置网络之后问题解决。节点,即可连接上。5.2 Slave_SQL_Running:NO 操作过程中还出现了如下所示的从节点的SQL线程运行错误排错思路如下:查看是否使用了nat的网络结构到导致网络有问题,连接不上。有可能my.cnf有问题,配置文件授权的问题,replication slave和file权限是必须的。实际情况,换工位后网络IP出现问题,连接不上,重新配置网络之后问题解决。关于MySQL主从复制,你学废了么?
Redis是一个非常火的非关系型数据库,火到什么程度呢?只要是一个互联网公司都会使用到。Redis相关的问题可以说是面试必问的,下面我从个人当面试官的经验,总结几个必须要掌握的知识点。介绍:Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API的非关系型数据库。传统数据库遵循 ACID 规则。而 Nosql(Not Only SQL 的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称) 一般为分布式而分布式一般遵循 CAP 定理。Github 源码:https://github.com/antirez/redisRedis 官网:https://redis.io/01Redis支持的数据类型?String字符串:格式: set key valuestring类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。string类型是Redis最基本的数据类型,一个键最大能存储512MB。Hash(哈希)格式: hmset name key1 value1 key2 value2Redis hash 是一个键值(key=>value)对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。List(列表)Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)格式: lpush name value在 key 对应 list 的头部添加字符串元素格式: rpush name value在 key 对应 list 的尾部添加字符串元素格式: lrem name indexkey 对应 list 中删除 count 个和 value 相同的元素格式: llen name返回 key 对应 list 的长度Set(集合)格式: sadd name valueRedis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。zset(sorted set:有序集合)格式: zadd name score valueRedis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。02什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。Redis 提供了两种持久化方式:RDB(默认) 和AOFRDB:rdb是Redis DataBase缩写功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数AOF:Aof是Append-only file缩写每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作aof写入保存:WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。存储结构:内容是redis通讯协议(RESP )格式的命令文本存储。比较:1、aof文件比rdb更新频率高,优先使用aof还原数据。2、aof比rdb更安全也更大3、rdb性能比aof好4、如果两个都配了优先加载AOF03刚刚上面你有提到redis通讯协议(RESP ),能解释下什么是RESP?有什么特点?(可以看到很多面试其实都是连环炮,面试官其实在等着你回答到这个点,如果你答上了对你的评价就又加了一分)RESP 是redis客户端和服务端之前使用的一种通讯协议;RESP 的特点:实现简单、快速解析、可读性好For Simple Strings the first byte of the reply is “+” 回复For Errors the first byte of the reply is “-” 错误For Integers the first byte of the reply is “:” 整数For Bulk Strings the first byte of the reply is “$” 字符串For Arrays the first byte of the reply is “*” 数组04Redis 有哪些架构模式?讲讲各自的特点单机版特点:简单问题:1、内存容量有限 2、处理能力有限 3、无法高可用。主从复制Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。特点:1、master/slave 角色2、master/slave 数据相同3、降低 master 读压力在转交从库问题:无法保证高可用没有解决 master 写的压力哨兵Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。特点:1、保证高可用2、监控各个节点3、自动故障迁移缺点:主从模式,切换需要时间丢数据没有解决 master 写的压力集群(proxy 型):Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器; Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。特点:1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins2、支持失败节点自动删除3、后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致缺点:增加了新的 proxy,需要维护其高可用。failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预集群(直连型):从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。特点:1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。缺点:1、资源隔离性较差,容易出现相互影响的情况。2、数据通过异步复制,不保证数据的强一致性05什么是一致性哈希算法?什么是哈希槽?这两个问题篇幅过长 网上找了两个解锁的不错的文章https://www.cnblogs.com/lpfuture/p/5796398.htmlhttps://blog.csdn.net/z15732621582/article/details/7912121306Redis是基于CAP理论的,什么是CAP理论?可以参考我的上一篇文章。如果有人问你CAP理论是什么,就把这篇文章发给他。07Redis常用命令?Keys pattern*表示区配所有以bit开头的查看Exists key是否存在Set设置 key 对应的值为 string 类型的 value。setnx设置 key 对应的值为 string 类型的 value。如果 key 已经存在,返回 0,nx 是 not exist 的意思。删除某个key第一次返回1 删除了 第二次返回0Expire 设置过期时间(单位秒)TTL查看剩下多少时间返回负数则key失效,key不存在了Setex设置 key 对应的值为 string 类型的 value,并指定此键值对应的有效期。Mset一次设置多个 key 的值,成功返回 ok 表示所有的值都设置了,失败返回 0 表示没有任何值被设置。Getset设置 key 的值,并返回 key 的旧值。Mget一次获取多个 key 的值,如果对应 key 不存在,则对应返回 nil。Incr对 key 的值做加加操作,并返回新的值。注意 incr 一个不是 int 的 value 会返回错误,incr 一个不存在的 key,则设置 key 为 1incrby同 incr 类似,加指定值 ,key 不存在时候会设置 key,并认为原来的 value 是 0Decr对 key 的值做的是减减操作,decr 一个不存在 key,则设置 key 为-1Decrby同 decr,减指定值。Append给指定 key 的字符串值追加 value,返回新字符串值的长度。Strlen取指定 key 的 value 值的长度。persist xxx(取消过期时间)选择数据库(0-15库)Select 0 //选择数据库move age 1//把age 移动到1库Randomkey随机返回一个keyRename重命名Type 返回数据类型08使用过Redis分布式锁么,它是怎么实现的?先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!09使用过Redis做异步队列么,你是怎么用的?有什么缺点?一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。10什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?缓存穿透一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。如何避免?1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。缓存雪崩当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。如何避免?1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。成很大的压力。这就叫做缓存穿透。如何避免?1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。缓存雪崩当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。如何避免?1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。关于面试必问的Redis,你学废了么?
下图展示了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法。具体分解如下:1.INNER JOIN(内连接)SELECT <select_list> FROM Table_A A INNER JOIN Table_B B ON A.Key = B.Key 2.LEFT JOIN(左连接)SELECT <select_list> FROM Table_A A LEFT JOIN Table_B B ON A.Key = B.Key 3.RIGHT JOIN(右连接)SELECT <select_list> FROM Table_A A RIGHT JOIN Table_B B ON A.Key = B.Key 4.OUTER JOIN(外连接)SELECT <select_list> FROM Table_A A FULL OUTER JOIN Table_B B ON A.Key = B.Key 5.LEFT JOIN EXCLUDING INNER JOIN(左连接-内连接)SELECT <select_list> FROM Table_A A LEFT JOIN Table_B B ON A.Key = B.Key WHERE B.Key IS NULL6.RIGHT JOIN EXCLUDING INNER JOIN(右连接-内连接)SELECT <select_list> FROM Table_A A RIGHT JOIN Table_B B ON A.Key = B.Key WHERE A.Key IS NULL 7.OUTER JOIN EXCLUDING INNER JOIN(外连接-内连接)SELECT <select_list> FROM Table_A A FULL OUTER JOIN Table_B B ON A.Key = B.Key WHERE A.Key IS NULL OR B.Key IS NULL关于join,你学废了么?
MySQL的日志主要分为六类:(1) 错误日志log_error:记录MySQL服务的启动、运行或停止MySQL服务时出现的问题(2) 查询日志general_log:记录简历的客户端连接和执行的语句(3) 慢查询日志slow_query_log:记录所有执行时间超过long_query_time的所有查询或不使用索引的查询(4)二进制日志binlog:记录所有更改数据的语句,可以用于数据复制(5)事务日志innodb_log:用来记录数据库更新情况的文件,它可以记录针对数据库的任何操作,并将记录的结果保存到独立的文件中(6)中继日志relay_log:复制中使用各类日志简介1.二进制日志binlog主要记录MySQL数据库的变化,二进制日志以一种有效的格式,并且是事务安全的方式包含更新日志中可用的所有信息,二进制日志包含了所有更新了数据或者已经潜在更新了数据的语句,语句以“事件”的形式保存,描述数据更改。二进制日志还包含关于每个更新数据库的语句的执行时间信息,不包含没有修改任何数据的语句,如果想要记录所有的语句,需要使用一般查询日志,使用二进制日志的主要目的是最大可能的恢复数据库,因为二进制日志包含备份后进行的所有更新。binlog附加参数max_binlog_size:设置binlog的最大存储上线,当日志达到该日志的上限时,mysql会重新创建一个日志开始记录,不过偶尔也会超出该设置的binlog,一般都是因为即将达到上限时候,产生了一个比较大的事物,为了保证事物的安全,mysql不会将同一个事物分开记录到两个binlogbinlog-do-db=db_name:明确告诉mysql,需要对某个数据库记录binlog,如果有了binlog-do-db=db_name 显示指定,mysql会忽略正对其他书库执行query,而仅仅记录只对指定数据库执行的querybinlog-ignore-db=db_name:显示的指定忽略某个数据库的binlog记录。binlog-do-db 和binlog-ignore-db参数:有一个共同的概念,参数db_name 不是指query 语句更新的数据所在的数据库,而是执行query的时候,当前所处的数据库。不论更新哪个数据库的数据,mysql仅仅比较当前连接所处的数据库与参数设置的数据库名。而不会分析query语句所更新的数据库所在数据库。binlog_cache_size :当使用事务的存储引擎InnoDB时,所有未提交的事务会记录到一个缓存中,等待事务提交时,直接将缓冲中的二进制日志写入二进制日志文件,而该缓冲的大小由binlog_cache_size决定,默认大小为32KB,此外,binlog_cache_size是基于回话的,也就是,当一个线程开始一个事务时,mysql会自动分配一个大小为binlog_cache_size的缓存,因此该值得设置需要相当小心,可以通过show global status 查看binlog_cache_use、binlog_cache_disk_use的状态,可以判断当前binlog_cache_size的设置是否合适。sync_binlog:参数sync_binlog=[N]表示每写缓存多少次就同步到磁盘,如果将N设置为1,则表示采用同步写磁盘的方式来写二进制日志,该参数很重要,这个以后还会提到。值得注意的是,在将该参数设置为1时,也应该将innodb_support_xa设为1来解决,这可以确保二进制日志和InnoDB存储引擎数据文件的同步expire_logs_days:定义了MySQL清楚过期日志的时间二进制日志的开启方式:(1)指定日志路径mysqld_safe --user=mysql --log-bin=[path] &如果没有指定文件名 默认mysql-bin,默认路径为datadir目录(2)编辑my.cnf[mysqld]log-bin=[path]log-bin= /var/log/mysql/mysql-bin.log --指定二进制日志的名称log_bin_index= /var/log/mysql/mysql-bin.log.index–二进制日志索引的名称relay_log= /var/log/mysql/mysql-bin.relay —中继日志的名称relay_log_index= /var/log/mysql/mysql-bin.relay.index—中继日志索引的名称二进制日志的查看方式:show binary logs可以查看当前的二进制日志文件个数以及文件名±----------------±-----------+| Log_name | File_size |±----------------±-----------+| mysqlbin.000001 | 27365 || mysqlbin.000002 | 1029074 || mysqlbin.000003 | 3457 || mysqlbin.000004 | 126 || mysqlbin.000005 | 1074144657 || mysqlbin.000006 | 1074572441mysqlbinlog命令可以用来查看当前日志里面的内容如果执行FLUSH LOGS,log-bin 会使用新的二进制日志编号2.通用查询日志general_log通用查询日志记录在MySQL上执行过的SQL语句,包含查询语句与启动时间。建议不是在调试环境下不要开启查询日志,因为它会不断占据磁盘空间,并且产生大量的IO,一般是在需要采样分析或者调试的时候才开启。通用日志的开启方法:(1)执行命令开启:set global general_log=1;//=0就是关闭通用查询日志此时在默认在mysql的data目录中生成了localhost.log文件,该文件就是通用查询日志文件(2)my.cnf中配置的方式,在my.cnf文件的[mysqld]下面任意一行增加或修改配置:general_log-file[=path/[filename]] //=后面都是可选的,即有默认的保存日志的文件general_log=1 //表示开启通用查询日志推荐使用第一种方式开启或关闭通用查询日志,因为my.cnf的修改要生效需要重启mysql服务,并且这种通用查询日志的开启不需要一直开启而是短时间开启就需要关闭,所以在 my.cnf关闭时又要重启mysql服务。3.错误日志err_log错误日志文件包含了当mysqld启动和停止时,以及服务器在运行过程中发生严重错误时候的相关信息,在mysql中,错误日志非常有用,MySQL会将启动和停止数据库信息以及一些错误信息记录保存到错误日志文件中。默认时错误日志的存放位置在数据目录中,名称为“server_name.err”错误日志记录的事件:a)、服务器启动关闭过程中的信息b)、服务器运行过程中的错误信息c)、事件调试器运行一个事件时间生的信息d)、在从服务器上启动从服务器进程时产生的信息查看与日志相关的变量:mysql> SHOW GLOBAL VARIABLES LIKE ‘%log_error%’;my.cnf中错误日志开启:log_error=/PATH/TO/ERROR_LOG_FILENAME例如:log_error = /mydata/data/hostname.err定义错误日志文件。作用范围为全局或会话级别,可用于配置文件,属非动态变量。log_warnings=#设定是否将警告信息记录进错误日志。默认设定为1,表示启用;可以将其设置为0以禁用;而其值为大于1的数值时表示将新发起连接时产生的“失败的连接”和“拒绝访问”类的错误信息也记录进错误日志。删除错误日志之后要想重建日志:在运行状态下删除错误日志文件后,mysql并不会自动创建日志文件,flush logs在重建加载日志的时候,如果文件不存在,则会自动创建,所以在删除错误日志之后,如果需要重建日志文件,需要在服务端执行以下命令:mysqladmin -uroot -p flush-logs4.慢查询日志log-slow-queries慢查询日志是记录查询时长超过指定时间的日志,慢查询日志主要用来记录执行时间较长的查询语句,mysql中慢查询日志默认是关闭的,开启方法如下:(1)可以通过配置文件my.cnf中的log-slow-queries选项打开,设定是否启用慢查询日志。0或OFF表示禁用,1或ON表示启用。日志信息的输出位置取决于log_output变量的定义,如果其值为NONE,则即便slow_query_log为ON,也不会记录任何慢查询信息。作用范围为全局级别,可用于选项文件,属动态变量。(2)也可以在MySQL服务启动的时候使用–log-slow-queries[=file_name]启动慢查询日志启动慢查询时,需要在my.cnf文件中配置long_query_time选项指定记录阈值,如果某条查询语句的查询时间超过了这个值,这个查询过程将被记录到慢查询日志文件中。5.事务日志Innodb主要是通过事务日志实现ACID特性事务日志包括:重做日志redo和回滚日志undo事务日志文件名为"ib_logfile0"和“ib_logfile1”,默认存放在表空间所在目录,它是用来记录数据库更新情况的文件,它可以记录针对数据库的任何操作,并将记录的结果保存到独立的文件中。对于每一次数据库更新的过程,事务日志文件都有非常全面的记录。根据这些记录可以恢复数据库更新前的状态。与事务日志相关变量:innodb_log_group_home_dir=/PATH/TO/DIR:设定InnoDB重做日志文件的存储目录。在缺省使用InnoDB日志相关的所有变量时,其默认会在数据目录中创建两个大小为5MB的名为ib_logfile0和ib_logfile1的日志文件。作用范围为全局级别,可用于选项文件,属非动态变量。innodb_log_file_size={108576 … 4294967295}设定日志组中每个日志文件的大小,单位是字节,默认值是5MB。较为明智的取值范围是从1MB到缓存池体积的1/n,其中n表示日志组中日志文件的个数。日志文件越大,在缓存池中需要执行的检查点刷写操作就越少,这意味着所需的I/O操作也就越少,然而这也会导致较慢的故障恢复速度。作用范围为全局级别,可用于选项文件,属非动态变量。innodb_log_files_in_group={2 … 100}设定日志组中日志文件的个数。InnoDB以循环的方式使用这些日志文件。默认值为2。作用范围为全局级别,可用于选项文件,属非动态变量。innodb_log_buffer_size={262144 … 4294967295}设定InnoDB用于辅助完成日志文件写操作的日志缓冲区大小,单位是字节,默认为8MB。较大的事务可以借助于更大的日志缓冲区来避免在事务完成之前将日志缓冲区的数据写入日志文件,以减少I/O操作进而提升系统性能。因此,在有着较大事务的应用场景中,建议为此变量设定一个更大的值。作用范围为全局级别,可用于选项文件,属非动态变量。innodb_flush_log_at_trx_commit = 1# 表示有事务提交后,不会让事务先写进buffer,再同步到事务日志文件,而是一旦有事务提交就立刻写进事务日志,并且还每隔1秒钟也会把buffer里的数据同步到文件,这样IO消耗大,默认值是"1",可修改为“2”innodb_locks_unsafe_for_binlog OFF#这个变量建议保持OFF状态,详细的原理不清楚innodb_mirrored_log_groups = 1#事务日志组保存的镜像数6.中继日志在复制环境中产的的日志信息与中继日志相关的变量:log_slave_updates用于设定复制场景中的从服务器是否将从主服务器收到的更新操作记录进本机的二进制日志中。本参数设定的生效需要在从服务器上启用二进制日志功能。relay_log=file_name设定中继日志的文件名称,默认为host_name-relay-bin。也可以使用绝对路径,以指定非数据目录来存储中继日志。作用范围为全局级别,可用于选项文件,属非动态变量。relay_log_index=file_name设定中继日志的索引文件名,默认为为数据目录中的host_name-relay-bin.index。作用范围为全局级别,可用于选项文件,属非动态变量。relay-log-info-file=file_name设定中继服务用于记录中继信息的文件,默认为数据目录中的relay-log.info。作用范围为全局级别,可用于选项文件,属非动态变量。relay_log_purge={ON|OFF}设定对不再需要的中继日志是否自动进行清理。默认值为ON。作用范围为全局级别,可用于选项文件,属动态变量。relay_log_space_limit=#设定用于存储所有中继日志文件的可用空间大小。默认为0,表示不限定。最大值取决于系统平台位数。作用范围为全局级别,可用于选项文件,属非动态变量。max_relay_log_size={4096…1073741824}设定从服务器上中继日志的体积上限,到达此限度时其会自动进行中继日志滚动。此参数值为0时,mysqld将使用max_binlog_size参数同时为二进制日志和中继日志设定日志文件体积上限。作用范围为全局级别,可用于配置文件,属动态变量。。relay_log_space_limit=#设定用于存储所有中继日志文件的可用空间大小。默认为0,表示不限定。最大值取决于系统平台位数。作用范围为全局级别,可用于选项文件,属非动态变量。max_relay_log_size={4096…1073741824}设定从服务器上中继日志的体积上限,到达此限度时其会自动进行中继日志滚动。此参数值为0时,mysqld将使用max_binlog_size参数同时为二进制日志和中继日志设定日志文件体积上限。作用范围为全局级别,可用于配置文件,属动态变量。关于MySQL日志,你学废了么?
1. 面向对象和面向过程的区别面向过程优点: 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。缺点: 没有面向对象易维护、易复用、易扩展面向对象优点: 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护缺点: 性能比面向过程低2. Java语言有哪些特点?简单易学;面向对象(封装,继承,多态);平台无关性(Java虚拟机实现平台无关性);可靠性;安全性;支持多线程(C++语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而Java语言却提供了多线程支持);支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的,因此Java语言不仅支持网络编程而且很方便);编译与解释并存;3. 什么是JDK?什么是JRE?什么是JVM?三者之间的联系与区别这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。JDK: 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。JRE: 普通用户而只需要安装JRE(Java Runtime Environment)来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。JVM: 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因。区别与联系:JDK用于开发,JRE用于运行java程序 ;JDK和JRE中都包含JVM ;JVM是java编程语言的核心并且具有平台独立性。4. 什么是字节码?采用字节码的最大好处是什么?先看下java中的编译器和解释器:Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。采用字节码的好处:Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。5. Java和C++的区别我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!都是面向对象的语言,都支持封装、继承和多态Java不提供指针来直接访问内存,程序内存更加安全Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。Java有自动内存管理机制,不需要程序员手动释放无用内存6. 什么是Java程序的主类?应用程序和小程序的主类有何不同?一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。7. Java应用程序与小程序之间有那些差别?简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。8. 字符型常量和字符串常量的区别形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)占内存大小上: 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)9. 构造器Constructor是否可被override在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以Constructor也就不能被override,但是可以overload,所以你可以看到一个类中有多个构造函数的情况。10. 重载和重写的区别重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。11. Java 面向对象编程三大特性:封装、继承、多态封装封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。继承继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。关于继承如下3点请记住:子类拥有父类非private的属性和方法。子类可以拥有自己属性和方法,即子类可以对父类进行扩展。子类可以用自己的方式实现父类的方法。(以后介绍)。多态所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。12. String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?可变性String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。线程安全性String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。性能每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。对于三者使用的总结:如果要操作少量的数据用 = String 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer13. 自动装箱与拆箱装箱:将基本类型用它们对应的引用类型包装起来;拆箱:将包装类型转换为基本数据类型;14. 在一个静态方法内调用一个非静态成员为什么是非法的?由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。15. 在Java中定义一个不做事且没有参数的构造方法的作用Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。16. import java和javax有什么区别刚开始的时候JavaAPI所必需的包是java开头的包,javax当时只是扩展API包来说使用。然而随着时间的推移,javax逐渐的扩展成为Java API的组成部分。但是,将扩展从javax包移动到java包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定javax包将成为标准API的一部分。所以,实际上java和javax没有区别。这都是一个名字。17. 接口和抽象类的区别是什么?接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法接口中的实例变量默认是final类型的,而抽象类中则不一定一个类可以实现多个接口,但最多只能实现一个抽象类一个类实现接口的话要实现接口的所有方法,而抽象类不一定接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口行为的抽象,是一种行为的规范。18. 成员变量与局部变量的区别有那些?从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰;从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。20. 什么是方法的返回值?返回值在类的方法里的作用是什么?方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!21. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。22. 构造方法有哪些特性?名字与类名相同;没有返回值,但不能用void声明构造函数;生成类的对象时自动执行,无需调用。23. 静态方法和实例方法有何不同?在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制.24. 对象的相等与指向他们的引用相等,两者有什么不同?对象的相等 比的是内存中存放的内容是否相等而引用相等 比较的是他们指向的内存地址是否相等。25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?帮助子类做初始化工作。26. ==与equals(重要)== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型比较的是值,引用数据类型比较的是内存地址)equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。举个例子:public class test1 { public static void main(String[] args) { String a = new String("ab"); // a 为一个引用 String b = new String("ab"); // b为另一个引用,对象的内容一样 String aa = "ab"; // 放在常量池中 String bb = "ab"; // 从常量池中查找 if (aa == bb) // true System.out.println("aa==bb"); if (a == b) // false,非同一对象 System.out.println("a==b"); if (a.equals(b)) // true System.out.println("aEQb"); if (42 == 42.0) { // true System.out.println("true"); } }} 说明:String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。27. hashCode与equals(重要)面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”hashCode()介绍hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)为什么要有hashCode我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。hashCode()与equals()的相关规定如果两个对象相等,则hashcode一定也是相同的两个对象相等,对两个对象分别调用equals方法都返回true两个对象有相同的hashcode值,它们也不一定是相等的因此,equals方法被覆盖过,则hashCode方法也必须被覆盖hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)28. Java中的值传递和引用传递值传递是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。)引用传递是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。)29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。30. 线程有哪些基本状态?这些状态是如何定义的?新建(new):新创建了一个线程对象。可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种: (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。 (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 (三). 其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。备注: 可以用早起坐地铁来比喻这个过程:还没起床:sleeping起床收拾好了,随时可以坐地铁出发:Runnable等地铁来:Waiting地铁来了,但要排队上地铁:I/O阻塞上了地铁,发现暂时没座位:synchronized阻塞地铁上找到座位:Running到达目的地:Dead关于Java基础面试题,你学废了么?
缓存一致性问题当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。这就比较依赖缓存的过期和更新策略。一般会在数据发生更改的时,主动更新缓存中的数据或者移除对应的缓存。缓存并发问题缓存过期后将尝试从后端数据库获取数据,这是一个看似合理的流程。但是,在高并发场景下,有可能多个请求并发的去从数据库获取数据,对后端数据库造成极大的冲击,甚至导致 “雪崩”现象。此外,当某个缓存key在被更新时,同时也可能被大量请求在获取,这也会导致一致性的问题。那如何避免类似问题呢?我们会想到类似“锁”的机制,在缓存更新或者过期的情况下,先尝试获取到锁,当更新或者从数据库获取完成后再释放锁,其他的请求只需要牺牲一定的等待时间,即可直接从缓存中继续获取数据。缓存穿透问题缓存穿透在有些地方也称为“击穿”。很多朋友对缓存穿透的理解是:由于缓存故障或者缓存过期导致大量请求穿透到后端数据库服务器,从而对数据库造成巨大冲击。这其实是一种误解。真正的缓存穿透应该是这样的:在高并发场景下,如果某一个key被高并发访问,没有被命中,出于对容错性考虑,会尝试去从后端数据库中获取,从而导致了大量请求达到数据库,而当该key对应的数据本身就是空的情况下,这就导致数据库中并发的去执行了很多不必要的查询操作,从而导致巨大冲击和压力。可以通过下面的几种常用方式来避免缓存传统问题:1.缓存空对象对查询结果为空的对象也进行缓存,如果是集合,可以缓存一个空的集合(非null),如果是缓存单个对象,可以通过字段标识来区分。这样避免请求穿透到后端数据库。同时,也需要保证缓存数据的时效性。这种方式实现起来成本较低,比较适合命中不高,但可能被频繁更新的数据。2.单独过滤处理对所有可能对应数据为空的key进行统一的存放,并在请求前做拦截,这样避免请求穿透到后端数据库。这种方式实现起来相对复杂,比较适合命中不高,但是更新不频繁的数据。缓存颠簸问题缓存的颠簸问题,有些地方可能被成为“缓存抖动”,可以看做是一种比“雪崩”更轻微的故障,但是也会在一段时间内对系统造成冲击和性能影响。一般是由于缓存节点故障导致。业内推荐的做法是通过一致性Hash算法来解决。缓存的雪崩现象缓存雪崩就是指由于缓存的原因,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。导致这种现象的原因有很多种,上面提到的“缓存并发”,“缓存穿透”,“缓存颠簸”等问题,其实都可能会导致缓存雪崩现象发生。这些问题也可能会被恶意攻击者所利用。还有一种情况,例如某个时间点内,系统预加载的缓存周期性集中失效了,也可能会导致雪崩。为了避免这种周期性失效,可以通过设置不同的过期时间,来错开缓存过期,从而避免缓存集中失效。从应用架构角度,我们可以通过限流、降级、熔断等手段来降低影响,也可以通过多级缓存来避免这种灾难。此外,从整个研发体系流程的角度,应该加强压力测试,尽量模拟真实场景,尽早的暴露问题从而防范。缓存无底洞现象该问题由 facebook 的工作人员提出的, facebook 在 2010 年左右,memcached 节点就已经达3000 个,缓存数千 G 内容。他们发现了一个问题——memcached 连接频率、效率下降了,于是加 memcached 节点,添加了后,发现因为连接频率导致的问题,仍然存在,并没有好转,称之为”无底洞现象”。目前主流的数据库、缓存、Nosql、搜索中间件等技术栈中,都支持“分片”技术,来满足“高性能、高并发、高可用、可扩展”等要求。有些是在client端通过Hash取模(或一致性Hash)将值映射到不同的实例上,有些是在client端通过范围取值的方式映射的。当然,也有些是在服务端进行的。但是,每一次操作都可能需要和不同节点进行网络通信来完成,实例节点越多,则开销会越大,对性能影响就越大。主要可以从如下几个方面避免和优化:1.数据分布方式有些业务数据可能适合Hash分布,而有些业务适合采用范围分布,这样能够从一定程度避免网络IO的开销。2.IO优化可以充分利用连接池,NIO等技术来尽可能降低连接开销,增强并发连接能力。3.数据访问方式一次性获取大的数据集,会比分多次去获取小数据集的网络IO开销更小。当然,缓存无底洞现象并不常见。在绝大多数的公司里可能根本不会遇到。据访问方式**一次性获取大的数据集,会比分多次去获取小数据集的网络IO开销更小。当然,缓存无底洞现象并不常见。在绝大多数的公司里可能根本不会遇到。关于缓存在高并发场景下的常见问题,你学废了么?
子查询关键字-ALL、ANY、SOME、IN、EXISTSALLselect from where c > all(查询语句) 等价于 select from where c > result1 and c > result2 and c > result3 特点: 1:all与子查询返回的所有值比较为true 则返回true 2:ALL可以与= > < >= <= <>结合使用 3:all表示指定列中的值必须要大于子查询集中的每一个值 eg:查询年龄大于'1003'部门所有年龄的员工信息 select * from emp3 where age > all(select age from emp3 where dept_id='1003'); 查询不属于任何一个部门的员工信息 select * from emp3 where dept_id != all(select deptno from dept3); ANY SOMEselect from where c > any(查询语句) 等价于 select from where c > result1 or c > result2 or c > result3 特点: 1:any与子查询返回的所有值比较为true 则返回true 2:any可以与= > < >= <= <>结合使用 3:any表示指定列中的值要大于子查询集中任意的一个值 eg:查询年龄大于'1003'部门任意一个员工年龄的员工信息 select * from emp3 where age > any(select age from emp3 where dept_id='1003'); some和any的作用是一样的,some可以理解为是any的别名 INselect from c in (查询语句) 等价于 select from where c =result1 or c=result2 or c=result3 特点: in关键字,用于判断某个记录的值,是否在指定的集合中 在in关键字前面加上not可以将条件反过来 eg:查询研发部和销售部的员工信息,包括员工工号,员工名字 select c.cid,c.name from cmp3 c where dept_id in (select deptno from dept3 where name='研发部' or name='销售部'); EXISTSselect from where exists(查询语句) 特点: 该子查询如果"有数据结果"(至少返回一行数据),则该EXISTS()的结果为true 外层查询执行 该子查询如果"没有数据结果"(没有任何数据返回),则该EXISTS()的结果为false 外层查询不执行 注意:EXISTS关键字,比in关键字的运算效率高,在实际开发中 特别是数据量大的时候推荐使用exists关键字 eg:查询公司是否有大于60岁的员工,有则输出 select * from epm3 a where exists (select * from emp3 b where a.age>60) 查询所属部门的员工信息 select *from dept3 a where exists (select * from emp3 b where a.deptno=b.dept_id)
Shell内值命令之exit介绍: exit 用于退出当前shell环境进程结束运行,并且可以返回一个状态码.一般使用$?可以获取状态码. 语法: 正确退出语法 exit #默认返回状态码0 ,一般代表命令执行成功 错误退出语法 exit 非0数字 #数字建议的范围0-255 一般代表命令执行失败 exit应用场景 1.结束当前shell进程 2.当shell进程执行出错退出时,可以返回不同的状态值代表不同的错误. 比如执行一个脚本文件里面操作一个文件时,可以返回1表示文件不存在,2表示文件没有读取权限,3表示文件类型不对. 实例需求:编写shell脚本使用exit退出,退出时返回一个非0数字状态值,执行脚本文件并打印返回状态值步骤: 1.创建exit.sh文件 2.编辑exit.sh文件,使用exit数字退出结束当前shell 3.执行文件,打印返回状态值演示vim exit.sh #!/bin/bash echo 'hello' exit 2 echo 'word' #执行 exit.sh sh exit.sh #只会输出 hello echo $? #输出2 获取上一个命令执行返回的状态码 小结exit的应用场景 结束当前shell进程 可以返回不同的状态码,用于不同的业务处理
shell的自定义变量目标理解自定义变量的分类能够自定义变量进行增删改查自定义变量介绍:就是自己定义的变量自定义变量1—局部变量介绍:就是定义在一个脚本文件中的变量,只能在这个脚本文件中使用的变量就是绝不变量 定义语法:var _name=value 变量定义规则 1.变量名称可以由字母数字下滑线,但是不能以数字开头 2.等号两侧不能有空格 3.在bash环境中,变量的默认类型都是字符创类型,无法直接进行数值计算 4.变量的值如果有空格必须使用双引号括起来 5.不能使用shell的关键字为变量名称查询变量值的语法 语法1:直接使用变量名查询 $name 语法2:使用花括号${name} 区别:花括号方式适合拼接字符串 eg: var name=张三 echo $name123 无法使用 echo ${name}123 输出 张三123 删除变量的语法unset 变量名 • 1自定义变量2—常量介绍:就是变量设置值之后不可以修改的变量叫常量 也叫只读变量 语法:readonly name=张三 自定义变量3—全局变量父子shell环境介绍 eg:有两个shell脚本文件a.sh和b.sh,如果a.sh脚本文件中执行了b.sh脚本文件,那么a.sh就是父shell环境,b.sh就是子shell环境 自定义全局变量介绍:就是在当前脚本文件中定义全局变量,这个全局变量可以在当前shell环境与子shell环境中都可以使用. 语法:export name1 name2=value 案例实现步骤1.创建2个脚本文件demo2.sh和demo3.sh 2.编写demo2.sh 命令1:定义全局变量var4 命令2:执行demo3.sh脚本文件 3.编写demo3.sh脚本文件 输出全局变量var4 4.执行demo2.sh脚本文件 代码如下touch demo2.sh demo3.sh vim demo2.sh #!/bin/bash export var4=value sh demo3.sh vim demo3.sh #!/bin/bash echo "输出全局变量var4的值为${var4}" sh demo3.sh 特殊符号变量目标 能够说出常用的特殊变量有哪些 $n $# $* s@ $? $$ • 1 • 2特殊变量$n语法:$n $0是用于获取当前脚本文件名称的 $1-$9代表获取第一输入参数到第九个输入参数,第十个及以上的输入参数获取的格式是${10},否则无法获取 执行脚本文件传入参数语法 sh 脚本文件 输入参数1 输入参数2 ....案例需求:打印脚本文件名字,第一个输入参数,第二个输入参数1.创建脚本文件demo4.sh 2.编辑demo4.sh的文件内容 打印当前脚本文件的名字 打印第一个输入参数 打印第二个输入参数 打印第十个输入参数 3.执行脚本文件demo4.sh touch demo4.sh vim demo4.sh #!/bin/bash echo "当前脚本文件名称:$0" echo "第一个输入参数$1" echo "第二个输入参数$2" echo "第十个输入参数${10}" 特殊符号变量 $#语法:$# 含义:获取所有输入参数的个数 • 1 • 2案例需求:获取demo4.sh中输入参数的个数vim demo4.sh echo "输入参数的个数为:$#" 特殊符号变量 $* $@含义:都是获取所有输入参数,用于以后输出所有参数 区别: 1.不使用双引号括起来,功能一样 $*和$@获取所有输入参数,格式为:$1 $2 ....$n 2.使用双引号括起来 "$*" 获取所有参数拼接为一个字符串,格式为:"$1 $2 ... $n" "$@" 获取一组参数列表对象,格式为:"$1" "$2"..."$n" 使用循环打印所有输入参数可以看出区别 循环语法: for var in 列表变量 do #循环开始 命令 #循环体 done #循环结束 案例需求:在demo4.sh 脚本文件中循环打印输出所有输入参数,体验∗ 和 *和∗和@区别特殊符号变量$?含义:用于获取上一个shell命令的退出状态码,或者是函数的返回值 解释:每个shell命令的执行都会有一个返回值,这个返回值用于说明命令执行是否成功.一般来说返回0代表命令执行成功,非0代表失败特殊符号变量$$含义:用于获取当前shell环境的进程id号 • 1小结$n 获取第几个输入参数 除了$0外 因为$0是获取当前shell脚本的名字 $# 获取输入参数个数 $* $@ 获取输入参数 $?获取上个shell退出状态码或者函数返回值 $$获取shell环境的进程id
多命令处理案例需求一已知目录/root/itheima目录,执行batch.sh脚本,实现在/root/itheima/目录下创建一个one.txt,在one.txt文件中增加内容"Hello Shell"1:使用mkdir命令创建/root/itheima目录 2:创建bash.sh脚本文件 3:编辑脚本文件 3.1:创建/root/itheima/one.txt文件 3.2:输出数据"Hello Shell"到one.txt文件中 数据文件的命令 数据 >> 文件 4:执行脚本文件 mkdir /root/itheima touch bash.sh vim bash.sh #!/bin/bash touch /root/itheima/one.txt echo "Hello Shell" >> /root/itheima/one.txt :wq sh bash.sh变量目标: 什么是系统环境变量 掌握常用的系统变量有哪些 变量就是用于存储临时的数据,这些数据都是在运行内存中的变量类型1:系统环境变量2:自定义变量3:特殊符号变量系统环境变量 是系统提供的共享变量,是linux系统加载shell的配置文件中定义的变量共享给所有的shell程序使用 shell的配置文件分类 全局配置文件 /etc/profile /etc/profile.d/*.sh /etc/bashrc 个人配置文件 当前用户/.bash_profile 当前用户/.bashrc 一般情况下,我们都是针对全局配置进行操作 环境变量分类 在linux系统中,环境变量按照其作用范围把不同大致可以分为系统级环境变量和用户级环境变量. 系统级环境变量:shell环境加载全局配置文件中的变量共享给所有用户所有shell程序使用.全局共享 用户级环境变量:shell环境加载个人配置文件中的变量共享给当前用户的shell程序使用,登录用户使用 查看当前shell系统环境变量 env 查看shell变量(系统环境变量+自定义变量+函数) set 常用的系统变量PATH 与windows环境变量PATH功能一样,设置命令的搜索路径,已冒号为分隔 HISTFILE 显示当前用户执行命令的历史列表文件:/root/.bash_history LANG 设置当前系统语言环境(查看系统的字符集) :zh_CN.UTF-8 小结系统环境变量是什么? 是系统提供的环境变量,通过加载shell配置文件中的变量数据共享给shell程序使用 环境变量的分类 系统级环境变量 shell环境加载全局配置文件中定义的变量 用户级环境变量 shell环境加载个人配置文件中定义的变量 evn和set区别 evn查看系统级环境变量 set 系统级环境变量+自定义变量+函数
Redis主从复制的时候网络断开怎么解决?Reids的主从大家应该并不陌生,但是配置的话日常工作中并不会经常操作,在这里简单的介绍下主从的相关配置.1:主从模式Redis 中设置主从的方式很简单,通常有两种:通过在配置文件 redis.conf 中设置 slaveof 方式(永久);直接在客户端执行 slaveof ip port 的方式(临时);2、主-从-从模式对于主-从-从的模式来说,配置也与上边的操作类似,在这里就不多赘述了。主从一致性原理了解了主从配置后,下面就要进入正题了。在主从中,通常的操作是主库用来写入数据,从库用来读取数据。这样的好处是避免了所有的请求压力都打在了主库上,同时系统的伸缩性也得到了很大的提升。但是问题就来了,读从库时的数据要与主库保持一致,那就需要主库的数据在写入后同步到从库中。如何保持主库与从库的数据一致性,当有多个从库时,又如何做到呢?1、全量复制这是第一次同步时所发生的传递关系。看名字就知道,主库第一次就毫无保留的把所有数据都传递给了从库。我们先来看下它们是如何发生第一次关系的(就知道你会想歪)。图中的同步流程已经很清晰了,总共分为三部分:(1)主从节点建立联系当从节点与主节点第一次建立联系时,从节点会向主节点发送 psync 命令,表示要进行数据同步。正如你看到的 psync 命令后会带有两个参数:一个是 runID,一个是偏移量 offset。**runID:**每个Redis实例生成的随机且唯一的ID,在这里表示的是主节点的ID。**offset:**复制偏移量。在图中第一次复制时因为不知道主库ID和偏移量,因此用“?”和“-1”分别来表示runID 和 offset。当主节点接收到 psync 命令后,会使用 FULLSYNC命令向从节点发送 runID 及offset 两个参数。从节点将其信息保存下来。到这里关系算是建立了下来。(2)主节点同步RDB文件RDB文件,这是一个老面孔了,持久化时会用到的二进制文件。在这里起着主从数据同步的作用,也就是说主从同步是依赖 RDB 文件来实现的。从节点接收到 RDB 文件后,在本地完成数据加载,算是完成了主从同步。到这里你有没有发现什么问题?我们回想下 RDB 文件是如何生成的。在持久化那篇文章里,我们介绍过,父进程 fork 了一个子进程来进行生成 RDB 文件。父进程并不阻塞接收处理客户端的命令。那么问题就产生了,当主节点把 RDB 文件发送给从节点时,主节点同时接收的命令又该如何来处理?(3)主节点同步缓冲区命令这一步就是来解决 RDB 文件生成后,父进程又接收到写命令同步的问题的。为了保证主从节点数据的一致性,主节点中会使用缓冲区来记录 RDB 文件生成后接收到的写操作命令。在 RDB 文件发送完成后会把缓冲区的命令发送给从节点来执行。到这里,主从节点的数据同步算是完成了。2、级联操作我们再来回顾下整个同步流程,从建立关系,生成 RDB 文件,传输给从节点到最后缓冲区命令发送给从节点。这是一个从节点与主节点同步的完整流程。那么我们再来思考:当有多个从节点,也就是一主多从时,第一次连接时都要进行全量复制。但是在生成 RDB 文件时,父进程 fork 子进程时可能会出现阻塞,同时在传输 RDB 文件时也会占用带宽,浪费资源。这种情况我们该如何来解决呢?不知道你对文章开头的 主-从-从模式是否还有印象。通过对从节点再建立从节点。同步数据时从级联的从节点上进行同步,从而就减轻了主节点的压力。网络开小差了上面的流程我们已经知道了正常情况下主从节点的复制过程了,但是当网络中断导致主从连接失败等异常情况下,主从同步又是如何来进行的?在这里要提到一个增量复制的名词,与全量复制不同的是,它是根据主从节点的偏移量来进行数据同步的。什么意思呢?还记得在全量复制里我们所提到过的缓冲区吗?就是用来存储生成 RDB 文件后的写命令的,这里我们称为缓冲区A。主从节点断开连接后,除了会将后续接收到的写命令写入缓冲区A的同时,还会写入到另一个缓冲区B里。在缓冲区B里,主从节点分别会维护一个偏移量 offset。刚开始时,主节点的写位置与从节点的读位置在同一起点,随着主节点的不断写入,偏移量也会逐渐增大。同样地,从节点复制完后偏移量也在不断增加。当网络断开连接时,从节点不再进行同步,此时主节点由于不断接收新的写操作的偏移量会大于从节点的偏移量。当连接恢复时,从节点向主节点发送带有偏移量的psync 命令,主节点根据偏移量来进行比较,只需将未同步写命令同步给从节点即可。总结主从一致性原理从节点第一次进行连接时,主节点会生成 RDB 文件进行全量复制,同时将新写入的命令存储进缓冲区,发送给从节点,从而保证数据一致性;为了减少数据同步给主节点带来的压力,可以通过从节点级联的方式进行同步。网络开小差了点会生成 RDB 文件进行全量复制,同时将新写入的命令存储进缓冲区,发送给从节点,从而保证数据一致性;为了减少数据同步给主节点带来的压力,可以通过从节点级联的方式进行同步。网络开小差了网络断连重新连接后,主从节点通过分别维护的偏移量来同步写命令。
Mysql8.0新特性服务器功能方面: 1:所有元数据使用InnoDB引擎储存,无frm文件 2:系统表采用InnoDB存储并采用独立表空间 3:支持定义资源管理组(目前仅支持CPU资源) 4:支持不可见索引和降序索引,支持直方图优化 5:支持窗口函数 6:支持在线修改全局参数持久化 用户及安全方面: 1:默认使用caching_sha2_password认证插件(同一个密码加密出来的密文密码不一样) 2:新增支持定义角色 3:新增密码历史记录功能,限制重复使用密码 InnoDB功能方面: 1:InnoDB DDL语句支持原子操作 2:支持在线修改UNDO表空间 3:新增管理视图用于监控INNODB表状态 4:新增innodb_dedicated_server配置项
css的三种布局机制 浮动 清除浮动记忆:CSS布局的三种机制:普通流(标椎流),浮动和定位. • 1理解:普通流在布局中的特点: 为什么用浮动: 为什么要清除浮动: 浮动让盒子从普通流中浮动起来,主要作用让多个块级盒子一行显示 虽然行内块(inline-block)可以解决在一行内显示多个块级元素但是有缺陷,而每个行内块之间有间隙 为什么要浮动: 让多个块级盒子一行显示, 实现盒子的指定左对齐右对齐等 浮动最早是用来控制图片,实现文字环绕图片的效果 什么是浮动: 设定了浮动属性的元素会脱离标准普通流的控制,移动到指定的位置 语法:选择器{float:属性值;}//none 元素不浮动 left元素向左浮动 right 元素向右浮动 记忆浮动的特点: 浮动就不属于标准流了,会在标椎流的上方浮动起来,而且把自己的位置让给下一个盒子来占用.也就是俗称的脱标 float属性会改变元素的display属性,生成类似于行内块 注意:浮动的元素互相贴靠一起,但是如果父级宽度装不下这些浮动的盒子,多出的盒子会另起一行对齐 浮动小结: 我们使用浮动的核心目的是让多个块级盒子在同一行显示,因为这是常见的布局方式. 浮: 加了浮动的盒子是浮起来的,漂浮在其他标椎流盒子的上面 漏: 加了浮动的何止是不占位置的,他原来的位置漏给了标准流的盒子 特: 特别注意浮动元素会改变display属性,累死转换为行内块,但是元素之间没有空白的间隙 CSS布局的三种机制:普通流(标椎流),浮动和定位.用于设置盒子摆放的位置普通流(标椎流)块级元素会独占一行,从上向下顺序排列 常见的元素有:div,li ul ol,h1-h6,p,hr,dl,form,table 行内元素会按照顺序,从左到右顺序排列,碰到父元素边缘则自动换行 常用的元素:a,b,strong,span,em,i 浮动让盒子从普通流中浮动起来,主要作用让多个块级盒子一行显示 虽然行内块(inline-block)可以解决在一行内显示多个块级元素但是有缺陷,而每个行内块之间有间隙 为什么要浮动: 让多个块级盒子一行显示, 实现盒子的指定左对齐右对齐等 浮动最早是用来控制图片,实现文字环绕图片的效果 什么是浮动: 设定了浮动属性的元素会脱离标准普通流的控制,移动到指定的位置 语法:选择器{float:属性值;}//none 元素不浮动 left元素向左浮动 right 元素向右浮动 记忆浮动的特点: 浮动就不属于标准流了,会在标椎流的上方浮动起来,而且把自己的位置让给下一个盒子来占用.也就是俗称的脱标 float属性会改变元素的display属性,生成类似于行内块 注意:浮动的元素互相贴靠一起,但是如果父级宽度装不下这些浮动的盒子,多出的盒子会另起一行对齐 浮动小结: 我们使用浮动的核心目的是让多个块级盒子在同一行显示,因为这是常见的布局方式. 浮: 加了浮动的盒子是浮起来的,漂浮在其他标椎流盒子的上面 漏: 加了浮动的何止是不占位置的,他原来的位置漏给了标准流的盒子 特: 特别注意浮动元素会改变display属性,累死转换为行内块,但是元素之间没有空白的间隙 浮动的应用因为浮动会脱标的,会影响下面的标椎流元素,因此我们需要给浮动的元素添加一个标准流的父亲,这样,最大化的减少了对其他标椎流的影响. 浮动的扩展: 浮动元素与父盒子的关系: 子盒子的附佛那个参照父盒子对齐 不会与父盒子的变框重叠,也不会超过父盒子的内边距 浮动元素与兄弟盒子的关系: 在一个父级盒子中,如果前一个兄弟盒子是: 浮动的,那么当前盒子会与前一个盒子的顶部对齐. 普通流的,那么当前盒子会显示在掐一个兄弟盒子的下方 注意:浮动只会影响当前的和后面的标准的盒子,不会影响前面的标准盒子 建议:如果一个盒子里面有多个盒子,如果其中的一个盒子浮动了,其他兄弟也应该浮动,防止引起问题 清除浮动清除浮动的本质: 清除浮动主要是为了解决父类元素因为子盒子浮动引起内部高度为0的问题,清除浮动之后,父级就会根据浮动的子盒子自动检测高度,父级就有了高度,就不会影响下面的标椎流了. 语法:选择器{clear:属性值} //left不允许左侧有浮动元素(清除左侧浮动的影响) right(清除右侧浮动的影响)both(同时清除左右两侧浮动的影响) 清除浮动的方法:1:额外标签法(隔墙法)通过在浮动元素标签的后面添加一个空的标签<div style="clear:both"></div> 优点:通俗易懂,书写方便 缺点:添加许多无意义的标签,结构化较差 2:父级添加overflow属性可以给父类添加:overflow为hidden|auto|scroll都可以实现 优点:代码简洁 缺点:内容增多时候容易造成不会自动换行导致内容被隐藏掉,无法显示需要溢出的元素 3:使用after伪元素清除浮动after方式为空元素额外标签的升级版,好处是不用单独加标签了 写法: .clearfix:after{ content:""; display:block; height:0; clear:both; visibility:hidden; } .clearfix{/*ie6,7专有*/ *zoom:1; } 优点:符合闭合浮动思想,结构语义化正确 缺点:由于ie6-7不支持:after使用zoom:1出发hasLayout 代表网站:百度,淘宝,网易等 4:使用双伪元素清除浮动.clearfix:before,.clearfix:after{ context:""; display:table; } .clearfix:after{ clear:both; } .clearfix{ *zoom:1; } 优点:代码简洁 缺点:由于ie6-7不支持after,使用zoom:1出发hasLayout 清除浮动的总结:
盒子模型 边框border 外边距 内边距css学习的三大重点:css盒子模型,浮动,定位盒子模型盒子模型由四部分组成:内容(content),内边距(padding),边框(border),外边距(margin) • 1盒子边框border-width:边框宽度 border-style:soild(实线),dashed(虚线),dotted(点线) border-color:边框颜色 边框的总和写法:变宽粗细 边框样式 边框颜色 border:5px solied red border-collapse:collapse/*合并相邻重叠的边框*/ 内边距盒子的实际大小的宽度=内容的宽度+内边距*2+边框*2 内边距是边框与内容之间的距离 padding-left padding-right padding-top padding-bottom padding:上 右 下 左 注意:当我们给盒子指定padding的时候发生两件事1盒子会变大,2内容和边框有了距离,添加了内边框 盒子的大小=内容的宽度或者高度+内边距+边框 解决添加内边距撑大盒子的方法: 因为内边距一定要给的,我们只能改变内容的宽度,让内容的宽度减去内边距*2就好了. padding不影响盒子大小的情况如果没有给一个盒子指定的宽度(不包活继承也就是继承的不算给的宽度),此时如果给这个盒子指定padding,则不会撑开这个盒子 eg: <head> <meta charset="UTF-8"> <title>Document</title> <style> .nav{ width:200px; height: 41px; border: 1px solid red; } p{ height: 20px; background-color: pink; padding-left: 30px; } </style> </head> <body> <div class="nav"> <p>哈哈哈</p> </div> 外边距margin属性用于设置外边距,margin就是控制盒子和盒子之间的距离 块盒子水平居中的条件: 盒子必须指定宽度 然后给左右外边距都设置auto eg:.header{width:960px;margin:auto;} 文字居中和盒子居中的区别1:盒子内的文字水平居中是text-align:center 而且还可以让行内元素和行内块居中对齐 2:块级盒子水平居中 左右margin:auto 插入图片和背景图片的区别1:插入图片 我们用的最多的 比如产品展示类 移动位置只能靠盒模型padding margin 2:背景图片我们一般用于小图标背景或者超大背景图片 背景图片只能通过background-position img{ width:200px;//插入图片更改大小 width和height height:210px; margin-top:30px;/*插入图片更改为位置 可以用margin或padding盒模型*/ margin-left:30px;/*插入当图片也是一个盒子*/ } div{ width:400px; height:400px; border:1px solid purple; background:#fff url() no-repeat background-position:30px 50px ; } 清除元素的默认内外边距(重要)*{ padding:0; margin:0; } 注意:行内元素为了照顾兼容性,尽量只设置左右内外边距,不要设置上下内外边距 相邻块元素垂直外边距的合并上边外边距是margin-bottom:200px;下边外边距是margin-top:100px;则两个块元素之间的聚利时取两值中的最大值 决绝办法尽量只给一个盒子添加margin值 嵌套块元素垂直外边距的合并(塌陷)对于两个嵌套关系的块元素,如果父元素没有上内边距及边框父元素的上外边距会与子元素的上外边距发生合并合并后的外边距为两者中的较大者解决方法: 可以为父级元素定义上边框 可以为父级元素定义上内边距 可以为父级元素添加overflow:hidden 还有其他方法,比如浮动,固定,绝对定位的盒子不会有问题,后面在总结.取消li列表的样式li{ list-style:none;}圆角变边框语法:border-radius:length; 其中每个值可以为数值或者百分比形式 技巧: 让一个正方形编程元圈border-radius:50% 如果是矩形只用高度的一半,如果是正方形使用高度或者宽度的一半 盒子阴影语法:box-shadow:水平阴影,垂直阴影,模糊距离(虚实) 阴影尺寸(影子大小) 阴影颜色 内/外阴影总结:盒子模型的四部分组成:内容,内边距,外边距,边框 内边距的作用和对盒子的影响:调节内容和边框的距离,会使盒子撑大, padding参数的含义:上 右 下 左 盒子居中对齐的两个条件:给定宽度 margin:auto 外边距合并的解决办法:t | 可选,将外部阴影改为内部阴影 |总结:盒子模型的四部分组成:内容,内边距,外边距,边框 内边距的作用和对盒子的影响:调节内容和边框的距离,会使盒子撑大, padding参数的含义:上 右 下 左 盒子居中对齐的两个条件:给定宽度 margin:auto 外边距合并的解决办法:
group byuser表中有两个a,一个b,一个c用户(name),年龄分别为5,6,7 select name,count(*) from usr group by name; name count(*) a 2 b 1 c 1 group by 字段 with rollup (可以根据某字段排序然后计算其他字段的和,平均数等) select name,count(age) as age from usr group by name with rollup name age a 10 b 6 c 7 null 23(总和) 我们可以使用coalesce来设置一个取代null的名称,coalesce coalesce(a,b,c):a为空则取b,b为空则取c,c为空则为null,a不为空则取a select coalesce(name,'总数'),count(age) as age from usr group by name with rollup name age a 10 b 6 c 7 总数 23 连接查询外联接: 1->A左外连B无where条件取左表A所有 2->A左外连B有where条件(B is null) 取A除去B的部分 3->A右外连B无where条件取右表B所有 4->A右外连B有where条件(A is null) 取B除去A的部分 内连接: 1->取A和B共有的部分 full outer join: 1->无where条件取A和B的所有(相当于左外连和右外连接的结合取量表所有) 2->有where条件(A is null OR B is null)取A,B所有除去A,B共有的部分(相当于A,B内连接的取反) REGEXP正则用法: where 字段 regexp '^d' 操作符: ^ 以什么开头 $ 以什么结尾 xx 包含xx a|b|c 例如'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。 mysql事务mysql事务主要用于处理操作量大,复杂度高的数据,在一个人员管理系统中想要删除一个人员,需要删除他的邮箱,基本资料等,这些数据的操作语句就构成了一个事务.mysql索引索引分为单列索引和多列索引MYSQL索引的建立对于mysql的高效运行是很重要的,索引可以大大提高mysql的检索速度.实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录过多的滥用会降低更新表的速度,如对表进行insert,update,delete.因为更新表的时候,mysql不仅要保存数据,还要保存一下索引文件.MYSQL函数关于时间的函数://获取当前的日期和时间(2020-06-01 14:16:59) now() //获取当前的日期(2020-06-01) curdate() //获取当前的时间(14:16:59) curtime() //获取时间的指定片段(unix可以是day,hour等,xxx指的是时间的字段) extract(unix from xxx) //格式化指定时间为日期格式 date(xxx) //格式换时间格式 from_unixtime(xxx,'%y-%m-%d') //格式换时间格式 date_format(xxx,'%y-%m-%d') //向日期添加指定的时间间隔 date_add(date,interval expre type) //向日期减去指定的时间间隔 date_sub(xxx,interval expr type) //获取两个时间之间的差的天数 datediff(xxx,xxx) 字符串拼接//注意如果一个参数为null,则返回null concat() //注意分隔符为null则返回null,函数会忽略分隔符后面的null值 concat_ws()分隔符可以是一个字符串也可以是一个参数 select concat_ws(',','11','22','33')==>11,22,33 select concat_ws(',','11','22',null)==>11,22 group_cancat()//默认为逗号分隔如需设置分隔符group_concat(字段 separator ';') mysql> select * from aa; +------+------+ | id| name | +------+------+ |1 | 10| |1 | 20| |1 | 20| |2 | 20| |3 | 200 | |3 | 500 | +------+------+ select id group_concat(name order by name desc) from aa group by id +------+---------------------------------------+ |1 | 20,20,10 | |2 | 20| |3 | 500,200| +------+---------------------------------------+ 复制repeat(字段,份数)select repeat('ab',2); +----------------+ | abab | +----------------+ 字符串截取substring()1:从左开始截取字符串 left(截取的字段,截取的长度) 2:从右开始截取字符串 right(截取的字段,截取的长度) 3:截取字符串 substring(截取字段,从第几位开始,截取的长度) 4:按关键字截取字符串//注意如果出现得第几位为负数则从后面数 substring_index(截取字段,关键字,关键字出现的第几位) +----------------+ | abab | +----------------+ substring_index(字段,b,1)==>a substring_index(字段,b,2)==>aba substring_index(字段,b,3)==>abab substring_index(字段,b,-2)==>ab 数学函数所有的数学函数在发生错误的情况下都返回nullABS(x):返回x的绝对值 SIGN(x):返回参数是正数负数还是0;正数返回1,负数返回-1,0返回0 MOD(N,M)取模 FLOOR(x):返回不大于x的整数值 CEILING(x):返回不小于x的整数值 ROUND(X,D):将参数x四舍五入到最近的整数 DIV():整除
ajax和jsonAjax同步和异步: a标签是同步,调用完形成新的页面,页面会刷新.原页面在a标签后面的代码将不执行处于等待状态. 而异步请求不会.异步此页面的其他代码会继续执行 应用场景: 用户名是否存在的校验,百度自动补全功能,商品查找,搜索图片 注意: ajax不能跨函数,不能跨页面 异步:强大之处服务器处理不影响浏览器的其他操作,增强用户体验度原生ajax入门案例: 1:创建一个核心对象(XMLHttPRequest) 该对象称之为ajax引擎 2:编写onreadystatechange事件所调用的函数 回到函数 3:确定请求方式和请求路径 4:发送请求 5:处理返回结果 原生ajax案例:index.jsp<%-- Created by IntelliJ IDEA. User: hp Date: 2020/5/14 Time: 13:27 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <h3>原生ajax</h3> <a href="js_01.jsp">原生ajax入门</a> </body> </html> js_01.jsp<%-- Created by IntelliJ IDEA. User: hp Date: 2020/5/14 Time: 13:29 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <script type="text/javascript" src="/day07/js/jquery-3.3.1.js"></script> <script> function sendAjax() { //发送sjax请求 //1:创建ajax核心对象(xmlhttprequest)ajax引擎 var xmlhttp; if(window.XMLHttpRequest){ xmlhttp=new XMLHttpRequest(); }else{ xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } //2:编写onreaystatechange事件所调用的函数 xmlhttp.onreadystatechange=function () { //1:监听ajax引擎的状态 //5:处理结果 if(xmlhttp.readyState==4 && xmlhttp.status==200){ $("#spId").html(xmlhttp.responseText); } } //3:编写ajax请求的方式和地址 xmlhttp.open("post","demo1"); //4:发送请求 xmlhttp.send(); } </script> </head> <body> <input type="button" value="发送ajax请求" onclick="sendAjax()"/><br> <span id="spId"></span> </body> </html> Demo1Servlet.javaimport javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet(name = "Demo1Servlet", urlPatterns = "/demo1") public class Demo1Servlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("demo1servlet执行了"); response.getWriter().print("hello ajax 你好"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } } 原生ajax的属性onreadystatechange事件 在ajax对象状态发生改变时,就会出发该时间 对象.onreadystatechange=function(){} redaystate 存在XMLHTTPRequest的状态 0:请求未初始化(见不到的就相当于还没创建对象) 1:服务器连接已建立 2:请求已接收 3:请求处理中 4:请求已完后,且响应已就绪 status 响应状态码: if(xmlhttp.readystate==4&&xmlhttp.status==200){ //执行的代码片段 } responseText 获取服务器响应回来的文本信息 原生ajax的方法open(method,url,[async]); 设置请求的类型,请求地址以及是否异步处理请求 method:请求的类型;get或post url:文件在服务器上的位置 async:true(异步)或false(同步) send([string]); 将请求发送到服务器 string:存放post请求携带的参数 在post请求时要设置请求参数的mime类型 xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); jQuery中的Ajax$.post(url,[params],fn,[type])发送post请求 url:请求的路径 params:请求的参数 格式1:字符串key1=value1&key2=value2 格式2:json格式: {“key1”:“value1”,“key2”:“value2”} fn:回调函数 function(data){ //data:响应回来的数据(xmlHttp.responseText) } type:返回内容的格式 text xml json 默认返回的是text类型的数据,一般不需要自己设置,设置的话一般设置为jsonajax的post请求案例:index.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <h3>原生ajax</h3> <a href="js_01.jsp">原生ajax入门</a> <h3>jQuery的ajax</h3> <a href="js_02.jsp">jQuery的ajax</a> </body> </html> js_02.jsp<%-- Created by IntelliJ IDEA. User: hp Date: 2020/5/14 Time: 13:29 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <script type="text/javascript" src="js/jquery-3.3.1.js"></script> <script> function sendAjax() { var url = "demo2"; var params ={"username":"汤姆","password":"123"}; function fun(data) { alert(data); } $ .post(url, params, fun,"text"); } </script> </head> <body> <input type="button" value="发送ajax请求" onclick="sendAjax()"/><br> <span id="spId"></span> </body> </html> Demo02Servlet.java @WebServlet(name = "Demo3Servlet", urlPatterns = "/demo2") public class Demo3Servlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //解决乱码 response.setContentType("text/html;charset=utf-8"); response.getWriter().print(request.getParameter("username")+":"+request.getParameter("password")); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } } 如果换为get请求则 $.get(url,[params],fn,[type])即可$.ajax写法想发post或者get都可以$.ajax({url,[settings]}) url:请求路径 type:请求方式 data:请求参数 success:请求成功后的回调函数 error:请求失败后调用的函数 dateType:服务器返回的数据类型 一般不需要自己设置,要设置的话一般设置为json async:设置是否为异步提交,默认为true(异步提交) eg: $.ajax({ url:url, data:params, type:"post", success:f, error:ef, async:true }) json概述: JavaScript对象表示法,是存储和交换文本信息的语法,类似xml, 比xml更小,更快,更衣解析. 格式: 格式1:json对象 {"key":"value","key2":"value2"}//key为String类 型,value任意类型 格式2:json数组 ["aa","bb"] 格式3:混合json [{"name":"张三","age":"11"},{"name":"张三2","age":"21"}] {"student":["张三","李四"]} 获取json对象格式的值直接:对象.key java对象转换为json数据常见的工具类: jsonlib fastjson(阿里巴巴的) gson(谷歌的) Jackjson(为springmvc默认的转换方式) 案例一:检测用户名是否被注册需求分析: 再注册页面上,当用户在用户名的输入框输入完成后,也就是失去焦点事件发送ajax请求,检验用户名是否存在 该用户名已存在: 提示:"该用户名已被占用" 该用户名不存在: 提示:"√" 技术分析: blur:失去焦点事件 ajax: $.ajax({ url:"", data:"", type:"", success:f, error:f, dataType:"text", async:true }) 步骤分析: 前台: 提供注册页面,给用户名的输入框添加失去焦点事件 输入框对象.blur(function(){ //发送ajax请求 }) demo1.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <script type="text/javascript" src="js/jquery-3.3.1.js"></script> <script> //页面加载成功事件 $(function () { //用户名输入框的失去焦点事件 $("input[name='username']").on("blur",function () { //获取用户输入的name值 var username=$(this).val(); //使用ajax异步处理 $.ajax({ url:"user", data:{"username":username}, type:"post", success:function (data) { $("#nameMsg").html(data); }, dataType:"text", async:true }) }) }) </script> </head> <body> <form action=""> <table align="center" border="1px solid red" width="300px" height="100px"> <tr> <th colspan="2" align="center"> 用户表单 </th> </tr> <tr> <td>用户名:</td> <td> <input type="text" name="username"> <span id="nameMsg"></span> </td> </tr> <tr> <td>密码:</td> <td> <input type="password" name="pwd"> </td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" id="subId" value="提交"/> </td> </tr> </table> <br> </form> </body> </html> UserServlet.java @WebServlet(name = "UserServlet", urlPatterns = "/user") public class UserServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); //获取前端传来的userName String username = request.getParameter("username"); //调用service查看此用户名是否存在 UserService userService = new UserService(); User exist = userService.exist(username); if (exist==null) { response.getWriter().print("√"); }else{ response.getWriter().print("用户名已存在"); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } } UserService.java public class UserService { public User exist(String username){ UserDao userDao = new UserDao(); return userDao.exist(username); } } UserDao.java public class UserDao { public User exist(String username){ try { String sql = "select * from user where name = ? "; JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); return template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username); } catch (DataAccessException e) { return null; } } } 案例二:异步自动填充demo2.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <style type="text/css"> .content{ width:643px; margin:200px auto; text-align: center; } input[type='text']{ width:530px; height:40px; font-size: 14px; } input[type='button']{ width:100px; height:46px; background: #38f; border: 0; color: #fff; font-size: 15px } .show{ width: 535px; border: 1px solid #999; border-top: 0; display: none; } </style> <script type="text/javascript" src="js/jquery-3.3.1.js"></script> <script type="text/javascript"> //搜索框联想词的单击事件 function fn(ele) { $("input[name='word']").val($(ele).html()); } $(function () { $("input[name='word']").on("keyup",function () { $.ajax({ url:"demo2", data:{"username":$(this).val()}, type:"post", success:function (data) { if(data!="null"&&data.length>0){ var str=""; $("div>div").empty(); $.each(data,function (index,ele) { str += "<span οnclick='fn(this)'>"+ele+"</span><br>"; }); $("div>div").html(str); $("div>div").show(); }else{ $("div>div").hide(); } }, dataType:"json", async:true }) }) }) </script> </head> <body> <div class="content"> <img alt="" src="img/logo.png"><br/><br/> <input type="text" name="word"> <input type="button" value="搜索一下"> <div class="show"></div> </div> </body> </html> Demo2Servlet.java@WebServlet(name = "Demo2Servlet", urlPatterns = "/demo2") public class Demo2Servlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); List<String> userList = new UserService().likeName(request.getParameter("username")); //将list转换为json String listJson = new ObjectMapper().writeValueAsString(userList); System.out.println("listJson======="+listJson); response.getWriter().print(listJson); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } } service.javapublic class UserService { public User exist(String username){ UserDao userDao = new UserDao(); return userDao.exist(username); } public List<String> likeName(String username){ UserDao userDao = new UserDao(); return userDao.likeName(username); } } dao.javapublic class UserDao { public User exist(String username){ try { String sql = "select * from user where name = ? "; JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); return template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username); } catch (DataAccessException e) { return null; } } public List<String> likeName(String username){ try { String sql = "select name from user where name like ? "; JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); return template.queryForList(sql, String.class, username+"%"); } catch (DataAccessException e) { return null; } } }
servlet中的三大作用域request 创建: 请求来的时候 销毁: 响应信息生成的时候 作用域范围: 当前请求session 创建: 请求不携带jsessionId就会创建session对象 请求携带jsessionId,但未找到对应的session空间 销毁: 服务器非正常关闭 session超时 手动销毁(session.invalidate) 作用域范围: 当前请求servletContext 创建: 服务器启动时 销毁: 服务器关闭时,或项目从服务器上移除时 作用范围: 当前项目jsp执行流程:第一次访问jsp页面时,服务器接收请求,由jspservlet来处理此请求,1,jspSevlet回去查找对应的jsp文件2,找到之后,服务器会将jsp文件转换成java文件3,服务器编译java文件生成class文件.4,服务器运行class文件,生成动态的内容,并返回给浏览器.<% 编写java代码 %> //中的代码将会复制到_jspService方法中 <%! 声明全局变量也可定义方法 %> <%= 输出 %> jsp特有内容(三大指令,四大作用域,九大内置对象)三大指令:page,taglib,include四大作用域: pageContext(pageContext) request(HttpServletRequest) session(httpSession) application(servletContext)九大内置对象 request response session page pageContext out exception application configEL表达式(jsp2.0后内置)作用就是代替<%= %> 也就是代替输出格式:${表达式}常用功能: 获取作用域中的数据<% pageContext.setAttribute("pkey","pvalue"); request.setAttribute("rkey","rvalue"); request.setAttribute("aa.bb","ralue"); %> 原始方法获取: <%=pageContext.getAttribute("pkey")%> <%=request.getAttribute("rkey")%> <%=request.getAttribute("aa.bb")%> EL获取: ${pageScope.pkey}; ${requestScope.rkey} ${requestScope["aa.bb"]} 注意:如果域中没有,原始的方法取出来的是null而EL取出来的是空字符 便携方式获取<% pageContext.setAttribute("key","pvalue"); request.setAttribute("key","rvalue"); session.setAttribute("keyralue"); appllication.setAttribute("key","ralue"); appllication.setAttribute("aa.bb","ralue"); %> ${applicationScope["aa.bb"]}//如果参数名称中有特殊字符那么便捷查找方式不好用,只能用域对象取值 ${key}获取出来的是pvalue 因为会从四个域中查找数据信息(从小到大)找到即返回 pageContext<request<session<application 获取复杂的数据数组:${属性名称[index]} list:${属性名称[index]} map:${属性名称.key} 实体对象:${属性名称.key}//本质上调用的是实体类的get方法
Bootstrap概述:是最受欢迎的HTML,CSS和JS框架,用于开发响应式布局,移动设备优先的WEB项目.Bootstrap是基于jquery开发的,在使用时需要引入jquery的js文件.响应式:一个网页可以兼容多种终端JQuery是对js的封装下载BootStrap:www.bootcss.com官网地址模板☆1:导入BootStrap的css2:导入jquery的js文件(1.8+)3:导入BootStrap的js4:设置视口(支持移动设备)<meta name="viewport" content="width=dvice-width,initial-scale=1"> 5:添加一个局部容器 给div设置一个class属性: class="containner"固定宽度 class="container-fluid"类似于100%<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!--4.添加视口--> <meta name="viewport" content="width=device-width, initial-scale=1"> <title></title> <!--1.导入Bootstrap的css文件--> <link href="css/bootstrap.css" rel="stylesheet"> <!--2.导入jQuery的js文件--> <script src="js/jquery-3.2.1.min.js"></script> <!--3.导入bootstrap的js文件--> <script src="js/bootstrap.min.js"></script> </head> <body> <div style="border: solid 1px red;height: 20px;"></div> <!--5.添加布局容器--> <div class="container-fluid"> <div style="border: 1px solid red;height: 20px;"></div> </div> <div class="container"> <div style="border: 1px solid red;height: 20px;"></div> </div> </body> </html>
JS案例(Demo)1:全选全不选需求分析: 当点击"全选/全不选"选框时,让其他的复选框状态和"全选/全不选"复选框的状态保持一致.技术分析: 点击事件 操作复选框的checked属性步骤分析: 1:确定事件 给"全选/全不选"复选框添加点击事件 2:编写函数 a.获取"全选/全不选"复选框的状态(checked) b.获取其他复选框.并设置装态(checked)写法一:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script> //设置全选/全不选 function selectAll() { //1:获取全选全不选标签对象 var allObj= document.getElementById("all"); //2:获取checked属性 var checkObj=allObj.checked; //3:获取商品价格对象列表对象数组 var arrObj= document.getElementsByName("item"); //4:遍历数组 for(var i=0;i<arrObj.length;i++){ //5:把全选全不选的值赋值到每个商品标签上 arrObj[i].checked = checkObj; } } //设置反选 function reverseSelect() { //1:获取商品价格对象列表对象数组 var arrObj= document.getElementsByName("item"); //2:遍历数组 for(var i=0;i<arrObj.length;i++){ //5:把全选全不选的值取反赋值到每个商品标签上 arrObj[i].checked = !arrObj[i].checked; } } </script> </head> <body> <h3>商品价格列表</h3> <input type="checkbox" name="item" value="1500" /> 山地自行车1500<br /> <input type="checkbox" name="item" value="200" /> 时尚女装200<br /> <input type="checkbox" name="item" value="3000" /> 笔记本电脑3000元<br /> <input type="checkbox" name="item" value="800" /> 情侣手表800<br /> <input type="checkbox" name="item" value="2000" /> 桑塔纳2000<br /> <hr/> <input type="checkbox" id="all" onclick="selectAll()" />全选/全不选 &nbsp; <input type="button" id="reverse" onclick="reverseSelect()" value=" 反 选 "/>&nbsp; </body> </html> 写法二:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script> //设置全选/全不选 function selectAll(obj) { //3:获取商品价格对象列表对象数组 var arrObj= document.getElementsByName("item"); //4:遍历数组 for(var i=0;i<arrObj.length;i++){ //5:把全选全不选的值赋值到每个商品标签上 arrObj[i].checked = obj; } } //设置反选 function reverseSelect() { //1:获取商品价格对象列表对象数组 var arrObj= document.getElementsByName("item"); //2:遍历数组 for(var i=0;i<arrObj.length;i++){ //5:把全选全不选的值取反赋值到每个商品标签上 arrObj[i].checked = !arrObj[i].checked; } } </script> </head> <body> <h3>商品价格列表</h3> <input type="checkbox" name="item" value="1500" /> 山地自行车1500<br /> <input type="checkbox" name="item" value="200" /> 时尚女装200<br /> <input type="checkbox" name="item" value="3000" /> 笔记本电脑3000元<br /> <input type="checkbox" name="item" value="800" /> 情侣手表800<br /> <input type="checkbox" name="item" value="2000" /> 桑塔纳2000<br /> <hr/> <!--this关键字代表当前标签对象(写在哪个标签上代表哪个标签对象)--> <input type="checkbox" id="all" onclick="selectAll(this.checked)" />全选/全不选 &nbsp; <input type="button" id="reverse" onclick="reverseSelect()" value=" 反 选 "/>&nbsp; </body> </html> 2:省市二级联动需求分析 当省份的下拉选改变时,获取选中的省份,查询该省份所对应的是数 组,遍历市数组拼成option插入到市的下拉选中技术分析 改变事件:onchange innertHtml步骤分析 1:确定事件 给省份的下拉选添加onchange事件 2:编辑函数 function changePro(){ 二维数组 }<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script> // 定义二维数组: var arr = new Array(4); arr[0] = new Array("哈尔滨","齐齐哈尔","大庆","佳木斯"); arr[1] = new Array("长春市","吉林市","四平市","通化市"); arr[2] = new Array("沈阳市","锦州市","大连市","铁岭市"); arr[3] = new Array("郑州市","洛阳市","安阳市","南阳市"); </script> <script> function changePro(val) { //遍历市数组 var optionStr = "<option>==请选择==</option>"; for(var i=0;i<arr[val].length;i++){ //把每一个市拼接到option中 optionStr += "<option>" + arr[val][i] + "</option>"; } //把option插入到市下拉选中 document.getElementById("city").innerHTML = optionStr; } </script> </head> <body> <form action="#" method="get"> 籍贯: <select name="pro" onchange="changePro(this.value)"> <option>请选择</option> <option value="0">黑龙江</option> <option value="1">吉林</option> <option value="2">辽宁</option> <option value="3">河南</option> </select> <select id="city"> <option >-请选择-</option> </select> </form> </body> </html> 3:隔行换色需求分析页面加载成功后,给表格的奇数行和偶数行添加不同的背景颜色技术分析 onload js操作css步骤分析<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script> //表头除外奇数行为红色,偶数行为绿色 onload=function (ev) { var trArrObj=document.getElementsByTagName("tr"); for(var i=1;i<trArrObj.length;i++) { if(i%2==0){ //偶数行 trArrObj[i].style.backgroundColor = "green"; }else{ //奇数行 trArrObj[i].style.backgroundColor = "red"; } } } </script> </head> <body> <table id="tab1" border="1" width="800" align="center" > <tr style="background-color: #999999;"> <th>分类ID</th> <th>分类名称</th> <th>分类描述</th> <th>操作</th> </tr> <tr> <td>1</td> <td>手机数码</td> <td>手机数码类商品</td> <td><a href="">修改</a>|<a href="">删除</a></td> </tr> <tr> <td>2</td> <td>电脑办公</td> <td>电脑办公类商品</td> <td><a href="">修改</a>|<a href="">删除</a></td> </tr> <tr> <td>3</td> <td>鞋靴箱包</td> <td>鞋靴箱包类商品</td> <td><a href="">修改</a>|<a href="">删除</a></td> </tr> <tr> <td>4</td> <td>家居饰品</td> <td>家居饰品类商品</td> <td><a href="">修改</a>|<a href="">删除</a></td> </tr> </table> </body> </html>
Day_05(html5,css)html:根标签 head:存放页面的重要信息一般不再页面上显示 title:标题标签 meta:存放页面的重要信息不在页面显示 link:样式 style:样式 body:存放页面上需要展示的内容form表单子标签属性name属性: 给单选框或者复选框进行分组,在当前组内进行单选. 输入框的值要想提交给服务器,那么输入框必须要有name属性value属性: 给按钮起名称 设置提交给服务器的值disableed=“disableed”:设置标签可不用readonly="readonly"设置文本框只读给表单中的标签添加默认值:text和password用value属性;radio和checkbox用checked=“checked”;select下拉选需要给option标签添加selected=“selected”placeholder="";设置输入框灰色的显示文字select下拉选是一组标签配合option使用,size属性是展示的个数.multiple="multiple"设置下拉选可以多选textare 文本域, 格式: 属性:rows:行 只是展示初始化文本框的大小无意义 cols:列 只是展示初始化文本框的大小无意义form表单属性action:表单提交的路径method:设置表单提交的方式div+csscss语法:选择器{css属性:属性值;css属性:属性值;}css和html整合方式:行内样式,内部样式,外部样式 优先级(行内>外部|内部(就近原则))行内样式:直接添加style属性即可内部样式:在head里面添加标签,在标签中写样式即可eg:<head> <meta charset="UTF-8"> <title>整合css</title> <!--2:内部样式 <style> 选择器{//选择页面中指定的标签 css样式:值; css样式:值; } </style> --> <style> #divid1{ background-color: green; } </style> </head> <body> <!--1:行内样式--> <div style="background-color: #FFE388">如今的现在早已不是曾经说好的以后</div> <div id="divid1">如今的现在早已不是曾经说好的以后</div> <div id="divid2">如今的现在早已不是曾经说好的以后</div> </body> 外部样式:first.css#divid2{ background-color: #FC8989; } class选择器给一类标签设置相同的样式通过.class的名称选中这类标签标签选择器<head> <style> span{ background-color: red; } </style> </head> <body> <span>哈哈</span> <span>哈哈</span> <span>哈哈</span> </body> 选择器的优先级:id>class>标签css样式浮动:切记用到浮动就要清除浮动 未添加浮动之前div是竖着展示的<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>浮动与清除浮动</title> <style> .cls{ border: 1px solid red; width: 50px; height: 50px; float: left; } </style> </head> <body> <div class="cls">1</div> <div class="cls">2</div> <div class="cls">3</div> <div class="cls">4</div> </body> </html> 浮动之后清除浮动:清除浮动然后在浮动下一排<head> <meta charset="UTF-8"> <title>浮动与清除浮动</title> <style> .cls{ border: 1px solid red; width: 50px; height: 50px; float: left; } .clr{ clear: both; } </style> </head> <body> <div class="cls">11</div> <div class="cls">12</div> <div class="cls">13</div> <div class="cls">14</div> <div class="clr"></div> <div class="cls">21</div> <div class="cls">22</div> <div class="cls">23</div> <div class="cls">24</div> </body> 显示类型:<div>你好</div> <span>我的</span> <span>大中国</span> //会显示两行,div独占一行,两个span标签在一行,因为span标签是行内标签 • 1 • 2 • 3 • 4<head> <style> #sp{//把span的行内标签变为行级标签 display:block; } div{//把行级标签变为行内标签 display:inline; } #sp{//隐藏不显示 display:none; } </style> </head> <body> <div>你好</div> <span>我的</span> <span id="sp">大中国</span> </body> 框模型(盒子模型)内边距 边框和内容之间的距离 padding:(顺时针 上右下左) padding:20px;上下左右 padding:20px上下;30px左右; ..... 外边距 边框和相邻边框的距离 margin:(顺时针 上右下左) box-sizing:怪异的盒子(border-box) 一旦设置完宽高则代表整个div的宽高 块级元素居中 margin:auto demo用户主页页面<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> *{ padding: 0px; margin: 0px; box-sizing: border-box; } #outDiv{ width: 888px; height: 550px; border: 8px solid #eee; margin: 20px auto; } #id1,#id2,#id3{ float: left; height: 530px; } #id1{ width: 28%; padding-top: 20px; padding-left: 20px; } #id1 > p:first-child{ font-size: 20px; color: #FFE388; } #id1 > p:last-child{ font-size: 20px; color: #A6A6A6; } #id2{ width: 40%; padding-top: 20px; } #id2 .lf{ width: 30%; text-align: right; padding-right: 20px; } #id2 .rt{ height: 50px; } input[type="text"], input[type="date"], input[type="password"] { width: 256px; /* 行高与高度相同,则表示垂直居中 */ height: 32px; line-height: 32px; padding: 6px 12px; border-radius: 4px; border: 1px solid #a6a6a6; float: right; } #id3{ width: 28%; } #id3 > p{ padding-top: 20px; text-align: right; } #id3 > p > a{ color: #FC8989; } </style> </head> <body> <div id="outDiv"> <div id="id1"> <p> 新用户注册 </p> <p>USER REGISTER</p> </div> <div id="id2"> <form action="#" method="get"> <table> <tr> <td class="lf">用户名</td> <td class="rt"><input id="name" type="text" name="name" placeholder="请输入用户名"/></td> </tr> <tr> <td class="lf">密码</td> <td class="rt"><input id="pwd" type="password" name="pwd" placeholder="请输入密码" /></td> </tr> <tr> <td class="lf">Email</td> <td class="rt"><input type="text" id="email" name="email" placeholder="请输入邮箱"/></td> </tr> <tr> <td class="lf">姓名</td> <td class="rt"><input type="text" id="username" name="username" placeholder="请输入真实名称"/></td> </tr> <tr> <td class="lf">手机号</td> <td class="rt"><input type="text" id="phone" name="phone" placeholder="请输入手机号" /></td> </tr> <tr> <td class="lf">性别</td> <td class="rt"><input type="radio" name="sex" value="1"/>男 <input type="radio" name="sex" value="0"/>女 </td> </tr> <tr> <td class="lf">生日</td> <td class="rt"><input type="date" id="birthday" name="birthday" placeholder="请输入生日" /></td> </tr> <tr> <td class="lf">验证码</td> <td class="rt"><input type="text" id="code" name="birthday" placeholder="输入验证码" /></td> </tr> </table> </form> </div> <div id="id3"> <p>已有账号? <a href="#">立即登录</a></p> </div> </div> </body> </html>
Day_02内容:1.数据库设计的三大范式和反第三范式第一范式:组成表中的各列不可再拆分,第二范式:一张表只说一件事(数据库是二维表格描述一件事)第三范式:在同一张表中,消除传递依赖(如果表中有A和B,且根据A,B能得到C,就不要把C展示出来),两张表中也存在传递依赖.拿空间换时间.反第三范式:不满足第三范式,键.建立出传递依赖的字段.拿空间换时间.说到第三范式就先说一下mybatis的加载策略:立即加载和延迟加载.eg:酒水分为茅台和江小白,饮料分为可乐和雪碧立即加载:查询可乐的时候一并将分类(饮料)查出来 特征:对一 设计思想:拿空间换时间延迟加载:查询饮料的时候没必要知道雪碧和可乐是不是属于饮料 特征:对多 设计思想:拿时间换空间外键:存在于从表中该列的取值只能来源于主表的主键或者是null外键要是not null 和unique则表之间为一对一确定表关系: 找外键,有外键的就是从表,没有中间表且外键为not null 和unique是一对一,否则一对多,有中间表则为多对多2:类型的转换: a:自定义类型转换器 b:基于注解的类型转换@DateTimeFormat
Java(JDK)13新特性之Reimplement the Legacy Socket API使用易于维护和调试的更简单、更现代的实现替换 java.net.Socket 和 java.net.ServerSocket API。java.net.Socket和java.net.ServerSocket的实现非常古老,这个JEP为它们引入了一个现代的实现。现代实现是Java 13中的默认实现,但是旧的实现还没有删除,可以通过设置系统属性jdk.net.usePlainSocketImpl来使用它们。运行一个实例化Socket和ServerSocket的类将显示这个调试输出。这是默认的(新的):java -XX: TraceClassLoading JEP353 | grep Socket [0.033s][info ][class,load] java.net.Socket source: jrt:/java.base [0.035s][info ][class,load] java.net.SocketOptions source: jrt:/java.base [0.035s][info ][class,load] java.net.SocketImpl source: jrt:/java.base [0.039s][info ][class,load] java.net.SocketImpl$$Lambda$1/0x0000000800b50840 source: java.net.SocketImpl [0.042s][info ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base [0.042s][info ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base [0.043s][info ][class,load] sun.nio.ch.SocketDispatcher source: jrt:/java.base [0.044s][info ][class,load] java.net.DelegatingSocketImpl source: jrt:/java.base [0.044s][info ][class,load] java.net.SocksSocketImpl source: jrt:/java.base [0.044s][info ][class,load] java.net.ServerSocket source: jrt:/java.base [0.045s][info ][class,load] jdk.internal.access.JavaNetSocketAccess source: jrt:/java.base [0.045s][info ][class,load] java.net.ServerSocket$1 source: jrt:/java.base 上面输出的sun.nio.ch.NioSocketImpl就是新提供的实现。如果使用旧的实现也是可以的(指定参数jdk.net.usePlainSocketImpl):$ java -Djdk.net.usePlainSocketImpl -XX: TraceClassLoading JEP353 | grep Socket [0.037s][info ][class,load] java.net.Socket source: jrt:/java.base [0.039s][info ][class,load] java.net.SocketOptions source: jrt:/java.base [0.039s][info ][class,load] java.net.SocketImpl source: jrt:/java.base [0.043s][info ][class,load] java.net.SocketImpl$$Lambda$1/0x0000000800b50840 source: java.net.SocketImpl [0.046s][info ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base [0.047s][info ][class,load] java.net.AbstractPlainSocketImpl source: jrt:/java.base [0.047s][info ][class,load] java.net.PlainSocketImpl source: jrt:/java.base [0.047s][info ][class,load] java.net.AbstractPlainSocketImpl$1 source: jrt:/java.base [0.047s][info ][class,load] sun.net.ext.ExtendedSocketOptions source: jrt:/java.base [0.047s][info ][class,load] jdk.net.ExtendedSocketOptions source: jrt:/jdk.net [0.047s][info ][class,load] java.net.SocketOption source: jrt:/java.base [0.047s][info ][class,load] jdk.net.ExtendedSocketOptions$ExtSocketOption source: jrt:/jdk.net [0.047s][info ][class,load] jdk.net.SocketFlow source: jrt:/jdk.net [0.047s][info ][class,load] jdk.net.ExtendedSocketOptions$PlatformSocketOptions source: jrt:/jdk.net [0.047s][info ][class,load] jdk.net.ExtendedSocketOptions$PlatformSocketOptions$1 source: jrt:/jdk.net [0.048s][info ][class,load] jdk.net.LinuxSocketOptions source: jrt:/jdk.net [0.048s][info ][class,load] jdk.net.LinuxSocketOptions$$Lambda$2/0x0000000800b51040 source: jdk.net.LinuxSocketOptions [0.049s][info ][class,load] jdk.net.ExtendedSocketOptions$1 source: jrt:/jdk.net [0.049s][info ][class,load] java.net.StandardSocketOptions source: jrt:/java.base [0.049s][info ][class,load] java.net.StandardSocketOptions$StdSocketOption source: jrt:/java.base [0.051s][info ][class,load] sun.net.ext.ExtendedSocketOptions$$Lambda$3/0x0000000800b51440 source: sun.net.ext.ExtendedSocketOptions [0.057s][info ][class,load] java.net.DelegatingSocketImpl source: jrt:/java.base [0.057s][info ][class,load] java.net.SocksSocketImpl source: jrt:/java.base [0.058s][info ][class,load] java.net.ServerSocket source: jrt:/java.base [0.058s][info ][class,load] jdk.internal.access.JavaNetSocketAccess source: jrt:/java.base [0.058s][info ][class,load] java.net.ServerSocket$1 source: jrt:/java.base 上面的结果中,旧的实现java.net.PlainSocketImpl被用到了。
Java(JDK)13新特性之Dynamic CDS Archives同一个物理机/虚拟机上启动多个JVM时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的,所以引入了CSD(class Data Sharing)通过一些核心类在每个JVM间共享,每个JVM只需要装载自己的应用类,启动时间较少了,另外核心类是共享的,所以JVM的内存占用也减少了.CDS只能作用于Boot Class Loader加载的类,不能作用于App Class Loader 或者自定义的Class Loader加载的类.在java10中,则将CDS扩展为AppCDS,AppCDS不止能够作用于Boot Class Loader,App Class Loader和自定义的Class Loader也能够起作用,大大加大了CDS的适用范围,也就是开发的自定义的类也可以装载给多个JVM共享了.Java 10包含JEP310的通过跨不同Java进程共享公共类源数据减少了内存的占用和改进了启动时间.但是,JEP310中,使用AppCDS的过程还是比较复杂的,需要下面的步骤1:决定要Dump 哪些Class2:将类的内存Dump到归档文件中3:使用Dump出来的归档文件加快应用的启动速度这一次的Java13中的JEP350在310的基础上,又做了一些扩展,允许java应用程序执行结束时动态归档类,归档类将包括默认的基础层CDS存档中不存在的所有已加载的应用程序类和库类.也就是说,在Java 13中再使用AppCDS的时候,就不在需要这么复杂了。
Java(JDK)13新特性之Text Blocks语句总概Text Blocks文本块,是一个多行字符串文字,它避免了对大多数转义序列的需要,以可预测的方式自动化字符串,并在需要时让开发人员控制格式.之前从外部copy一段文本到java中,会被自动转义,如下<html> <body> <p>Hello,World</p> </body> </html> 将其复制到Java的字符串中,会展示一下的内容"<html>\n" " <body>\n" " <p>Hello, world</p>\n" " </body>\n" "</html>\n"; 即被自动的进行了转义,在JDK13中可以使用一下的语法""" <html> <body> <p>Hello,World</p> </body> </html> """; 使用"" "作为文本块的开始符合结束符,在其中就可以放置多行的字符串,不需要进行任何的转义,例如常见的sqlString sql=""" select `city_name` where `city`=`beijing` order by `city_id` """;
Java(JDK)13新特性之Switch语句总概Switch Expressions 在Switch表达式中(不需要返回值)则使用break;(需要返回值)则使用yield;之前的Switch表达式语法int i; switch(x){ case "1": i=1; break; case "2": i=2; break; default: i=x.length(); break; } Java13的Switch语法一int i=switch(x){ case "1":yield 1; case "2":yield 2; default :{ int len=x.length(); yield len; } }; 或者int i=switch(x){ case "1" ->1; case "2" ->2; dafault ->{ int len=x.length(); yield len; } }; yield和return的区别return会直接跳出当前循环或者方法yield只会跳出当前的switch块
SSM搭建详解第一步骤:保证spring的Ioc独立运行导入依赖 <!--spring和springmvc坐标--> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.4.RELEASE</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <!--spring整合mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <!--导入德鲁伊数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency> <!--导入junit测试坐标--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--spring整合junit--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.4.RELEASE</version> </dependency> <!--jstl--> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <!--jsp--> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> </dependency> <!--解析切入点表达式的坐标--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <!--事务的坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.4.RELEASE</version> </dependency> </dependencies> 编写业务层代码Service接口 public interface CompanyService { //查询所有 List<Company> findAll(); //根据id查询 Company findById(String id); //更新 void update(Company company); } ServiceImpl实现类@Service public class CompanyServiceImpl implements CompanyService { private CompanyDao companyDao; public List<Company> findAll() { System.out.println("执行了查询所有的方法"); return null; } public Company findById(String id) { return null; } public void update(Company company) { } } spring配置文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置spring容器要扫描的包--> <context:component-scan base-package="包名....."> <!--排除Controller注解--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter> </context:component-scan> </beans> 测试保证spring的IOC独立运行public class Test01SpringIoc { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); CompanyService serviceImpl = applicationContext.getBean("companyServiceImpl",CompanyService.class); System.out.println(serviceImpl.findAll()); } } 结果[外链图片转存失败(img-vub6kYlh-1569163462217)(D:\Zone\CSDN\TimeFriends\九月\9月8日\springIOC.png)]第二步保证Mybatis独立运行创建mybatis的配置文件SqlMapConfig.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--引入properties文件--> <properties resource="jdbc.properties"> </properties> <!--配置环境--> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </dataSource> </environment> </environments> <!--指定映射文件的位置--> <mappers> <package name="com.itheima.dao"></package> </mappers> </configuration> jdbc.properties配置文件jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/company_e88 jdbc.username=root jdbc.password=root CompanyDao.xml映射文件<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--命名空间操作的是哪个dao--> <mapper namespace="com.itheima.dao.CompanyDao"> <!--配置数据库列名和实体类名称的对应关系--> <resultMap id="companyMap" type="com.itheima.dao.CompanyDao"> <id column="id" property="id"></id> <result column="name" property="name"></result> <result column="expiration_date" property="expirationDate"></result> <result column="address" property="address"></result> <result column="license_id" property="licenseId"></result> <result column="representative" property="representative"></result> <result column="phone" property="phone"></result> <result column="company_size" property="companySize"></result> <result column="industry" property="industry"></result> <result column="remarks" property="remarks"></result> <result column="state" property="state"></result> <result column="balance" property="balance"></result> <result column="city" property="city"></result> </resultMap> <!--配置查询所有--> <select id="findAll" resultMap="companyMap"> select * from ss_company; </select> <!--配置根据id查询--> <select id="findById" resultMap="companyMap" parameterType="java.lang.String" > select * FROM ss_company where id=#{id} </select> <!--配置更新--> <update id="update" parameterType="com.itheima.domain.Company"> update ss_company set `name`=#{name}, expiration_date=#{expirationDate}, address=#{address}, license_id=#{licenseId}, representative=#{representative}, phone=#{phone}, company_size=#{companySize}, industry=#{industry}, remarks=#{remarks}, state=#{state}, balance=#{balance}, city=#{city} WHERE id=#{id} </update> </mapper> 编写测试类:public class Test02Mybatis { public static void main(String[] args) throws IOException { //1.读取主配置文件 InputStream stream = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.构建sqlSessionFactory SqlSessionFactory factory = builder.build(stream); //4.创建sqlSession SqlSession sqlSession = factory.openSession(); //5.创建dao接口的代理实现类 CompanyDao proxyMapper = sqlSession.getMapper(CompanyDao.class); //6.执行dao的方法 Company company = proxyMapper.findById("2"); System.out.println(company); //7.释放资源 sqlSession.close(); stream.close(); } } 第三步整合spring和mybatis思路:spring接管SqlSessionFactory的创建,以及dao接口的代理实现类的创建在applicationContext.xml中<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置spring容器要扫描的包--> <context:component-scan base-package="com.itheima"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter> </context:component-scan> <!-----------------------------------------------------------------------------> <!--整合spring和mybatis--> <!--指定properties文件的位置--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--注入数据源操作数据库--> <property name="dataSource" ref="dataSource"> </property> </bean> <!--配置数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!--自动创建dao接口的代理实现类并且存入Ioc容器--> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.itheima.dao"></property> </bean> </beans> 第四步保证spring事务的正常使用在applicationContext.xml中配置事务<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置spring容器要扫描的包--> <context:component-scan base-package="com.itheima"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter> </context:component-scan> <!-----------------------------------------------------------------------------> <!--整合spring和mybatis--> <!--指定properties文件的位置--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--注入数据源操作数据库--> <property name="dataSource" ref="dataSource"> </property> </bean> <!--配置数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!--自动创建dao接口的代理实现类并且存入Ioc容器--> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.itheima.dao"></property> </bean> <!-----------------------------------------------------------------------------> <!--配置事务--> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--配置事务的属性--> <tx:attributes> <tx:method name="*" read-only="false" propagation="REQUIRED"/> <tx:method name="find*" read-only="true" propagation="SUPPORTS"></tx:method> </tx:attributes> </tx:advice> <!--配置aop--> <aop:config> <!--配置切入点:.*.*(..) 任意类下的任意方法的有无返回值都可--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--建立切入点表达式和事务通知的关系--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config> </beans> 在service实现类中加入注解@Autowried注入dao@Autowired private CompanyDao companyDao; • 1 • 2测试spring和mybatis的整合结果/** * 测试spring和mybatis的整合结果 */ /*创建容器*/ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class Test03SpringMybatis { @Autowired private CompanyService companyService; @Test public void testFindAll(){ List<Company> companyList = companyService.findAll(); for (Company company : companyList) { System.out.println(company); } } @Test public void testUpdate(){ Company company = companyService.findById("2"); company.setExpirationDate(new Date()); companyService.update(company); } } 第五步保证springMvc的独立运行web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!--配置前端控制器--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置servlet的初始化参数,指定springmvc配置文件的位置--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--配置servlet启动时候就创建--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!--*.do与/的区别:/访问静态资源的时候需要配置mvc:resources--> <url-pattern>*.do</url-pattern> </servlet-mapping> <!--配置字符集过滤器,过滤器的生命周期是项目启动--> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!--配置过滤器的初始化参数,指定使用的字符集--> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <!--拦截过滤当前项目下的所有资源--> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> springmvc.xml配置文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置springmvc创建容器时扫描的包--> <context:component-scan base-package="com.itheima.controller"></context:component-scan> <!--配置视图解析器--> <bean id="ViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!--开启springmvc的注解支持--> <mvc:annotation-driven></mvc:annotation-driven> </beans> Controller层@Controller @RequestMapping("/company") public class CompanyController { //没有注入是因为这一步骤是保证springmvc的独立运行 private CompanyService companyService; /** * 配置查询所有 * @return */ @RequestMapping("/list") public String findAll(){ System.out.println("执行了查询所有"); return ""; } } 在web.xml中配置监听器(监听项目启动的时候就创建容器解决注入不进去的问题)CompanyController类注意:update需要重定向,如果请求转发那么地址栏不会发生变化,刷新会导致再次更新.所以update需要重定向.@Controller @RequestMapping("/company") public class CompanyController { @Autowired private CompanyService companyService; /** * 配置查询所有 * @return */ @RequestMapping("/list") public String findAll(HttpServletRequest request){ List<Company> companyList = companyService.findAll(); //存到请求域中 request.setAttribute("list",companyList); //转发到列表页面 return "company-list"; } /** * 前往更新页面,根据id查询 * @return */ @RequestMapping("/toUpdate") public String toUpdate(String id,HttpServletRequest request){ Company company = companyService.findById(id); //存入到请求域中 request.setAttribute("company",company); companyService.update(company); return "company-update"; } @RequestMapping("/edit") public String edit(Company company){ companyService.update(company); //重定向到列表页面 return "redirect:/company/list.do"; } } }/** * 前往更新页面,根据id查询 * @return */ @RequestMapping("/toUpdate") public String toUpdate(String id,HttpServletRequest request){ Company company = companyService.findById(id); //存入到请求域中 request.setAttribute("company",company); companyService.update(company); return "company-update"; } @RequestMapping("/edit") public String edit(Company company){ companyService.update(company); //重定向到列表页面 return "redirect:/company/list.do"; } }
Object类九大方法之finalize方法finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。finalize的作用(1)finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性(2)不建议用finalize方法完成“非内存资源”的清理工作,但建议用于:① 清理本地对象(通过JNI创建的对象);② 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize方法中显式调用其他资源释放方法。其原因可见下文[finalize的问题][finalize的问题](1)一些与finalize相关的方法,由于一些致命的缺陷,已经被废弃了,如System.runFinalizersOnExit()方法、Runtime.runFinalizersOnExit()方法(2)System.gc()与System.runFinalization()方法增加了finalize方法执行的机会,但不可盲目依赖它们(3)Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行(4)finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行(5)对象再生问题:finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的(6)finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)finalize的执行过程(生命周期)(1) 首先,大致描述一下finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。(2) 具体的finalize流程:对象可由两种状态,涉及到两类状态空间,一是终结状态空间 F = {unfinalized, finalizable, finalized};二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}。各状态含义如下:unfinalized: 新建对象会先进入此状态,GC并未准备执行其finalize方法,因为该对象是可达的finalizable: 表示GC可对该对象执行finalize方法,GC已检测到该对象不可达。正如前面所述,GC通过F-Queue队列和一专用线程完成finalize的执行finalized: 表示GC已经对该对象执行过finalize方法reachable: 表示GC Roots引用可达finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达unreachable:对象不可通过上面两种途径可达(1)新建对象首先处于[reachable, unfinalized]状态(A)(2)随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D)或unreachable(E, F)状态(3)若JVM检测到处于unfinalized状态的对象变成f-reachable或unreachable,JVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)。(4)在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K或J)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N)(5)处于finalizable状态的对象不能同时是unreahable的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因(6)程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为(7)若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)(8)若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)achable,回收其内存(I)(8)若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)(9)注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法
微信小程序授权登录流程1:登录小程序(wx.login)获取code码请求发送code码到开发者服务器2:携带code码进行登录凭证校验接口(appid+appsecret+code)到微信接口服务3:由微信接口服务返回sesion_key和openid等数据到开发者服务器4:开发者服务器自定义登录状态与openid和session_key关联5:由开发者服务器返回自定义的登录状态到小程序6:小程序将自定义的状态保存到storage7:由小程序携带自定义登录状态发送请求到开发者服务器8:开发者服务器通过传过来的自定义登录状态获取session_key和openid进行比对9:进行业务逻辑处理流程图如下:逻辑处理流程图如下:
Object类九大方法之wait方法wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中有以下三种形式调用wait等方法。wait();//方式1: this.wait();//方式2: super.wait();//方式3 void notifyAll()解除所有那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。void notify()随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常.void wait()导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。void wait(long millis) 和void wait(long millis,int nanos)导致线程进入等待状态直到它被通知或者经过指定的时间。这些方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized方法内部或者synchronized块内部,这是因为:**这几个方法要求当前正在运行object.wait()方法的线程拥有object的对象锁。**即使你确实知道当前上下文线程确实拥有了对象锁,也不能将object.wait()这样的语句写在当前上下文中。
Object类九大方法之HashCode方法提到hashcode方法就避免不了equals方法无论何时这个equals方法被重写那么都是有必要去重写hashCode方法,这个是为了维持hashCode的一种特定,相同的对象必须要有相同的hashCode值hashCode方法解释1.再同一次的java程序应用过程中,对应同样的对象多次调用hashCode方法,hashCode方法必须一致性的返回同样的一个 地址值,前提是这个对象不能改变.2.两个对象相同是依据equals方法的,那么其中的每一个对象调用hashCode方法都必须返回相同一个integer值,也就是对象的地址,equals方法相等,那么hashCode方法也必须相等.3.如果两个对象依据equals方法返回的结果不相等,那么对其中的每一个对象调用hashCode方法返回的结果也不是一定必须得相等(也就是说,equals方法的结果为false,那么hashCode方法返回的结果可以相同也可以不相同)但是对于我们来说针对两个对象的不相等,如果生层相同的hashCode则可以提高应用程序的性能.例题:已知二叉树后序遍历序列是dabec,中序遍历序列是debac,那么问题是他的前序遍历顺序是 (D)A:acbedB:decabC:deabcD:cedba推导如下:1、从后序可知树根为C,因为最后的节点是树根。2、从中序的规则可知树根在中间,树根的左边是左孩子,右边是右孩子。很明显树根C是没有右孩子,只有左孩子DEBA。中序遍历:DEBA后序遍历:DABE推出E是左子树的根结点,并且存在左子树D,右子树BA,因为从中序遍历可知E的左边是D,右边是BA中序遍历:BA后序遍历:AB推出B是右子树的根结点,并且存在右子树,但没有左子树,因为从中序遍历可知B只有右子树,没有左子树。还原二叉树如下图:前序为:CEDBA推导的方法只需记住下面的规则即可,然后逐步分割法,就像我上面那样推导。拿到左右子树反复套用下面的遍历规则,很快就可以还原一棵完整的树。1.先序遍历:根、左、右2.中序遍历:左、根、右c_ngpagmjz&rsv_dl=gh_pc_zhidao):根、左、右2.中序遍历:左、根、右3.后序遍历: 左、右、根
深入理解TokenToken解决的问题1.Token完全由应用管理,所以它可以避开同源策略.2.Token可以避免CSRF攻击(跨域请求仿造).3.Token可以是无状态的,可以在多个服务间共享.Token是在服务端产生的,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么服务端会反返回Token给前端,前端可以在每次请求的时候带上Token证明自己的合法地位,如果这个Token在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌.基于Token的身份验证过程如下:1.用户通过用户名和密码发送请求请求登录2.程序验证用户名和密码3.创建一个签名的Token返回给客户端4.客户端每次发送请求且携带Token5.服务端验证Token6.返回状态码token应该在HTTP的头部发送从而保证了Http请求无状态,我们同样通过设置服务器属性"Access-Control-Allow-Origin",让服务器能接受到来自所有域的请求,主要的是在ACAO头部标明(designnating)时,不得带有像http认证,客户端ssl证书和cookies的证书.AccessToken的默认存活时间是一个月,RefereshToken默认的存活时间是两个月,所谓的长期有效是用refereshtoken不断刷新获取新的.因为Token有存活时间,所以过期如何处理?1.过期了,就该要求用户重新登录.2.可以设计一些复杂的机制,比如Referesh Token每次使用的时候,都更新他的过期时间.思路:查到与他的创建时间与当前时间相比,已经超过非常长的时间(自定义迭代的时间).这等于是在相当长一段时间内允许Referesh Token自动续费.到目前为止,Token 都是有状态的,即在服务端需要保存并记录相关属性。那说好的无状态呢,怎么实现?如果我们把所有状态信息都附加到Token上,服务器就可以不保存,但是服务端仍然需要认证Token有效,不过只要服务端能确认是自己签发的Token,而且信息未被改动过,那就可以认为Token有效—“签名”,可以做此保证.平常常说的签名都存在一方签发,另一方验证的情况,所以要是使用非对称的加密算法,但是在这里签发的验证的都是同一方,所以我么就可以使用对称的加密算法,而且对称加密算法比非对称加密算法要快的多(十倍左右)对称加密算法除了加密,还带有还原加密内容的功能,而这一功能在对token签名时没有必要,既然不需要解密,就采用散列算法会更快,eg:HMAC在使用无状态Token的时候在服务端会有一些变化,服务端虽然不保存有效的Token了,却需要保存未到期却已经注销的Token,如果一个Token未到期就被用户主动注销了,那么服务器需要保存这个被注销的Token,以便下次收到使用这个仍在有效期内的Token时判断其无效.前端可以做控制,一旦注销就丢掉本地保存的Token和RefereshToken,基于这样的约定,服务器拿到的Token就一定是没有注销的.使用无状态的Token的时候,有两点需要注意:1.Referesh Token有效时间较长,所以它应该在服务器端有状态,以增强安全性,确保用户注销时可控.2.应该考虑使用二次认证来增强敏感操作的安全性.上面说的是认证服务和业务服务集成在一起的情况.如果是分离的情况呢?分离认证服务当Token无状态之后,单点登录就变得容易了,前端拿到一个有效的Token,他就可以在任何同一体系的服务上认证通过------------只要他们使用同样的秘钥和算法来认证Token的有效性.当然,如果 Token 过期了,前端仍然需要去认证服务更新 Token:虽然认证和业务分离了,实际即并没产生多大的差异。当然,这是建立在认证服务器信任业务服务器的前提下,因为认证服务器产生 Token 的密钥和业务服务器认证 Token 的密钥和算法相同。换句话说,业务服务器同样可以创建有效的 Token。
Hash索引和B+树索引有什么区别或者说优劣势?首先要知道Hash索引和B+树索引的底层实现原理:hash索引底层就是hash表,进行查询时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据.B+树底层实现原理是多路平衡查找树,对于每一次的查询都是从根节点出发,查询到叶子节点方可以获得所查键值,然后查询判断是否需要回表查询.区别:hash索引1:hash索引进行等值查询更快(一般情况下)但是却无法进行范围查询.因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询.2:hash索引不支持模糊查询以及多列索引的最左前缀匹配,因为hash函数的不可预测,eg:AAAA和AAAAB的索引没有相关性.3:hash索引任何时候都避免不了回表查询数据.4:hash索引虽然在等值上查询叫快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时查询效率可能极差.5:hash索引不支持使用索引进行排序,因为hash函数的不可预测.B+树1:B+树的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似)自然支持范围查询.2:在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询.不需要回表查询.3:查询效率比较稳定,对于查询都是从根节点到叶子节点,且树的高度较低.结论大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度,而不需要使用Hash索引.上面提到了B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,什么是聚簇索引呢?聚簇索引:表数据按照索引的顺序来储存的,也就是说索引项的顺序与表中记录的物理顺序一致.在B+树的索引中叶子节点可能储存了当前的key值,也可能储存了当前的key值以及整行的数据,在一张表上最多只能创建一个聚簇索引,因为真实数据的物理顺序只有一种.非聚簇索引:表数据存储顺序与索引顺序无关,对于非聚簇索引,叶子节点包含索引字段值及指向数据页数据行的逻辑指针.聚簇索引和非聚簇索引总结: 聚簇索引是一种稀疏索引,数据页的上一级的索引页储存的是页指针,而不是行指针,而非聚簇索引,则是密集索引,在数据页的上一级索引页它为每一个数据行存储一条索引记录.在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引,如果没有唯一键,则隐式的生成一个键建立索引.当查询使用聚簇索引时,在对应的叶子节点,可以获得到整行数据,因此不用再次进行回表查询.非聚簇索引一定会回表查询吗?不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必在进行回表查询了.eg:假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age<20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询.
内存溢出内存溢出是什么1.系统已经不能再分配出你所需要的空间,比如你需要100M的空间,系统只剩90M了,这就叫内存溢出2.意思就是你用资源的时候为他开辟了一段空间,当你用完时忘记释放资源了,这时内存还被占用着,一次没关系,但是内存泄漏次数多了就会导致内存溢出内存溢出的原因1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;3.代码中存在死循环或循环产生过多重复的对象实体;4.使用的第三方软件中的BUG;5.启动参数内存值设定的过小解决方案1.修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)2.检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。3.对代码进行走查和分析,找出可能发生内存溢出的位置。4.使用内存查看工具动态查看内存使用情况对代码分析找出可能发生内存溢出的位置, 可能出现的几种情况:1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。2.检查代码中是否有死循环或递归调用。3.检查是否有大循环重复产生新对象实体。4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
Union和Union All的区别SQL 的Union操作符合并两个或者多个select语句的结果.请注意:Union内部的每个select语句必须拥有相同数量的列,且列必须有相似的数据类型,同时select语句中列的顺序必须相同.table1建表语句:CREATE TABLE `table1`( `id` INT(2) AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(2), `age` INT(2) ) INSERT INTO table1 VALUES('1','A','2'); INSERT INTO table1 VALUES('2','B','3'); INSERT INTO table1 VALUES('3','C','2'); INSERT INTO table1 VALUES('4','D','4'); table2建表语句:CREATE TABLE `table2`( `id` INT(2)AUTO_INCREMENT PRIMARY KEY, `name`VARCHAR(2), `age`INT(2) ) INSERT INTO table2 VALUES('1','a','2'); INSERT INTO table2 VALUES('2','b','5'); INSERT INTO table2 VALUES('3','c','2'); INSERT INTO table2 VALUES('4','d','3'); SQL Union语法:eg:1.1:执行sqlSELECT age FROM table1 UNION SELECT age FROM table2 1.2:结果结论:默认地Union操作符作查询操作查询的值不得重复.不区分大小写.如果允许有重复的值请使用Union All2.1:执行sqlSELECT age FROM table1 UNION SELECT `name` FROM table2 2.2:结果结论:Union操作列名总是等于Union中第一个select中的列名3.1:Union和Union All查询出的结果默认都不会进行排序,上面展示的数据貌似排序但是只是数据碰巧故此做出以下数的改变来证名我们的观点3.1.1:将表一的第二条记录的age字段进行调整如下3.1.2:执行sqlSELECT `age` FROM table1 UNION SELECT `age` FROM table2 3.1.3:结果3.1.4:结论:Union操作后不会自动进行排序4.1:Union操作如何排序4.1.1:执行sqlSELECT `age` FROM table1 UNION SELECT `age` FROM table2 ORDER BY age 4.1.2:结果注意1:Order By后面根据哪个字段排序,此字段必须是在Union连接的Select中都存在的字段.且两个表中都必须存在此字段.验证注意1:table1添加sex字段执行sqlSELECT `age`,`sex` FROM table1 UNION SELECT `age` FROM table2 ORDER BY sex 结果: 报错—>Unknown column ‘sex’ in ‘field list’table2添加sex字段执行sqlSELECT `age` FROM table1 UNION SELECT `age` FROM table2 ORDER BY sex 结果: 报错—>Unknown column ‘sex’ in 'order clause原因是要在select 后面加上分组的字段SELECT `age`,`sex` FROM table1 UNION SELECT `age`,`sex` FROM table2 ORDER BY sex 结果:证实了***注意1***4.2:Union操作多字段如何排序前面我们说了Union操作查询的值不重复,但是从上面看按照sex分组为女男男男,有三个男有重复的值.注意2:此时的重复值判断标准是按照查询出的字段联合判断的重复值,而并非通过某一个字段进行重复的判断.证实注意2:table1添加score字段table2添加score字段执行sqlSELECT `age`,`sex`,`score` FROM table1 UNION SELECT `age`,`sex` ,`score` FROM table2 ORDER BY score 结果证实了注意2根据score分组,Union虽然select出的值没有重复,它是根据age+sex+score三个字段联合来判断是否是重复的值.而非某一个字段来判断重复的值.SQL Union All语法Union All 同Union的上部分的那些特性一致,只是它select出来的会有重复的值.
微信支付微信支付的两种模式1:由微信生成二维码,客户扫描二维码后,确认支付,微信回调给当前系统.2:由系统调用统一下单API,取得预支付交易信息后,根据信息生成二维码,然后后台循环查询订单API接口查询付款状态(统一下单API,查询订单API)区别:模式一是跳到微信让微信给我们生成二维码 模式二是可控制高,比较自主.一:二维码1.1:二维码优势♦ 信息量大,可以容纳1850个大写字母或者2710个数字或500多个汉字♦ 应用范围广,支持文字,声音(存放地址,地址里播放声音其实还是地址),图片,指纹等等♦ 容错能力强,即是图片出现部分的破损也能使用♦ 成本低,容易制作二:微信支付2.1:一共12个API实现思路:微信接口只接收xml字符串需要sas解析或者dom4j解析.将map转成xml通过httpClient远程提交参数接收返回结果微信接口提供了一些工具类把map转换成xml统一下单接口最终就是通过它生成一串支付地址,然后把支付地址展示给前端页面,让用户支付.最终订单有没有完成,需要调用查询订单接口,查看完成状态.微信支付发送的是post请求,请求是xml格式的代码实现一:需要创建支付服务模块(pyg_pay_interface和pyg_pay_service)**A😗*pyg_pay_service1:pom.xml文件导入坐标//打包方式为war包为web项目 <packaging>war</packaging> <dependencies> //导入工具类依赖 <dependency> <groupId>com.pinyougou</groupId> <artifactId>pyg_common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> //依赖dao层操作数据库 <dependency> <groupId>com.pinyougou</groupId> <artifactId>pyg_dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> //依赖于pyg_pay_interface接口 <dependency> <groupId>com.pinyougou</groupId> <artifactId>pyg_pay_interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> //微信的坐标,可以使用它的工具类生成uuid <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency> //指定一台服务器 <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <!-- 指定端口 --> <port>9000</port> <!-- 请求路径 --> <path>/</path> </configuration> </plugin> </plugins> </build> </dependencies> 2:web.xml文件<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- 加载spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app> 3.1:applicationContext-service.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 访问dubbo要占用的当前主机端口,默认端口不写是20880 --> <dubbo:protocol name="dubbo" port="20880"></dubbo:protocol> <!-- 配置dubbo服务的名称 --> <dubbo:application name="pinyougou-pay-service"/> <!-- 配置dubbo注册地址--> <dubbo:registry address="zookeeper://192.168.25.134:2181"/> <!--配置dubbo服务注解扫描的包 --> <dubbo:annotation package="com.pinyougou.pay.service.impl" /> </beans> 3.2:applicationContext-tx.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 开启事务控制的注解支持 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans> 4.在pyg_common导入配置文件支付相关的信息appid=wx8397f8696b538317 //商户ID partner=1473426802 //商户号 partnerkey=T6m9iK73b0kn9g5v426MKfHQH7X8rKwb //商户的key值 notifyurl=http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify //回调地址,我们的二维码使我们自己生成的所以不需要回调 导入HttpClientUtils.java工具类package util; /** * http请求客户端 * * @author Administrator * */ public class HttpClientUtil { public static HttpClientContext context = null; static { System.out.println("====================begin"); context = HttpClientContext.create(); } private String url; private Map<String, String> param; private int statusCode; private String content; private String xmlParam; private boolean isHttps; public boolean isHttps() { return isHttps; } public void setHttps(boolean isHttps) { this.isHttps = isHttps; } public String getXmlParam() { return xmlParam; } public void setXmlParam(String xmlParam) { this.xmlParam = xmlParam; } public HttpClientUtil(String url, Map<String, String> param) { this.url = url; this.param = param; } public HttpClientUtil(String url) { this.url = url; } public void setParameter(Map<String, String> map) { param = map; } public void addParameter(String key, String value) { if (param == null) param = new HashMap<String, String>(); param.put(key, value); } public void post() throws ClientProtocolException, IOException { HttpPost http = new HttpPost(url); setEntity(http); execute(http); } public void put() throws ClientProtocolException, IOException { HttpPut http = new HttpPut(url); setEntity(http); execute(http); } public void get() throws ClientProtocolException, IOException { if (param != null) { StringBuilder url = new StringBuilder(this.url); boolean isFirst = true; for (String key : param.keySet()) { if (isFirst) url.append("?"); else url.append("&"); url.append(key).append("=").append(param.get(key)); } this.url = url.toString(); } HttpGet http = new HttpGet(url); execute(http); } /** * set http post,put param */ private void setEntity(HttpEntityEnclosingRequestBase http) { if (param != null) { List<NameValuePair> nvps = new LinkedList<NameValuePair>(); for (String key : param.keySet()) nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数 http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数 } if (xmlParam != null) { http.setEntity(new StringEntity(xmlParam, Consts.UTF_8)); } } private void execute(HttpUriRequest http) throws ClientProtocolException, IOException { CloseableHttpClient httpClient = null; try { if (isHttps) { SSLContext sslContext = new SSLContextBuilder() .loadTrustMaterial(null, new TrustStrategy() { // 信任所有 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslContext); httpClient = HttpClients.custom().setSSLSocketFactory(sslsf) .build(); } else { httpClient = HttpClients.createDefault(); } CloseableHttpResponse response = httpClient.execute(http,context); try { if (response != null) { if (response.getStatusLine() != null) statusCode = response.getStatusLine().getStatusCode(); HttpEntity entity = response.getEntity(); // 响应内容 content = EntityUtils.toString(entity, Consts.UTF_8); } } finally { response.close(); } } catch (Exception e) { e.printStackTrace(); } finally { httpClient.close(); } } public int getStatusCode() { return statusCode; } public String getContent() throws ParseException, IOException { return content; } } 导入生成支付二维码的页面<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE"> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" /> <title>微信支付页</title> <link rel="icon" href="/assets/img/favicon.ico"> <script type="text/javascript" src="plugins/angularjs/angular.min.js"> </script> <script type="text/javascript" src="js/base.js"> </script> <script type="text/javascript" src="js/service/payService.js"> </script> <script type="text/javascript" src="js/controller/payController.js"> </script> <script type="text/javascript" src="plugins/qrious.min.js"></script> <link rel="stylesheet" type="text/css" href="css/webbase.css" /> <link rel="stylesheet" type="text/css" href="css/pages-weixinpay.css" /> </head> <body ng-app="pinyougou" ng-controller="payController" ng-init="createNative()"> <!--head--> <div class="top"> <div class="py-container"> <div class="shortcut"> <ul class="fl"> <li class="f-item">品优购欢迎您!</li> <li class="f-item">请登录 <span><a href="#">免费注册</a></span></li> </ul> <ul class="fr"> <li class="f-item">我的订单</li> <li class="f-item space"></li> <li class="f-item">我的品优购</li> <li class="f-item space"></li> <li class="f-item">品优购会员</li> <li class="f-item space"></li> <li class="f-item">企业采购</li> <li class="f-item space"></li> <li class="f-item">关注品优购</li> <li class="f-item space"></li> <li class="f-item">客户服务</li> <li class="f-item space"></li> <li class="f-item">网站导航</li> </ul> </div> </div> </div> <div class="cart py-container"> <!--logoArea--> <div class="logoArea"> <div class="fl logo"><span class="title">收银台</span></div> </div> <!--主内容--> <div class="checkout py-container pay"> <div class="checkout-tit"> <h4 class="fl tit-txt"><span class="success-icon"></span><span class="success-info">订单提交成功,请您及时付款!订单号:{{payNo}}</span></h4> <span class="fr"><em class="sui-lead">应付金额:</em><em class="orange money">¥{{payMoney}}</em>元</span> <div class="clearfix"></div> </div> <div class="checkout-steps"> <div class="fl weixin">微信支付</div> <div class="fl sao"> <p class="red">二维码已过期,刷新页面重新获取二维码。</p> <div class="fl code"> <img id="qrious" alt=""> <div class="saosao"> <p>请使用微信扫一扫</p> <p>扫描二维码支付</p> </div> </div> <div class="fl phone"> </div> </div> <div class="clearfix"></div> <p><a href="pay.html" target="_blank">> 其他支付方式</a></p> </div> </div> </div> <!-- 底部栏位 --> <!--页面底部--> <div class="clearfix footer"> <div class="py-container"> <div class="footlink"> <div class="Mod-service"> <ul class="Mod-Service-list"> <li class="grid-service-item intro intro1"> <i class="serivce-item fl"></i> <div class="service-text"> <h4>正品保障</h4> <p>正品保障,提供发票</p> </div> </li> <li class="grid-service-item intro intro2"> <i class="serivce-item fl"></i> <div class="service-text"> <h4>正品保障</h4> <p>正品保障,提供发票</p> </div> </li> <li class="grid-service-item intro intro3"> <i class="serivce-item fl"></i> <div class="service-text"> <h4>正品保障</h4> <p>正品保障,提供发票</p> </div> </li> <li class="grid-service-item intro intro4"> <i class="serivce-item fl"></i> <div class="service-text"> <h4>正品保障</h4> <p>正品保障,提供发票</p> </div> </li> <li class="grid-service-item intro intro5"> <i class="serivce-item fl"></i> <div class="service-text"> <h4>正品保障</h4> <p>正品保障,提供发票</p> </div> </li> </ul> </div> <div class="clearfix Mod-list"> <div class="yui3-g"> <div class="yui3-u-1-6"> <h4>购物指南</h4> <ul class="unstyled"> <li>购物流程</li> <li>会员介绍</li> <li>生活旅行/团购</li> <li>常见问题</li> <li>购物指南</li> </ul> </div> <div class="yui3-u-1-6"> <h4>配送方式</h4> <ul class="unstyled"> <li>上门自提</li> <li>211限时达</li> <li>配送服务查询</li> <li>配送费收取标准</li> <li>海外配送</li> </ul> </div> <div class="yui3-u-1-6"> <h4>支付方式</h4> <ul class="unstyled"> <li>货到付款</li> <li>在线支付</li> <li>分期付款</li> <li>邮局汇款</li> <li>公司转账</li> </ul> </div> <div class="yui3-u-1-6"> <h4>售后服务</h4> <ul class="unstyled"> <li>售后政策</li> <li>价格保护</li> <li>退款说明</li> <li>返修/退换货</li> <li>取消订单</li> </ul> </div> <div class="yui3-u-1-6"> <h4>特色服务</h4> <ul class="unstyled"> <li>夺宝岛</li> <li>DIY装机</li> <li>延保服务</li> <li>品优购E卡</li> <li>品优购通信</li> </ul> </div> <div class="yui3-u-1-6"> <h4>帮助中心</h4> <img src="img/wx_cz.jpg"> </div> </div> </div> <div class="Mod-copyright"> <ul class="helpLink"> <li>关于我们<span class="space"></span></li> <li>联系我们<span class="space"></span></li> <li>关于我们<span class="space"></span></li> <li>商家入驻<span class="space"></span></li> <li>营销中心<span class="space"></span></li> <li>友情链接<span class="space"></span></li> <li>关于我们<span class="space"></span></li> <li>营销中心<span class="space"></span></li> <li>友情链接<span class="space"></span></li> <li>关于我们</li> </ul> <p>地址:北京市昌平区建材城西路金燕龙办公楼一层 邮编:100096 电话:400-618-4000 传真:010-82935100</p> <p>京ICP备08001421号京公网安备110108007702</p> </div> </div> </div> </div> <!--页面底部END--> <script type="text/javascript" src="js/plugins/jquery/jquery.min.js"></script> <script type="text/javascript" src="js/plugins/jquery.easing/jquery.easing.min.js"></script> <script type="text/javascript" src="js/plugins/sui/sui.min.js"></script> <script type="text/javascript" src="js/widget/nav.js"></script> <script type="text/javascript"> $(function(){ $("ul.payType li").click(function(){ $(this).css("border","2px solid #E4393C").siblings().css("border-color","#ddd"); }) }) </script> </body> </html> 5.根据Dubbo服务扫描的包创建包(com.pinyougou.pay.service.impl)♦ 5.1:pyg_pay_interface模块下创建PayService.java接口public interface PayService{ //请求完微信生成支付地址的方法(二维码) //参数1:商户订单号 参数二:总金额 public Map createNative(String out_trade_no,String total_fee); //请求微信查询支付状态 public Map queryPayStatus(String out_trade_no); //根据用户id获取支付单对象 public TbPayLog searchPayLogFromRedis(String userId); //根据支付单号,更新付款信息 public void updateOrderStatus(String out_trade_no,String transaction_id); } ♦ 5.2:pyg_pay_service模块com.pinyougou.pay.service.impl路径下创建PayServiceImpl.java实现类@Service public class PayServiceImpl implement PayService{ //拿到配置文件的属性 @Value("${appid}") private String appid;//商户ID @Value("${partner}") private String partner;//商家号 @Value("${partnerkey}") private String partnerkey;//秘钥 @Value("${notifyurl}") private String notifyurl;//回调地址(必填,内容随便) @Autowride private RedisTemplate redisTemplate; @Autowride private TbPayLogMapper payLogMapper; @Autowride private TborderMapper orderMapper; @Override public Map createNative(String out_trade_no,String total_fee){ try{//请求微信的生成订单接口地址,获取支付的连接地址 HttpClientUtils util=new HttpClientUtils("https://api.mch.weixin.qq.com/pay/unifiedorder"); //请求内容数据的封装 Map map= new HashMap<>(); map.put("appid",appid); map.put("mch_id",partner); map.put("nonce_str",WXPayUtil.generateUUID());//随机32位字符串 map.put("body","品优购商品"); //描述信息 map.put("out_trade_no",out_trade_no);//商户订单号 double totalMoney=new Double(total_fee)*100; map.put("total_fee",(long)totalMoney+"") //商品总金额单位为分 map.put("spbill_create_ip","127.0.0.1"); //终端地址 map.put("notify_url",notifyurl); //通知地址 map.put("trade_type","NATIVE"); //交易类型 //通过微信的api将map转成xml格式的字符串 //生成带签名的xml String signedXml= WXPayUtil.generateSignedXml(map,partnerkey); Sytem.out.print("发送给微信"+signedXml) //设置请求参数setXmlParam();方法发送的是xml格式 util.setXmlParam(signedXml); //发送post请求,发送的是xml格式的 util.post(); String content=util.getContent(); Sytem.out.print("微信返回的"+content) //通过工具类将返回的String的xml内容转换成map Map responseMap=WXPayUtil.xmlToMap(content); Map resultMap=new HashMap<>(); resultMap.put("out_trade_no",out_trade_no); //支付单号 resultMap.put("total_fee",total_fee); //支付金额 resultMap.put("code_url",responseMap.get("code_url"));//支付地址 return resultMap; }catch(IOException e){ e.printStackTrace(); } return null; } } @Override public Map queryPayStatus(String out_trade_no){ try{ //1.请求微信的订单状态查询接口 HttpClient util new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery"); //2.封装数据 Map requestMap= new HashMap<>(); requestMap.put("appid",appid); requestMap.put("mch_id",mch_id); requestMap.put("out_trade_no",out_trade_no); requestMap.put("nonce_str",WXPayUtil.generateUUID()); //3.转换带签名的数据 String signedXml=WXPayUtil.generateSignedXml(requestMap,partnerkey); //4.设置到xml格式到post请求中 util.setXmlParam(signedXml); //5.发送请求(xml) util.post(); //6.获取返回值 String content= util.getContent(); Map resultMap= WXPayUtil.xmlToMap(content); return resultMap; }catch(Exception e){ e.printStackTrace(); return null; } } @Override public TbPayLog searchPayLogFromRedis(String userId){ TbPayLog payLog=(TbPayLog) redisTemplate.boundHashOps("payLog").get(userId); return payLog; } @Override //根据支付单号,更新付款信息 public void updateOrderStatus(String out_trade_no,String transaction_id){ //获取支付单对象 TbPayLog payLog= payLogMapper.selectByPrimaryKey(out_trade_no); //获取支付状态,微信的业务代码 payLog.setTransactionId(transaction_id); //支付时间 payLog.setPayTime(new Date()); //支付状态 payLog.setTradeState("1");//0未支付 1已支付 //更新数据库中的payLog数据 payLogMapper.updateByPrimaryKey(payLog); //根据支付单的订单列表,更新订单支付的相关信息 String[] orderIds=payLog.getOrdrList().split(","); for(String orderId :orderIds){ TbOrder order= orderMapper.selectByPrimaryKey(Long.parseLong(orderId); //状态:1 未付款 2 已付款 order.setStatus("2"); order.setPaymentTime(new Date()); order.setUpdateTime(new Date()); orderMapper.updateByPrimaryKey(order); } //将redis中的当前用户的付款信息清除 redisTemplate.boundHash("payLog").delete(payLog.getUserId()); } 6.创建PayController.java类@RestController @RequestMapping("/pay") public class PayController{ @Reference private PayService payService; @RequestMapping("/createNative") public Map createNative(String out_trade_no,String total_fee){ /* String outTradeNo=UUID.randomUUID().toString().replaceAll("_","");*/ //获取用户的支付单 String userId=SecurityContextHolder.getContext().getAuthentication().getName(); //从Redis中获取用户的支付单 TbPayLog payLog= payService.searchPayLogFromRedis(userId); //设置订单号 付款金额 return payService.createNative(payLog.getOutTradeNo(),payLog.getTotalFee()); } @RequestMapping("/queryPayStatus") public Result queryPayStatus(String out_trade_no){ int timeout=1; //无限循环查询订单支付状态 while(true){ Map resultMap=payService.queryPayStatus(out_trade_no); //失败的逻辑 if(resultMap==null){ return new Result(false,"付款失败!!!"); } //支付成功的逻辑 if("SUCCESS".equals(resultMap.get("trade_state"))){ //修改order的状态 修改paylog的状态 payService.updateOrderStatus(out_trade_no,resultMap.get("transaction_id").toString()); return new Result(true,"付款成功!!!"); } //每隔三秒查询订单的支付状态 try{ Thread.sleep(3000); }catch(Exception e){ } timeout++; //退出逻辑 ,超过该时间,停止支付查询 if(timeout>10){ return new Result(false,"timeout"); } } } }
Mysql索引优化一:索引介绍索引是关系型数据库中给数据库表中一列或者多列的值排序后的储存结构,SQL的主流索引结构有B+树以及Hash结构,聚集索引以及非聚集索引用的是B+树索引.MySql索引类型有:唯一索引,主键(聚集)索引,非聚集索引,全文索引.1.1:聚集索引聚集(clustered)索引,也叫做聚簇索引.定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引.注意:聚集索引做查询可以直接获取对应的全部列的数据.所以聚集查询较快.1.2非聚集索引定义:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引.除了聚集索引以外的索引都是非聚集索引,分成普通索引,唯一索引和全文索引.注意:非聚集索引查询在索引没覆盖到对应列的时候需要进行二次查询,索引非聚集查询较慢.1.2.1如何解决非聚集索引的二次查询问题复合索引(覆盖索引)建立两列以上的索引,即可查询复合索引里的列的数据而不需要进行回表二次查询,如index(col1,col2),执行下面的语句select col1,col2 from 表名 where col1=‘xxx’;要注意使用复合索引需要满足最左侧索引的原则,也就是查询的时候如果where条件里面没有最左边的一到多列,索引就不会起作用.二:索引的存储机制无索引的表,查询时,是按照顺序存序的方法扫描每个记录来查询符合条件的记录,这样效率很低.聚集索引和非聚集索引的根本区别在于表记录的排列顺序和索引的排列顺序是否一致.聚集索引就是在数据库被开辟一个物理空间放他的排列的值,例如1-100,所以当插入数据时,他会重新排列整个物理空间,而非聚集索引其实可以看做是一个含有聚集索引的表,它只仅包含原表中非聚集索引的列和指向实际物理表的指针,它只记录一个指针,其实就有点和堆栈差不多的感觉.三:建立索引的原则定义主键的数据列一定要建立索引。定义有外键的数据列一定要建立索引。对于经常查询的数据列最好建立索引。对于需要在指定范围内的快速或频繁查询的数据列;经常用在WHERE子句中的数据列。经常出现在关键字order by、group by、distinct后面的字段,建立索引。如果建立的是复合索引,索引的字段顺序要和这些关键字后面的字段顺序一致,否则索引不会被使用。对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。对于定义为text、image和bit的数据类型的列不要建立索引。对于经常存取的列避免建立索引限制表上的索引数目。对一个存在大量更新操作的表,所建索引的数目一般不要超过3个,最多不要超过5个。索引虽说提高了访问速度,但太多索引会影响数据的更新操作。对复合索引,按照字段在查询条件中出现的频度建立索引。在复合索引中,记录首先按照第一个字段排序。对于在第一个字段上取值相同的记录,系统再按照第二个字段的取值排序,以此类推。因此只有复合索引的第一个字段出现在查询条件中,该索引才可能被使用,因此将应用频度高的字段,放置在复合索引的前面,会使系统最大可能地使用此索引,发挥索引的作用。四:索引优化面试题select * from student s where s.stuName in(“张三”,“李四”,“王五”)and s.age>18 and s.sex=‘男’;思路:1.肯定要建立二级联合索引:index(stuName,age,sex)2.这里要优化一下sql语句中的in,改用union all优化后的sql:select * from student s where s.stuName=“张三” and s.age>18 and s.sex=‘男’ union all select * from student s where s.stuName=“李四” and s.age>18 and s.sex=‘男’ union all select * from student s where s.stuName=“王五” and s.age>18 and s.sex=‘男’ ;
A:结构层面delete是逐行删除,并且同时将该行的删除操作作为事务,在记录日志中保存以便进行回滚操作.可以与where一起使用删除某一条记录,不加where则删除所有记录.truncate则一次性的从表中删除所有的数据并不把单独的删除操作记录日志保存,删除行是不能恢复的,并且在删除的过程中不会激活与表有关的删除触发器,它的执行速度快.直接truncate+table不用加where.drop删除整个表(数据和结构)B:表和索引所占空间被truncate后这个表和索引所占用的空间会恢复到初始大小.delete操作不会减少表和索引所占用的空间.drop将表和索引所占用的空间全部释放.C:速度上drop>truncate>deleteD:应用范围delete可以是table和viewtruncate只能对table
1:Angular2不是从Angular1升级过来的,Angular2是重写的,所以他们之间的差别比较大2:Angular2使用了javascript的超集‘Typescript’,所以angular1和angular2从设定之初就是不一样的3:Angular1在设计之初主要是针对pc端的,对移动端支持较少(当然也有其他一些衍生框架如ionic),而Angular2是设计包含移动端的;4:Angular 1的核心概念是s c o p e , 但 是 a n g u l a r 2 中 没 有 scope,但是angular2中没有scope,但是angular2中没有scope,angular2使用zone.js来记录监测变化;5:Angular 1 中的控制器在angular2中不再使用,也可以说控制器在angular2中被‘Component’组件所替代:6:Angular 2主要的性能优化改进是使用了分层依赖注入系统。 Angular 2实现了基于单向树的变化检测,这再次提高了性能;这些优化改进是的angular2的速度比angular1的速度提高很多;7:Angular 2的大小是20kb左右,相对于angular1体积减少很多,在移动端的应用中,流量方便更占优势;8:Angular 2支持影子 DOM,支持 Android 和 iOS 的原生移动渲染,支持服务端渲染.
Java调优性能调优同样遵循2-8原则,80%的性能问题是由20%的代码产生的,因此优化关键代码事半功倍,同时,对性能的优化要做到按需优化,过度优化可能引起更多的问题,对于java性能优化,不仅要理解系统的架构,应用代码,同样需要关注JVM层甚至操作系统底层.1:基础性能优化这里的基础性能指的是硬件层级或者操作系统层级的升级优化,比如F5的使用和SDD硬盘的引入,包括新版本的Linux在NIO方面的升级,都可以极大的促进应用的性能提升.2:数据库性能优化包括常见的事务拆分,索引优化,sql优化,NoSql引入等,比如事务拆分时引入异步处理,最终达到一致性等做法的引用,包括在针对具体场景引入的各类NoSql数据库,都可以大大缓解传统数据库在高并发下的不足.3:应用加架构的优化引入一些新的计算或者存储框架,利用新特性解决原有集群计算性能瓶颈等,或者引入分布式策略,在计算和存储进行水平化,包括提前计算预处理等,利用典型的空间换时间的做法等,都可以在一定程度上降低系统负载.4:业务层面的优化技术并不是提升系统性能的唯一手段,在很多出现性能问题的场景中,其实可以看到很大一部分都是因为特殊的业务场景引起的,如果能在业务上进行规避或者调整,其实往往是最有效的.
死锁概览:Java语言通过synchronized关键字来保证原子性,这是因为每一个Object都有一个隐含的锁,这个也称作监视器对象,在进入synchronized之前自动自动获取此内部锁,而一旦离开此方式,无论是完成还是中断都会自动释放锁,虽然这是一个独占锁,每个锁请求之间是互斥的,相对于众多的高级锁(Lock/ReadWriteLock等),synchronized的代价都比后者要高,但是 synchronzied 的语法比较简单,而且也比较容易使用和理解.Lock 一旦调用了 lock() 方法获取到锁而未正确释放的话很有可能造成死锁,所以 Lock 的释放操作总是跟在 finally 代码块里面,这在代码结构上也是一次调整和冗余。synchronzied 都不可能避免死锁产生,那么死锁情况会是经常容易出现的错误,下面具体描述死锁发生的原因及解决方法。死锁描述:死锁是操作系统层面的一个错误,是进程死锁的简称.死锁是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面,如果没有外力的作用,那么死锁涉及到的各个进程都将永远处于封锁状态。死锁问题是多线程特有的问题,它可以被认为是线程间切换消耗系统性能的一种极端情况。在死锁时,线程间相互等待资源,而又不释放自身的资源,导致无穷无尽的等待,其结果是系统任务永远无法执行完成。死锁问题是在多线程开发中应该坚决避免和杜绝的问题。死锁的原因:一般来说,要出现死锁问题需要满足以下条件:互斥条件:一个资源每次只能被一个线程使用。请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。只要破坏死锁 4 个必要条件之一中的任何一个,死锁问题就能被解决。死锁的解决方案:死锁是由四个必要条件导致的,所以一般来说,只要破坏这四个必要条件中的一个条件,死锁情况就应该不会发生。如果想要打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低;避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。如果我们在死锁检查时发现了死锁情况,那么就要努力消除死锁,使系统从死锁状态中恢复过来。消除死锁的几种方式:最简单、最常用的方法就是进行系统的重新启动,不过这种方法代价很大,它意味着在这之前所有的进程已经完成的计算工作都将付之东流,包括参与死锁的那些进程,以及未参与死锁的进程;撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。一般来说,选择逐步撤消的进程时要按照一定的原则进行,目的是撤消那些代价最小的进程,比如按进程的优先级确定进程的代价;考虑进程运行时的代价和与此进程相关的外部作业的代价等因素;进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。MySQL 死锁情况解决方法假设我们用 Show innodb status 检查引擎状态时发现了死锁情况,如清单 7 所示。清单 7. MySQL 死锁WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 0 page no 843102 n bits 600 index `KEY_TSKTASK_MONTIME2` of table `dcnet_db/TSK_TASK` trx id 0 677833454 lock_mode X locks rec but not gap waiting Record lock, heap no 395 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 8; hex 8000000000000425; asc %;; 1: len 8; hex 800012412c66d29c; asc A,f ;; 2: len 8; hex 800000000097629c; asc b ;; *** WE ROLL BACK TRANSACTION (1) 我们假设涉事的数据表上面有一个索引,这次的死锁就是由于两条记录同时访问到了相同的索引造成的。我们首先来看看 InnoDB 类型的数据表,只要能够解决索引问题,就可以解决死锁问题。MySQL 的 InnoDB 引擎是行级锁,需要注意的是,这不是对记录进行锁定,而是对索引进行锁定。在 UPDATE、DELETE 操作时,MySQL 不仅锁定 WHERE 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 next-key locking;如语句 UPDATE TSK_TASK SET UPDATE_TIME = NOW() WHERE ID > 10000 会锁定所有主键大于等于 1000 的所有记录,在该语句完成之前,你就不能对主键等于 10000 的记录进行操作;当非簇索引 (non-cluster index) 记录被锁定时,相关的簇索引 (cluster index) 记录也需要被锁定才能完成相应的操作。再分析一下发生问题的两条 SQL 语句:当“update TSK_TASK set STATUS_ID=1064,UPDATE_TIME=now () where STATUS_ID=1061 and MON_TIME假设“update TSK_TASK set STATUS_ID=1067,UPDATE_TIME=now () where ID in (9921180)”几乎同时执行时,本语句首先锁定簇索引 (主键),由于需要更新 STATUS_ID 的值,所以还需要锁定 KEY_TSKTASK_MONTIME2 的某些索引记录。这样第一条语句锁定了 KEY_TSKTASK_MONTIME2 的记录,等待主键索引,而第二条语句则锁定了主键索引记录,而等待 KEY_TSKTASK_MONTIME2 的记录,这样死锁就产生了。我们通过拆分第一条语句解决了死锁问题:即先查出符合条件的 ID:select ID from TSK_TASK where STATUS_ID=1061 and MON_TIME < date_sub(now(), INTERVAL 30 minute);然后再更新状态:update TSK_TASK set STATUS_ID=1064 where ID in (….)。
知其然知其所以然:时如白驹,间似流水—TimeFriends一:上代码A:饿汉式public class EHan { //饿汉模式 //将构造函数私有化 private Singleton(){} //将对象实例化 private static EHan instance = new EHan(); //得到实例的方法 public static EHan getInstance() { return instance; } } B:饿汉式执行线程 public static void main(String[] args) { //创建一个可以存放20个线程的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(20); for (int i = 0; i < 20; i++) { //执行创建线程(Runable接口) threadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+EHan.getInstance()); } }); } //关闭线程池 threadPool.shutdown(); } C:执行结果pool-1-thread-4:EHan@6519891a pool-1-thread-14:EHan@6519891a pool-1-thread-10:EHan@6519891a pool-1-thread-8:EHan@6519891a pool-1-thread-5:EHan@6519891a pool-1-thread-12:EHan@6519891a pool-1-thread-1:EHan@6519891a pool-1-thread-9:EHan@6519891a pool-1-thread-6:EHan@6519891a pool-1-thread-2:EHan@6519891a pool-1-thread-16:EHan@6519891a pool-1-thread-3:EHan@6519891a pool-1-thread-17:EHan@6519891a pool-1-thread-13:EHan@6519891a pool-1-thread-18:EHan@6519891a pool-1-thread-7:EHan@6519891a pool-1-thread-20:EHan@6519891a pool-1-thread-11:EHan@6519891a pool-1-thread-15:EHan@6519891a pool-1-thread-19:EHan@6519891a 二:剖原理分析:饿汉式是在类加载的时候创建实例,所以线程是安全的.关键点:1:私有构造函数2:静态私有方法—在类加载时已经初始化3:公开访问点getInstance—不需要同步,因为在类加载时已经初始化完毕,也不需要判断null,直接返回即可.优点:类加载时完成初始化,获取对象的速度较快.缺点:类加载较慢.
我们都知道,MySQL中关于字符,有char和varchar两种常用的类型,可能在平时的使用过程中,大家不会去关心这两种类型的区别,只是会用就可以了,或者说看到过一些它们的区别,但是没有时间去测试,今天有时间了,我将这两种类型的具体情况实验一把,让大家直观感受下,纯属分享,大神请绕道。 先说说理论吧。 char类型为固定长度的字符串,比如说char(10),它定义了指定的字符串长度最大为10个字符,如果你现在输入一个字符串为'12345678',那么它在char类型中到底会占用多少个字符呢?答案是10个,后面缺少的2个字符,MySQL会自动补充为空值,然后进行存放。在取这个记录的时候,char类型的会使用trim()函数去掉多余的空格,所以我们看到的还是8个字符的记录。当输入的字符长度大于最大的长度时,MySQL会自动报错。 varchar类型是长度可变的字符串,varchar(M)表示最大长度是M个字符,varchar的最大实际长度由最大的行的大小和使用的字符集确定。例如varchar(50)定义了一个最大长度为50的字符串,如果插入的字符串只有20个字符,那么实际存储的字符串具有21个字符,因为varchar会自动包含一个字符串结束字符。varchar在值保存和检索时,尾部的空格仍然保留。 介绍完概念,我们来看具体的实践过程,本文中使用的测试版本为MySQL5.7.22版本。1.测试char的trim()功能 首先创建一个表,这个表里面包含两个字段,d_char和d_varchar,设定初始的字符长度都为4,如下:查看一下,此时,我们插入两条记录,每条记录都是'ab ',注意,ab后面有2个空格,然后我们使用mysql里面的concat函数进行字符连接,给每条记录的左右分别添加小括号,此时我们可以看到,d_char的ab后面的空格被取消掉了,而d_varchar后面的空格还依旧存在。2.测试两种字符类型的最大长度首先看看char的最大长度,我们设置的值为256,结果如下所以,char类型的长度取值范围为0~255个字符上面提到了varchar的最大实际长度由最大的行的大小和使用的字符集确定,这里我们进行实验:可以看到,字符集不一样,最后的max的值也是不一样的,utf8模式下是0~21845,一个字符占三个字节,最多能存 21844 个字符latin1模式下是0~65535,一个字符占一个字节,最多能存放 65532 个字符gbk模式下是0~32767,一个字符占两个字节,最多能存 32766 个字符若定义的时候超过上述限制,则varchar字段会被强行转为text类型,并产生warning。可能这里有人要问了,为什么最大值是32767,而最多只能放32766个字符呢?举两个例说明一下实际长度的计算。a) 若一个表只有一个varchar类型,如定义为create table t4(c varchar(N)) charset=gbk;则此处N的最大值为(65535-1-2)/2= 32766 个字符。减1的原因是实际行存储从第二个字节开始’;减2的原因是varchar头部的2个字节表示长度;除2的原因是字符编码是gbk。b) 若一个表定义为create table t4(c int, c2 char(30), c3 varchar(N)) charset=utf8;则此处N的最大值为 (65535-1-2-4-30*3)/3=21812减1和减2与上例相同;减4的原因是int类型的c占4个字节;减30*3的原因是char(30)占用90个字节,编码是utf8。如果被varchar超过上述的b规则,被强转成text类型,则每个字段占用定义长度为11字节,当然这已经不是“varchar”了。则此处N的最大值为 (65535-1-2-4-30*3)/3=218123.MySQL的字段长度模式 字段长度的模式分为严格模式和不严格模式,在严格模式下,如果我们想给一个字段中插入一个大于规定长度的字符串,MySQL会给出错误提示,例如我们的表: 当我们插入一个大于4字符的记录时, 如果在非严格模式下,mysql会自动截断超出最大长度的字符, 上面的操作是,我们先把字段模式改为非严格模式,然后查询更改,确保更改生效,接着我们插入'abcde'字符串,发现它可以被成功执行,但是包含两个警告,查看警告可以发现,一些数据被截断了, 实验部分的内容基本就完成了,这里我们进行几点分析:1.MySQL为什么要设置这两种类型?它们各自有什么优点? char是固定长度的,它的存取速度比varchar快,方便程序的存储于查找,但是它需要浪费一定的空间,可以看做是一种以空间换时间的方法。 而varchar的特点是可变长,当定义一个varchar(10)而只存入了4个字符,此时varchar会直接将字符记录的长度变为4,从而节省空间,它可以看做是一种用时间换取空间的方法。 char的存储方式是,对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节;而varchar的存储方式是,对每个英文字符占用2个字节,汉字也占用2个字节,两者的存储数据都非unicode的字符数据。2.两种类型适应的情况分析。关于char: CHAR适合存储很短的字符串,或者所有值都接近同一个长度。 对于经常变更的数据,CHAR也比VARCHAR更好,因为定长的CHAR类型不容易产生碎片。对于非常短的列,CHAR在存储空间上也更有效率。例如用char(1)来存储只有Y和N的值,只需要一个字节,但是varchar却需要两个字节,因为还一个记录长度的额外字节。关于varchar VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型 更节省空间,因为它仅使用必要的空间(例如,越短的字符串使用越少的空间)。 VARCHAR节省了存储空间,所以对性能也有帮助。但是,由于行是变长的,在UPDATE时可能使行变得比原来更长,这就导致需要做额外的工作。如果一个行占用 的空间增长,并且在页内没有更多的空间可以存储,在这种情况下,不同的存储引擎的处理方式是不一样的。例如,MyISAM会将行拆成不同的片段存储,InnoDB 则需要分裂页来使行可以放进页内。 VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于或等于255字节,则只使用1个字节表示,否则使用2个字节。假设采用latinl字符集 ,一个varchar(10)的列需要11个字节的存储空间。varchar(1000)的列则需要1002个字节,因为需要2个字节存储长度信息。适用情况: 1、对于MyISAM表,尽量使用Char,对于那些经常需要修改而容易形成碎片的myisam和isam数据表就更是如此,它的缺点就是占用磁盘空间; 2、对于InnoDB表,因为它的数据行内部存储格式对固定长度的数据行和可变长度的数据行不加区分(所有数据行共用一个表头部分,这个标头部分存放着指向各有关数据列的指针),所以使用char类型不见得会比使用varchar类型好。事实上,因为char类型通常要比varchar类型占用更多的空间,所以从减少空间占用量和减少磁盘i/o的角度,使用varchar类型反而更有利; 3、存储很短的信息,比如门牌号码101,201……这样很短的信息应该用char,因为varchar还要占个byte用于存储信息长度,本来打算节约存储的现在得不偿失。 4、固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占个长度信息。 5、十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。关于MySQL之char、varchar,你学废了么?
之前没有仔细研究过my.cnf文件,今天有时间研究了一下my.cnf中的一些概念,这里简单整理如下,如果有什么问题,还请大家指出。按照教程安装好MySQL之后,打开etc目录下的my.cnf文件,大概可看到下面这样的参数列表,可能不同版本的mysql参数多少会有一些不一致,但是并不妨碍我们理解。首先,我们可以看到这个文件里面有mysqld和mysql_safe两类参数,我们知道mysqld和mysql_safe都可以启动mysql服务,那么mysqld和mysql_safe这两个类之间有什么不同呢?要讨论这个问题,我们需要引入第三个类别mysql.server,并同时讨论这三种启动方式的区别。问题1.mysql.server,mysqld,mysqld_safe的区别mysql.server它是一个服务器启动的shell脚本,主要作用就是为了方便启动和关闭mysql服务,它使用mysql_safe来启动mysql服务器,在mysql.server启动服务器之前,它将目录转换到mysql安装目录里面去,然后调用mysqld_safe。mysql.server通过向服务器发送一个信号来停止它,也可以使用mysqladmin shutdown命令来停止服务器,如果你使用源码或者二进制格式安装mysql(没有自动安装mysql.server这个脚本),你可以手动安装; 这个脚本在mysql安装目录下的support-files目录里边或者在源码包里边;为了能使用service mysqld start命令启动mysql服务,此时需要做的是将mysql.server的脚本复制到/etc/init.d目录下,然后重命名为mysqld,最后给予执行权限。mysqld.server会从配置文件的[mysqld] [mysql.server] 区域读取配置选项;可以在全局配置文件/etc/my.cnf中配置mysql.server,mysql.server脚本支持下面这些选项;一旦指定,它们必须放在配置文件中,不能放到命令行中(mysql.server支持的命令行参数只有start和stop); --basedir mysql安装目录; --datadir 数据文件的路径; --pid-file 服务器写自己的进程号的文件;如果这个不指定,mysql使用默认的hostname.pid; The PID file value被传递给mysqld_safe,覆盖了[mysqld_safe]下面指定的值;因为mysql.server读取[mysqld]选项组而不读取[mysqld_safe]选项组,所以为了在使用mysql.server 调用mysqld_safe的时候, mysqld_safe能够获得一样的pid,我们可以让[mysqld]选项组和[mysqld_safe]选项组使用同一个pid-file; mysql_safe这是mysql服务启动脚本,它是mysqld的父进程,它调用mysqld启动数据库服务,并在启动MySQL服务器后继续监控其运行情况,并在其死机时重新启动它,当我们开启mysqld_safe命令的时候,可以防止mysql服务的意外终止,这里做一个小小的测试。首先查看当前的mysql服务:然后发现服务中有一个mysql_safe和一个mysqld,其中mysqld_safe的端口号是1929,mysqld的端口号是2228,这个时候,我们把2228的进程杀掉:我们发现,进程号为2228的mysqld进程已经被杀掉,进程号为1929的mysqld_safe进程还在,又重新生成了一个进程号为2288的mysqld进程,接下来,我们杀掉mysqld_safe的进程,kill -9 1929,得到的结果如下:我们发现杀掉mysqld_safe之后,只剩下进程号为2288的mysqld进程了,并没有生成新的mysqld_safe进程,这个时候,在再次杀掉mysqld进程2288,结果如下:此时,所有的进程都被关闭掉了,综合上述操作,我们可以发现,当mysqld_safe进程存在时,我们无法直接杀掉mysqld进程,当我们杀掉mysqld_safe进程的时候,此时才可以杀掉mysqld进程,这便是mysqld_safe的守护进程作用,它可以防止mysqld进程被意外终止。mysqldmysqld是关于服务器端的程序,要想使用客户端程序,该程序必须运行,因为客户端通过连接服务器来访问数据库。问题2.mysql的三种启动方式:1、mysqld启动mysql服务器:客户端连接:2、mysqld_safe启动mysql服务器:客户端连接:3、mysql.server启动mysql服务器:客户端连接:同1、2问题3.socket文件mysql.sock详解mysql有两种连接方式,一种是TCP/IP的方式,另外一种是socket的方式,mysql.sock主要用户程序与mysqlserver在同一机器上,发起本地连接的时候使用,即无需再连接服务时使用host和IP,mysql.sock是随着每一次mysql server的启动而生成的,当服务重启时,mysql.sock也会重新生成。利用mysql.sock连接服务的样例如下:linux下安装mysql连接的时候经常回提示说找不到mysql.sock文件,解决办法很简单: 1.如果是新安装的mysql,提示找不到文件,就搜索下,指定正确的位置。 2.如果mysql.sock文件误删的话,就需要重启mysql服务,如果重启成功的话会在datadir目录下面生成mysql.sock 到时候指定即可。问题4.查看mysql的配置文件调用顺序mysql --help|grep “my.cnf”,当启动mysql服务的时候,会从当前目录的my.cnf中去读对应的参数,优先级顺序和输出顺序保持一致。问题5.MySQL的pid文件介绍MySQL pid 文件记录的是当前 mysqld 进程的 pid,pid 亦即 Process ID。1、未指定pid 文件时,pid 文件默认名为 主机名.pid,存放的路径在默认 MySQL 的数据目录。通过 mysqld_safe 启动 MySQL时,mysqld_safe 会检查 pid 文件,如果 pid 文件不存在,不做处理;如果文件存在,且 pid 已占用则报错 "A mysqld process already exists",如果文件存在,但 pid 未占用,则删除 pid 文件。2、查看 MySQL 的源码可以知道,mysqld 启动后会通过 create_pid_file 函数新建 pid 文件,通过 getpid() 获取当前进程 pid 并将 pid 写入 pid 文件。3、因此,通过 mysqld_safe 启动时, MySQL pid 文件的作用是:在数据文件是同一份,但端口不同的情况下,防止同一个数据库被启动多次。,通过 mysqld_safe 启动时, MySQL pid 文件的作用是:在数据文件是同一份,但端口不同的情况下,防止同一个数据库被启动多次。关于MySQL之my.cnf配置文件,你学废了么?真诚地邀请您加入我们的大家庭.在这里不仅有技术知识分享,还有博主们之间的互帮互助不定期发红包,每月更有抽奖环节,游戏机和实体书相赠(包邮)让我们抱团取暖,抱团内卷.打造美好C站.期待您的加入.备注 : CSDN-xxxxxx (xxxxxx代表你csdn的昵称)
我这里以30道Java基础知识题目,带着大家回顾一下Java基础知识。1. 面向对象和面向过程的区别面向过程优点: 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。缺点: 没有面向对象易维护、易复用、易扩展面向对象优点: 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护缺点: 性能比面向过程低2. Java语言有哪些特点?简单易学;面向对象(封装,继承,多态);平台无关性(Java虚拟机实现平台无关性);可靠性;安全性;支持多线程(C++语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而Java语言却提供了多线程支持);支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的,因此Java语言不仅支持网络编程而且很方便);编译与解释并存;3. 什么是JDK?什么是JRE?什么是JVM?三者之间的联系与区别这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。JDK: 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。JRE: 普通用户而只需要安装JRE(Java Runtime Environment)来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。JVM: 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因。区别与联系:JDK用于开发,JRE用于运行java程序 ;JDK和JRE中都包含JVM ;JVM是java编程语言的核心并且具有平台独立性。4. 什么是字节码?采用字节码的最大好处是什么?先看下java中的编译器和解释器: Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。采用字节码的好处: Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。5. Java和C++的区别我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!都是面向对象的语言,都支持封装、继承和多态Java不提供指针来直接访问内存,程序内存更加安全Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。Java有自动内存管理机制,不需要程序员手动释放无用内存6. 什么是Java程序的主类?应用程序和小程序的主类有何不同?一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。7. Java应用程序与小程序之间有那些差别?简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。8. 字符型常量和字符串常量的区别形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)占内存大小上: 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)9. 构造器Constructor是否可被override在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以Constructor也就不能被override,但是可以overload,所以你可以看到一个类中有多个构造函数的情况。10. 重载和重写的区别重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。11. Java 面向对象编程三大特性:封装、继承、多态封装封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。继承继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。关于继承如下3点请记住:子类拥有父类非private的属性和方法。子类可以拥有自己属性和方法,即子类可以对父类进行扩展。子类可以用自己的方式实现父类的方法。(以后介绍)。多态所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。12. String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?可变性 String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。 线程安全性String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。 性能每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。对于三者使用的总结: 如果要操作少量的数据用 = String 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer13. 自动装箱与拆箱装箱:将基本类型用它们对应的引用类型包装起来;拆箱:将包装类型转换为基本数据类型;14. 在一个静态方法内调用一个非静态成员为什么是非法的?由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。15. 在Java中定义一个不做事且没有参数的构造方法的作用Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 16. import java和javax有什么区别刚开始的时候JavaAPI所必需的包是java开头的包,javax当时只是扩展API包来说使用。然而随着时间的推移,javax逐渐的扩展成为Java API的组成部分。但是,将扩展从javax包移动到java包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定javax包将成为标准API的一部分。所以,实际上java和javax没有区别。这都是一个名字。17. 接口和抽象类的区别是什么?接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法接口中的实例变量默认是final类型的,而抽象类中则不一定一个类可以实现多个接口,但最多只能实现一个抽象类一个类实现接口的话要实现接口的所有方法,而抽象类不一定接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。18. 成员变量与局部变量的区别有那些?从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰;从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。20. 什么是方法的返回值?返回值在类的方法里的作用是什么?方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!21. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。22. 构造方法有哪些特性?名字与类名相同;没有返回值,但不能用void声明构造函数;生成类的对象时自动执行,无需调用。23. 静态方法和实例方法有何不同?在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制.24. 对象的相等与指向他们的引用相等,两者有什么不同?对象的相等 比的是内存中存放的内容是否相等而引用相等 比较的是他们指向的内存地址是否相等。25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?帮助子类做初始化工作。26. ==与equals(重要)== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。举个例子:public class test1 { public static void main(String[] args) { String a = new String("ab"); // a 为一个引用 String b = new String("ab"); // b为另一个引用,对象的内容一样 String aa = "ab"; // 放在常量池中 String bb = "ab"; // 从常量池中查找 if (aa == bb) // true System.out.println("aa==bb"); if (a == b) // false,非同一对象 System.out.println("a==b"); if (a.equals(b)) // true System.out.println("aEQb"); if (42 == 42.0) { // true System.out.println("true"); } }}说明:String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。27. hashCode与equals(重要)面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”hashCode()介绍hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)为什么要有hashCode我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。hashCode()与equals()的相关规定如果两个对象相等,则hashcode一定也是相同的两个对象相等,对两个对象分别调用equals方法都返回true两个对象有相同的hashcode值,它们也不一定是相等的因此,equals方法被覆盖过,则hashCode方法也必须被覆盖hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)28. Java中的值传递和引用传递值传递是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。)引用传递是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。)29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。30. 线程有哪些基本状态?这些状态是如何定义的?新建(new):新创建了一个线程对象。可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种: (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。 (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 (三). 其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。备注: 可以用早起坐地铁来比喻这个过程:还没起床:sleeping起床收拾好了,随时可以坐地铁出发:Runnable等地铁来:Waiting地铁来了,但要排队上地铁:I/O阻塞上了地铁,发现暂时没座位:synchronized阻塞地铁上找到座位:Running到达目的地:Dead关于Java基础面试题,你学废了么?
2022年10月