android面试题-java

java方面

 1,arraylist linklist vector区别

2,set与map集合;hashmap和hashtable区别 ;hashmap实现原理;

3,接口和抽象类的区别

4,  泛型;泛型擦除;泛型的好处;通配符

5,String ,StringBuffer,StringBuilder区别,String a=a+b 创建几个对象

6,进程和线程;线程创建的几种方式;如何保证多个线程按顺序执行;start run 的用法;sleep yield wait的区别;threadLocal;线程安全的几个特性 (多线程的三个特征);如何正确的终止一个线程;四种常见的线程池

7,==和equals的区别

8,java引用

9,简述synchronized和java.util.concurrent.locks.Lock的异同 ?

10,Serializable接口与Parcelable接口的对比

11,多态

12, 反射

 13,static

 14、数组和链表的区别 

15,加密算法

16,垃圾回收机制

17,请解释StackOverflowError和OutOfMemeryError的区别?

 

详解

 1,arraylist linklist vector区别

         Arraylist和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以插入数据慢,查找有下标,所以查询数据快。Vector由于使用了synchronized方法-线程安全,所以性能上比ArrayList要差。LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项前后项即可,插入数据较快。

      1.1 ArrayList等支持线程安全

           如果需要ArrayList等支持线程安全,可以使用Collections提供的将集合变成线程安全的包装方法:synchronizedXxx()方法。

      1.2list扩容原理

           把原来的数组复制到另一个内存空间更大的数组中,把新元素添加到扩容以后的数组中

       1.3 arrayList扩容  https://blog.csdn.net/weixin_36378917/article/details/81812210

            传说中的动态数组,容量能动态增加,使用起来十分便利。向ArrayList中添加一个元素的时候,它才会触发扩容机制,elementData.length大于minCapacity,说明数组容量够用,就不需要扩容。反之,则传入minCapacity到grow()方法中,开始扩容。进入grow方法,我们会将newCapacity设置为旧容量的1.5倍,这也是ArrayList每次扩容都为原先容量1.5倍的由来。                      

2,set与map集合

     Set里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。

      map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

         2.1,hashmap和hashtable区别        

         hashmap线程不安全,key/value可以为null

         hashtable线程安全,键值对不能为null

        3.1 hashmap用过吗,您能说说他的主要用途吗;

            用过。它是基于map接口实现的一种键值对<key,value>存储结构,<key,value>可为null,同时非有序非同步。其底层是数组+链表+红黑树(jdk1.8增加了红黑树)。在存储数据时,是根据键key的hashcode的值来计算具体的存储位置。

       3.2 hashmap的实现原理

            将key的hash值与(数组长度-1)进行按位与运算,(求模运算,hash值与数组长度求模,会存在多个hashcode对应一个index,导致hash碰撞),得到索引值,将对应的键值对放到索引对应的链表中。插入成功后,判断当前存储键值对的数量 大于 阈值threshold 是则扩容。

       3.2 什么是哈希冲突,怎么解决哈希冲突

           键(key)经过hash函数得到的结果作为地址去存放当前的键值对(key-value)(hashmap的存值方式),但是却发现该地址已经有值了,就会产生冲突。这个冲突就是hash冲突了。

           链表法:将所有哈希地址相同的都链接到同一个链表中,因而查找、插入和删除主要在同义词链中进行。在同一个链表中,就可以对这个链表进行操作了,比如查询通过遍历hashmapentry,通过hash值就可以访问对应的value。hashmap就是用此方法解决冲突的。

        3.3 扩容问题。
         减少碰撞,提升效率。随着HashMap中元素的数量越来越多,发生碰撞的概率就越来越大,所产生的链表长度就会越来越长,这样势必会影响HashMap的速度,为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理。该临界点在当HashMap 中元素的数量等于table数组长度*加载因子。但是扩容是一个非常耗时的过程,因为它需要重新计算这些数据在新table数组中的位置并进行复制处理。所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

         hashmap初始化是在put方法里面初始化的,大小都处理成2的次幂,主要是方便二进制处理

       4.1 sparseArray,Arraymap

        aparseArray:HashMap + 二分查找。hashmap空间会造成25%的浪费,空间换时间,所以诞生sparseArray, key和value都是数组,value的下标和key的下标保持相同,key和value就对应起来了,这种数据结构int自动装箱,index唯一,没有冲突,所以不会浪费空间,相比hashmap能节约内存,

        arrayMap: 数据结构和sparseArray一致,两个数组,区别key可以不为int,

     4.2 Arraymap怎么把key转成int的。有没有hash冲突

         hash = key.hashCode();

         index = indexOf(key, hash);   

        由此可知,arraymap通过hashcode转成int值。所以会有hash冲突,处理方式是采用追加的方式,end++一直到没有冲突为止

  for (end = index + 1; end < N && mHashes[end] == hash; end++) {
      if (key.equals(mArray[end << 1])) return end;
  }

 3,接口和抽象类的区别

       语法层面上的区别

       1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

       2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

       3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

       4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

       抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。

 

4,泛型

     3.1 编译器如何处理泛型:Code specialization与Code sharing。

           3.1.1 Code sharing:对每个泛型类只生成唯一的一份目标代码;在需要的时候执行类型检查和类型转换

     3.2 类型擦除

          编译器生成的bytecode是不包涵类型信息的

     3.3 泛型擦除引发的问题

           1、泛型擦除导致泛型类型的继承关系失效,即泛型不变。由此引入通配符?和限定符extend、super来解决这一问题。

           2、编译期间已是无类型,所以可以通过反射给泛型注入任意类型。(不安全)

           3、基本类型无法作为泛型实参。

     3.4 什么是泛型,

           一种可以将类型参数化,并且不确定的类型。

                   ArrayList<E>中的E称为类型参数变量;

                   ArrayList<Integer>中的Integer称为实际类型参数。

                   整个称为ArrayList<E>泛型类型;

      3.5 泛型的好处

            省略了源代码的强转

            把运行的问题提前到了编译时期

            可读性和稳定性

      3.6 通配符

            如某个方法中需要传入一个不确定类型的集合作为参数时,通配符就比较好用了。

            方法参数定义为List<Object>,但是实际传入为List<String>,编译会报错,可以使用上界通配符 List < ? extends Object> 或者直接List<?>

           

5,String ,StringBuffer,StringBuilder区别,String a=a+b 创建几个对象

          String 字符串常量,内部是一个被 Final 修饰过的 char 数组,所以是不可变的,是不可扩容的

          StringBuffer 字符串变量(线程安全)

          StringBuilder 字符串变量(非线程安全)

          4.2 关于String创建了几个对象的问题

               String str = "a"+"b"+"c"+"d";  创建了一个对象

               String a="a";String b="b";String  c="c";String d=a+b+c;这里就创建了4个对象。String d=a+b+c,StringBuilde不创建对象

               

6,进程和线程;

      进程是cpu资源分配的最小单位,线程是cpu调度的最小单位
      进程之间不能直接共享资源,需要通过ipc跨进程通信,而线程共享所在进程的地址空间和其它资源

     5.1 进程运行在独立的内存空间,各个进程之间内存地址相互隔离。最小的资源管理单元,

           线程云线在进程中,是程序的执行者,一个进程至少一个主线程,也可以有更多的子线程。最小的执行单元

          协程:轻量级线程,一个线程可以用有多个协程。

     5.2 线程创建的几种方式

           通过继承自Thread

           通过实现Runnable接口

           使用callable+FutureTask   有返回值的一种方式

     5.3,  如何保证多个线程按顺序执行

           join:一个线程等待另一个线程完成。在某个线程中调用另一个线程的join方法,当前线程会阻塞,直到被join方法加入的join线程执行完毕。

         a,一个是join,阻塞当前线程,等待另一个线程执行完毕。

         b,java5以后多线程操作,创建线程池,FIFO顺序执行里面的线程。

             ExecutorService executor = Executors.newSingleThreadExecutor();

             executor.submit(new Thread1());

             executor.submit(new Thread2());

             executor.submit(new Thread3());

             executor.shutdown();

            c,CountDownLatch可以适用于一个线程去等待多个线程的情况

            5.3.2  那如何让两个线程按照指定方式有序交叉运行呢?

                      共享的对象锁 lock = new Object();

                                         lock.wait() 和另一个线程中 lock.notify() 

           https://www.jianshu.com/p/6c1c90bf6273

     5.4  start run 的用法

           线程创建完毕后,start会调用线程的run方法,使线程进入可运行状态,

           直接调用run,不会是线程进入可执行状态,run方法里面的代码还是运行在主线程

          start()方法被用来启建的线程,而且start()内部用了run()方法,和直接run()方法的效果不一。当你run()方法的候,只会是在原来的线程中用,没有新的线程启start()方法才会启线程。

      5.5 sleep yield wait的区别

           yield:当前线程回到就绪状态,给优先级更高的线程运行机会。

            1,wait只能在同步(synchronize)环境中被调用,而sleep不需要。

            2,进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒。

            3,wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。

            4, 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。

      5.6  threadLocal

             用来关联一个线程,线程级别的局部变量,为线程提供一个独立的变量副本,每个线程都可以独立的改变自己的副本。

      5.6 threadLocal原理

            每个线程有一个ThreadLocalMap对象,先通过getMap(t)获得ThreadLocalMap,有一个getEntry(ThreadLocal<?> key)方 法来获取entry对象,这个Entry是弱引用WeakReference类的子类。数据结构类似map的结构,存储的key也是通过hash算法取得相应数组角标进行的,e.value去取对应的值

        5.7线程安全的几个特性 (多线程的三个特征)

             原子性:相关操作不被其他线程干扰。同步机制实现

             可见性:一个线程修改了某个变量,使其他线程能够知晓。即将本地线程本地状态反映到主内存上,其他线程去主内存中读取。

             有序性:避免指令重排。volatile保证有序性,synchronized和lock也能保证同步线程的有序执行。

         5.8  如何正确的终止一个线程

                1,使用interrupt,这种不能及时终止线程

                2,在线程里面定义一个用volatile修饰的退出线程的标志参数,终止时在外部改变参数值  (推荐)

                3,使用stop,不推荐,相当于直接关计算机电源

         5.9   线程池

                 避免频繁的创建线程,而导致资源的消耗。

                  四种常见的线程池

                  FixedThreadPool    可重用的固定线程数量的线程池。未达到核心线程,则创建核心线程,否在加入无界队列等待核心线程执行完

                  CachedThreadPool  没有核心线程,使用不存储元素的阻塞队列,有元素进入就取走。会无线创建新的线程,60s自动销毁。适合有大量任务需要立即处理且耗时少的任务

                   SingleThreadExecutor  确保所有的任务在一个线程中按照顺序去执行。

                   ScheduledThreadPool  创建一个定长的线程池,而且支持定时的以及周期性的任务执行。

                  

                  线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规 则,规避资源耗尽的风险。

                  1)newFixedThreadPool和newSingleThreadExecutor:
                   主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
                   2)newCachedThreadPool和newScheduledThreadPool:
                 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

                    

                    线程池的几个重要参数:                   

                           corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

                          maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

                           keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

                           workQueue:一个阻塞队列,用来存储等待执行的任务,

                           unit:参数keepAliveTime的时间单位

                          threadFactory:用于设置创建线程的工厂 

                          handler:表示当拒绝处理任务时的策略

                  5.9.1 线程执行流程图

                           任务提交

                           ->判断核心线程是否已满(N创建新的核心线程执行任务) 

                             poolSize < corePoolSize,提交的runnable任务,会直接做为new一个Thread的参数,立马执行 

                          ->阻塞队列是否已满(N 添加到阻塞队列)

                             提交的任务数超过了corePoolSize,会将当前的runable提交到一个block queue中。

                          ->非核心线程是否已满(N 创建非核心线程执行任务)

                             有界队列满了之后,如果poolSize < maximumPoolsize时,会尝试new 一个Thread的进行救急处理,立马执行对应的runnable任务。

                           ->执行饱和策略

                   5.9.2 四种线程池任务拒绝策略(饱和策略)

                            AbortPolicy(默认) : 抛出异常,并删除任务。

                            CallerRunsPolicy:用调用者所在的线程处理任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度

                             DiscardPolicy:不执行任务,并将该任务删除

                             DiscardOldestPolicy :会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。

                5.10  在多线程情况下尽量采用系统安全的类,比如Vector,StringBuffer,hashTable,stack

 

 

7,== 和 equals 的区别

     ==比较的是变量内存中存放的两个对象的内存地址,多适用于基本数据类型的比较

     equals比较的两个对象的内容是否相等,如string

 

8,Java引用:

      强引用,有一个或以上时不可回收。

      软引用,当内存不足时可能会被回收。

      弱引用,当GC机制启用时就会被回收。

      虚引用:GC触发时进行回收,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,可以作为对象回收的监听器。

 

9,简述synchronized和java.util.concurrent.locks.Lock的异同 ?

     相同点:Lock能完成synchronized所实现的所有功能

     不同点:synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

 

10,Serializable接口与Parcelable接口的对比

   “序列化”就是将运行时的对象状态转换成二进制,然后保存到流、内存或者通过网络传输给其他端。

    Serializable 

      优点:使用简单

      缺点:开销大,使用大量的I/O操作

    Parcelable

      优点:效率高

      缺点:使用复杂

     使用场景:

        网络传输,序列化存储 使用Serializable

        运行时数据传递 使用Parcelable eg:Intent,Bundle

11,多态

      分为继承和实现关系,父类的引用指向子类的对象。

      接口的多种不同的实现方式即为多态。

      允许将子类类型的指针赋值给父类类型的指针,把不同的子类对象都当作父类来看。向上转型(酒 a=new 五粮液 五粮液向上转型为酒)。此种情况,父类只能调用父类定义的方法,只存在于子类中的方法和属性不能调用。

            比如你家有亲属结婚了,让你们家派个人来参加婚礼,邀请函写的是让你爸来,但是实际上你去了,或者你妹妹去了,这都是可以的,因为你们代表的是你爸,但是在你们去之前他们也不知道谁会去,只知道是你们家的人。可能是你爸爸,可能是你们家的其他人代表你爸参加。这就是多态。

       编译时多态:重载

       运行时多态:在运行的时候才会知道引用变量所指向的具体实例对象 酒 a=new 五粮液。

         重写

12 反射

      java反射机制:是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

       Class类啊:代表类的实体,在运行的java应用程序中表示类和接口

       Field类   :类的成员变量

       Method类:类的方法

       invoke(Object... args) 传递object对象及参数调用该对象对应的方法

       基本的使用
       Class class3 =  Class.forName("day07.Person");     

       Field[] fields = dataClass.getDeclaredFields();

        //通过setAge方法给age属性赋值
        Object obj = dataClass.newInstance();
        Method method2 = dataClass.getDeclaredMethod("setAge",int.class);
        method2.invoke(obj,27);

 13,static

 14、数组和链表的区别 

      数组:是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低。

链表:是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)

15,加密算法

       1.base64

        不是加密算法,是一种编码方式,因为加密后都是byte[],为了可读性。一般将byte[]转为base64编码

          2.md5

        也不是算法。是消息摘要算法第五版,是一种哈希算法,一般用于单向加密

16,垃圾回收机制

        16.1对象没有被其他对象引用给对象就是无用的,此对象就被称为垃圾,其占用的内存也要被销毁。判断为垃圾的算法

              16.1.1 引用计数法,

               对象中添加一个引用计算器,每当有一个地方引用,计算器就加一。当引用失效,计数器就减一,计数器为0的时候,  对象判别为垃圾

              优点 执行效率高,程序执行受影响较小

               缺点 无法检测出循环引用的情况,导致内存泄露

               16.1.2 可达性分析算法 :引用链,gc roots没有任何引用链相连的话,则证明对象不可用。  

        16.2 如何垃圾回收:

                 16.1 标记清除算法:分为标记和清除,标记-标记需要被回收的对象。清除,回收标记的对象所占用的空间。

                         效率问题

                         空间问题(标记清除后会产生大量不连续的碎片)

                  16.2 复制算法

                          将内存分为大小相同的两块,每次使用其中的一块。当第一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

                          简单来说就是该对象分为对象面以及空闲面,对象在对象面上创建,对象面上存活的对象会被复制到空闲面,接下来就可以清除对象面的内存。

                  16.3 标记整理算法

                           该算法标记阶段和标记清除一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存

                  16.4 分代收集算法  

                           新生代,选择复制算法,因为每次收集都会有大量对象死去,只需要付出少量对象的复制成本就可以完成垃圾收集。 

                           老年代的对象存活几率比较高,而且没有额外的空间对它进行分配担保,所以采用标记清楚,标记整理算法。

                           元空间(永久代)都不在GC范围内

17, 请解释StackOverflowError和OutOfMemeryError的区别?

      答:StackOverflowError栈溢出,一般由于递归过多,调用方法过多导致

      OutOfMemeryError堆内存溢出,即OOM,由于堆内存中没有被GC回收的对象过多导致。

      出现OOM的原因:

   (-).Java虚拟机的堆内存设置不够,可以通过参数-Xms和-Xmx来调优

   (二)程序中创建了大量对象,并且长时间不能被被垃圾回收器回收(存在引用)
 

         

             

 

             

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值