后端面试题总结

java面试题集合(重要重要重要):
https://www.jianshu.com/p/e7ae9063de16
https://blog.csdn.net/qq_41701956/article/details/82697927
1、重定向的状态码
https://blog.51cto.com/wiigood/929619
2xx (成功):::表示成功处理了请求的状态码
3xx (重定向):::要完成请求,需要进一步操作
4xx(请求错误):::这些状态码表示请求可能出错,妨碍了服务器的处理
200 OK:请求成功
301 Permanently:永久重定向,表示请求的资源已经永久的搬到了其他位置。
302 Found:临时重定向,表示请求的资源临时搬到了其他位置。
304(未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
400 Bad Request:表示请求报文存在语法错误或参数错误,服务器不理解。
403 Forbidden:服务器已经理解请求,但是拒绝执行它。
404 Not Found:请求失败,服务器找不到请求的资源。
500 Internet Server Error:服务器出错了。
502(错误网关)服务器作为网关或代理,从上游服务器收到无效响应
503 Service Unavailable:服务器没有准备好处理请求。
504(网关超时)服务器作为网关或代理,但是没有及时从上游服务器收到请求。
505(HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
5、多线程有序打印1和2,分别打印10次

public class Test {
    static final Object object = new Object();
    public static void main(String[] args){
        //线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<5;i++){
                    synchronized (object){
                        System.out.println("1");
                        object.notify(); //唤醒线程2
                        try {
                            object.wait();//线程1进入等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<5;i++){
                    synchronized (object){
                        System.out.println("2");
                        object.notify();//唤醒线程1
                        try {
                            object.wait();//线程2进入等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();

6、数据库表的优化
****通过拆分表提高表的访问效率
对数据表的拆分,分为两种拆分方式:垂直拆分,水平拆分
1.垂直拆分(分表)
即把主键和一些数据表的列放在一个表中,然后把主键和另一些数据表的列放在一个表中。
如果一个表的某些列常用,另一些不常用,则可以采用垂直拆分。垂直拆分可以使数据行变小,一个数据页就可以存放更多的数据,在查询时候可以减少I/O次数。其缺点是需要管理冗余列,查询所有数据时候需要join查找。
2.水平拆分(分表,分区)
即把数据表中的行根据一定规则放在多个独立的表或分区中。
****选择合适的数据类型
****适度冗余:把需要join的项写到一个表中,这样可以减少join的次数
在MySQL中,尽量使用JOIN来代替子查询.因为子查询需要嵌套查询,嵌套查询时会建立一张临时表,临时表的建立和删除都会有较大的系统开销,而连接查询不会创建临时表,因此效率比嵌套子查询高。
索引是提高数据库查询速度最重要的方法之一
1、创建索引
2、使用查询缓存
3、使用EXPLAIN关键字:使用 EXPLAIN 关键字可以知道MySQL是如何处理SQL语句的。这可以帮助分析查询语句或是表结构的性能瓶颈。EXPLAIN 的查询结果还会告诉索引主键被如何利用的,数据表是如何被搜索和排序的等等。
4、 避免 SELECT *:尽量使用更精确的结果
5、选择合适的字段长度
7、CMS和G1的区别----------------------------------------------------------------------------------------------------
****区别一: 使用范围不一样
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用
G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用
****区别二: STW的时间
CMS收集器以最小的停顿时间为目标的收集器。
G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)
****区别三: 垃圾碎片
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-压缩”算法,进行了空间整合,降低了内存空间碎片。
****区别四: 垃圾回收的过程不一样
CMS过程:
****初始标记:这个阶段是标记从GcRoots直接可达的老年代对象、新生代引用的老年代对象。这个过程是单线程的。
****并发标记:由上一个阶段标记过的对象,开始tracing过程,标记所有可达的对象,这个阶段垃圾回收线程和应用线程同时运行。
****重新标记:为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录。
****并发清除:回收所有的垃圾对象,整个过程中耗时最长的并发标记和并发清除都可以与用户线程一起工作,所以总体上说,CMS收集器的内存回收过程与用户线程一起并发执行。
CMS优缺点:
CMS是一款“标记–清除”算法实现的收集器,容易出现大量空间碎片;CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
并发收集,低停顿
8、手写Map的实现

package base;

/**
 * @time: Created in 22:43 2018/1/2
 * @desc 手动实现的一个简化版的HashMap,只支持put,get值,resize(扩容)功能
 */
public class HashMap<K, V> {

    //哈希表的初始容量
    static final int initial_capacity = 4;
    //加载的因子
    float load_factor = 0.75f;
    //记录entry的数量
    int count = 0;

    Entry<K, V>[] table;


    public K put(K key, V value) {
        Entry<K, V> newEntry = new Entry(key, value);
        int hash = hash(key);
        if (table == null) {
            table = new Entry[initial_capacity];
            count++;
        }
        Entry<K, V> head = table[hash];
        //执行扩容,
        if (count > initial_capacity * load_factor) {
            resize();
        }
        if (head == null) {
            table[hash] = newEntry;
            count++;
            return key;
        } else {
            Entry tail = new Entry<K, V>();
            if (head.next == null) {
                head.next = newEntry;
            } else {
                do {
                    tail = head;
                } while ((head = head.next) != null);
                tail.next = newEntry;
            }
            count++;
            return key;
        }
    }

    public V get(K key) {
        Entry<K, V> entry;
        return (entry = getEntry(hash(key), key)) == null ? null : entry.value;
    }

    public Entry<K, V> getEntry(int hash, K key) {
        Entry<K, V> entry = table[hash];
        if (entry == null) {
            return null;
        } else if (entry != null && entry.next == null) {
            return entry;
        } else if (entry.next != null) {
            do {
                if (hash == hash(entry.key) &&
                        (key == entry.key || (key != null && key.equals(entry.key)))) {
                    return entry;
                }
            } while ((entry = entry.next) != null);
            return entry;
        }
        return null;
    }

    public int resize() {
        int newCapacity = initial_capacity << 2;
        Entry[] newTable = new Entry[newCapacity];
        System.arraycopy(table, 0, newTable, 0, table.length);
        this.table = newTable;
        return newCapacity;
    }

    public final int hash(K key) {
        //key.hashCode可能产生负值,执行一次key.hashCode()& 0x7FFFFFFF操作,
        //变为整数,这里产生hash值直接模4, 保证产生的hash值不会因扩容而产生变化
        return (key == null) ? 0 : (key.hashCode() & 0x7FFFFFFF % initial_capacity);
    }

    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("name1", "严嵩");
        map.put("name2", "高拱");
        map.put("name3", "徐阶");
        map.put("name4", "张居正");
        map.put("name5", "申时行");
        System.out.println("map当前容量:" + map.count);
        System.out.println(map.get("name1"));
        System.out.println(map.get("name5"));
    }
}

9、进程如何创建?(底层实现。。。)
申请空白PCB,为进程分配资源,将进程加入到就绪队列
10、线程如何创建?(底层实现。。。)
//不会
11、线程如何互斥
设立临界区,并通过锁机制
12、锁有哪几种
公平锁/非公平锁、可重入锁与非可重入锁、乐观锁/悲观锁、共享锁/排它锁
13、快排时间复杂度?最差呢?优化?
最差情况下是O(n*n),可以采用随机选取法应对最差情况。选取数组开头,中间和结尾的元素,通过比较,选择中间的值作为快排的基准。
15、悲观锁和乐观锁的具体实现原理,CAS算法及ABA问题
Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
乐观锁采用版本号机制:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 当前最后更新的version与操作员第一次的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。
乐观锁采用CAS算法:CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

i++ 的原子性问题:int i=i++ 的操作实际上分为三个步骤“读-改-写”
第一步:int temp = i;
第二步:i = i + 1;
第三步:i = temp;

缺点:循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
缺点:只能保证一个共享变量的原子操作
缺点:ABA 问题:如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。
案例:
小明账户上有100元。现在小明取钱,小强汇钱,诈骗分子盗刷三个动作同时进行。1,小明取50元。2,诈骗分子盗刷50元。3,小强给小明汇款50元。此时,银行交易系统出问题,每笔交易无法通过短信告知小明。ABA问题就是:1,小明验证账户上有100元后,取出50元。——账上有50元。2,小强不会验证小明账户的余额,直接汇款50元。——账上有100元。3,诈骗分子验证账户有100元后,取出50元。——账上有50元。小强没有告诉小明自己汇钱,小明也没收到短信,那么小明就一直以为只有自己取款操作,最后损失了50元。

解决办法:加上一个版本号
16、几千万条数据,存在外存里,你如何找到最大的前100条,综合考虑时间和空间复杂度,,,大文本数据(数T),统计每个字符串的频率,,,一道大数据量的题目,A文件有3T,里面放的是uid+uname,B文件2T,里面放的是uid+unage,找出相同的uid并写成uid+uname+uage的样子,限制内存2G
首先通过hash映射(比如%1000),得到1000个小文件,然后对1000个小文件分别使用hashmap统计词频,并去除top100,并放入到1000个文件中,然后对文件进行归并排序
https://juejin.im/entry/5a27cb796fb9a045104a5e8c
设计一个数据结构,有(0-9亿)条数据,支持FIFO、LIFO,4G memory。要求动态设定长度,不能超出内存限制。
充分考虑空间因素,适当动态分配空间和删除空间即可,因此可以利用双向链表加数组来实现。
17、负载均衡算法
轮询法:将请求按照顺序轮流的分配到服务器上,他均衡的对待每一台后端的服务器,不关心服务器的的连接数和负载情况.以下代码演示了这种算法。
随机法:通过系统的随机函数,根据后端服务器列表的大小来随机获取其中的一台来访问,随着调用量的增大,实际效果越来越近似于平均分配到没一台服务器.和轮询的效果类似。
源地址hash法:源地址hash法的思想是获取客户端访问的ip地址,通过hash函数计算出一个hash值,用该hash值对服务器列表的大小进行取模运算,得到的值就是要访问的服务器的序号。
18、对象的生命周期
创建阶段、应用阶段(对象至少被一个强引用持有着)、不可见阶段(程序本身不再持有该对象的任何强引用)、不可达阶段(对象处于不可达阶段是指该对象不再被任何强引用所持有)、收集阶段
19、布隆过滤器
应用场景:在网络爬虫里,一个网址是否被访问过,yahoo, gmail等邮箱垃圾邮件过滤功能,即如何判断一个元素是否存在一个集合中。
方法:
假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。
21、JMM在这里插入图片描述
Java内存模型规定了所有的变量都存储在主内存(Main Memory)中,每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。主内存主要对应Java堆中的对象实例数据部分,工作内存则对应虚拟机栈的部分区域。
25、栈内存为什么要用栈结构
栈内存一般存储的是函数的调用信息和函数中申明的变量,因为函数的调用是递归的,外层函数一定比内层被调用的函数先加载和执行,而一定等到内层被调用函数结束后才能结束,这个先进后出的机制就是为什么叫栈内存的原因。
29、联合索引什么时候不会生效
mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描;不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
30、Redis有几种数据结构?排序集合怎么实现的?跳表怎么实现的,跳表的增删查时间复杂度
有序集合的编码可以是 ziplist 或者 skiplist和字典 。
ziplist 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。压缩列表内的集合元素按分值从小到大进行排序, 分值较小的元素被放置在靠近表头的方向, 而分值较大的元素则被放置在靠近表尾的方向。
有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现, 但无论单独使用字典还是跳跃表, 在性能上对比起同时使用字典和跳跃表都会有所降低。
查找、插入、删除,跳表的时间复杂度都是O(logn)。推算时可以假设每两个节点抽出一个节点。空间复杂度是O(N)。
在这里插入图片描述
skiplist与平衡树、哈希表的比较

skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
从算法实现难度上来比较,skiplist比平衡树要简单得多。
31、数据库的脏读和幻读?如何解决、可重复读和串行化实现的原理,InnoDB 默认是什么级别?可重复读会加读锁吗?提交读会加吗?为什么?
四种隔离级别就是脏读、不可重复读和幻读的解决方法
脏读:A对数据进行了修改,但未提交,B读取了未提交的数据
不可重复读:A读取了两次,但B在两次之间进行了修改,故A读取的数据前后不一致。
幻读:A对整个表进行了修改,B对其中某些数据进行了修改。A读取的前后数据量不一致
措施------------------------------------------------------------------------------------------------------------------------------------------------:
READ_UNCOMMITED 的原理:
事务对当前被读取的数据不加锁;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。
READ_COMMITED 的原理:
事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
REPEATABLE READ 的原理(innodb默认级别):
事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
SERIALIZABLE 的原理:
事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放;
事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。
由于 B+ 树分支比二叉树更多,所以相同数量的内容,B+ 树的深度更浅,深度代表什么?代表磁盘 io 次数啊!数据库设计的时候 B+ 树有多少个分支都是按照磁盘一个簇上最多能放多少节点设计的啊!所以,涉及到磁盘上查询的数据结构,一般都用 B+ 树啦。
33、不可变类,机制,实现原理
不可变类(Immutable Class):所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。
可变类(Mutable Class):相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
方法:使用private final修饰,不提供setter方法
优点:
线程安全:不可变对象是线程安全的,在线程之间可以相互共享,不需要利用特殊机制来保证同步问题,因为对象的值无法改变。可以降低并发错误的可能性,因为不需要用一些锁机制等保证内存一致性问题也减少了同步开销。
易于构造、使用和测试
34、数据库的回滚与日志
在这里插入图片描述
35、IOC的反射机制是怎么实现的
实例化一个 person()对象, 不使用反射,需要new person(); 如果想变成实例化其他类,那么必须修改源代码,并重新编译。
使用反射: class.forName(“person”).newInstance(); 而且这个类描述可以写到配置文件中,如 .xml文件, 这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。在JavaWeb中加载数据库驱动时会用到。
正常方式:通过完整的类名 > 通过new实例化 > 取得实例化对象 反射方式:实例化对象 > getClass()方法 > 通过完整的类名
36、
用户线程:我们平常创建的普通线程。
守护线程:用来服务于用户线程;不需要上层逻辑介入
当主线程结束时,结束其余的子线程(守护线程)自动关闭,就免去了还要继续关闭子线程的麻烦。如:Java垃圾回收线程就是一个典型的守护线程;内存资源或者线程的管理,但是非守护线程也可以。
38、锁的开销
这样我们大致可以把锁冲突的开销分成三部分,“纯上下文切换”开销,大概是150ns,调度器开销(把线程从睡眠变成就绪或者反过来)大概是900ns,在多核系统上,还存在跨处理器调度的开销,那部分开销很大,超过2000ns。在真实的应用场景里,还要考虑上下文切换带来的cache不命中和TLB不命中的开销,开销只会进一步加大。
41、post和delete区别
DELETE请求顾名思义,就是用来删除某一个资源的,该请求就像数据库的delete操作。
46、进程间通信和线程间通信的区别
线程间资源是共享的,讲安全:信号量,锁,原子操作;进程间资源是独立的,讲通讯:管道,共享内存。
线程间通信:由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度)。进程间的通信则不同,它的数据空间的独立性决定了它的通信相对比较复杂,需要通过操作系统。以前进程间的通信只能是单机版的,现在操作系统都继承了基于套接字(socket)的进程间的通信机制。这样进程间的通信就不局限于单台计算机了,实现了网络通信。
47、TCP server最多可以建立多少个TCP连接
因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2的48次方。
49、一个进程,有十个线程,其中一个线程fork了一个进程后,子进程有几个线程
fork后子进程只能有一个线程
50、计算机冷启动,内存和寄存器怎么加载的
第一阶段:BIOS阶段
第二阶段:确定激活分区
第三阶段:确定操作系统的位置
第四阶段:加载操作系统内核到内存中
后操作系统内核完成系统的初始化,并允许用户与操作系统进行交互
54、 trie树(字典树)
根节点不包含字符,除根节点外每一个节点都只包含一个字符。从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。每个节点的所有子节点包含的字符都不相同。
b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:
在这里插入图片描述
插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,Trie 的缺点是空间消耗很高。
55、接口与抽象类区别
1、抽象类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现。
2、抽象类可以有构造器,而接口不能有构造器
3、抽象方法可以有public、protected和default这些修饰符 ,接口方法默认修饰符是public。你不可以使用其它修饰符。
4、抽象类在java语言中所表示的是一种继承关系,一个子类只能存在一个父类,但是可以存在多个接口。
56、多态的类型
实现方式:接口实现、继承父类进行方法重写、同一个类中进行方法重载
57、spring beans单例和多例使用条件
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。
对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变)用多例
58、b+树比红黑树好在哪里
B树是多路树,红黑树是二叉树!红黑树一个节点只能存出一个值,B树一个节点可以存储多个值,红黑树的深度会更大,定位时 红黑树的查找次数会大一些。AVL 数和红黑树基本都是存储在内存中才会使用的数据结构。
二叉查找树,局限性在于一个二叉查找树是由n个节点随机构成,所以,对于某些情况,二叉查找树会退化成一个有n个节点的线性链。
AVL树是带有平衡条件的二叉查找树,由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。
红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是red或black。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍。它是一种弱平衡二叉树(由于是若平衡,可以推出,相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数变少,所以对于搜索、插入、删除操作多的情况下,我们就用红黑树。
61、url—6位短地址的映射,lru算法
短网址:顾名思义,就是将长网址缩短到一个很短的网址,用户访问这个短网址可以重定向到原本的长网址(也就是还原的过程)。这样可以达到易于记忆、转换的目的,常用于有字数限制的微博、二维码等等场景。https://blog.csdn.net/xlgen157387/article/details/79863301 被转换为:http://t.cn/RuPKzRW
因为我们总共有 62 个字母,我们可以自创一种进制,叫做 62 进制。其规则如下:
0 → a
1 → b

25 → z

52 → 0
61 → 9
对于每一个长地址,我们可以根据它的ID,得到一个6位的 62 进制数,这个6位的 62 进制数就是我们的短址。对于 ID = 138,通过 base62(138), 我们得到 value = [14, 2]。根据上面的对应规则表,我们可以得到其对应的短址为:aaaacn 。
62、实现Java的RLock WLock RULock UWLock
///应该就是读写锁
63、用Java实现一个信号量
Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。

public class TestSemaphore {
                public static void main(String[] args) {
                // 线程池
                ExecutorService exec = Executors.newCachedThreadPool();
                // 只能5个线程同时访问
                final Semaphore semp = new Semaphore(5);
                 // 模拟20个客户端访问
                 for (int index = 0; index < 20; index++) {
                              final int NO = index;
                              Runnable run = new Runnable() {
                                                 public void run() {
                                                            try {
                                                                    // 获取许可
                                                                    semp.acquire();
                                                                    System.out.println("Accessing: " + NO);
                                                                    Thread.sleep((long) (Math.random() * 10000));
                                                                    // 访问完后,释放
                                                                    semp.release();
                                                                    System.out.println("-----------------"+semp.availablePermits());
                                                            } catch (InterruptedException e) {
                                                                    e.printStackTrace();
                                                            }
                                                  }
                                      };
                      exec.execute(run);
             }
             // 退出线程池
             exec.shutdown();
       }
} 
执行结果如下:
Accessing: 0
Accessing: 1
Accessing: 3
Accessing: 4
Accessing: 2
-----------------0
Accessing: 6
-----------------1
Accessing: 7
-----------------1
Accessing: 8
-----------------1
Accessing: 10
-----------------1
Accessing: 9
-----------------1
Accessing: 5
-----------------1
Accessing: 12
-----------------1
Accessing: 11
-----------------1
Accessing: 13
-----------------1
Accessing: 14
-----------------1
Accessing: 15
-----------------1
Accessing: 16
-----------------1
Accessing: 17
-----------------1
Accessing: 18
-----------------1
Accessing: 19

64、为什么不建议使用订单号作为主键
1、订单号不连续,每次在插入的时候,都会因为不是有序的,都要去执行一次B+树重排序,引起没必要的计算和内存开销
2、订单号太长,会占用大量的物理空间。
66、jvm 的局部变量表是做什么的?
存储局部变量、函数调用时传递参数,很多字节码指令都是对局部变量表和操作数栈进行操作的。
方法区的数据引用、当前代码处的局部变量,基本就是用户能通过代码引用到的。
68、说一下建表时,建索引有哪些要注意的
选区分度比较大的,选数据类型比较小的比如整数而不要选长字符串,选where子句中出现的
70、锁效率比较低,怎么解决
分段锁:有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争
71、线程池的链表
线程池里的阻塞队列,似乎都是用链表实现的,没有用数组。用到的队列似乎默认都是动态扩容的,最大为整数最大值。如果队列满了又不支持动态扩容,可以通过设置饱和策略来处理,默认是中止,也就是抛出 RejectedExecutionException。
72、保证并发计数正确
原子变量,于是说 AtomicInteger,因为用了CAS算法
74、
如果有多个变量要更新,要保证一致性,怎样加锁来保证正确性,效率又比较高?只在写时加锁,读不加锁
76、io为什么会阻塞、nio的底层实现原理、nio 为什么是非阻塞的,NIO、bio/nio/aio------------------------------------------------------------
非常重要*******
https://juejin.im/post/5dbba5df6fb9a0204a08ae55
https://blog.csdn.net/skiof007/article/details/52873421
https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/BIO-NIO-AIO.md
https://github.com/justtreee/blog/issues/17
NIO也叫Non-Blocking IO 是同步非阻塞的IO模型。线程发起io请求后,立即返回(非阻塞io)。同步指的是必须等待IO缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待IO缓冲区,可以先做一些其他操作,但是要定时轮询检查IO缓冲区数据是否就绪。Java中的NIO 是new IO的意思。其实是NIO加上IO多路复用技术。普通的NIO是线程轮询查看一个IO缓冲区是否就绪,而Java中的new IO指的是线程轮询地去查看一堆IO缓冲区中哪些就绪,这是一种IO多路复用的思想。IO多路复用模型中,将检查IO数据是否就绪的任务,交给系统级别的select或epoll模型,由系统进行监控,减轻用户线程负担。
NIO主要有buffer、channel、selector三种技术的整合,通过零拷贝的buffer取得数据,每一个客户端通过channel在selector(多路复用器)上进行注册。服务端不断轮询channel来获取客户端的信息。channel上有connect,accept(阻塞)、read(可读)、write(可写)四种状态标识。根据标识来进行后续操作。所以一个服务端可接收无限多的channel。不需要新开一个线程。大大提升了性能。
selector:
Selector 是Java NIO实现多路复用的基础,简单的讲,Selector 会不断地轮询注册在其上的 Channel,如果某个Channel 上面发生读或者写事件,这个Channel 就处于就绪状态,会被Selector轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。这样,一个单独的线程可以管理多个 Channel ,从而管理多个网络连接,跟 I/O多路复用模型思想一样。
77、arrayList如何动态扩展
而是将现有的个数乘以2,然后将原来的元素复制过去。当数据容不下时,ArrayList会再创建一个更大的数组,数组长度为之前所说的那样,然后将之前的数据拷贝到新数组中。这就是ArrayList基于数组实现的可变长度原理。
78、为什么hashmap线程不安全,如何保证hashmap线程安全,ConcurrentHashMap与HashMap的区别、ConcurrentHashMap是如何实现线程安全的(CAS,当时忘了),写代码:实现一个 hashtable
put的时候导致的多线程数据不一致。线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。
如何保证:使用Hashtable、ConcurrentHashMap
在这里插入图片描述
使用CAS + synchronized 来保证并发安全性。普通操作:根据 key 计算出 hashcode 。判断是否需要进行初始化。f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。如果都不满足,则利用 synchronized 锁写入数据。如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
写代码:实现一个 hashtable///暂时还没写
Hashtable的方法中基本上都是有synchronized关键字修饰的,因此是线程安全的。
79、gc发生的时间、gc root节点的种类,什么可以作为GCRoot,GCroot可以为哪些?
方法区的数据引用、当前代码处的局部变量,基本就是用户能通过代码引用到的。
答:a. java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
b.方法区中的类静态属性引用的对象。
c.方法区中的常量引用的对象。
d.本地方法栈中JNI本地方法的引用对象。
81、生产者消费者模型的实现(阻塞队列,代码)--------------------------------------------------------------------------------------------
https://www.jianshu.com/p/66e8b5ab27f6
83、url各部分是什么,结合后端举例回答、url里的参数encode是什么作用,urlencode是什么
URI:统一资源标识符
URL:统一资源点位符
URN:统一资源名称
URI=URL+URN
protocol 😕/ hostname[:port] / path / [;parameters][?query]#fragment
协议+主机名+端口号+路径+文件名+参数
encode主要是为了给非英语语言进行编码
86、Java代码编译过程
在这里插入图片描述
在这里插入图片描述
87、JIT是什么,作用
JIT 是 just in time 的缩写, 也就是即时编译器。
在这里插入图片描述
在主流商用JVM(HotSpot、J9)中,Java程序一开始是通过解释器(Interpreter)进行解释执行的。当JVM发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“热点代码(Hot Spot Code)”,然后JVM会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为:即时编译器(Just In Time Compiler,JIT)
92、设计一个支持售卖(A 到 B 站)和余票查询的功能,再写出功能的 sql 语句
UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]
93、索引为abc且查询条件为a=xxx order by b时能用上哪个索引,order by 能用到索引吗,索引有哪几种
索引有单列索引,多列索引,普通索引,唯一索引,,组合索引,全文索引,空间索引
//第一个问题不太清楚
95、java线程池添加任务的过程,一致性哈希是什么------------------------------------------------------------------------------------------------
https://zhuanlan.zhihu.com/p/34985026
97、如何实现分布式锁
redis可以实现分布式锁
98、redis和java中原子类自增怎么是实现
通过引入unsafe类来实现的
102、equals和==的区别?我要比较内容呢?
答:equals:是用来比较两个对象内部的内容是否相等的。
:是用来判断两个对象的地址是否相同,即是否是指相同一个对象。
如果没有重写equals时,是直接用
判断的
如果是基本类型和基本型封装,则仍然为比较内容。
103、java的jar包是源代码吗?
jar包就是一堆.class文件
104、java的编译是怎么一个过程呢?
答:java编译器的话经过四个步骤,词义分析,语义分析,语法分析和代码生成。
105、java 的虚函数是怎么样的?
答:java里面是抽象函数和接口
106、java内存空间是怎么分配的?
一, 对象优先在新生代Eden区分配
二, 大对象直接进入老年代
三, 长期存活对象将进入老年代(虚拟机设计了一个对象年龄计数器,该阀值默认为15)
四, 动态对象年龄判定 如果Survivor区中相同年龄所有对象大小的总和大于Survivor区空间的一半,年龄大于或等于该年龄的对象在Minor GC时将复制至老年代
五, 空间分配担保 当Minor GC时如果存活对象过多,无法完全放入Survivor区,就会向老年代借用内存存放对象,以完成Minor GC
手写mysql左连接查询,把表也模拟写出来,查询结果也写出来
编程题:多路归并
可重入锁:

// 演示可重入锁是什么意思,可重入,就是可以重复获取相同的锁,synchronized和ReentrantLock都是可重入的
// 可重入降低了编程复杂性
public class WhatReentrant {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (this) {
					System.out.println("第1次获取锁,这个锁是:" + this);
					int index = 1;
					while (true) {
						synchronized (this) {
							System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
						}
						if (index == 10) {
							break;
						}
					}
				}
			}
		}).start();
	}
}

每一条tcp连接唯一的被两端的两个套接字所确定,套接字=IP地址+端口号

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值