Java、Android—零碎难记笔试考点(持续更新)

String类是final类

“对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。

public class Test{
    public static void main(String[] args){
        String a = "aaaa";
        String b = a.replace('a', 'b');
        System.out.println(a);
        System.out.print(b);
    }
}

当对String类对象进行subString(),replace()等,应该赋值给新的String对象,因为a还是原来的内容。

String str="hello world"和String str=new String("hello world")的区别:

public class Test{
    public static void main(String[] args){
        String a = new String("aaaa");
        String b = new String("aaaa");
        String c = "aaaa";
        System.out.println(a==b);
        System.out.println(a.equals(b));
        System.out.println(a==c);
        System.out.println(a.equals(c));
    }
}

String c = "aaaa";在编译期间生成了 字面常量和符号引用,运行期间字面常量"aaaa"被存储在运行时常量池(当然只保存了一份)。通过这种方式来将String对象跟引用绑定的话,JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。

通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。

String类的equals方法只比较内容。所以equals返回true。

StringBuilder和StringBuffer类区别:

StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,StringBuffer类是线程安全的

transient关键字

将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。保证属性不会被传递,安全。

static关键字

  • 不能修饰外部类,只有内部类才可以。
  • 静态的方法可以被继承,但是不能重写。
  • 静态变量在JVM初始化阶段就被赋值。

类初始化的顺序:

父类静态变量->父类静态代码块->子类静态变量->子类静态代码块->父类普通变量->父类普通代码块->父类构造函数->子类普通变量->子类普通代码块->子类构造函数

假设类A有静态内部类B和非静态内部类C,创建B和C的区别为:
A a=new A();
A.B b=new A.B();
A.C c=a.new C();

final关键字

  • final修饰类不可以被继承,但是可以继承其他类。
  • final修饰的变量称为常量,这些变量只能赋值一次。
  • final修饰的方法,不可以重写,但可以继承使用。

抽象类与接口区别:

  • 抽象类要被子类继承,接口要被类实现。
  • 接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现。
  • 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
  • 抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量。
  • 接口可以继承接口,抽象类可以实现接口,抽象类可以继承实体类。

匿名类又称匿名内部类:

new 类名/接口名/抽象类名(){定义子类/实现类的内容}

Callable与Future

使用Callable和Future,我们可以方便的获得线程的执行结果。

public interface Callable<V> {
    V call() throws Exception;
}

类型参数V即为异步方法call的返回值类型。

Future可以对具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成以及获取结果。可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();                    //表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
    boolean isDone();                         //任务是否已经完成,若任务完成,则返回true
    V get() throws InterruptedException, ExecutionException; //获取执行结果,这个方法会阻塞
    V get(long timeout, TimeUnit unit)                       //在指定时间内,还没获取到结果,就直接返回null
        throws InterruptedException, ExecutionException, TimeoutException;
}

线程池: 

  线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

 多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。

线程池的返回值ExecutorService简介:

         ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程

//返回一个带缓存的线程池,该池在必要的时候创建线程,在线程空闲60s后终止线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 
cachedThreadPool.execute(new Runnable(){public void run() {};});

//返回一个线程池,线程数目由threads参数指明
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int theads);
fixedThreadPool.execute(new Runnable(){public void run() {};});

//返回只含一个线程的线程池,它在一个单一的线程中依次执行各个任务
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(new Runnable(){public void run() {};});

//创建一个定长线程池,支持定时及周期性任务执行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int theads);
scheduledThreadPool.schedule(new Runnable() {
   public void run() {
      System.out.println("延迟1秒执行");
   }
}, 1, TimeUnit.SECONDS);

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
   public void run() {
      System.out.println("延迟1秒后每3秒执行一次");
   }
}, 1, 3, TimeUnit.SECONDS);
  • newCachedThreadPool():先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务.
  • newFixedThreadPool(int n):线程数目由创建时指定,并一直保持不变。若提交给它的任务多于线程池中的空闲线程数目,那么就会把任务放到队列中,当其他任务执行完毕后再来执行它们;
  • newSingleThreadExecutor():返回一个大小为1的线程池,由一个线程执行提交的任务。
  • newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行

同步器(Synchronizer)

 java.util.concurrent包提供了几个帮助我们管理相互合作的线程集的类,这些类的主要功能和适用场景如下:

  • CyclicBarrier:它允许线程集等待直至其中预定数目的线程到达某个状态(这个状态叫公共障栅(barrier)),然后可以选择执行一个处理障栅的动作。适用场景:当多个线程都完成某操作,这些线程才能继续执行时,或都完成了某操作后才能执行指定任务时。对CyclicBarrier对象调用await方法即可让相应线程进入barrier状态,等到预定数目的线程都进入了barrier状态后,这些线程就可以继续往下执行了
//构造函数,parties 是参与线程的个数,Runnable 参数,最后一个到达线程要做的任务。
CyclicBarrier(int parties)
CyclicBarrier(int parties, Runnable barrierAction)

//线程调用await()表示到达栅栏,Exception 表示栅栏已经被破坏,可能是其中一个线程await()时被中断或者超时
int await() throws InterruptedException, BrokenBarrierException
int await(long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException, TimeoutException
  • CountDownLatch:使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
public void await() throws InterruptedException { };   //调用该方法的线程会进入阻塞状态,直到count值为0才继续执行

//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };

public void countDown() { };  //将CountDownLatch对象count值(初始化时作为参数传入构造方法)减1
  • Exchanger:一个线程在完成一定的事务后想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据。
Exchanger<Integer> exchanger = new Exchanger<Integer>();

//等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象,否则阻塞。
data = exchanger.exchange(int);

//等待另一个线程到达此交换点(除非当前线程被中断或超出了指定的等待时间),然后将给定的对象传送给该线程,并接收该线程的对象。
exchange(V v, long timeout, TimeUnit unit):
  • Semaphore:可以控制同时访问资源的线程个数
void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。

void release():释放一个许可,将其返回给信号量。

int availablePermits():返回此信号量中当前可用的许可数。

boolean hasQueuedThreads():查询是否有线程正在等待获取。
  • SynchronousQueue:允许一个线程把对象交给另一个线程。适用场景:在没有显式同步的情况下,当两个线程准备好将一个对象从一个线程传递到另一个线程。

反射

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

要想解剖一个类,必须先要获取到该类的字节码文件对象。既Class clz = Class.forName("包名.类名");

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

反射就是把Java类中的各种成分映射成一个个的Java对象。

//获取类的 Class 对象实例
Class clz = Class.forName("包名.类名");
//根据 Class 对象实例获取 Constructor 对象
Constructor phoneConstructor = clz.getConstructor();
//使用 Constructor 对象的 newInstance 方法获取反射类对象
Object phoneObj = phoneConstructor.newInstance();
//获取方法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
//利用 invoke 方法调用方法
setPriceMethod.invoke(phoneObj, 6000);

new与反射区别

  1. new属于静态编译,而反射属于动态编译,new时所有模块都加载了,而反射是用到的时候才加载。
  2. new出来的对象,无法反问它的私有属性,而反射可以(通过setAccessible()取访问)
  3. new关键字是强类型的,效率相对较高。 反射是弱类型的,效率低。
  4. 反射提供了一种更加灵活的方式创建对象,得到对象的信息。EventBus框架,通过反射获取类中"onEvent"开头的订阅方法。

单链表逆置

//单链表定义

typedef struct ListNode{

        int m_nValue;

        ListNode* pNext;

};

//单链表逆置实现
ListNode* ReverseList(ListNode* pHead)
{
    if (pHead == NULL || pHead->pNext == NULL)
    {
        retrun pHead;
    }
 
    ListNode* finalList = NULL;

    ListNode* originList = pHead;

    while(originList != NULL)

    {

        ListNode* tempList = originList;   // 步骤①

        originList = originList->pNext;       // 步骤②

        tempList -> next = finalList;      // 步骤③

        finalList = tempList;

    }
    return finalList;
}

排序算法:

插入排序:

直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。

当数据正序时,执行效率最好,每次插入都不用移动前面的元素,时间复杂度为O(N)。
当数据反序时,执行效率最差,每次插入都要前面的元素后移,时间复杂度为O(N^2)。

希尔排序:

第一趟排序中,通过计算gap1=N/2(即10/2),将10个元素分为5组,即(9,4),(1,8),(2,6),(5,3),(7,5),然后对每组内的元素进行插入排序。
第二趟排序中,把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。分组后依旧对每组的元素进行插入排序。
第三趟排序中,再次把 gap 缩小一半,即gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。再进行一次插入排序。
需要注意的是,图中有两个相等数值的元素 5 和 5 。我们可以清楚的看到,在排序过程中,两个元素位置交换了。所以,希尔排序是不稳定的算法。

时间复杂度为O(N^(1.3—2))

冒泡排序算法:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素会是最大的数。

其时间复杂度依然为O(N^2)

选择排序:
1.从待排序序列中,找到最小的元素;
2.如果最小元素不是待排序序列的第一个元素,将其和待排序序列的第一个元素互换;

时间复杂度为 O(N*2)

递归通常用栈来实现。

异常:

所有的异常都是继承Throwable的,自定义异常不可以继承自Error。

URI与URL

URL 比较实体   表示一个具体的

URI 比较抽象 表示一个相对的意思

URL --   比如 http://www.baidu.com/124/123    是一个绝对的路径

URI -- 比如 /124/123 是一个相对的路径

泛型中的限定通配符和非限定通配符:

限定通配符包括两种:
1. 表示类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类
2. 表示类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类

非限定通配符:类型为<T>,可以用任意类型来替代。

迭代器(Iterator)

  迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。

  Java中的Iterator功能比较简单,并且只能单向移动:

  (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

  (2) 使用next()获得序列中的下一个元素。

  (3) 使用hasNext()检查序列中是否还有元素。

  (4) 使用remove()将迭代器新返回的元素删除。

  Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

File实现了 Serializable,可以用Intent传递。

android:screenOrientation属性:

  • unspecified——默认值,由系统选择显示方向,在不同的设备可能会有所不同。
  • landscape——横向
  • portrait——纵向
  • user——用户当前的首选方向
  • behind——与在活动堆栈下的活动相同方向
  • sensor——根据物理方向传感器确定方向,取决于用户手持的方向,当用户转动设备,他能随意改变。
  • nosensor——不经物理方向传感器确定方向,该传感器被忽略,所以当用户转动设备,显示不会跟随改变,除了这个却别,系统选择相同的政策取向对于“未指定”设置,系统根据“未指定”(unspecified)设定选择相同显示方向。

从 Android 3.2 (API级别 13)以后

  1. 不设置Activity的android:configChanges时,或 设置Activity的android:configChanges="orientation"时,或设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次方法。
  2. 配置 android:configChanges="orientation|screenSize",才不会销毁 activity,且只调用 onConfigurationChanged方法。

图片加载库对比

Picasso:120K

Glide:475K

Fresco:3.4M

Android-Universal-Image-Loader:162K

图片函数库的选择需要根据APP的具体情况而定,对于严重依赖图片缓存的APP,例如壁纸类,图片社交类APP来说,可以选择最专业的Fresco。对于一般的APP,选择Fresco会显得比较重,毕竟Fresco3.4M的体量摆在这。根据APP对图片的显示和缓存的需求从低到高,我们可以对以上函数库做一个排序。

Picasso < Android-Universal-Image-Loader < Glide < Fresco

Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多如果项目中网络请求本身用的就是okhttp或者retrofit(本质还是okhttp),那么建议用Picasso,体积会小很多(Square全家桶的干活)。Glide的好处是大型的图片流,比如gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用。

ANR(Application Not Responding):

应用程序无响应:在一定的时间内没有做完相应的处理。

应用程序的响应性是由Activity Manager和WindowManager系统服务监视的 。

响应输入input的事件时间超过5S,broadcastReceiver超过10S,前台service处理超过20S,后台service超过200S

线程中start()和run()的区别

start():启动相应的线程,让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法。

run():线程体,包含了线程要执行的内容。直接调用run只是一个普通的函数调用,并没有新建线程的作用。

 

普通内部类如何访问外部类:

  1. 编译器自动为内部类生成一个带参构造方法, 参数只有一个,类型是外部类。
  2. 编译器自动为内部类添加一个成员变量,通过第一步的构造函数赋值, 这个成员变量就是指向外部类对象的引用;
  3. 内部类通过该引用访问外部类属性。

线程阻塞与唤醒方法:

    1. sleep() 方法    不释放锁

  sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态。不会释放资源。(暂停线程,不会释放锁)

  2.suspend() 和 resume() 方法

  挂起和唤醒线程,suspend()使线程进入阻塞状态,只有对应的resume()被调用的时候,线程才会进入可执行状态。(不建议用,容易发生死锁)

  3. yield() 方法     不释放锁

  会使的线程放弃当前分得的cpu时间片,但此时线程任然处于可执行状态,随时可以再次分得cpu时间片。yield()方法只能使同优先级的线程有执行的机会。调用 yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知)

  4.wait() 和 notify() 方法       底层调用了wait,释放锁

  两个方法搭配使用,wait()使线程进入阻塞状态,调用notify()时,线程进入可执行状态。wait()内可加或不加参数,加参数时是以毫秒为单位,当到了指定时间或调用notify()方法时,进入可执行状态。(属于Object类,而不属于Thread类,wait()会先释放锁住的对象,然后再执行等待的动作。由于wait()所等待的对象必须先锁住,因此,它只能用在同步化程序段或者同步化方法内,否则,会抛出异常IllegalMonitorStateException.)

  5.join()方法          释放锁

  也叫线程加入。是当前线程A调用另一个线程B的join()方法,当前线程转A入阻塞状态,直到线程B运行结束,线程A才由阻塞状态转为可执行状态。

sleep和wait的区别有
  1,这两个方法来自不同的类分别是Thread和Object
  2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

synchronized(x){
      x.notify()
     //或者wait()
   }

volatile关键字

所有线程的共享变量都存储在主存(既内存)中,每一个线程都有一个独有的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自己的工作内存中,只操作工作内存中的数据。当修改完毕后,再把修改后的结果放回到主内存中。这就导致多线程的环境下可能会出现脏数据,加上volatile关键字修饰的话,它可以保证当线程对变量值做了变动之后,会立即刷回到主内存中,而其它线程读取到该变量的值也作废,强迫重新从主内存中读取该变量的值,这样在任何时刻,线程总是会看到变量的同一个值。

synchronized关键字

java中共有两种类型的锁:

  • 类锁:只有synchronized修饰静态方法或者修饰一个类的class对象时,才是类锁。
  • 对象锁:处了类锁,所有其他的上锁方式都认为是对象锁。比如synchronized修饰普通方法或者synchronized(this)给代码块上锁等

规则:

  • 加了相同锁的东西,它们的访问规则是相同的,即当某个访问者获得该锁时,它们一起向该访问者开放访问,向其他没有获得该锁的访问者关闭访问。
  • 加了不同锁的东西访问互相不干扰 。
  • 而没有加锁的东西随时都可以任意访问,不受任何限制。

判断:

  • 不同类型的锁不是同一把锁。
  • 加的是对象锁,那么必须是同一个对象实例才是同一把锁 。
  • 加的是类锁,那必须是同一类才是同一把锁。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章