JAVASE-面试解惑

版权声明:博观而约取,厚积而薄发。 https://blog.csdn.net/BruceLiu_code/article/details/89070662

1.谈谈你对面向对象的理解

  面向对象是当今主流的程序设计思想,面向对象是对现实世界中事物的一种理解和抽象,在现实生活中,任何物体都可以归为一类事物,而这每一个个体都是一类事物的实例。面向对象的程序核心是由对象组成的,每个对象包含着对用户公开的特定功能和隐藏的实现部分。对于程序开发人员来说,面向对象的思想是我们看待事物的更加好的一种思维方式。面向对象主要包含以下几个特点:
 (1)面向对象是一种常见的思想,比较符合人们的思考习惯;
 (2)面向对象可以将复杂的业务逻辑简单化,增强代码复用性;
 (3)面向对象具有抽象封装继承多态等特性。
  面向对象的编程语言主要有:C++、Java、C#等。

补充:这是一个开放性的问题,完全可以用自己的理解来表达,总而言之,一切皆对象!

2.类和对象之间的关系

  • :类是一类事物的统称,人们在认识事物的时候,自觉或者不自觉的都会把事物进行分类,例如狗类、猫类等…“物以类聚”就是这个道理。类是对某类事物的普遍一致性特征、功能的抽象、描述和封装,是构造对象的模版或蓝图,类之间主要有:依赖、聚合、继承等关系。
  • 对象:对象是一类事物中,某一个具体的实例。对应到Java程序中,就是使用 new 关键字或反射技术创建的某个类的实例。对象是通过类来创建的,也就是说类是对象的模板。同一个类的所有对象,都具有相似的数据(比如人的年龄、性别)和行为(比如人的吃饭、睡觉),但是每个对象都保存着自己独特的状态。

回答这类问题,可以举例,例如人类就是一个类,张三、刘德华就是一个具体的对象

3. 面向对象的特性表现在哪些方面

  面向对象的特征主要有以下几个方面:

  • 继承(Inheritance):继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。继承的核心思想不仅仅是重用现有的代码,用一些已有的类去创建新的类这么简单。除了这一点,还产生了另一种奇妙的东西叫做向上转型或向下转型,就是说在代码中可以将子类对象只想父类的引用,这也是后面多态的一种基础。
  • 封装(Encapsulation):又叫隐藏实现(Hiding the implementation)。就是只公开代码单元的对外接口,而隐藏其具体实现。封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。比如你的手机,笔记本电脑等,就是其对外的接口。你只需要知道如何按键就可以使用手机,而不需要了解手机内部的电路是如何工作的。封装机制就像手机一样只将对外接口暴露,而不需要用户去了解其内部实现。细心观察,现实中到处都是封装的例子。
  • 多态(Polymorphism):多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。其中编译时的多态也称为静态绑定,运行时的多态称为动态绑定。方法重载(overload)实现的是编译时的多态性(也称为静态绑定),而方法重写(override)实现的是运行时的多态性(也称为动态绑定)。运行时的多态是面向对象编程中最精髓的东西,在各种设计模式中,多态应用到了极致。要实现多态需要做两件事:
    (1)方法重写(子类继承父类并重写父类中已有的或抽象的方法);
    (2)对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
  • 抽象(Abstract):抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。具体的实现交给实现类来负责。

默认情况下面向对象有 3 大特性,封装、继承、多态,如果面试官问让说出 4 大特性,那么我们把抽象加上去,这些属于基础理解内容,理解记忆即可。

4. 聊聊JAVA中封装的步骤

  封装是将对象的信息隐藏在对象内部,禁止外部程序直接访问对象内部的属性和方法。
  java封装类通过三个步骤实现:
  (1)修改属性的可见性,限制访问。
  (2)设置属性的读取方法。
  (3)在读取属性的方法中,添加对属性读取的限制。

  • 示例:
    编写一个包含名为Calculator类的程序。定义两个整型属性以及对该两个变量的setter和getter,编写用于对两个属性执行加、减、乘、除运算方法。在main方法里创建该类的对象并访问这些方法。
/**
 * @author bruceliu
 * @time 2019年4月8日上午11:57:49
 * @Description 
 */
public class Calculator {

	private int a;// 定义属性
	private int b;
	int c;

	public void setA(int a) { 
		this.a = a;
	}

	public int getA() {
		return a; // 返回到private int a;
	}

	public void setB(int b) {
		this.b = b;
	}

	public int getB() {
		return b;
	}

	public void augment() { // 方法
		c = a + b;
		System.out.println(c);
	}

	public void reduce() {
		c = a - b;
		System.out.println(c);
	}

	public void ride() {
		c = a * b;
		System.out.println(c);

	}

	public void divide() {
		c = a / b;
		System.out.println(c);
	}
}
  • 测试类
/**
 * @author bruceliu
 * @time 2019年4月8日上午11:59:23
 * @Description
 */
public class TestCalculator {

	public static void main(String[] args) {
		
		 // 创建计算器对象
		Calculator calculator = new Calculator();
		calculator.setA(24);
		calculator.setB(45);
		System.out.println("请输出a+b:");
		calculator.augment(); // 调用calculator类中的方法
		System.out.println("请输出a-b:");
		calculator.reduce();
		System.out.println("请输出a*b");
		calculator.ride();
		System.out.println("请输入a/b");
		calculator.divide();
	}

}

封装的思想在程序中处处可见,基础内容,不解释!

5. JAVA中常见的访问修饰符

在Java编程语言中有四种权限访问控制符,这四种访问权限的控制符能够控制类中成员的可见性。

修饰符 当前类 同包 子类 其他位置
public
protected ×
默认的 × ×
private × × ×
  • public:公共的
    (1)定义:public是公共的,被public所修饰的成员可以在任何类中都能被访问到。
    (2)修饰的成分:
      public能用来修饰类,在一个java源文件中只能有一个类被声明为public,而且一旦有一个类为public,那这个java源文件的文件名就必须要和这个被public所修饰的类的类名相同,否则编译不能通过。一个类作为外部类的时候只能被public或者默认访问修饰符所修饰,但是一个类如果作为内部类的时候,则可以被四种访问修饰符所修饰,因为一个类作为内部类的时候,就作为外部类的一个成员属性了,因此可以有四种访问修饰符修饰,这是内部类和外部类的一个区别。
      public用来修饰类中成员(变量和方法),被public所修饰的成员可以在任何类中都能被访问到。通过操作该类的对象能随意访问public成员。
      public在类的继承上的体现,被public所修饰的成员能被所有的子类继承下来。
  • protected:受保护的
    (1)定义:protected是受保护的,受到该类所在的包所保护。
    (2)作用域:被protected所修饰的成员会被位于同一package中的所有类访问到。同时,被protected所修饰的成员也能被该类的所有子类继承下来。(注意:这里是指同一个package或者不同的package中的子类都能访问)
  • 默认,缺省的
    (1)定义:在成员的前面不写任何的访问修饰符的时候,默认就是default。
    (2)作用域:同一package中的所有类都能访问。所修饰的成员只能被该类所在同一个package中的子类所继承下来。(也就是说只有在同一个package中的子类才能访问到父类中默认的修饰的成员)
  • private:私有的
    (1)定义:private是私有的,即只能在当前类中被访问到,它的作用域最小

该题目比较简单,不同的权限修饰符的区见表格。

6.重载(overload)和重写(overwrite)区别

重载的规则(两同一不同)

  1. 同一个类中
  2. 方法名相同
  3. 参数列表不同(个数,顺序,类型不同都算)
  4. 和返回值无关

重写的规则

  1. 方法名、参数列表必须和父类完全一致
  2. 返回值类型要么相同,要么子类方法的返回值类型是父类方法返回值类型的子类!
    在这里插入图片描述
  3. 访问修饰符要么相同,要么子类访问修饰符范围大于父类!
     在这里插入图片描述
  4. 方法中抛出的异常,要么相同。要么子类方法抛出的异常比父类被重写方法抛出的异常更小或相同!
    在这里插入图片描述

这道题在初级程序员面试的时候,出镜率相当高!但是注意图中黄颜色的部分,答题时如果能把细微的区别答出来,才足以证明知识点的全面,注意细节!

7.接口和抽象类的异同点

abstract class和interface是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。具体看下表:

Abstract Class Interface
实例化 不能 不能
一种继承关系,一个类只能继承一个直接父类 可以实现多个接口,接口同时可以继承接口
成员 抽象的方法在抽象类中,也可以有其他的普通方法和属性等 只能有静态的常量和抽象方法,抽象方法是public abstract修饰,静态常量默认public static final修饰
设计理念 表示为"is a"关系 表示为"like a"关系
实现 需要继承,重写抽象方法,除非这个类也是抽象类 需要实现,重写抽象方法,除非这个类也是抽象的

接口其实理解为一个类,接口是完全抽象的,抽象类是部分抽象的!

8.Java中的代码块

  Java中的代码块有4种:
在这里插入图片描述

  • 普通代码块
    普通代码块:在方法或语句中出现的{}就称为普通代码块。
    普通代码块和一般的语句执行顺序由他们在代码中出现的次序决定–“先出现先执行”
/*
 * 普通代码块
 */
public class Demo {
	public static void main(String[] args) {
		{
			int x = 3;
			System.out.println("普通代码块内的变量x=" + x);
		}

		int x = 1;
		System.out.println("主方法内的变量x=" + x);

		{
			int y = 7;
			System.out.println("2,普通代码块内的变量y=" + y);
		}

	}
}
  • 构造代码块
    构造块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。
    构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造方法。
public class Demo1 {
	{
		System.out.println("第一代码块");
	}

	public Demo1() {
		System.out.println("构造方法");
	}

	{
		System.out.println("第二构造块");
	}

	public static void main(String[] args) {
		new Demo1();
		new Demo1();
		new Demo1();
	}
}
  • 静态代码块
    静态代码块: 在java中使用static关键字声明的代码块。静态块用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。由于JVM在加载类时会执行静态代码块,所以静态代码块先于主方法执行。
    如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。
    注意:
    静态代码块不能存在于任何方法体内。
    静态代码块不能直接访问静态实例变量和实例方法,需要通过类的实例对象来访问。
/*
 * 静态代码块
 */
public class Demo2 {

	

	static {
		System.out.println("Demo2的静态代码块");
	}

	public Demo2() {
		System.out.println("Demo2的构造方法");
	}

{
		System.out.println("Demo2的构造块");
	}


	public static void main(String[] args) {
		System.out.println("Demo2的主方法");
		new Demo2();
		new Demo2();
	}
}
  • 执行顺序
    执行顺序: 静态代码块>构造代码块>构造方法。(优先级从高到低。)
    其中静态代码块只执行一次。构造代码块在每次创建对象是都会执行。
    静态代码块一般做初始化!!

9.Java中的内部类

  • 概念:在Java中支持一个类中可以再定义一个类,如果类中包含类,那么就叫做内部类。
  • 内部类的分类
    成员内部类
    静态内部类
    局部内部类
    匿名内部类
  • 成员内部类
    概念:一个普通的类中嵌套一个普通的类。类之间有包含关系。成员内部类可以直接访问外部类的私有变量。
public class Outer{
     private int id;
     private String name;
     class Inner{
           private String name;
           public void fun(){
                 id = 100; // 访问外部类的属性
                  this.name="zhangsan";  // 访问 内部类的属性
                 Outer.this.name = "张三";  // 访问外部类的属性
           }
     }
}

调用方式:

成员内部类的使用:
内部类的对象必须依赖外部类的对象存在
使所以在创建内部类对象时,要先创建外部类对象
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

  • 成员内部类
    概念:一个类中嵌套一个静态的类,称作静态内部类。
/**
 * @author bruceliu
 * @time 2019年4月16日上午11:54:35
 * @Description 静态内部类演示
 */
public class Computer {

	private static String name;
	private double price;
	
	/**
	 * 静态内部类
	 */
	public static class CPU{
		
		private int hs;
		
		public void run(){
			//访问外部类静态属性
			System.out.println("我是CPU,计算机品牌是:"+name);
			System.out.println("CPU的核心数是:"+hs);
		}

		public int getHs() {
			return hs;
		}

		public void setHs(int hs) {
			this.hs = hs;
		}
		
	}
}

测试静态内部类:


/**
 * @author bruceliu
 * @time 2019年4月16日上午11:57:04
 * @Description
 */
public class TestComputer {

	public static void main(String[] args) {

		Computer.CPU cpu = new Computer.CPU();
		cpu.setHs(4);
		cpu.run();

	}
}

  • 局部内部类
    概念:Java方法中定义一个类,那么这个类就叫做局部内部类
/**
 * @author bruceliu
 * @time 2019年4月16日下午12:00:33
 * @Description 局部内部类
 */
public class Outer {

	int num=4;
	
	class MyIn{
		
	}
	
	public void method(final int y){
		
		final int x=3;
		
		class Inner{
			void show(){
				System.out.println("Show run--"+Outer.this.num);
				System.out.println("x="+x);
				System.out.println("y="+y);
			}
		}
		
		new Inner().show();
	}
}

访问:可以在包含局部内部类的方法中直接创建局部内部类的对象调用局部内部类的成员。
访问外部类的私有成员变量可以直接访问,但访问包含它的方法中定义的变量以及方法的参数时,参数必须别final修饰。
在Java中,方法的局部变量位于栈上,对象位于堆上。因为局部变量的范围被限制在该方法内,当一个方法结束时,栈结构被删除,该变量消失。但是,定义在这个类中的内部类对象仍然存活在堆上(将会找不到方法的局部变量的引用了),所以内部类对象不能使用局部变量。除非这些局部变量被标识为最终的(final),final修饰的变量位于堆。

在JDK8之前,如果我们在匿名内部类中需要访问局部变量,那么这个局部变量必须用final修饰符修饰。在JDK8之后,可以不用final修饰。

  • 匿名内部类
    内部类书写。(为了回调)
    匿名内部类的前提:必须继承一个父类或者是实现一个接口。
    匿名内部类的格式:

new 父类或者接口(){ 执行代码….};

/**
 * @author bruceliu
 * @time 2019年4月16日下午12:09:35
 * @Description 
 */
public class TestClass {

	Inner inner=new Inner() {
		
		@Override
		public void show2() {
			System.out.println("show1");
		}
		
		@Override
		public void show1() {
			System.out.println("show2");
		}
	};
	
	public void print(){
		inner.show1();
		inner.show2();
	}
}

interface Inner{
	void show1();
	void show2();
}

使用匿名内部类时,如果需要调用匿名内部类的两个方法或者两个方法以上。可以使用变量指向该对象。

10.为什么要用 clone

  在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现**clone()**方法是其中最简单,也是最高效的手段!

11.new一个对象的过程和clone一个对象的过程区别

  new操作符的本意是分配内存。程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
  clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和原对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

12. 复制对象和复制引用的区别

Personp=newPerson(23,"zhang");
Personp1=p;
System.out.println(p);
System.out.println(p1);

当Personp1=p;执行之后,是创建了一个新的对象吗?首先看打印结果:

com.bruceliu.Person@2f9ee1ac
com.bruceliu.Person@2f9ee1ac

可以看出,打印的地址值是相同的,既然地址都是相同的,那么肯定是同一个对象。p和p1只是引用而已,他们都指向了一个相同的对象Person(23,“zhang”)。可以把这种现象叫做引用的复制。上面代码执行完成之后,内存中的情景如下图所示:
在这里插入图片描述
而下面的代码是真真正正的克隆了一个对象。

Personp=newPerson(23,"zhang");	
Personp1=(Person)p.clone();
System.out.println(p);
System.out.println(p1);

从打印结果可以看出,两个对象的地址是不同的,也就是说创建了新的对象,而不是把原对象的地址赋给了一个新的引用变量:

com.bruceliu.Person@2f9ee1ac
com.bruceliu.Person@67f1fba0

以上代码执行完成后,内存中的情景如下图所示:
在这里插入图片描述

13. 深拷贝和浅拷贝

  上面的示例代码中,Person中有两个成员变量,分别是name和age,name是String类型,age是int类型。代码非常简单,如下所示:

public class Person implements Cloneable{

	private intage;
	private String name;

	public Person(intage,Stringname){
		this.age=age;
		this.name=name;
	}

	public Person(){
	}

	public int getAge(){
		return age;
	}

	public Stringg etName(){
		return name;
	}

	@Override
	protected Objectclone() throws CloneNotSupportedException{
		return (person)super.clone();
	}
}

由于age是基本数据类型,那么对它的拷贝没有什么意义,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的,它只是一个引用,指向一个真正的String对象,那么对它的拷贝有两种方式:直接将原对象中的name的引用值拷贝给新对象的name字段,或者是根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的person对象的name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝的原理如下图所示:
在这里插入图片描述

下面通过代码进行验证。如果两个Person对象的name的地址值相同,说明两个对象的name都指向同一个String对象,也就是浅拷贝,而如果两个对象的name的地址值不同,那么就说明指向不同的String对象,也就是在拷贝Person对象的时候,同时拷贝了name引用的String对象,也就是深拷贝。验证代码如下:

public static void main(String[] args)throws Exception{
		Person p=new Person(23,"zhang");
		Person p1=(Person)p.clone();
		String result=p.getName()==p1.getName()?"clone是浅拷贝的":"clone是深拷贝的";
		System.out.println(result);
}

打印结果为:clone是浅拷贝的。

如何进行深拷贝:
由上内容可以得出如下结论:如果想要深拷贝一个对象,这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份,这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。那么,按照上面的结论,实现以下代码Body类组合了Head类,要想深拷贝Body类,必须在Body类的clone方法中将Head类也要拷贝一份。代码如下:

public class Maintest{

	static class Body implements Cloneable{
		public Head head;

		public Body(){
		}

		public Body(Head head){
			this.head=head;
		}

		@Override
		protected Object clone()throws CloneNotSupportedException{
			Body newBody=(Body)super.clone();
			newBody.head=(Head)head.clone();
			return newBody;
		}
	}

	static class Head implements Cloneable{
		public Head(){
		}

		@Override
		protected Object clone()throws CloneNotSupportedException{
			returnsuper.clone();
		}
	}

	public static void main(String[]args)throws CloneNotSupportedException{
		Body body=new Body(newHead());
		Body body1=(Body)body.clone();
		System.out.println("body==body1:"+(body==body1));
		System.out.println("body.head==body1.head:"+(body.head==body1.head));
	}
}

打印结果

body==body1:false
body.head==body1.head:false

14.两个对象值相同(x.equals(y)==true),但却可有不同的hashCode,这句话对不对?

  不对,如果两个对象x和y满足x.equals(y)==true,它们的哈希码(hashCode)应当相同。
  Java对于eqauls方法和hashCode方法是这样规定的:
(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
(2)如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
首先equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true时,y.equals(x)也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false。
实现高质量的equals方法的诀窍包括:
1.使用==操作符检查"参数是否为这个对象的引用";
2.使用instanceof操作符检查"参数是否为正确的类型";
3.对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
4.编写完equals方法后,问自己它是否满足对称性、传递性、一致性;
5.重写equals时总是要重写hashCode;
6.不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。

关于equals和hashCode方法,很多Java程序员都知道,但很多人也就是仅仅知道而已,在JoshuaBloch的大作《EffectiveJava》(很多软件公司,《EffectiveJava》、《Java编程思想》以及《重构:改善既有代码质量》是Java程序员必看书籍,如果你还没看过,那就赶紧去买一本吧)中是这样介绍equals方法的。

15.为什么函数不能根据返回类型来区分重载

  因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。例如:

1float max(int a, int b);
2int max(int a, int b);

当调用max(1, 2);时无法确定调用的是哪个,单从这一点上来说,仅返回值类型不同的重载是不应该允许的。
再比如对下面这两个方法来说,虽然它们有同样的名字和自变量,但其实是很容易区分的:

1void f() {}
2int f() {}

若编译器可根据上下文(语境)明确判断出含义,比如在int x=f()中,那么这样做完全没有问题。然而,我们也可能调用一个方法,同时忽略返回值;我们通常把这称为“为它的副作用去调用一个方法”,因为我们关心的不是返回值,而是方法调用的其他效果。所以假如我们像下面这样调用方法:f(); Java 怎样判断f()的具体调用方式呢?而且别人如何识别并理解代码呢?由于存在这一类的问题,所以不能。
  函数的返回值只是作为函数运行之后的一个“状态”,他是保持方法的调用者与被调用者进行通信的关键。并不能作为某个方法的“标识”。

16.char型变量中能不能存储一个中文汉字

  char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。

使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于C程序员来说,要完成这样的编码转换恐怕要依赖于union(联合体/共用体)共享内存的特征来实现了。

17.Java有没有goto语句

  goto是Java中的保留字,在目前版本的Java中没有使用。根据JamesGosling(Java之父)编写的《TheJava Programming Language》一书的附录中给出了一个Java关键字列表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意义,因为熟悉C语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合都被视为保留字。

18.&和&&的区别

  &运算符有两种用法:
  (1)按位与;
  (2)逻辑与。
  &&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。
  &&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为 username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

19.在Java中,如何跳出当前的多重嵌套循环

  在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环。(Java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像要避免使用goto一样,应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用)。

20.是否可以继承String

  String 类是 final 类,不可以被继承。
  继承 String 本身就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。

21.抽象的(abstract)方法是否可同时是静态的(static), 是否可同时是本地方法(native),是否可同时被 synchronized

  都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized 和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的.

22.阐述静态变量和实例变量的区别

  静态变量: 是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
  实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。

23.==和equals 的区别

  equals 和== 最大的区别是一个是方法一个是运算符。
  ==:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。
  equals():用来比较方法两个对象的内容是否相等。

注意:equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址。

24.break 和 continue 的区别

  break 和 continue 都是用来控制循环的语句。break 用于完全结束一个循环,跳出循环体执行循环后面的语句。continue 用于跳过本次循环,执行下次循环。

25.String s=“Hello”;s=s+“world!”;这两行代码执行后,原始的 String 对象中的内容到底变了没有

  没有。因为 String 被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s 原先指向一个 String 对象,内容是 “Hello”,然后我们对 s 进行了“+”操作,那么 s 所指向的那个对象是否发生了改变呢?答案是没有。这时,s 不指向原来那个对象了,而指向了另一个 String 对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是 s 这个引用变量不再指向它了。
  通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用 String 来代表字符串的话会引起很大的内存开销。因为 String 对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个 String 对象来表示。这时,应该考虑使用 StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都 new 一个 String。例如我们要在构造器中对一个名叫 s 的 String 引用变量进行初始化,把它设置为初始值,应当这样做:

public class Demo {
   private String s;
     ...
     s = "Initial Value";
     ...
   }
}

而非

s = new String("Initial Value");

后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为 String 对象不可改变,所以对于内容相同的字符串,只要一个 String 对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的 String 类型属性 s 都指向同一个对象。
  上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java 认为它们代表同一个String 对象。而用关键字 new 调用构造器,总是会创建一个新的对象,无论内容是否相同。 至于为什么要把 String 类设计成不可变类,是它的用途决定的。其实不只 String,很多 Java 标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以 Java 标准类库还提供了一个可变版本,即 StringBuffer。

26.Java中异常的体系结构

  如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
在这里插入图片描述

27.Java中的异常处理机制的简单原理和应用

  当JAVA 程序违反了JAVA 的语义规则时,JAVA 虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2 种情况。
  一种是JAVA 类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null 的对象时会引发NullPointerException。
  另一种情况就是JAVA 允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw 关键字引发异常。所有的异常都是java.lang.Thowable 的子类。

28.Java中的异常的分类

  • Error
    Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果
    出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。
    Exception(RuntimeException、CheckedException)
  • Exception 又有两个分支,一个是运行时异常 RuntimeException ,一个是CheckedException。
    RuntimeException 如 : NullPointerException 、 ClassCastException ;一个是检查异常
    CheckedException,如 I/O 错误导致的 IOException、SQLException。 RuntimeException 是
    那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一定是程序员的错误.检查异常 CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常举例:
    1.试图在文件尾部读取数据
    2.试图打开一个错误格式的 URL
    3.试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在
    4…

29.error和exception有什么区别

  error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;
  exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况;

30.throw和throws的区别

  • throw
    1)throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
    2)throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常。
  • throws
    1)throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
    2)throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
    3)throws 表示出现异常的一种可能性,并不一定会发生这种异常。

31.final、finally、finalize的区别

1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
2)finally:异常处理语句结构的一部分,表示总是执行。
3)finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。

32.请写出你最常见的5个RuntimeException

下面列举几个常见的 RuntimeException。
1)java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
2)java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序
试图通过字符串来加载某个类时可能引发异常。
3)java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对象时发生。
5)java.lang.IllegalArgumentException 方法传递参数错误。
6)java.lang.ClassCastException 数据类型转换异常。
7)java.lang.NoClassDefFoundException 未找到类定义错误。
8)SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
9)java.lang.InstantiationException 实例化异常。
10)java.lang.NoSuchMethodException 方法不存在异常。

33.类Example A 继承Exception,类ExampleB 继承Example A

有如下代码片断:

try{
   throw new ExampleB(“b”)}catch(ExampleA e){
   System.out.printfln(“ExampleA”);
}catch(Exception e){
   System.out.printfln(“Exception”);
}

输出的内容应该是:
A:ExampleA     B:Exception     C:b     D:无
答:输出为A。

34.JAVA 语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try 块中可以抛出异常吗?

  Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java 中,每个异常都是一个对象,它是Throwable 类或其它子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java 的异常处理是通过5 个关键词来实现的:try、catch、throw、throws 和finally。一般情况下是用try 来执行一段程序,如果出现异常,系统会抛出(throws)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理;try 用来指定一块预防所有“异常”的程序;catch 子句紧跟在try 块后面,用来指定你想要捕捉的“异常”的类型;throw 语句用来明确地抛出一个“异常”;throws 用来标明一个成员函数可能抛出的各种“异常”;finally 为确保一段代码不管发生什么“异常”都被执行一段代码;可以在一个成员函数调用的外面写一个try 语句,在这个成员函数内部写另一个try 语句保护其他代码。每当遇到一个try 语句,“异常”的框架就放到堆栈上面,直到所有的try 语句都完成。如果下一级的try 语句没有对某种“异常”进行处理,堆栈就会展开,直到遇到有处理这种“异常”的try 语句。

35.java 异常处理机制

  Java 对异常进行了分类,不同类型的异常分别用不同的 Java 类表示,所有异常的根类为 java.lang.Throwable,Throwable 下面又派生了两个子类:Error 和 Exception,Error 表示应用程序本身无法克服和恢复的一种严重问题。
  Exception 表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
  java 为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须 try…catch 处理或用 throws 声明继续抛给上层调用方法处理,所以普通异常也称为 checked 异常,而系统异常可以处理也可以不处理,所以,编译器不强制用 try…catch 处理或用 throws 声明,所以系统异常也称为 unchecked 异常。

36.Java的基本数据类型都有哪些各占几个字节

如下表所示:

四类 八种 字节数 数据表示范围
整型 byte 1 -128~127
整型 short 2 -32768~32767
整型 int 4 -2147483648~2147483647
整型 long 8 -263~263-1
浮点型 float 4 -3.403E38~3.403E38
浮点型 double 8 -1.798E308~1.798E308
字符型 char 2 表示一个字符,如(‘a’,‘A’,‘0’,‘家’)
布尔型 boolean 1 只有两个值 true 与 false

37.String是基本数据类型吗?

String 是引用类型,底层用 char 数组实现的。

38.short s1 = 1; s1 = s1 + 1; 有错吗?short s1 = 1; s1 += 1 有错吗?

前者不正确,后者正确。对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short)(s1 + 1);其中有隐含的强制类型转换.

39.int 和 和 Integer 有什么区别?

Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
原始类型: boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

/**
 * @author bruceliu
 * @create 2019-04-24 10:50
 * 自动拆箱和装箱测试
 */
public class Demo1 {
    public static void main(String[] args) {

        Integer a=new Integer(3);
        Integer b=3;  //装箱:将3自动装箱为Integer类型
        int c=3;

        System.out.println(a==b); // false 两个引用没有指向同一个对象
        System.out.println(a==c); // true 自动拆箱 int类型再和c比较

    }
}

40.下面 Integer 类型的数值比较输出的结果为?

/**
 * @author bruceliu
 * @create 2019-04-24 10:54
 */
public class Demo2 {

    public static void main(String[] args) {

        Integer f1=100,f2=100,f3=150,f4=150;
        
        System.out.println(f1==f2);
        System.out.println(f3==f4);

    }
}

运行结果:

true
false

如果不明就里很容易认为两个输出要么都是 true 要么都是 false。首先需要注意的是 f1、f2、f3、f4 四个变量都是 Integer 对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个 Integer 对象赋一个 int 值的时候,会调用 Integer 类的静态方法 valueOf,如果看看 valueOf 的源代码就知道发生了什么。

    /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

IntegerCache 是 Integer 的内部类,其代码如下所示:

/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

简单的说,如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的 Integer 对象,所以上面的面试题中 f1==f2 的结果是 true,而 f3==f4 的结果是 false。

越是貌似简单的面试题其中的玄机就越多,需要面试者有相当深厚的功力。

41.String 类常用方法

方法 说明
int length() 获取字符串的长度,其实也就是字符个数
char charAt(int index) 获取指定索引处的字符
int indexOf(String str) 获取str在字符串对象中第一次出现的索引
String substring(int start) 从start开始截取字符串
String substring(int start,int end) 从start开始,到end结束截取字符串。包括start,不包括end
boolean equals(Object obj) 比较字符串的内容是否相同
boolean equalsIgnoreCase(String str) 比较字符串的内容是否相同,忽略大小写
boolean startsWith(String str) 判断字符串对象是否以指定的str开头
boolean endsWith(String str) 判断字符串对象是否以指定的str结尾
char[] toCharArray() 把字符串转换为字符数组
String toLowerCase() 把字符串转换为小写字符串
String toUpperCase() 把字符串转换为大写字符串
String trim() 去除字符串两端空格
String[] split(String str) 按照指定符号分割字符串
byte[] getbytes() 将字符串转化为字节数组

42.String 类常用几个构造方法

方法名 说明
String(String original) 把字符串数据封装成字符串对象
String(char[] value) 把字符数组的数据封装成字符串对象
String(char[] value, int index, int count) 把字符数组中的一部分数据封装成字符串对象

43.String、StringBuffer、StringBuilder 的区别

  • 可变不可变
    String:字符串常量,在修改时不会改变自身;若修改,等于重新生成新的字符串对象。
    StringBuffer:在修改时会改变对象自身,每次操作都是对 StringBuffer 对象本身进行修改,不是生成新的对象;使用场景:对字符串经常改变情况下,主要方法:append(),insert()等。
  • 线程是否安全
    String:对象定义后不可变,线程安全。
    StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操作字符串缓冲区大量数据。
    StringBuilder:是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。
  • 共同点
    StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder(抽象类)。
    StringBuilder、StringBuffer 的方法都会调用 AbstractStringBuilder 中的公共方法,如 super.append(…)。只是 StringBuffer 会在方法上加 synchronized 关键字,进行同步。最后,如果程序不是多线程的,那么使用StringBuilder 效率高于 StringBuffer。

44.StringBuffer常用的方法

方法 说明
public int capacity() 返回当前容量 (理论值)
public int length() 返回长度(已经存储的字符个数)
public StringBuilder append(任意类型) 添加数据,并返回自身对象
public StringBuilder reverse() 反转功能
public String toString() 通过toString()就可以实现把StringBuilder转成String

45.数据类型之间的转换

  • 字符串如何转基本数据类型?
    调用基本数据类型对应的包装类中的方法 parseXXX(String)或 valueOf(String)即可返回相应基本类型。
  • 基本数据类型如何转字符串?
    一种方法是将基本数据类型与空字符串(“”)连接(+)即可获得其所对应的字符串;另一种方法是调用 String 类中的 valueOf()方法返回相应字符串。

46.Object类中常用的方法

方法 说明
getClass() 返回此 Object 的运行类
hashCode() 用于获取对象的哈希值
equals(Object obj) 用于确认两个对象是否“相同”
clone() 创建并返回此对象的一个副本
toString() 返回该对象的字符串表示
notify() 唤醒在此对象监视器上等待的单个线程
notifyAll() 唤醒在此对象监视器上等待的所有线程
wait(long timeout 超过指定的时间量前,导致当前线程等待
wait(long timeout, int nanos) 超过指定的时间量前,导致当前线程等待
wait() 用于让当前线程失去操作权限,当前线程进入等待序列
finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法

47.Native关键字作用

java关键字,Native Method 用以修饰非 java 代码实现的方法(C || C++), 类似java调用非java代码的接口。

48. Java中有几种类型的流

按照流的方向:输入流(inputStream)和输出流(outputStream)。
按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)和处理(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。)
按照处理数据的单位:字节流和字符流。字节流继承于 InputStream 和 OutputStream,字符流继承于InputStreamReader 和 OutputStreamWriter。
在这里插入图片描述
这么庞大的体系里面,常用的就那么几个,我们把它们抽取出来,如下图:
在这里插入图片描述

49.字节流如何转为字符流

字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。

50.字节流和字符流的区别

字节流读取的时候,读到一个字节就返回一个字节;
字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在UTF-8码表中是3个字节)时。先去查指定的编码表,将查到的字符返回。字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。字节流主要是操作byte类型数据,以byte数组为准,主要操作类就是OutputStream、InputStream字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。在程序中一个字符等于两个字节,java提供了Reader、Writer两个专门操作字符流的类。

猜你喜欢

转载自blog.csdn.net/BruceLiu_code/article/details/89070662