java面试要点

1、java基础

1.1java基础语法

1.1.1、变量,数据类型

(1)int和integer的区别
1、Integer是int的包装类,int则是java的一种基本数据类型 
2、Integer变量必须实例化后才能使用,而int变量不需要 
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 
4、Integer的默认值是null,int的默认值是0

延伸: 

关于Integer和int的比较 

1、由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false

2、Integer变量和int变量比较时,只要两个变量的值是相等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)

Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); //true

3、非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)

Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false

4、对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false
对于第4条的原因: 

java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的valueOf的定义如下:(源码)

public static Integer valueOf(int i){
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high){
        return IntegerCache.cache[i + (-IntegerCache.low)];
    }
    return new Integer(i);
}

java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了

(2)==和equals的区别

1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;

  如果作用于引用类型的变量,则比较的是所指向的对象的地址

2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量

     如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

     诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

(3)String 能被继承吗? StringBuider StringBuffer  String之间的区别

都不能被继承:

源码是这样的:

public final class String extends Object

public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence

public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence

因为Sting是这样定义的:public final class String extends Object,里边有final关键字,所以不能被继承。

(1)String 类 

  String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间。 

String a = "a"; //假设a指向地址0x0001 

a = "b";//重新赋值后a指向地址0x0002,但0x0001地址中保存的"a"依旧存在,但已经不再是a所指向的,a 已经指向了其它地址。 

因此String的操作都是改变赋值地址而不是改变值操作。

(2)StringBuffer

        StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。 每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。 

StringBuffer buf=new StringBuffer(); //分配长16字节的字符缓冲区 

StringBuffer buf=new StringBuffer(512); //分配长512字节的字符缓冲区 

StringBuffer buf=new StringBuffer("this is a test")//在缓冲区中存放了字符串,并在后面预留了16字节的空缓冲区。 

3.StringBuffer 

  StringBuffer和StringBuilder类功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。对于经常要改变值的字符串应该使用StringBuffer和StringBuilder类。 

String 字符串常量

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

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

速度比较

一般情况下,速度从快到慢:StringBuilder>StringBuffer>String,这种比较是相对的,不是绝对的。 

(1).如果要操作少量的数据用 = String 

(2).单线程操作字符串缓冲区 下操作大量数据 = StringBuilder 

(3).多线程操作字符串缓冲区 下操作大量数据 = StringBuffer 

1.1.2、if  循环语句

(1)乘法口诀表
	for (int i = 1; i <= 9; i++) {
		for (int j = 1; j <= i; j++) {
			System.out.print(i + "x" + j + "=" + i * j + "\t");
		}
		System.out.println();
	}
(2)冒泡排序
	int[] arr = {12,21,14,22,11,2};
	for (int i = 0; i < arr.length ; i++) {
		for (int j = 0; j < arr.length - 1 - i ; j++) {
			if (arr[j] > arr[j + 1]) {
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		
	}
	for(int m:arr) {
		System.out.print(m+",");
	}
public class MaopaoSrot {

	public static void maopaoSort(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			for (int j = 0; j < arr.length-1-i; j++) {
				if (arr[j] > arr[j+1]) {
					int temp = arr[j];
					arr[j] = arr[j+1];
					arr[j+1]=temp;
				}
			}
		}
		printArray(arr);
	}
	public static void printArray(int[] arr) {
		
		System.out.print("(");
		for (int i = 0; i < arr.length; i++) {
			if (i != arr.length-1) {
				System.out.print(arr[i] + ",");
			}else {
				System.out.print(arr[i]);
			}
		}
		System.out.print(")");
	}
	
	public static void main(String[] args) {
		System.out.println("冒泡排序");
		int[] array = {22,1,2,43,32,2,2,1,};

		printArray(array);
		maopaoSort(array);
	}
}
(3)快速排序
import java.util.Arrays;  
  
/**  
* @author  LPJ 
* @version 1.0 创建时间:2018年3月3日 上午11:23:57  
* describe:原理:采用单轴双向比较方法,设置基准值,首次循环从后向前对比,当小于基准值时,交换,然后从开始 
* 位置与基准值进行比较,当大于基准值时,进行交换。全部结束后,元素分为两部分,右侧为大于基准值,左侧为小于基准值 
* 然后将两部分分别递归调用排序方法 
*/  
public class QucikSort {  
    public static void main(String[] args) {  
        int [] aa = {3,2,45,21,5};  
        System.out.println(Arrays.toString(aa));  
        aa = quickSort1(aa);  
        System.out.println(Arrays.toString(aa));  
    }  
      
      
    static int[] quickSort1(int [] a) {         
        return quickSort(a, 0, a.length - 1);  
    }  
//快速排序方法  
    static int[] quickSort(int [] a,int low, int high) {          
        int start = low;//记录起始位置  
        int end = high;//记录终点位置  
        int key = a[low];//记录分界点值,基准值  
//首次循环  
        while(start < end) {  
//从后向前比较,如果大于基准值,继续向前  
            while(start < end && a[end] >= key)   
                end--;  
//如果小于基准值,进行交换  
            a[start] = a[end] + (a[end] = a[start]) * 0;  
//从前向后比较,如果小于基准值,继续向后  
            while(start < end && a[start] <= key)  
                start++;     
//如果大于基准值,进行交换           
            a[start] = a[end] + (a[end] = a[start]) * 0;              
        }  
//递归,分成两部分,此时start = end  
//基准值以及以前的部分,重复递归end-1  
//基准值以及以后的部分,重复递归end+1  
        if(start > low)  quickSort(a, low, end - 1);  
        if(end < high)   quickSort(a, end + 1, high);    
     return a;  
    }  
  
}
(4)说说你认识的所有的排序的算法,让你按效率排序一下。

快速排序 > 插入排序 > 选择排序 > 冒泡排序

1.1.3、集合

(1)HashTable HashMap的区别

    HashMap不是线程安全的

            hastmap是一个接口 是map接口的子接口,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap允许null key和null value,而hashtable不允许。

HashTable是线程安全的一个Collection。

HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。 HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差

	public static void main(String args[]) { 
		
		
		Hashtable<String, Integer> h=new Hashtable<String, Integer>(); 
		h.put("用户1",new Integer(90)); 
		h.put("用户2",new Integer(50)); 
		h.put("用户3",new Integer(60)); 
		h.put("用户4",new Integer(70)); 
		h.put("用户5",new Integer(80)); 
		
		Enumeration<Integer> e=h.elements(); 
		while(e.hasMoreElements()){ 
			System.out.println(e.nextElement()); 
		}
	}

总结:

hashmap线程不安全允许有null的键和值效率高一点、方法不是Synchronize的要提供外同步有containsvalue和containsKey方法HashMap 是Java1.2 引进的Map interface 的一个实现HashMap是Hashtable的轻量级实现
hashtable线程安全不允许有null的键和值效率稍低、方法是是Synchronize的有contains方法方法Hashtable 继承于Dictionary 类Hashtable 比HashMap 要旧
(2)HashMap和HashSet的区别
什么是HashSet
HashSet实现了Set接口,它不允许集合中有重复的值,当我们提到HashSet时,第一件事情就是在将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。如果我们没有重写这两个方法,将会使用这个方法的默认实现。
public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true。

什么是HashMap

HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap允许键和值为null。HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。

public Object put(Object Key,Object value)方法用来将元素添加到map中。
HashMapHashSet
HashMap实现了Map接口HashSet实现了Set接口
HashMap储存键值对HashSet仅仅存储对象
使用put()方法将元素放入map中使用add()方法将元素放入set中
HashMap中使用键对象来计算hashcode值HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
HashMap比较快,因为是使用唯一的键来获取对象HashSet较HashMap来说比较慢
(3)Collection和Collections的区别
1、java.util.Collection 是一个 集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
 Collection   
├List   
│├LinkedList   
│├ArrayList   
│└Vector   
│ └Stack   
└Set 

2、java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

public class TestCollections {
    
    public static void main(String args[]) {
        //注意List是实现Collection接口的
        List list = new ArrayList();
        double array[] = { 112, 111, 23, 456, 231 };
        for (int i = 0; i < array.length; i++) {
            list.add(new Double(array[i]));
        }
        Collections.sort(list);
        for (int i = 0; i < array.length; i++) {
            System.out.println(list.get(i));
        }
        // 结果:23.0 111.0 112.0 231.0 456.0
    }
}
(4)某个集合背后的数据结构

Hashmap数据结构:

HashSet类:

Java.util.HashSet类实现了Java.util.Set接口。

它不允许出现重复元素;

不保证和政集合中元素的顺序

允许包含值为null的元素,但最多只能有一个null元素

List(列表):

List的特征是其元素以线性方式存储,集合中可以存放重复对象。

  List接口主要实现类包括:

  ArrayList() : 代表长度可以改变得数组。可以对元素进行随机的访问,向ArrayList()中插入与与删除元素的速度慢。

  LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。对于List的随机访问来说,就是只随机来检索位于特定位置的元素。

Map(映射):

  Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。

  Map没有继承于Collection接口

  从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
(5)某个集合线程安全不安全

Java中各种集合(字符串类)的线程安全性!!!

线程安全(Thread-safe)的集合对象:

Vector 
HashTable
StringBuffer

非线程安全的集合对象:

ArrayList :
LinkedList:
HashMap:
HashSet:
TreeMap:
TreeSet:
StringBulider:

1.1.4、IO流

(1)说出常用的IO流的类,都有些什么特点

(1)InputStream 是所有的输入字节流的父类,它是一个抽象类;

(2)StringBufferInputStream从StringBuffer中读取数据;

(3)FileInputStream从本地文件中读取数据;

(4)OutputStream 是所有的输出字节流的父类,它是一个抽象类;

(5)FileOutputStream往本地文件中写入数据;

(6)Reader 是所有的输入字符流的父类,它是一个抽象类;

(7)Writer 是所有的输出字符流的父类,它是一个抽象类;

1.1.5、多线程(并发)

(1)sleep 和wait的区别

sleep()

sleep() 方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。 

因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。

wait()

wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程

(2)线程的创建的方式有几种,都有些什么特别。

1)继承Thread类创建线程类

1. 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

2. 创建Thread子类的实例,即创建了线程对象。

3. 调用线程对象的start()方法来启动该线程。

2)通过Runnable接口创建线程类

1. 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

2. 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

3. 调用线程对象的start()方法来启动该线程。

3)通过Callable和Future创建线程

1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。

4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

4、创建线程的三种方式的对比

采用实现Runnable、Callable接口的方式创见多线程时,优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式创建多线程时优势是:

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

线程类已经继承了Thread类,所以不能再继承其他父类。
(3)消费者和生产者

线程优先级:

  优先级范围:1—10
  设置线程t优先级的方法:t.setPriority(1--10)
  获取线程t优先级的方法:t.getPriority()
  线程优先级的默认值是:5
  线程优先级为10的优先级最高,为1的最低

下面是一个生产者与消费者的示例,加深对多线程的理解与运用!!!

package com.thread.src;  
  
public class Test {  
    public static void main(String[] args) {  
          
        //工具类  
        Utils u = new Utils();  
          
        //创建一个生产者线程  
        Productor p = new Productor(u);  
        //创建一个消费者线程  
        Customer c = new Customer(u);  
          
        //启动生产者和消费者线程   
        p.start();  
        c.start();  
    }  
}
package com.thread.src;  
/** 
 * 生产者 
 * @author winy_lm 
 * 
 */  
public class Productor extends Thread{  
    //工具类  
    private Utils u;  
    //构造函数  
    public Productor(Utils u){  
        this.u = u;  
    }  
  
    //线程执行方法  
    public void run(){  
        while(true){  
            //每循环一次向data中放一个数据  
            //调用Utils类中的procuce()方法来生产  
            u.produce();  
        }  
    }  
} 
package com.thread.src;  
/** 
 * 消费者 
 * @author winy_lm 
 * 
 */  
public class Customer extends Thread{  
    //工具类  
    private Utils u;  
    //构造函数  
    public Customer(Utils u){  
        this.u = u;  
    }  
      
    //线程执行方法  
    public void run(){  
        while(true){  
            //每次从data中取一个数据  
            u.consume();  
        }  
    }  
}
package com.thread.src;  
  
/** 
 * 工具类 
 * @author winy_lm 
 * 
 */  
public class Utils {  
  
    //被操作的数组,最多只能放三个,超过三个就等待,为0个的时候也等待  
    private int[] data = new int[3];  
    // 定义一个count用来标识data数据的个数  
    private int count = 0;  
    // 定义一个num用来表示,生产者上一次生产的数据  
    private int num = 0;  
  
    /* 
     * 如果一个类中的方法同时被2个或者多个线程访问, 那么我们就要把这个/这些方法同步,避免破坏数据完整性 
     */  
  
    /** 
     * 此方法是完成生产向data放数据的功能 
     */  
    synchronized public void produce() {  
        try {  
            if (count < 3) {  
                data[count] = num + 1;  
                System.out.println("生产者:" + (num + 1));  
                num = num + 1;  
                count++;  
                // 生产者生产数据之后 data中有数据了,消费者可以消费了  
                // 所以要唤醒消费者  
                notifyAll();  
            } else {  
                //等待被唤醒  
                wait();  
            }  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
  
    /** 
     * 此方法用来供消费者线程调用,消费一个数据 
     */  
    synchronized public void consume() {  
        try {  
            if (count > 0) {  
                int m = data[count - 1];  
                data[count - 1] = 0;  
                count--;  
                System.out.println("消费者:" + m);  
                // 因为我们已经消费了一个数据,data有空位,可以生产了  
                // 所以我们调用notifyAll()来唤醒生产者生产  
                notifyAll();  
            } else {  
                // else表明data中是空的,消费者不能消费,只有等待生产者生产  
                //等待被唤醒  
                wait();  
            }  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  
(4)自定义一个队列,保证这个队列去取数据的时候线程安全



(5)非/有阻塞的集合有哪些?你用过哪些?



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值