秋招小记

1、分库分表带来了哪些问题?

  1. 同一数据库中的表分布在了不同的数据库中,无法使用 join操作
    1.   手动进行数据封装,在一个数据库中查询到一个数据之后。再根据他到另外一个数据库中查找
  2. 事务问题:单个操作涉及到多个数据库,数据库的自带事务就无法满足需求
  3. 分布式id:单个数据库的自增id无法保证全局唯一性,解决的方法是:另开一个服务产生 分布式id(雪花算法

雪花算法生成的id的结果是一个 64bit大小 的整数,

1bit符号位:0表示正数。生成的id一般都是用整数,所以最高位固定为0;

 

41bit时间戳:2^41换算成年,大概是69年。雪花算法的时间纪元从2016年11月1日零时开始;

10bit-工作机器id:可以部署1024个服务节点,业务中大多用 5bit 服务器机房 + 5bit 服务器

12bit序列号:同一毫秒,同一主机能产生4096个id;

如果遇到时间回拨导致分布式id重复怎么办?

  • 报错;
  • 或者时间戳小于上次的时间戳这段时间不允许生成ID;
  • 如果发生了时钟回拨,此时你看看时钟汇报到了之前的哪一毫秒里去maxTimeStamp,直接接着在那一毫秒里的最大的id继续自增就可以了;(如果回拨的时间较长)

3、redis的对象

 Redis的5种基本数据结构:String(字符串)、List(列表)、Set(集合)、Hash(散列)和ZSet(有序集合)

3.1 ZSET是则么实现的?跳跃表 + 哈希表(key记录成员对象,value计算得分score)

3.2 跳跃表的平均时间复杂度为O(logN),最坏时间复杂度为O(N)(方法就是在每个节点维护多个指向其他节点的指针和跨度,从而达到快速随机访问的目的);

3.3 跳跃表节点的层高:随机生成一个介于1-32之间的值作为level数组的大小;

3.4 跳跃表的查找:

  1. 优先从高层查找
  2. 若当前节点的值, 小于要查找的值, 并且next指针指向节点的值也大于目标值, 就要降一级寻找, 或者next指针指向了null, 那么也需要降一级查找

4、缓存雪崩:大量请求的 key 根本不存在于缓存

解决方式:

  1. 参数校验:不合法的参数直接拦截(数据库id小于0)
  2. 缓存无效的key(适用于黑客攻击的key变化不频繁)
  3. 布隆过滤器(给定数据是否存在于海量数据中):布隆过滤器说某个元素存在,小概率会误判(不同的字符串可能哈希出来的位置相同)。布隆过滤器说某个元素不在,那么这个元素一定不在

5、缓存雪崩:缓存在同一时间大面积的失效(缓存崩了)

解决办法:

  1. 如果是redis服务器的问题
    1. 限流,避免同时处理大量的请求。
    2. 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使
  2. 如果是过期键设置:错开过期时间

6、topK问题

6.1 采用大根堆或者小根堆,对于求前K小的元素:(比快排慢)

  • 采用小根堆:需要把全部元素都入堆,查询的时间复杂度为O(NlogN) ×
  • 采用大根堆(其中每个节点的值都大于或等于其子节点):用一个容量为 K 的大根堆,遍历所有的数字N,每次 poll 出最大的数,那堆中保留的就是前 K 小啦,时间复杂度为O(NlogK) √
// 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
// 1. 若目前堆的大小小于K,将当前数字放入堆中。
// 2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过;
//    反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 默认是小根堆,实现大根堆需要重写一下比较器。
        Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
        for (int num: arr) {
            if (pq.size() < k) {
                pq.offer(num);
            } else if (num < pq.peek()) {
                pq.poll();
                pq.offer(num);
            }
        }
        
        // 返回堆中的元素
        int[] res = new int[pq.size()];
        int idx = 0;
        for(int num: pq) {
            res[idx++] = num;
        }
        return res;
    }
}

 

6.2 快速排序的思想(直接通过快排切分排好第 K 小的数(下标为 K-1),那么它左边的数就是比它小的另外 K-1 个数啦~) 

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 最后一个参数表示我们要找的是下标为k-1的数
        return quickSearch(arr, 0, arr.length - 1, k - 1);
    }

    private int[] quickSearch(int[] nums, int lo, int hi, int k) {
        // 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
        int j = partition(nums, lo, hi);
        if (j == k) {
            return Arrays.copyOf(nums, j + 1);
        }
        // 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
        return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
    }

    // 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
    private int partition(int[] nums, int lo, int hi) {
        int v = nums[lo];
        int i = lo, j = hi + 1;
        while (true) {
            while (++i <= hi && nums[i] < v);
            while (--j >= lo && nums[j] > v);
            if (i >= j) {
                break;
            }
            int t = nums[j];
            nums[j] = nums[i];
            nums[i] = t;
        }
        nums[lo] = nums[j];
        nums[j] = v;
        return j;
    }
}

6.3 计数排序(数据范围有限,不支持海量数据)

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        // 统计每个数字出现的次数
        int[] counter = new int[10001];
        for (int num: arr) {
            counter[num]++;
        }
        // 根据counter数组从头找出k个数作为返回结果
        int[] res = new int[k];
        int idx = 0;
        for (int num = 0; num < counter.length; num++) {
            while (counter[num]-- > 0 && idx < k) {
                res[idx++] = num;
            }
            if (idx == k) {
                break;
            }
        }
        return res;
    }
}

7、海量数据的topk问题

7.1 直接排序,数据量太大,否决(×)

7.2 局被淘汰:用一个容器保存前K个数,然后将剩余的所有数字——与容器内的最小数字相比,如果所有后续的元素都比容器内的K个数还小,那么容器内这个K个数就是最大K个数。如果某一后续元素比容器内最小数字大,则删掉容器内最小元素,并将该元素插入容器,最后遍历完这1亿个数,得到的结果容器中保存的数即为最终结果了(实际实现可以用小根堆

7.3 分治算法(用快排找子问题的前K个大的数字)将1亿个数据分成100份,每份100万个数据,找到每份数据中最大的100个(即每份数据的TopK),最后在剩下的100*100个数据里面找出最大的100个(再合并得到最终的答案)。

7.4 在使用小根堆 或者 快速排序前 可以使用hash去重

 八、cookie和session的区别

  • 客户端向服务器端发送一个请求的时,服务端向客户端发送一个Cookie然后浏览器将Cookie保存(什么是cookie)
  • 用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话(Web中的session)(Session对象存储特定用户会话所需的属性及配置信息

区别:相同:都是用来保存用户的相关信息的

1、存储位置:cookie存储在浏览器;session只能存储在服务端

2、安全性:session比cookie安全,因为cookie保存在浏览器,可以通过拦截或本地文件中找到你的cookie然后进行攻击

3、大小限制:cookie具有容量限制,单个Cookie保存的数据不能超过4K;Session是没有大小限制和服务器的内存大小有关。

4、性能:Session占用服务器性能,Session过多,增加服务器压力

5、是否禁用:无论客户端浏览器做怎么样的设置,session都应该能正常工作。客户端可以选择禁用cookie

6、session可以保存任意Java对象,cookie一般保存String对象

九、HTTP和HTTPS

  • 端口号不同,80vs443
  • URL前缀不同:http:// vs https://
  • 安全性:http运行在tcp之上,所有传输的内容都是明文;HTTTPS在TCP之上又加了一个SSL/TLS(安全套接字层),所有传输的内容使用了对称加密,但是 对称加密的密钥用服务器方的证书进行了非对称加密;

非对称加密:非对称加密采用两个密钥——一个公钥,一个私钥(生成算法依赖于单向陷门函数)

对称加密:通信双方共享唯一密钥 k,加解密算法已知,加密方利用密钥 k 加密,解密方利用密钥 k 解密,保密性依赖于密钥 k 的保密性

区别:

1、对称加密中加密和解密使用的秘钥是同一个;非对称加密中采用两个密钥,一般使用公钥进行加密,私钥进行解密。

2、对称加密解密的速度比较快,非对称加密和解密花费的时间长、速度相对较慢。(实际通信中,非对称加密的计算代价高 ,效率低)

3、对称加密的安全性相对较低,非对称加密的安全性较高

问题:既然对称加密不如非对称加密安全,为什么不采用非对称加密直接加密传输内容?

因为非对称加密的效率低;

问题:那对称加密如何保证安全?

对称加密保密性依赖于密钥 k 的保密性。在双方通信之前,需要商量一个用于对称加密的密钥。我们知道网络通信的信道是不安全的,传输报文对任何人是可见的,密钥的交换肯定不能直接在网络信道中传输。因此,使用非对称加密,对对称加密的密钥进行加密,保护该密钥不在网络信道中被窃听。这样,通信双方只需要一次非对称加密,交换对称加密的密钥,在之后的信息通信中,使用绝对安全的密钥,对信息进行对称加密,即可保证传输消息的保密性。

问题:非对称加密的公钥传输?容易被窃听,那么公钥怎么传输?也就是说接收者接收到的公钥如何确定是发送者的公钥呢?

通过CA证书来保证,CA是权威的第三方机构。客户端获取了服务器的证书后,由于证书的信任性是由第三方信赖机构认证的,而证书上又包含着服务器的公钥信息,客户端就可以放心的信任证书上的公钥就是目标服务器的公钥。

问题:那CA证书是如何防伪的呢?

第三方信赖机构 CA 之所以能被信赖,就是 靠数字签名技术 (是 CA 在给服务器颁发证书时,使用散列+CA加密的组合技术,在证书上盖个章,以此来提供验伪的功能。)。

  1. 设有服务器 S,客户端 C,和第三方信赖机构 CA。
  2. S 信任 CA,CA 是知道 S 公钥的,CA 向 S 颁发证书。并附上 CA 私钥对消息摘要的加密签名。
  3. S 获得 CA 颁发的证书,将该证书传递给 C。
  4. C 获得 S 的证书,信任 CA 并知晓 CA 公钥,使用 CA 公钥对 S 证书上的签名解密,同时对消息进行散列处理,得到摘要。比较摘要,验证 S 证书的真实性。
  5. 如果 C 验证 S 证书是真实的,则信任 S 的公钥(在 S 证书中)。

十、IO多路复用的原理是什么?链接

IO多路复用是一种机制,底层可以通过系统调用select/poll/epoll来监听多个文件描述符,一旦某个文件描述符准备就绪(读就绪或者写就绪),就能通知程序进行响应的读写操作

对比:

  1. 描述fd集合的方式:select采用fd_set结构,有最大连接数的限制,默认为1024;而poll采用链式存储,没有连接的限制;epoll也没有fd的限制,每个epoll监听一个fd,所以最大数量与能打开的fd数量有关
  2. 是否将fd集合拷贝到内核空间:select、poll都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来;epoll在用epoll_ctl函数进行事件注册的时候,已经将fd复制到内核中,所以不需要每次都重新复制一次
  3. 查询fd集合的方式:select、poll采用主动轮询,遍历每一个fd文件描述符,检查文件描述符是否处于就绪态;epoll采用回调机制(被动出发),每个fd指定一个回调函数,当数据准备好之后,就会把就绪fd加入一个就绪队列。最后的方式就是检查就绪队列中有没有就绪的fd,然后唤醒(随着fd的增加,select和poll的效率会线性降低)
  4. epoll是select和poll的改进方案,在 linux 上可以取代 select 和 poll,可以处理大量连接的性能问题

十一、linux的相关命令

1、查看日志,一般用的比较多的是

  • tail:将文件的最后部分输出到标准设备,-f可以动态监视日志的增长;
  • cat:显示整个文件的内容,可以配合 |grep 进行过滤;
  • more:(显示一页)让画面在显示满一页时暂停,此时可按空格健继续显示下一个画面,或按 q 键停止显示;
  • less:和more功能类似,利用上下键来卷动文件;

2、查看进程

  • top 实时显示进程动态
  • ps -ef | grep:ps 命令的作用是显示进程信息的

3、查看端口

  • netstat -anp |grep 3306 命令的作用是显示进程信息的

十一、Hashtable、HashMap和CurrentHashMap

  • 线程安全性:Hashtable(线程安全,内部使用synchronized修饰)
  • 是否支持null:HashMap支持null的key,但只能有一个;null作为value,可以有多个。(从源码来看,HashMap的put方法)调用hash()方法来计算key的hashcode值,可以从hash算法中看出当key==null时返回的值为0。因此key为null时,hash算法返回值为0,不会调用key的hashcode方法;Hashtable不支持null的key和value,会抛出异常。
  • 扩容机制HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小
  • 底层数据结构:

HashMap在1.7和1.8有什么区别

 

在1.7中使用的是头插法,1.8改成了尾插法,为了避免头插法导致的在多线程的情况下hashmap在put元素时产生的环形链表的问题,但1.8仍然存在数据覆盖的问题,所以仍然不是线程安全的

 扩容有两种时期:

1:当添加某个元素后,数组的总的添加元素数大于了 数组长度 * 0.75(负载因子)(默认,也可自己设定),数组长度扩容为两倍。(如开始创建HashMap集合后,数组长度为16,临界值为16 * 0.75 = 12,当加入元素后元素个数超过12,数组长度扩容为32,临界值变为24)

2:当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)

 

currentHashMap如何保证线程安全?

jdk1.7,ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成

 每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁。也就是说,对同一 Segment 的并发写入会被阻塞,不同 Segment 的写入是可以并发执行的。Segment 的个数一旦初始化就不能改变。 Segment 数组的大小默认是 16,也就是说默认可以同时支持 16 个线程并发写(Segment继承了ReentrantLock)synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升。

jdk1.8之后采用了Node数组 + 链表 + 红黑树,采用了synchronized和cas操作来保证线程安全性,具体体现如下:

CAS:在判断Node数组中当前位置为null的时候,使用CAS来把这个新的Node写入数组中对应的位置

synchronized :当数组中的指定位置不为空时,synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升

 

synchronized和reentrantLock的区别:

1、相同点:都是可重入锁(未释放锁之前还可以再次获取锁)=》不可重入锁会造成死锁

2、不同点:

  • synchronized依赖于JVM,优化都是基于JVM层面;ReentrantLock依赖于jdk
  • ReentrantLock更加高级:能够实现
  • 一、可以中断等待
  • 二、synchronized只能实现非公平;reen能使实现公平和非公平
  • reen能够实现选择性通知(syn配合wait/notify 不能选择某一个线程唤醒,需要配合flag变量;reen可以借助condition唤醒等待某一codition实例的线程)

十二、string、stringbuilder、stringbuffer

  • 可变性:string不可变,stringbuffer和stringbuilder是可变的
  • 线程安全性:string是常量,可以理解为线程安全的;stringbuffer对方法加了同步锁,是线程安全的;builder不安全
  • 性能:StringBuilder性能比stringbuffer性能能好

十三、Java对象能存到栈里吗?

1、栈上分配?

并不是所有的Java对象都在堆上分配,通过JVM逃逸分析,如果一个对象没有逃逸,就把他分配到Java虚拟机栈(这样的优点是:由于栈帧是随着方法调用而创建,随着方法结束而销毁。无需进行垃圾回收,减少内存分配的压力)

2、逃逸分析指的是什么?

逃逸是指在某个方法之内创建对象,除了在方法体之内被引用外,还在方法体之外被其他变量引用到。这样的后果是该方法执行完毕之后,该方法创建的对象无法被GC回收。

3、逃逸分析/栈上分配的优势

  • 消除同步。线程同步的代价是相当高的,同步的后果是降低并发性和性能。逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作,这样就能大大提高并发程度和性能。
  • 矢量替代。逃逸分析方法如果发现对象的内存存储结构不需要连续进行的话,就可以将对象的部分甚至全部都保存在CPU寄存器内,这样能大大提高访问速度。
  • (不用GC,随着栈帧出栈销毁)由于栈帧是随着方法调用而创建,随着方法结束而销毁。无需进行垃圾回收,减少内存分配的压力

劣势:(受限于空间

  • 栈上分配受限于栈的空间大小,一般自我迭代类的需求以及大的对象空间需求操作,将导致栈的内存溢出;故只适用于一定范围之内的内存范围请求。

4、一个Java虚拟机栈默认有多大?

JDK1.5,JVM设置Java虚拟机栈的默认大小为1M

5、GC Roots有哪些?

  • 虚拟机栈引用的对象
  • 本地方法栈引用的对象
  • 方法区中  类静态属性 引用的对象
  • 方法区中 常量引用的 对象
  • 所有 被同步锁持有的对象

6、可达性分析的具体过程

CMS进行可达性分析的过程中,由于用户线程的运行,导致引用发生了变化,会产生哪些问题,如何解决?

  • 一个本应该是垃圾的对象被视为了非垃圾(浮动垃圾,下次GC的时候处理即可
  • 一个本应该不是垃圾的对象被视为了垃圾

对于第二种的解决办法:

增量更新(站在新增引用的角度来看) 

当一个黑色对象新增一个白色对象的引用时, 就通过写屏障将这个引用关系记录下来。然后在重新标记阶段,再以这些引用关系中的黑色对象为跟,再扫描一次,以保证不会漏标(重新标记需要STW);

原始快照(站在减少引用的角度的)

十四、消息队列的两种模式:pull 和 push

  • push模式:
  1. 说明:broker(消息队列)主动将消息推送到consumer(消费者);
  2. 优点:push模式消息实时性高,Broker接受完消息之后可以立马推送给Consumer(消费者);
  3. 缺点:慢消费问题,消费者消费速度比生产者慢,就会导致消息堆积;(当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了)
  • pull模式:
  1. 说明:consumer主动到broker中拉取消息;
  2. 优点:cosumer可以按需消费;
  3. 缺点:消息延迟,消费者无法准确地决定何时去拉取最新的消息,并且会有很多无效的拉取(关于无效地拉取,kafka的优化式都采用了一种优化的做法-长轮询,消费者请求拉取消息,当有消息的时候 Broker 会直接返回消息,如果没有消息,Broker会阻塞,等到有新消息来的时候再notify并返回消息)

kafka采用的是第二种pull的方式

Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。

 十五、Dubbo接口之间的调用是同步的(synchronized)还是异步的(asynchronized)?

dubbo同步调用异步调用是否返回结果配置

(1)dubbo默认为同步调用,并且有返回结果

(2)dubbo异步调用配置,设置async="true"异步调用可以提高效率。

(3)dubbo默认是有返回结果,不需要返回,可以设置return="false",不需要返回值,可以减少等待结果时间。

http://t.zoukankan.com/xkzhangsanx-p-11256043.html

 十六、GC的种类 和 时机

1、Java的内存管理 实际上就是对象的管理,其中包括对象的分配释放;

2、JVM常见的GC主要包括三种:

  1. Minor GC/Young GC:只是新生代的垃圾收集;
  2. Major GC/Old GC:只是老年代的垃圾收集;(需要注意的是Major GC在有的语境中也用于 指代整堆收集只有CMS的concurrent collection是这个模式
  3. Full GC(整堆收集):对整个Java堆方法区的垃圾收集;

3、GC触发的时机

  1. 对象优先在 Eden 区分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC(Java对象大多具备朝生夕死的特征,所以Minor GC非常频繁,回收速度也非常块。Minor GC会引发STW,暂停其他用户线程);

  2. 大对象直接进入老年代、长期存活的对象将进入老年代。当老年代空间不足时,会触发Major GC(Major GC的速度一般比Minor GC慢10倍以上);
  3. Full GC触发机制:①、当准备要触发一次 young GC时,如果发现统计数据说之前young GC的平均晋升大小目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC;②、如果有永久代perm gen分配空间但已经没有足够空间时,也要触发一次full GC;③、Systerm.gc()函数也会触发full gc。

4、Java堆的分代?

 

 

  • 新生代再细分,可以分为Eden区、S0区和S1区,比例为8:1:1;
  • 在同一个时间点上,S0和S1只能有一个区有数据,另外一个是空的;
  • 每进行一次Minor GC,From区中对象的年龄就会+1,Eden区中所有存活的对象会被复制到To区,From区中还能存活的对象有两个去处;
  1. 若对象年龄达到之前设置好的对象阈值,此时对象会被移动到Old区;
  2. 没有达到年龄阈值的对象会被复制到To区;
  • 此时,Eden区和From区已经被清空。From 和 To交换角色,之前的From变成了To,之前的To变成了From;

5、为什么需要Survivor区间?只有Eden是不行的

  •  如果没有Survivor区,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。这样一来,老年代很快被填满,然后触发Major GC(老年代的内存空间远远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多);
  • Survivor的存在意义,就是减少被送到老年代的对象,减少Full GC/Major GC的发生(Survivor的筛选保证了只有经历了16次Minor GC,还能在新生代中存活的对象,才会被送到老年代)

6、为什么需要两个Survivor区?

两个Survivor同一时刻只能使用一个,最大的好处是解决内存碎片化。例子:

刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。

7、为什么Eden:Survivor = 8:1

  • 我觉得这是从统计的角度来看待的。新生代中的对象98%都是“朝生夕死的”。但是我们也无法保证存活的对象一定<2%,考虑到实验偏差以及实际的多样性,所以默认预留了10%的内存用于存放存活的对象。
  • 因为JVM规定,两个Survivor区中的from 和 to是相对的。根据每次进行Minor GC后哪个区被清空没有对象了,这个区就变成to区(还存活下的对象所在的那个区,就是from区,From和To区的大小是相等的)。
  • 所以最终:Eden:Survivor0:Survivor = 8:1:1

8、GET和POST方法

在GET方法中,数据将作为URL的参数发送,参数通常是由&符号分隔的<名称,值>的字符串;

在POST方法中,是将数据放在请求的数据体(request-body)中,按照(名称/值)相对应的方式存放;

区别:

  • 发送的数据量:
  1. 在Get中,只能发送有限数量的数据,因为数据是在URL中发送的;
  2. 在POST中,可以发送大量的数据,因为数据是在URL中发送的;
  • 安全性:
  1. GET发送的数据不受保护,因为数据在URL栏中公开,增加了漏洞和黑客攻击的风险;
  2. POST发送的数据是安全的,因为数据未在URL栏中公开,还可以在其中使用很多编码技术
  • 数据包的个数
  1. GET产生一个TCP数据包:对于GET的请求方式,浏览器会把HTTP的header 和 data一起发送出去,服务器响应200;
  2. ;POST产生两个TCP数据包:浏览器先发送header,服务器响应100,浏览器继续发送data,服务器响应200;

 9、HashMap为什么采用两倍扩容

  • HashMap采用2倍扩容,容器中的元素要么保持在原来的索引,要么以二次幂的偏移量出现在新表中(也就是说2倍扩容,可以尽可能地减少元素位置的移动);
  • HashMap采用2倍扩容,可以使元素均匀地散布在HashMap中,减少Hash碰撞(并且若n是2的幂次方,那么hash&(n-1)进行位运算不仅效率很高,而且散列地很均匀 );

10、接口和抽象类有什么区别?

相同点:

  1. 都不能 实例化;
  2. 都可以包含 抽象方法;
  3. 都可以有 默认实现的方法;

区别:

  • 目的不一样:接口主要用于 对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系
  • 一个类只能继承一个类,但是可以实现多个接口
  • 接口中的成员变量,只能是 public static final 类型的。不能被修改且必须有初始值;而抽象类的成员变量默认 default,可以在子类中 被重新定义,也可被重新赋值;

11、抽象类抽象方法的关系?

  • 抽象方法的类 只能定义成 抽象类(当然抽象类中 可以包含0个或多个抽象方法,也可以包含 非抽象方法)
  • 抽象方法 必须被子类实现;如果子类不能实现父类的抽象方法,那么子类也必须是抽象类;

12、查询平均成绩大于60分的同学的学号和平均成绩

select stu_id, avg(stu_score) from stu group by stu_id having avg(stu_score) > 60;  // having 用于分组之后的筛选(过滤)

13、怎么防止脏读,幻读怎么解决?

  • 脏读:可以设置RC事务隔离级别来避免,底层使用MVCC在实现,read view生成的时机为 事务每次执行之前生成;
  • 不可重复度:可以设置RR事务隔离界别来避免,底层也是使用MVCC来实现,不过read view的生成时机为 事务第一次执行时生成,此后同一事务对同一数据行操作时使用第一次生成的read view
  • 幻读:MVCC可以解决部分幻读(如果是 快照读可以解决);当前读
    SELECT ... FOR UPDATE (X锁)
    SELECT ... LOCK IN SHARE MODE (S锁)
    必须使用MVCC + Next key

14、清空mysql数据表的三种方式

  1. delete 逐行删除 
    Delete from tablename where id=1; 
  2. truncate:删除所有数据,但是保留表的结构
    truncate table tablename;
  3. drop:删除表中数据以及结构
    drop 
    

15、Redis有哪些特性?

  1. 速度快:数据存在于内存;
  2. 持久化:通过 rdb 和 aof 持久化到磁盘;
  3. 支持多种数据结构:string、list、哈希、set和zset
  4. 支持多种编程语言:java、python、php等
  5. 功能丰富:如发布订阅

16、 redis单线程模型你了解吗,redis单线程在干什么?用多线程的时候在干什么?

基于Reactor模式开发了自己的网络事件处理模型,处理器被称为文件事件处理器

 

 redis的 文件事件处理器 使用 I/O多路复用程序 同时来监听多个套接字(socket连接),并根据套接字目前执行的任务(连接应答accept、读取read、写入write、关闭close)为套接字关联不同的事件处理器

17、redis性能咋样?

虽然文件事件处理器单线程方式运行,但是redis使用I/O多路复用程序来监听多个套接字。文件事件处理器既实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接

18、既然是单线程,那怎么监听大量的客户端连接呢?

I/O多路复用技术的使用让redis不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗(与NIO中selector组件很像)

19、文件事件处理器(file event handler)主要包含4个部分

  • 多个socket(客户端连接)
  • IO多路复用程序(支持多个客户端连接的关键)
  • 文件事件分派器(根据socket目前执行的任务,将socket关联到相应的事件处理器)
  • 事件处理器(连接应答accept处理器、命令请求/回复处理器)

20、redis也曾经引入过多线程,后来为什么不用了?

  • 单线程编程容易 并且 更容易维护;
  • Redis的性能瓶颈不在CPU,主要在内存网络
  • 多线程会存在死锁线程上下文切换等问题,甚至会影响性能

21、系统调用?

  为什么要进行系统调用?:用户程序中,凡是与资源有关的操作(如存储分配、l/O操作、文件管理等),都必须通过系统调用的方式向操作系统提出服务请求,由操作系统代为完成。这样可以保证系统的稳走性和安全性,防止用户进行非法操作

  从应用程序的角度来看,应用程序对操作系统的内核发起了IO调用(系统调用),具体执行IO操作的是操作系统的内核,我们的应用程序只是发起了IO操作的调用。应用程序发起I/O调用后,会经历两个步骤:

  1. 内核等待I/O设备准备好数据;
  2. 内核将数据从内核空间拷贝到用户空间;

22、虚拟内存解决了什么问题?怎么实现的

  很多时候我们使用了多个占内存的软件,这些软件占用的内存可能已经远远超出了我们电脑本身具有的物理内存。

  虚拟内存为每个进程提供了一个一致的私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉。

  

虚拟内存怎么实现的?

  在实际中,虚拟内存 映射多个物理内存碎片上,有的还映射到了外部磁盘存储器上,在需要时进行数据交换。

  对程序来说,虚拟内存它能提供一大块连续的地址空间,对程序来讲是连续的、完整的;

没有虚拟内存技术,导致没有连续的虚拟地址空间,程序编写的难度就会降低。虚拟内存的好处:

  1. 虚拟内存的地址空间是连续的,没有碎片;为每个进程提供一个一致的地址空间,降低了程序员对内存管理的复杂性;
  2. 虚拟内存能提供比内存更大的地址空间

 23、在Controller层,创建成员变量会发生什么事情?

Spring MVC的Controller默认是 单例Singleton的,单例模式的好处:

  • 提高性能,单例不用每次new创建对象;

controller定义很多属性,如果是单例模式会出现竞争访问,不同用户共享数据变量是不安全的;故

  • 尽量不要在Controller中定义成员变量;
  • 如果非要定义一个非静态成员变量,则通过@Scope("prototype")将其设置为多例模式;
  • 在Controller中使用ThreadLocal变量;

案例

posted @ 2022-09-17 21:41  Peterxiazhen  阅读(33)  评论(0编辑  收藏  举报