zoukankan      html  css  js  c++  java
  • 对一次面试知识点内容的总结和补充

    这是由一次面试开始的第一篇Blog

    本次面试方向为Java架构方向,涉及到Java新的应用场景,相当于打开了Java的另一扇大门。由此开始了我的第一篇博客,对相关知识点和内容进行总结和实践。

    其中包括如下知识点:

    • Java中的hashCode()方法及相关应用
    • ==和equal()的细节问题
    • jvm虚拟机内存结构
    • IO拥塞及5种IO模型
    • 数据库分表分库
    • Redis缓存数据库
    • MyCat中间件

    • 快捷目录

    Java中的hashCode()方法及相关应用

    Java中的Object类为所有类的父类,在Object下面包含了很多基础方法,譬如equals()、getClass()、hashCode()、toString()等。

    hashCode()作为基础方法中重要的一员,用于返回对象的hash值,其返回值为int类型。

    但是仅仅返回对象的hash值有什么意义吗?要想真正理解hashCode()方法的作用,必须要从hash散列和Java集合说起。

    • hash散列:把任意长度的输入,通过散列算法变换成固定长度的输出,该输出就是散列值。通过散列计算地址来存储数据,再使用hash散列函数访问数据,能使对一个数据序列的访问过程更加迅速有效,数据元素将被更快地定位。hash算法会尽量选择更好的方案来减少碰撞,但在某些情况下,hash散列也会得出相同的值,此时便需要根据实际需要进行冲突的解决,因此,相同的hash值并不意味着目标对象是相同的。

    • Java集合:在Java中,HashMap是使用非常多的集合,它是基于哈希表的 Map 接口的实现,以key-value的形式存在,而HashSet仅存储对象,同时HashSet中不允许有重复的元素。当向一个Set集合中添加元素时,先调用这个元素的hashCode()方法,就能直接确定该元素的物理地址,当这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较;但是如果这个位置上已经有元素,就需要调用它的equals()方法与新元素进行比较,相同的话就不再存储,不相同就散列其它的地址。可以发现,在以上过程中涉及到equals()方法和hashCode()方法性能上的差距,Set集合在添加元素时,并没有直接通过equals()方法全权负责,而是先由hashCode()方法进行判断,再根据结果选择性调用equlas()方法。相关源代码如下:

    public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            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))))
                    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;
                        }
                        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;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }

    由此,可以总结如下:

    1. 相同的对象具有相同的hash值
    2. equals()相等的对象一定具有相同的hash值
    3. hashCode()相同的对象不一定equals()相等
    4. 在比较场景下,hashCode()方法性能较equals()方法性能较好(一个用值来比较,一个用对象来比较)
    5. hashCode()与equals()的重写问题:若重写了equals()方法,则有必要重写hashCode()方法(此处可以深入探讨)

    ==和equal()的细节问题

    equals()是java.lang.Object类的方法,要想说明==和equals()的区别,还要从代码开始:

            String str1, str2, str3 = "abc", str4 = "abc";
            str1 = new String("abc");
            str2 = new String("abc");
            StringBuffer strb1 = new StringBuffer("abc");
            StringBuffer strb2 = new StringBuffer("abc");
    
            System.out.println(str1==str2);
            System.out.println(str1.equals(str2));
            System.out.println(str3==str4);
            System.out.println(str3.equals(str4));
            System.out.println(strb1==strb2);
            System.out.println(strb1.equals(strb2));

    控制台打印结果如下:

    false
    true
    true
    true
    false
    false

    可是为什么会造成这样的结果呢,这就设计到==和equals()的一些细节问题。
    经过查阅资料和源代码得知,==和equals()在比较上存在以下区别:

    1. “==”比较两个字符串变量本身的值,即两个对象在内存中的首地址
    2. equals()比较字符串中所包含的内容是否相同,因为在String类中重写了equals()方法,比较的是字符串的内容而不是其地址,涉及到的源代码如下:
    public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
    1. StringBuffer类中没有重新定义equals这个方法,因此这个方法就来自Object类,而Object类中的equals方法是用来比较地址的,所以等于false
    2. 对于str3和str4是由字符串生成的变量,因此所存放的内存地址是相同的

    下面再来测试一段代码:

            int i1 = 1, i2 = 2, i3 = 1;
    
            System.out.println(i1==i2);
            System.out.println(i1==i3);

    控制台输出结果如下:

    false
    true

    由以上结论,可知==比较的是对象在内存中的首地址。这样以来可以说明jvm编译机制:int类型常量会根据大小区分指令,0~5一段,-128~127一段,-32768-32767一段,其余会作为常量放入常量池。其常量池的地址是相同的。

    接下来再尝试下Integer类的情况:

            Integer ig1 = new Integer(123);
            Integer ig2 = new Integer(123);
            Integer ig3 = new Integer(321);
            int i4 = ig3;
    
            System.out.println(ig1==ig2);
            System.out.println(ig1==ig3);
            System.out.println(ig1.equals(ig2));
            System.out.println(ig1.equals(ig3));
            System.out.println(ig3==i4);
            System.out.println(ig3.equals(i4));

    控制台输出显示如下:

    false
    false
    true
    false
    true
    true

    可以发现,在Integer的实例下,==是比较地址的,而equals()是比较内容的。仔细查看源代码:

    public boolean equals(Object obj) {
            if (obj instanceof Integer) {
                return value == ((Integer)obj).intValue();
            }
            return false;
        }

    可见,Integer类重写了equals()方法,并且自动拆箱是调用Integer.valueOf()方法。


    jvm虚拟机内存结构

    由第二部分可知java代码在编译过程中涉及到内存机制问题,在这里简单设计下jvm虚拟机的内存结构。
    借用下《深入理解Java虚拟机(第二版)》中的描述:

    jvm内存结构

    这里暂时作为参考和总结,具体细节比较复杂,在之后再做深入讨论。


    IO拥塞及5种IO模型

    I/O:Input/Output 输入/输出
    大致可分为内存IO、网络IO、磁盘IO

    5种IO模型分别是阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型、异步IO模型;前4种为同步IO操作,只有异步IO模型是异步IO操作。

    此处参考大牛博客,对于消息同步异步以及IO模型进行了清晰和详细的阐述:

    https://www.cnblogs.com/wxl-dede/p/5134636.html


    数据库分表分库

    数据库分表分库思想,其英文名为Sharding,意为“分片”。

    此处参考大牛博客,其全面的总结和中间件的汇总,让人十分受益:

    https://www.cnblogs.com/jshen/p/7682502.html


    Redis缓存数据库

    Redis 是一个高性能的key-value数据库。

    Redis 与其他 key - value 缓存产品有以下三个特点:

    • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用

    • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

    • Redis支持数据的备份,即master-slave模式的数据备份

    Redis与其他key-value存储有什么不同?

    • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象

    • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问


    MyCat中间件

    正如百度百科定义,MyCat是代替昂贵的oracle的MySQL集群中间件。

    Mycat的原理并不复杂,复杂的是代码,如果代码也不复杂,那么早就成为一个传说了

    Mycat的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的SQL语句,首先对SQL语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此SQL发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户

    参考相关博客:

    https://www.cnblogs.com/andy6/p/6622324.html


    其中内容有详有略,有些部分还需要不断地学习,先做引用之后会不断深入。记第一篇博客,加油!


    做一个仰望星空的极客
  • 相关阅读:
    排序算法研究
    SqlParameters参数
    winfrom项目
    方法参数中有out和in关键字是什么意思?
    在C#中使用存储过程
    11Book系列多表群操作
    7drf过滤排序分页异常处理
    12RBAC基于角色的访问控制
    5drf路由组件
    8drf自动生成接口文档
  • 原文地址:https://www.cnblogs.com/aqiu18/p/9568538.html
Copyright © 2011-2022 走看看