【猿小白】常见Java面试问题汇总~~~持续更新~~~直到拿到心仪的offer

来源:互联网 发布:汽车故障检测软件 编辑:程序博客网 时间:2024/05/18 01:21

眼看秋招已过大半,依旧没有拿到心仪的offer,但还是要坚持住最初的信念,必要的时候还是得喝点心灵鸡汤,不管上一场面试结果如何,还是得重整旗鼓,卯足了劲往前冲,所谓百面成钢,前提也得是总结分析失败的教训。下面的一些问题是一些我在面试的时候遇到的问题,会与不会的我都一并整理了下来,查缺补漏扫扫盲。这一篇主要是关于Java常见的面试问题

**

Java中常见的面试问题

**

1、Java开发中常用的设计模式
设计模式(Design Patterns)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
一、设计模式的分类
总体来说设计模式分为三大类:
创建性模式
结构性模式
行为性模式

2、方法重写和方法重载的区别
方法重载和方法重写在英文中分别是overload和override,很多人在学习Java的过程中总是分不清重写和重载这两个方法,实际上,这两个方法还是有很大的差别的,重载和重写这两个方法虽然名字有些类似,但他们之间有很少的联系,除了二者都是发生在方法之间,并要求方法名相同之外,没有太大的相似之处。重载主要发生在同一个类的多个重名方法之间,重写则发生在子类和父类同名方法之间,当然父类方法和子类方法之间也可以发生重载,因为子类会获得父类的方法,如果子类定义了一个与父类方法有相同的方法名,但参数列表不同的方法,就会形成父类方法和子类方法的重载。
方法重载要遵循的原则:两同一不同
(1)两同:同一个类中的方法名相同
(2)一不同:参数列表(个数或类型)不同
至于方法中的其他部分,如方法返回值类型、修饰符等,与方法重载没有任何关系。
方法重写要遵循的原则:两同两小一大
(1)两同:方法名相同、形参列表相同
(2)两小:子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等
(3)一大:是子类方法的访问权限应比父类方法的访问权限更大或相等

3、static关键字
在《Java编程思想》P86页有这样一段话:
“static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。”
这段话虽然只是说明了static方法的特殊之处,但是可以看出static关键字的基本作用,简而言之,一句话来描述就是:
方便在没有创建对象的情况下来进行调用(方法/变量)。
很显然,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
static可以用来修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能。
1)static方法
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
静态方法可以直接通过类名调用,任何的实例也都可以调用,
因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。
因为实例成员与特定的对象关联!这个需要去理解,想明白其中的道理,不是记忆!!!
因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
例如为了方便方法的调用,Java API中的Math类中所有的方法都是静态的,而一般类内部的static方法也是方便其它类对该方法的调用。
静态方法是类内部的一类特殊方法,只有在需要时才将对应的方法声明成静态的,一个类内部的方法一般都是非静态的
2)static变量
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。
两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
所以一般在需要实现以下两个功能时使用静态变量:
(1)在对象之间共享值时
(2)方便访问变量时
3)static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。

4、垃圾回收机制
垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)。
程序无法精确控制垃圾回收后的运行,垃圾回收会在合适的时候运行。当对象永久的失去引用后,系统会在合适的时候回收它所占用的资源。
在垃圾回收机制回收任何资源之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
当一个对象在堆内存中运行时,根据它被引用变量所引起的状态,可以把它所处的状态分成如下三种。
可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的实例变量和方法。
可恢复状态:如果程序中某个变量不再有任何变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理,如果系统在调用finalize()方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态,否则该对象进入不可达状态。
不可达状态:当对象与所有引用变量的关联都被切断,切系统已经调用所有对象的finalize()方法后依然没有使该对象编程可达状态,那么这个对象将永久性的失去引用,最后变成不可达状态,只有当一个对象变成不可达状态时,系统才会真正回收该对象占有的资源。
强制垃圾回收
系统无法精确控制Java系统垃圾回收的时机,但依然可以强制系统进行垃圾回收——这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定。大部分时候,程序强制系统垃圾回收后总会有一些效果。强制系统垃圾回收有如下两种方式:
1、调用System类的gc()静态方法:Syatem.gc()。
2、调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc().

5、创建线程的方法
Java中创建线程的方法主要有三种方式;
1、继承Thread类创建线程
(1)定义Thread的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务,因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。

package com.thread;  public class FirstThreadTest extends Thread{      int i = 0;      //重写run方法,run方法的方法体就是现场执行体      public void run()      {          for(;i<100;i++){          System.out.println(getName()+"  "+i);          }      }      public static void main(String[] args)      {          for(int i = 0;i< 100;i++)          {              System.out.println(Thread.currentThread().getName()+"  : "+i);              if(i==20)              {                  new FirstThreadTest().start();                  new FirstThreadTest().start();              }          }      }  }  

2、通过Runnable接口来创建线程类
(1)定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法体同样是该线程的程序执行体。
(2)创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用对象的start()方法来启动线程。

package com.thread;  public class RunnableThreadTest implements Runnable  {      private int i;      public void run()      {          for(i = 0;i <100;i++)          {              System.out.println(Thread.currentThread().getName()+" "+i);          }      }      public static void main(String[] args)      {          for(int i = 0;i < 100;i++)          {              System.out.println(Thread.currentThread().getName()+" "+i);              if(i==20)              {                  RunnableThreadTest rtt = new RunnableThreadTest();                  new Thread(rtt,"新线程1").start();                  new Thread(rtt,"新线程2").start();              }          }      }  }

3、通过Callable的Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

package com.thread;  import java.util.concurrent.Callable;  import java.util.concurrent.ExecutionException;  import java.util.concurrent.FutureTask;  public class CallableThreadTest implements Callable<Integer>  {      public static void main(String[] args)      {          CallableThreadTest ctt = new CallableThreadTest();          FutureTask<Integer> ft = new FutureTask<>(ctt);          for(int i = 0;i < 100;i++)          {              System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);              if(i==20)              {                  new Thread(ft,"有返回值的线程").start();              }          }          try          {              System.out.println("子线程的返回值:"+ft.get());          } catch (InterruptedException e)          {              e.printStackTrace();          } catch (ExecutionException e)          {              e.printStackTrace();          }      }      @Override      public Integer call() throws Exception      {          int i = 0;          for(;i<100;i++)          {              System.out.println(Thread.currentThread().getName()+" "+i);          }          return i;      }  }  

三种方法比较:
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
6、&和&&的区别
&&:与,前后两个操作数必须都是true才返回true,否则返回false
&:不短路与,作用与&&相同,但不会短路。
7、Java的三大特征并举例说明
三大特征:继承、封装、多态
封装就是private public 一般属性是private主要是防止别的类直接访问这个属性
对应的setter和getter方法是public,提供对该属性的获取和修改
继承就是extends,主要用来对功能的扩展
多态的体现就是接口,
8、set、map和list的区别
list和set是实现了collection接口的,Map是个顶级接口
List:1.可以允许重复的对象。
   2.可以插入多个null元素。
3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
4.常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
Set:1.不允许重复对象
   2. 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
   3.只允许一个 null 元素
   4.Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
Map:1、Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
2、TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
3、Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
4、Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
什么场景下使用list,set,map呢?
(或者会问为什么这里要用list、或者set、map,这里回答它们的优缺点就可以了)
答:如果你经常会使用索引来对容器中的元素进行访问,那么 List 是你的正确的选择。如果你已经知道索引了的话,那么 List 的实现类比如 ArrayList 可以提供更快速的访问,如果经常添加删除元素的,那么肯定要选择LinkedList。
如果你想容器中的元素能够按照它们插入的次序进行有序存储,那么还是 List,因为 List 是一个有序容器,它按照插入顺序进行存储。
如果你想保证插入元素的唯一性,也就是你不想有重复值的出现,那么可以选择一个 Set 的实现类,比如 HashSet、LinkedHashSet 或者 TreeSet。所有 Set 的实现类都遵循了统一约束比如唯一性,而且还提供了额外的特性比如 TreeSet 还是一个 SortedSet,所有存储于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 进行排序。LinkedHashSet 也按照元素的插入顺序对它们进行存储。
如果你以键和值的形式进行数据存储那么 Map 是你正确的选择。你可以根据你的后续需要从 Hashtable、HashMap、TreeMap 中进行选择。
9、List和ArrayList的区别
List是一个接口,而ArrayList是List接口的一个实现类
这里写图片描述

从图中我们可以看出:
1. List是一个接口,它继承与Collection接口,代表有序的队列。
2. AbstractList是一个抽象类,它继承与AbstractCollection。AbstractList实现了List接口中除了size()、get(int location)之外的方法。
3. AbstractSequentialList是一个抽象类,它继承与AbstrctList。AbstractSequentialList实现了“链表中,根据index索引值操作链表的全部方法”。
4. ArrayList、LinkedList、Vector和Stack是List的四个实现类,其中Vector是基于JDK1.0,虽然实现了同步,但是效率低,已经不用了,Stack继承与Vector,所以不再赘述。
5. LinkedList是个双向链表,它同样可以被当作栈、队列或双端队列来使用。
ArrayList和LinkedList区别
我们知道,通常情况下,ArrayList和LinkedList的区别有以下几点:
1. ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构;
2. 对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针;
3. 对于添加和删除操作add和remove,一般大家都会说LinkedList要比ArrayList快,因为ArrayList要移动数据。但是实际情况并非这样,对于添加或删除,LinkedList和ArrayList并不能明确说明谁快谁慢,下面会详细分析。
我们结合之前分析的源码,来看看为什么是这样的:
ArrayList中的随机访问、添加和删除部分源码如下:

//获取index位置的元素值  public E get(int index) {      rangeCheck(index); //首先判断index的范围是否合法      return elementData(index);  }  //将index位置的值设为element,并返回原来的值  public E set(int index, E element) {      rangeCheck(index);      E oldValue = elementData(index);      elementData[index] = element;      return oldValue;  }  //将element添加到ArrayList的指定位置  public void add(int index, E element) {      rangeCheckForAdd(index);      ensureCapacityInternal(size + 1);  // Increments modCount!!      //将index以及index之后的数据复制到index+1的位置往后,即从index开始向后挪了一位      System.arraycopy(elementData, index, elementData, index + 1,                       size - index);       elementData[index] = element; //然后在index处插入element      size++;  }  //删除ArrayList指定位置的元素  public E remove(int index) {      rangeCheck(index);      modCount++;      E oldValue = elementData(index);      int numMoved = size - index - 1;      if (numMoved > 0)          //向左挪一位,index位置原来的数据已经被覆盖了          System.arraycopy(elementData, index+1, elementData, index,                           numMoved);      //多出来的最后一位删掉      elementData[--size] = null; // clear to let GC do its work      return oldValue;  }

LinkedList中的随机访问、添加和删除部分源码如下:

//获得第index个节点的值  public E get(int index) {      checkElementIndex(index);      return node(index).item;  }  //设置第index元素的值  public E set(int index, E element) {      checkElementIndex(index);      Node<E> x = node(index);      E oldVal = x.item;      x.item = element;      return oldVal;  }  //在index个节点之前添加新的节点  public void add(int index, E element) {      checkPositionIndex(index);      if (index == size)          linkLast(element);      else          linkBefore(element, node(index));  }  //删除第index个节点  public E remove(int index) {      checkElementIndex(index);      return unlink(node(index));  }  //定位index处的节点  Node<E> node(int index) {      // assert isElementIndex(index);      //index<size/2时,从头开始找      if (index < (size >> 1)) {          Node<E> x = first;          for (int i = 0; i < index; i++)              x = x.next;          return x;      } else { //index>=size/2时,从尾开始找          Node<E> x = last;          for (int i = size - 1; i > index; i--)              x = x.prev;          return x;      }  }  

10、final,finally,finalize的区别
final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等
final—修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。
finally—再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
finalize—方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
11、遍历
在遍历数组时,使用foreach语句更简单

package captain;  public class ArrayDemo2 {      public static void main(String[] args) {          // TODO Auto-generated method stub          int arr[][] = new int[][]{{4,3},{1,5}};          //foreach语句遍历二维数组。          System.out.println("数组中的元素是:");          for(int x[]:arr){  //外层遍历得到一维数组              for(int e:x){  //内层遍历得到数组元素                      System.out.print(e);              }              System.out.println();          }      }  } 

利用Arrays工具类中的toString静态方法可以将一维数组转化为字符串形式并输出

import java.util.Arrays;  public class ArrayDemo3 {      public static void main(String[] args) {          // TODO Auto-generated method stub          //Arrays工具类的toString静态方法遍历二维数组。          int arr[][] = new int[][]{{9,8},{7,6,5}};          for(int i = 0; i < arr.length; i++){//循环得到一维数组              System.out.println(Arrays.toString(arr[i]));//将一维数组转化为字符串输出          }      }  }  

12、equals和==的区别
1. == 是一个运算符。
  2.Equals则是string对象的方法,可以.(点)出来。
  
  我们比较无非就是这两种 1、基本数据类型比较 2、引用对象比较
  1、基本数据类型比较
  ==和Equals都比较两个值是否相等。相等为true 否则为false;
  
  2、引用对象比较
  ==和Equals都是比较栈内存中的地址是否相等 。相等为true 否则为false;
  
  需注意几点:
  1、string是一个特殊的引用类型。对于两个字符串的比较,不管是 == 和 Equals 这两者比较的都是字符串是否相同;
  2、当你创建两个string对象时,内存中的地址是不相同的,你可以赋相同的值。
  所以字符串的内容相同。引用地址不一定相同,(相同内容的对象地址不一定相同),但反过来却是肯定的;
  3、基本数据类型比较(string 除外) == 和 Equals 两者都是比较值;
13、sleep和wait的区别
① 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
③ 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
synchronized(x){
x.notify()
//或者wait()
}
14、介绍下你了解的排序方法,分别是内排还是外排
内排序是在排序整个过程中,待排序的所有记录全部被放置在内存中。外排序是由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行。我们这里主要就介绍内排序的多种方法。常见的内部排序方法包括:冒泡排序、堆排序、直接插入排序、归并排序、快递排序、基数排序、选择排序、希尔排序等;
对于内排序来说,排序算法的性能主要是受3个方面影响:
1.时间性能
排序是数据处理中经常执行的一种操作,往往属于系统的核心部分,因此排序算法的时间开销是衡量其好坏的最重要的标志。在内排序中,主要进行两种操作:比较和移动。比较指关键字之间的比较,这是要做排序最起码的操作。移动指记录从一个位置移动到另一个位置,事实上,移动可以通过改为记录的存储方式来予以避免(这个我们在讲解具体的算法时再谈)。总之,高效率的内排序算法应该是具有尽可能少的关键字比较次数和尽可能少的记录移动次数。

2.辅助空间
评价排序算法的另一个主要标准是执行算法所需要的辅助存储空间。辅助存储空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间。

3.算法的复杂性
注意这里指的是算法本身的复杂度,而不是指算法的时间复杂度。显然算法过于复杂也会影响排序的性能。

根据排序过程中借助的主要操作,我们把内排序分为:插入排序、交换排序、选择排序和归并排序。可以说,这些都是比较成熟的排序技术,已经被广泛地应用于许许多多的程序语言或数据库当中,甚至它们都已经封装了关于排序算法的实现代码。因此,我们学习这些排序算法的目的更多并不是为了去在现实中编程排序算法,而是通过学习来提高我们编写算法的能力,以便于去解决更多复杂和灵活的应用性问题。

这里写图片描述
下面对几个常见的排序方法的思想做一下介绍:
(1)冒泡排序法
基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的数往上冒。即:每当两相邻的数比较后,发现他们的排序与排序要求相反时,就将他们互换。
(2)插入排序
插入即表示将一个新的数据插入到一个有序数组中,并继续保持有序。例如有一个长度为N的无序数组,进行N-1次的插入即能完成排序;第一次,数组第1个数认为是有序的数组,将数组第二个元素插入仅有1个有序的数组中;第二次,数组前两个元素组成有序的数组,将数组第三个元素插入由两个元素构成的有序数组中……第N-1次,数组前N-1个元素组成有序的数组,将数组的第N个元素插入由N-1个元素构成的有序数组中,则完成了整个插入排序。
(3)快速排序
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

15、解释下反射机制
概念:主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以运行时装配,无需在组件之间进行源代码链接,但是反射使用不当会成本很高。
反射机制的作用:
(1)反编译:.class->.java
(2)通过反射机制访问Java对象的属性、方法、构造方法等

16、进程之间的通信方式有哪些?
进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET.
管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
17、谈一下对面向对象的理解
18、接口和抽象类的区别
接口和抽象类的概念不一样,接口是对动作的抽象,抽象类是对根源的抽象。
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类变量,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类的抽象方法,,那么该子类只能是抽象类。同样,一个类实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现,接口是设计的结果,抽象类是重构的结果。
7、抽象类里可以没有抽象方法,
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、出现方法要被实现,所以不能是静态的,也不能是私有的
10、接口可继承接口,并可多继承接口,但类只能单根继承。

1、抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高
2、抽象类可以有具体的方法 和属性, 接口只能有抽象方法和不可变常量
3、抽象类主要用来抽象类别,接口主要用来抽象功能.
4、抽象类中,且不包含任何实现,派生类必须覆盖它们。接口中所有方法都必须是未实现的。
18、进程有几种状态,分别是什么?
进程的基本状态有三种:运行(run)、就绪(ready)、等待(wait)。
运行态(Run):进程占有处理机资源,正在运行。显然,在单处理机系统中任一时刻只能有一个进程处理此种状态。
就绪态(Ready):进程本身具备运行条件,但由于处理机的个数少于可运行进程的个数,暂未投入运行,即相当于等待处理机资源。
等待态(wait):也成挂起态、封锁态、睡眠态。进程本身不具备运行条件,即使分给他处理机也不运行。进程正等待某一个事件的发生,如等待某一资源被释放,等待与该线程相关的I/O传输的完成信号等。

进程的三个基本状态之间是可以相互转换的。具体地说,当一个就绪进程获得处理机时,其状态由就绪变为运行;当一个运行进程被剥夺处理机时,如用完系统分给它的时间片、出现更高优先级别的其它进程,其状态由运行变为就绪;当一个运行进程因某事件受阻时,如所申请资源被占用、启动I/O传输未完成,其状态由运行变为等待;当所等待事件发生时,如得到申请资源、I/O传输完成,其状态由等待变为就绪。

19、进程和程序的区别?
1、程序是永存的,进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
2、程序是静态的观念,进程是动态的观念,
3、进程具有并发性,而程序没有
4、进程是竞争计算机资源的基本单位、程序不是
5、进程和程序不是一一对应的:一个程序可以对应多个进程即多个进程可以执行同一程序,一个进程可以执行一个或几个程序。

阅读全文
0 0
原创粉丝点击