牛客模拟面试--002

简述一下 GDB 常见的调试命令

(gdb)help:查看命令帮助,具体命令查询在gdb中输入help + 命令,简写h
(gdb)run:重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件),简写r
(gdb)start:单步执行,运行程序,停在第一执行语句
(gdb)list:查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数),简写l
(gdb)set:设置变量的值
(gdb)next:单步调试(逐过程,函数直接执行),简写n
(gdb)step:单步调试(逐语句:跳入自定义函数内部执行),简写s
(gdb)backtrace:查看函数的调用的栈帧和层级关系,简写bt
(gdb)frame:切换函数的栈帧,简写f
(gdb)info:查看函数内部局部变量的数值,简写i
(gdb)finish:结束当前函数,返回到函数调用点
(gdb)continue:继续运行,简写c
(gdb)print:打印值及地址,简写p
(gdb)quit:退出gdb,简写q
(gdb)break+num:在第num行设置断点,简写b
(gdb)info breakpoints:查看当前设置的所有断点
(gdb)delete breakpoints num:删除第num个断点,简写d
(gdb)display:追踪查看具体变量值
(gdb)undisplay:取消追踪观察变量
(gdb)watch:被设置观察点的变量发生修改时,打印显示
(gdb)i watch:显示观察点
(gdb)enable breakpoints:启用断点
(gdb)disable breakpoints:禁用断点
(gdb)x:查看内存x/20xw 显示20个单元,16进制,4字节每单元
(gdb)run argv[1] argv[2]:调试时命令行传参
(gdb)set follow-fork-mode child#Makefile项目管理:选择跟踪父子进程(fork())

说一说ConcurrentHashMap的实现原理

【得分点】

数组+链表+红黑树、锁头节点

【参考答案】

标准回答

在JDK8中,ConcurrentHashMap的底层数据结构与HashMap一样,也是采用“数组+链表+红黑树”的形式。同时,它又采用锁定头节点的方式降低了锁粒度,以较低的性能代价实现了线程安全。底层数据结构的逻辑可以参考HashMap的实现,下面我重点介绍它的线程安全的实现机制。

  1. 初始化数组或头节点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换(原子操作,基于Unsafe类的原子操作API)。

  2. 插入数据时会进行加锁处理,但锁定的不是整个数组,而是槽中的头节点。所以,ConcurrentHashMap中锁的粒度是槽,而不是整个数组,并发的性能很好。

  3. 扩容时会进行加锁处理,锁定的仍然是头节点。并且,支持多个线程同时对数组扩容,提高并发能力。每个线程需先以CAS操作抢任务,争抢一段连续槽位的数据转移权。抢到任务后,该线程会锁定槽内的头节点,然后将链表或树中的数据迁移到新的数组里。

  4. 查找数据时并不会加锁,所以性能很好。另外,在扩容的过程中,依然可以支持查找操作。如果某个槽还未进行迁移,则直接可以从旧数组里找到数据。如果某个槽已经迁移完毕,但是整个扩容还没结束,则扩容线程会创建一个转发节点存入旧数组,届时查找线程根据转发节点的提示,从新数组中找到目标数据。

加分回答
ConcurrentHashMap实现线程安全的难点在于多线程并发扩容,即当一个线程在插入数据时,若发现数组正在扩容,那么它就会立即参与扩容操作,完成扩容后再插入数据到新数组。在扩容的时候,多个线程共同分担数据迁移任务,每个线程负责的迁移数量是 (数组长度 >>> 3) / CPU核心数。
也就是说,为线程分配的迁移任务,是充分考虑了硬件的处理能力的。多个线程依据硬件的处理能力,平均分摊一部分槽的迁移工作。另外,如果计算出来的迁移数量小于16,则强制将其改为16,这是考虑到目前服务器领域主流的CPU运行速度,每次处理的任务过少,对于CPU的算力也是一种浪费。

介绍一下Java中的IO流

【得分点】

输入流与输出流、字节流与字符流、节点流与处理流

【参考答案】

标准回答
流是Java对不同输入源输出源的抽象,代表了从起源到接收的有序数据,有了它程序就可以采用统一的方式来访问不同的输入源和输出源了。

按照数据的流向,可以将流分为输入流和输出流。其中,输入流只能读取数据、不能写入数据,而输出流只能写入数据、不能读取数据。
按照数据的类型,可以将流分为字节流和字符流。其中,字节流操作的数据单元是byte(8位的字节),而字符流操作的数据单元是char(16位的字符)。
按照使用的场景,可以将流分为节点流和处理流。其中,节点流可以直接从/向一个特定的IO设备读/写数据,也称为低级流。而处理流则是对节点流的连接或封装,用于简化数据读/写功能或提高效率,也成为高级流。
Java中的IO流主要有4个基类:InputStream、OutputStream、Reader、Writer。其中,InputStream代表字节输入流,OutputStream代表字节输出流,Reader代表字符输入流,Writer代表字符输出流。其他的IO流都是从这4个基类派生而来的,并且子类的名字往往以基类的名字结尾,所以通过类名我们很容易识别某个流的作用。
Java为我们提供了大量的IO流实现,我们没办法逐个介绍,下面举一些较为常用的例子:

  1. 用于访问文件的FileInputStream、FileOutputStream、FileReader、FileWriter。

  2. 带有缓冲功能的BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。

  3. 具有转换功能的InputStreamReader、OutputStreamWriter。

  4. 支持打印功能的PrintStream、PrintWriter。

加分回答
Unix IO一共包含5种模型,分别是阻塞式IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO。Java的IO流实现是依附于平台的,换句话说它依赖于上述5种模型,实际上Java的IO流是基于阻塞式IO实现的。阻塞式IO是性能最差的IO模型了,所以Java的IO流并不是一个高效的实现,在Java的早期人们对Java性能的诟病也正源于此。
从1.4开始,Java提供了新的IO模型(NIO),这种IO模型是基于IO多路复用实现的。从1.7开始,Java又对IO模型进行了升级(NIO2),在本次升级中Java提供了异步IO模型(AIO)。这两种IO模型的引入,使得Java处理IO的性能大大的提高了,我们在处理IO问题时也应该尽量选择NIO,而少用IO流。

【延伸阅读】

下表给大家整理了一些常用的类,黑色字体是抽象基类,红色字体是节点流,蓝色字体是处理流。
在这里插入图片描述

算法题

在这里插入图片描述

利用HashMap

import java.util.*;
public class Solution {
    public int search (int[] nums, int target) {
        // write code here
        HashMap<Integer,Integer> hm = new HashMap<>();
        for(int i =0; i<nums.length; i++){
            hm.put(nums[i],i);
        }
        Arrays.sort(nums);
        int left = 0, right= nums.length-1;
        while(left<=right){
            int mid = left+(right - left)/2;
            if(nums[mid] == target) return hm.get(nums[mid]);
            if(nums[mid] > target)right = mid-1;
            if(nums[mid] < target)left = mid+1;
        }
        return -1;
    }
}

直接二分

import java.util.*;
public class Solution {
    public int search (int[] nums, int target) {
        // write code here
        int lo=0,hi=nums.length-1;
        while(lo <= hi){
            int mid = lo+ (hi-lo)/2;
            if(nums[mid] == target) return mid;
            if(nums[mid] <nums[hi] && nums[mid]<target && target<=nums[hi])
                lo = mid+1;
            else if(nums[mid] >nums[hi] &&!( nums[lo] <=target && target < nums[mid]))
                lo = mid+1;
            else
                hi = mid-1;
        }
        return -1;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值