完美世界笔试总结~

昨天下午参加了完美世界的宣讲会,现场进行了笔试,我选择了游戏Java工程师这个职业,今天空了,把昨天的笔试题搜一搜看看自己答错在何处。

1、考察wait()方法在哪些代码块中被调用? 

wait()方法在同步化代码块中被使用。扩展一下,wait()方法为什么一定要在同步代码块中被调用,是因为wait()方法就是释放锁,那么在释放锁的前提下,必须先获得锁,只有先获得锁,才能释放锁。

2、关于sleep()和wait()方法描述,寻找错误的?

正确描述有: sleep是线程类(Thread)的方法,wait是Object类的方法; 

                        sleep暂停线程、但监控状态仍然保持,结束后会自动恢复 ;

                       sleep不释放对象锁,wait放弃对象锁 。

错误的描述为:     wait后进入等待锁定池,只有针对此对象发出notify方法后获得对象锁进入运行状态

原因就是:进入就绪状态。

3、垃圾回收器描述正确的是?

Serial收集器 - 串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。

G1收集器以堆内存分隔的方式并发回收。

 CMS收集器是基于“标记-清除”算法实现的,它使用多线程的算法去扫描堆(标记)并对发现的未使用的对象进行回收(清除),收集完了之后会整理内存空间 。

parallel old收集器使用多线程和标记清除算法。

4、关于sleep()方法描述错误的是?

调用sleep()方法需要进行异常处理。

5、给定一段代码,寻求运行的结果

abstract class A{
    public void f(){
        System.out.println("good");
    }
}
public class B extends A{
    public void f(){
        System.out.println("bad");
    }

    public static void main(String[] args) {
        A b=new B();
        b.f();
    }
}

打印输出的应该是:bad。 b在编译期被确定为A类型,在运行期被确定为B类型,故执行B类型的f()方法。

6、给定一段代码,寻求正确的运行结果:

public static void main(String[] args) {
        List<String> list=new ArrayList<>(10);
        for(int i=0;i<20;i++){
            list.add("铜币");
        }
        for(String s:list){
            list.remove("铜币");
        }
        System.out.println("size:"+list.size());
    }

正确的结果是:抛出ConcurrentModificationException异常。

运行上述代码,从异常信息可以发现,异常出现在checkForComodification()方法中。

关键点就在于:调用list.remove()方法导致modCount和expectedModCount的值不一致。注意,像使用for-each、Iterator进行迭代实际上也会出现这种问题。

7、寻求正确的原子操作:凡是自增、自减的以及运算的即可以拆分为几个操作的都被排除掉。

8、关于Sun JDK监控操作描述正确的是?【下面的描述都是改正之后的,都是正确的表述】

jmap用于生成hempdump文件 虚拟机的内存转储快照。

jstack命令主要用来查看Java线程的调用堆栈的,可以用来分析线程问题。

jhat用于分析虚拟机的内存转储快照,通常与jmap结合使用。

jstat用于收集hotspot虚拟机各方面的运行数据。

9、给定一段代码,寻求正确的运行结果:

public static void main(String[] args) {
        Thread t=new Thread(){
            public void run(){
                pong();
            }
        };
        t.start();
        System.out.println("ping");
    }
    static void pong(){
        System.out.println("pong");
    }

运行结果为:pingpong和pongping都可以。执行是随机的!!!

10、给定一段代码,寻求正确的选项:

这个代码不再给出,错误行很明显,final修饰的变量必须初始化。

11、Java中下面哪个可以用作正确的变量名称?

3D    name      extends      implements【首字母是英文字母、$和下划线,由字母、数字和下划线组成。变量名不要使用Java关键字。故name是正确的变量名称】

12、给定一段代码,寻求正确的运行结果:

public static void main(String[] args) {
        Integer a=null;
        int b=a;
        System.out.println(b);
    }

运行的结果为:NullPointerException!故选择编译正常,但是运行过程中报出NullPointerException。执行int b=a,在底层会执行自动拆箱!底层将会调用a.intValue(),a为null,自然会抛出 NullPointerException。

15、关于annotation注解描述错误的是?

注解可以标注构造方法;注解可以标注字段;子类可以继承父类上的注解;注解可以通过java反射获取其值

16、下列哪个操作是不合法的?

int i=s.length();String ts=s.trim();String t=s+"!";【s>>>=3;错误的】放在编译器上进行运行得出。

17、面向字符的输入流是哪个?

【面向字符的输入流类都是Reader的子类】

BufferedWriter缓冲输入字符流                 InputStreamReader 将字节转换到字符的输入流              

ObjectInputStream 字节流的输入流               BufferInputStream

18、给定一段代码,寻求正确的运行结果:

 public static void main(String[] args)throws Throwable {
      try{
          throw new Throwable("巨额元宝增加");
      }catch(Exception e){
          System.out.println("发生元宝异常现象");
      }
        System.out.println("元宝增加");
    }

运行结果:抛出Throwable异常。

19、在JDK1.8环境下,要保证s内对象按照长度升序排列,哪个选项的代码可以实现?Set<String> s=new TreeSet<String>(//YODO);

A:(V1,V2)->return v2.length()-V1.length();

B:(V1,V2)->V2.length()-V1.length();

C:(V1,V2)->V2.length()>V1.length();

D:(V1,V2)->return V2.length()>V1.length();

A跟B选项应该是一组;C跟D选项应该是一组;根据自定义比较器类的实现以及JDK1.8引入函数式编程,应该选择B

20、如果你想列出当前目录以及子目录下的所有扩展名为“.txt”的文件,那么可以使用的命令为: find . –name “.txt”

ls列出当前目录或者指定目录的列表          find 可以找出当前目录以及子目录下所有的文件

21、以下哪个配置文件可以修改Tomcat的端口号?Tomcat的默认端口号在server包下的server.xml里面。

23、SQL语言中修改表结构的命令是:ALTER TABLE

24、下列SQL语句语法正确的是:

A:SELECT * FROM table WHERE name=bob;

B:UPDATE table VALUES(1)WHERE id=2;

C:DELETE table WHERE id=1;

D:SELECT * FROM table WHERE age NOT IN(1,2,3);

26、下列说法中,哪些描述是正确的?

A:对象锁在synchronized()语句执行完了之后由持有它的线程返还

B:对象锁在synchronized()语句中出现异常时由持有它的线程返还

C:当持有锁的线程调用了该对象的wait()方法时,线程将释放其持有的锁

D:当持有锁的线程调用了该对象的构造方法时,线程将释放其持有的锁

对象的锁在如下几种情况下由持有线程返还:当synchronized()语句块执行完后;synchronized()语句块中出现异常(exception);当持有锁的线程调用该对象的wait()方法,此时该线程将释放对象的锁。

27、以下关于垃圾回收器描述正确的是?

A:程序员可以在指定调用时间调用垃圾回收器释放内存

B:垃圾回收器始终监视着每个对象

C:对象的finaliz()方法在对象被垃圾回收之前获得调用

D:垃圾回收器可以保证JAVA程序从不会产生内存溢出

28、关于synchronized和java.util.concurrent.locks.Lock正确的是?

A:Lock能完成synchronized所实现的功能

B:synchronized要求程序员手动释放

C:Lock是采用CPU悲观锁机制,当很多线程争锁的时候,会引起CPU频繁的上下文切换导致效率很低

D:synchronized在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定的代码块中,括号中表示需要锁的对象。

synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。而Lock用的是乐观锁方式。

29、关于try-catch-finally逻辑下列说法正确的是?

A:try不可以省略  B:可以使用多重catch  C:finally可以省略   D:try与finally同时使用时必须包含catch

30、以下对TCP和UDP描述正确的是?

A:UDP可以保证数据准确无误的传达到目的地   B:TCP头部开销比UDP的头部开销大

C:TCP的逻辑通信信道不是全双工的可靠信道   D:TCP连接只能是点到点的,UDP支持一对一,一对多

31、类Apple继承自类Fruit,以下哪些是正确的?

A:Apple a=new Fruit()   B:Fruit a=new Apple()   C:Apple a=new Apple()   D:Fruit a=new Fruit()

32、执行int[] x=new int[25]语句后,以下哪些选型是正确的?

A:x[24]的值等于0   B:x[25]值等于0   C:x[25]值等于null   D:x.length值等于25

33、有以下代码,可以填入横线中的是?

public class Demo{
    public int test(int a,int b){
        return a+b;
    }
    横线部分
}

A:public void test(int a){}   B:public void test(int c,int d){}   C:public void test(long a,int b){}   D:public void test(int c,int d,int c){} 

方法的重写需要符合有继承关系,在横线部分应该填入的是方法的重载。重载只需要符合方法名相同,参数列表不同即可。

34、根据代码String s=null;会抛出NullPointerException异常的有?

A:if((s!=null)&(s.length()>0))  B:if((s!=null)&&(s.length()>0))   C:if((s==null)|(s.length()==0))  D:if((s==null)||(s.length()==0)) 

&不仅可以作为按位与,也可以作为逻辑与使用,“&”是将所有对象全部比较之后(出现false时不会立即停止),最后再比较。

&&具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式

有些人习惯把||叫做短路或,把&&叫做短路与。&&和&的区别和||与|的区别类似

简答题

1、请简述你所了解的垃圾回收算法的原理及其优点,例如:CMS,G1,serial copying,paralled scavenge等

CMS收集器:适合多CPU,频繁和用户交互的业务场景,追求的是停顿时间最短,采用标记清除的回收算法(因为标记清除不会涉及存活对象的移动:复制和压缩,从而降低STW的时间)

回收4个阶段

1、初始标记:STW,快速获取到老年代中的GC Roots对象,以及遍历新生代中GC Roots对象引用到老年代中的对象, 默认是单线程执行,也可以设置多线程数并行进行。
2、并发标记:并发客户线程,只分配一个线程来根据初始标记里面的对象开始,逐个标记 初始标记成功的对象的引用的对象。如果并发期间,对象的引用关系发生改变,则会被标记为dirty对象。
3、重新标记:STW,修正并发标记期间,引用关系的改变,这个过程比初始标记稍长。
4、并发清除:专门分配一个线程来清除标记过的对象,不影响用户操作,此阶段会有新的浮动垃圾生成,如果太多,会造成PromotionFailure
G1回收器:G1通过把堆分成多个相对独立的Region块,并行的进行选择性的回收,实现一个两者兼顾的回收器。

1、初始标记,STW,分为老年代和年轻代两部分。先会触发一次YGC(排除掉年轻代对老年代有引用,但是该年轻代对象已经死亡的情况),然后再对Survivor to区进行遍历(确保所有的年轻代的对象都是活的,因为经历了一次YGC),查找并标记S区对象对老年代的直接引用的所有老年代对象(这里是直接引用,比如Survivor to区A对象,引用了老年代的B对象,就标记B对象,虽然B对象还可能引用了C、D、E对象)。第二部分是针对老年代,标记老年代中所有GC Roots直接引用的对象。
2、并发标记,根据可达性分析,找到所有GCRoots的对象引用,一层一层的标记,这个时候没有STW,用户线程正常执行。
3、最终标记,STW,修正并发标记期间的对象关系变更,根据Remembered Set Logs,来修改Remember Set,该阶段可以多个线程并行
4、筛选回收,STW,会跟踪各个Rigion里面的垃圾堆积的价值大小(比如需要回收的对象的空间大小以及回收所需时间的经验值)排序,根据用户设定的停顿时间,回收特定Region,因为每个Region块都有独立的Remembered Set,所以每个Region可以独立的进行回收,也就是多个Region块并行多线程的垃圾回收。

2、请列举你所了解的Java类库中用到的设计模式

单例模式:用于Runtime,Calendar和其他一些类中,主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 使用单例模式的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收。

单例模式主要有3个特点:1、单例类确保自己只有一个实例。2、单例类必须自己创建自己的实例。3、单例类必须为其他对象提供唯一的实例。

单例模式的实现方式:懒汉单例类和饿汉单例类。

对于懒汉模式,我们可以这样理解:该单例类非常懒,只有在自身需要的时候才会行动,从来不知道及早做好准备。它在需要对象的时候,才判断是否已有对象,如果没有就立即创建一个对象,然后返回,如果已有对象就不再创建,立即返回。

懒汉模式只在外部对象第一次请求实例的时候才去创建。

对于饿汉模式,我们可以这样理解:该单例类非常饿,迫切需要吃东西,所以它在类加载的时候就立即创建对象。

工厂模式:用于各种不可变的类如Boolean,像Boolean.ValueOf,工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。 

工厂模式可以分为三类: 1)简单工厂模式; 2)工厂方法模式; 3)抽象工厂模式

简单工厂模式 :建立一个工厂(一个函数或一个类方法)来制造新的对象, 简单工厂模式又称静态工厂方法模式。

工厂方法模式:去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。

 抽象工厂模式:工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。

观察者模式:被用于Swing和很多的事件监听中,观察者模式是指在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。

抽象主题角色:主题角色把所有观察者对象的引用保存在一个聚集里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
抽象观察者:为所有具体观察者定义一个接口,在得到主题通知时更新自己,这个接口叫做更新接口。
具体主题角色:将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过的观察者发出通知。
具体观察者角色:存储与主题状态自恰的状态。

优点:
第一、观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
第二、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知。

缺点:
第一、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
第二、如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
第三、如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
第四、虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。

装饰器设计模式:被用于多个JavaIO类中

Decorator装饰器,顾名思义,就是动态地给一个对象添加一些额外的职责,就好比为房子进行装修一样。因此,装饰器模式具有如下的特征:它必须具有一个装饰的对象。它必须拥有与被装饰对象相同的接口。它可以给被装饰对象添加额外的功能。

用一句话总结就是:保持接口,增强性能。

好处:可以让我们对之前的类进行很好的扩展。比起继承类的方式,装饰器模式对功能的扩展是很动态的。可以添加撤销,因为你还可以添加判断条件。你还可以编写很多各种各样的装饰类,以各样的顺序和次数进行装饰。

不好的地方:装饰类多了,功能相似,出了问题的时候,排错花的时间就长了。

编程题

1、编写程序判断数组中是否有两个数的和为15,要求时间复杂度为O(n)

public static boolean isNum(int[] arr) {
        HashMap<Integer,Integer> hashMap=new HashMap<>();
        for(int i=0;i<arr.length;i++){
            hashMap.put(arr[i],i);
        }
        for(int i=0;i<arr.length;i++){
            int num=15-arr[i];
            if(hashMap.containsKey(num) && hashMap.get(num)!=i){
                return true;
            }
        }
        return false;
    }

2、编程实现在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。

解题思路:

第一遍扫描这个数组时,每碰到一个字符,在哈希表中找到对应的项并把出现的次数增加一次。这样在进行第二次扫描时,就能直接从哈希表中得到每个字符出现的次数了。

1.初始化哈希数组,数组长度为256,每个元素都初试化为0

2.从头遍历输入字符串,每碰到一个字符,以此字符的ascii码值为数组下标,在哈希表中找到对应的项并吧出现的次数加一次

3.从头遍历输入字符串,每碰到一个字符,查看其在hash表中对应的下标是否为1,如果是,则返回该数组,否则继续循环

public static char FindFirstNotRepeat(String str) {
       if(str==null){
           System.out.println("输入为空!");
           return ' ';
       }
       int[] hash=new int[256];
       //初始化哈希数组
        for(int i=0;i<hash.length;i++){
            hash[i]=0;
        }
        for(int i=0;i<str.length();i++){
            hash[str.charAt(i)]++;
        }
        for(int i=0;i<str.length();i++){
            if(hash[str.charAt(i)]==1){
                return str.charAt(i);
            }
        }
        return ' ';
    }

猜你喜欢

转载自blog.csdn.net/qq_40303781/article/details/88740841