Java基础面试题2021-5-26

1. 理解面向对象思想

面向对象是一种思想,是基于面向过程而言的,就是说面向对象是将功能等通过对象来实现,将功能封装进对象之中,让对象去实现具体的细节;这种思想是将数据作为第一位,而方法或者说是算法作为其次,这是对数据一种优化,操作起来更加的方便,简化了过程。面向对象有三大特征:封装性、继承性、多态性,其中封装性指的是隐藏了对象的属性和实现细节,仅对外提供公共的访问方式,这样就隔离了具体的变化,便于使用,提高了复用性和安全性。对于继承性,就是两种事物间存在着一定的所属关系,那么继承的类就可以从被继承的类中获得一些属性和方法;这就 提高了代码的复用性。继承是作为多态的前提的。多态是说父类或接口的引用指向了子类对A象,这就提高了程序的扩展性,也就是说只要实现或继承了同一个接口或类,那么就可以使用父类中相应的方法,提高程序扩展性,但是多态有一点不好之处在于:父类引用不能访问子类中的成员。

举例来说:就是:比如说你要去饭店吃饭,你只需要饭店,找到饭店的服务员,跟她说你要吃什么,然后叫会给你做出来让你吃,你并不需要知道这个饭是怎么错的,你只需要面向这个服务员,告诉他你要吃什么,然后他也只需要面向你吃完收到钱就好,不需要知道你怎么对这个饭进行吃。

特点:

1:将复杂的事情简单化。

2:面向对象将以前的过程中的执行者,变成了指挥者。

3:面向对象这种思想是符合现在人们思考习惯的一种思想。

1.1、面向对象都有哪些特性以及你对这些特性的理解

继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。

多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时,B 系统有多种提供服务的方式, 但一切对 A 系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

1.2  Java 中实现多态的机制是什么?

靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

2. OOP的基本特性和设计原则有哪些。

什么是OOP,Object Oriented Programming,是面向对象的编程,还有OOD(面向对象的设计),OOA(面向对象的分析)

对于面向对象的特性,一般有两种说法:一种是有三大特性,分别是封装,继承,多态,另一种说法是有四大特性,封装,继承,多态,抽象。讲三大特性的时候更多一些,注意,这里的提问方式是面向对象的特性,而不是Java的特性。

简洁介绍:

面向对象的三大特性:封装,继承、多态

1.封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式

好处:将变化隔离、便于使用、提高复用性、提高安全性

原则:将不需要对外提供的内容隐藏起来;把属性隐藏,提供公共方法对其访问

2.继承:提高代码复用性;继承是多态的前提

注:①子类中所有的构造函数都会默认访问父类中的空参数的构造函数,默认第一行有super();若无空参数构造函数,子类中需指定;另外,子类构造函数中可自己用this指定自身的其他构造函数。

3.多态: 是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象

好处:提高了程序的扩展性

弊端:当父类引用指向子类对象时,虽提高了扩展性,但只能访问父类中具备的方法,不可访问子类中的方法;即访问的局限性。

前提:实现或继承关系;覆写父类方法。

五大基本原则:

1、单一职责原则SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,跟杂货铺似的。
2、开放封闭原则OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
3、里式替换原则LSP(the Liskov Substitution Principle LSP)
子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
4、依赖倒置原则DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的是抽象的中国人,而不是你是xx村的。
5、接口分离原则ISP(the Interface Segregation Principle ISP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

详细介绍:

OOP的三大基本特性是抽象与封装,继承与多态:

1.抽象与封装:

抽象是把系统中需要处理的数据和在这些数据上的操作结合在一起,根据功能、性质和用途

等因素抽象成不同的抽象数据类型。每个抽象数据类型既包含了数据,又包含了针对这些数

据的授权操作。在面向对象的程序设计中,抽象数据类型是用“类”这种结构来实现的,每个类

里都封装了相关的数据和操作。

封装是指利用抽象数据类型和基于数据的操作结合在一起,数据被保护在抽象数据类型的内

部,系统的其他部分只有通过包裹在数据之外被授权的操作,才能与这个抽象数据类型进行

交互。

2. 继承:

它是与传统方法不同的一个最有特色的方法。它是面向对象的程序中两个类之间的一种关

系,即一个类可以从另一个类(即它的父类)继承状态和行为。继承父类的类称为子类。

继承的优越性:通过使用继承,程序员可以在不同的子类中多次重新使用父类中的代码,使

程序结构清晰,易于维护和修改,而子类又可以提供一些特殊的行为,这些特殊的行为在父

类中是没有的

3.多态:

是指一个程序中同名的方法共存的情况,调用者只需使用同一个方法名,系统会根据不同情

况,调用相应的不同方法,从而实现不同的功能。多态性又被称为“一个名字,多个方法”。

设计原则有五种:

1. 单一职责原则(Single-Resposibility Principle)。"对一个类而言,应该仅有一个引起它变化的原

因。"本原则是我们非常熟悉地"高内聚性原则"的引申,但是通过将"职责"极具创意地定义

为"变化的原因",使得本原则极具操作性,尽显大师风范。同时,本原则还揭示了内聚性和耦

合生,基本途径就是提高内聚性;如果一个类承担的职责过多,那么这些职责就会相互依

赖,一个职责的变化可能会影响另一个职责的履行。其实OOD的实质,就是合理地进行类的

职责分配。

2. 开放封闭原则(Open-Closed principle)。"软件实体应该是可以扩展的,但是不可修改。"本原

则紧紧围绕变化展开,变化来临时,如果不必改动软件实体裁的源代码,就能扩充它的行

为,那么这个软件实体设计就是满足开放封闭原则的。如果说我们预测到某种变化,或者某

种变化发生了,我们应当创建抽象类来隔离以后发生的同类变化。在Java中,这种抽象是指抽

象基类或接口;在C++中,这各抽象是指抽象基类或纯抽象基类。当然,没有对所有情况都贴

切的模型,我们必须对软件实体应该面对的变化做出选择。

3. Liskov替换原则(Liskov-Substituion Principle)。"子类型必须能够替换掉它们的基类型。"本原

则和开放封闭原则关系密切,正是子类型的可替换性,才使得使用基类型模块无需修改就可

扩充。Liskov替换原则从基于契约的设计演化而来,契约通过为每个方法声明"先验条

件"和"后验条件";定义子类时,必须遵守这些"先验条件"和"后验条件"。当前基于契的设计发

展势头正劲,对实现"软件工厂"的"组装生产"梦想是一个有力的支持。

4. 依赖倒置原则(Dependecy-Inversion Principle)。"抽象不应依赖于细节,细节应该依赖于抽

象。"本原则几乎就是软件设计的正本清源之道。因为人解决问题的思考过程是先抽象后具

体,从笼统到细节,所以我们先生产出的势必是抽象程度比较高的实体,而后才是更加细节

化的实体。于是,"细节依赖于抽象"就意味着后来的依赖于先前的,这是自然而然的重用之

道。而且,抽象的实体代表着笼而统之的认识,人们总是比较容易正确认识它们,而且本身

也是不易变的,依赖于它们是安全的。依赖倒置原则适应了人类认识过程的规律,是面向对

象设计的标志所在。

5. 接口隔离原则(Interface-Segregation Principle)。"多个专用接口优于一个单一的通用接口。"本

原则是单一职责原则用于接口设计的自然结果。一个接口应该保证,实现该接口的实例对象

可以只呈现为单一的角色;这样,当某个客户程序的要求发生变化,而迫使接口发生改变

时,影响到其他客户程序的可能生性小。

良性依赖原则。"不会在实际中造成危害的依赖关系,都是良性依赖。"通过分析不难发现,本

原则的核心思想是"务实",很好地揭示了极限编程(Extreme Programming)中"简单设计"各"重

构"的理论基础。本原则可以帮助我们抵御"面向对象设计五大原则"以及设计模式的诱惑,以

免陷入过度设计(Over-engineering)的尴尬境地,带来不必要的复杂性。

3. Java多态的具体体现 

多态是面向对象很重要的一个特性,转型是多态的具体体现。多态还包括重载和重写。

转型:转型分为向上转型和向下转型。向上转型是子类对象转换成父类对象,直接转就可以,向下转型是父类对象转换成子类对象,这需要强转,在转换过程中要保证此父类对象“本体”是由子类对象实例化的。

多态有四种体现形式:

1. 接口和接口的继承。

2. 类和类的继承。

3. 重载。

4. 重写。

其中重载和重写为核心。

重载:重载发生在同一个类中,在该类中如果存在多个同名方法,但是方法的参数类型和个数不一样,那么说明该方法被重载了。

重写:重写发生在子类继承父类的关系中,父类中的方法被子类继承,方法名,返回值类型,参数完全一样,但是方法体不一样,那么说明父类中的该方法被子类重写了。

构造器是否可以被重写?

构造器(构造方法)Constructor不能被继承,因此不能重写Override,但可以被重载Overload。

4. 抽象类和接口的区别

抽象类由public abstract修饰,接口由public interface修饰。

含有abstract修饰符的class即为抽象类,abstract类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

下面比较一下两者的语法区别:

1. 抽象类可以有构造方法,接口中不能有构造方法。

2. 抽象类中可以有普通成员变量,接口中没有普通成员变量,  接口中只能定义static final变量,并且需要进行初始化

3. 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中的抽象方法的访问类型可以是public,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

5. 抽象类中可以包含静态方法,接口中不能包含静态方法

6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

7. 一个类可以实现多个接口,但只能继承一个抽象类。

8.有抽象方法的类一定是抽象类,但是抽象类里面不一定有抽象方法;

9. 类(抽象类也是类)只支持单继承,但接口可以实现多个接口。接口不是类,接口和类同属于Java中的一个类型。

10.抽象类里由构造器、常量、变量、抽象方法、普通方法构成,接口里只包括常量和抽象方法,没有构造器和普通方法。

11.是否是使用抽象类还是接口,主要看想要实现什么样的事情,如果侧重于描述事务,应该选择抽象类,如果侧重于定义功能,建议选择使用接口。

12. 抽象类由public abstract修饰,接口由public interface修饰。

下面接着再说说两者在应用上的区别:

接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约;

而抽象类在代码实现方面发挥作用,可以实现代码的重用。

  关系

接口和接口      继承

接口和抽象类    抽象类实现接口

类和抽象类      类继承抽象类

类和类          继承

修饰符的作用

修饰符

同一个类中

同一个包中

子类中

全局

private

Yes

 

 

 

Default

Yes

Yes

 

 

protected

Yes

Yes

Yes

 

public

Yes

Yes

Yes

Yes

 

 5. Java介绍

 Java是面向对象的,跨平台的,它通过java虚拟机来进行跨平台操作,它可以进行自动垃圾回收的(c语言是通过人工进行垃圾回收)。

 java还会进行自动分配内存。(c语言是通过指定进行分配内存的),只需要new一个对象,这个对象占用了多少空间,不需要我们来管,java虚拟机负责管这些,用完之后也不需要我们来释放,java虚拟机会自动释放。

5.1  J2SE、J2EE、J2ME三者区别 

1.Java SE = Java Standard Edition=j2se = java  标准版

2.Java EE = Java Enterprise Edition=j2ee= java 企业版

3.Java ME = Java Mobile Edition=j2me = java  移动版

三者特点:

J2SE主要用于桌面程序(swing),控制台开发(main程序)。

J2EE企业级开发(JSP,EJB,Spring MVC,Struts,hibernate,ibatis等),用于企业级软件开发,网络开发,web开发。

J2ME嵌入式开发(手机,小家电,PDA)。[苹果的ios,黑莓]。

三者之间的关系:

Java SE(Java Platform,Standard Edition,Java标准版)就是基于JDK和JRE的。

Java SE为Java EE提供了基础。

Java EE除了基于我们这个所谓的Java SE外,还新加了企业应用所需的类库。

5.2   JDK、JRE、JVM区别  (Jvm内存结构)

Jdk【Java Development ToolKit】就是java开发工具箱, JDK是整个JAVA的核心里边包含了jre,它除了包含jre之外还包含了一些javac的工具类,把java源文件编译成class文件,java文件是用来运行这个程序的,除此之外,里边还包含了java源生的API,java.lang.integer在rt的jar包里边【可以在项目中看到】,通过rt这个jar包来调用我们的这些io流写入写出等。

JDK有以下三种版本:

J2SE,standard edition,标准版,是我们通常用的一个版本

J2EE,enterpsise edtion,企业版,使用这种JDK开发J2EE应用程序

J2ME,micro edtion,主要用于移动设备、嵌入式设备上的java应用程序

Jre【Java Runtime Enviromental】是java运行时环境,那么所谓的java运行时环境,就是为了保证java程序能够运行时,所必备的一基础环境,也就是它只是保证java程序运行的,不能用来开发,而jdk才是用来开发的,所有的Java程序都要在JRE下才能运行。

包括JVM和JAVA核心类库和支持文件。与JDK相比,它不包含开发工具——编译器、调试器和其它工具。Jre里边包含jvm。

Jvm:【Java Virtual Mechinal】因为jre是java运行时环境,java运行靠什么运行,而底层就是依赖于jvm,即java虚拟机,java虚拟机用来加载类文件,java中之所以有跨平台的作用,就是因为我们的jvm。

三者关系:J2se是基于jdk和jre,JDK是整个JAVA的核心里边包含了jre,Jre里边包含jvm。

JRE与JDK的区别?

JRE:java运行时的环境,JDK:包含JRE并且可以查看源码

JDK常用的包

java.lang: 这个是系统的基础类,比如String、Math、Integer、System和Thread,提供常用功能。

java.io: 这里面是所有输入输出有关的类,比如文件操作等。

java.net: 这里面是与网络有关的类,比如URL,URLConnection等。

java.util : 这个是系统辅助类,特别是集合类Collection,List,Map等。 

java.sql: 这个是数据库操作的类,Connection,Statememt,ResultSet等。

jvm的内存结构

java虚拟机的内存结构分为堆(heap)和栈(stack),堆里面存放是对象实例也就是new出来的对象。栈里面存放的是基本数据类型以及引用数据类型的地址。

对于所谓的常量是存储在方法区的常量池里面。

堆和栈

堆是先进先出

栈是先进后出 

堆和的区别:  

Java的内存分为两类,一类是栈内存,一类是堆内存。

栈中存储的是当前线程的方法调用、基本数据类型和对象的引用,栈是有序的。

堆中存储的是对象的值,堆是无序的。

全局变量存放在哪里?

方法中的局部变量使用final修饰后,放在堆中,而不是栈中。

图解java面试题 - JVM

   内容大纲

  1. GC是什么?为什么要有GC?

2. 垃圾回收的优点和原理,并考虑两种回收机制

3. 垃圾回收器的基本原理是什么

4.Java中会有内存泄漏吗

5.ClassLoader如何加载class

 

6.JVM内存模型图

内存泄漏 (memory leak) 和 内存溢出(out of memory)

内存泄漏 (memory leak),是指应用程序在申请内存后,无法释放已经申请的内存空间。一次内存泄露危害可以忽略,但如果任其发展最终会导致内存溢出(out of memory)。如读取文件后流要进行及时的关闭以及对数据库连接的释放。

内存溢出(out of memory)是指应用程序在申请内存时,没有足够的内存空间供其使用。如我们在项目中对于大批量数据的导入,采用分段批量提交的方式。

 

 Java中会有内存泄漏

会,原因:

如果对象被集合类引用时,如果只是添加,而不删除,会引起内存泄漏,严重时会发出内存溢出。

Java中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏。

内存泄漏的另外一种情况:当一个对象被存储进HashSet或HashMap中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。

 

5.3  JVM 垃圾回收机制和常见算法

什么时候一个对象会被GC(引用计数法、可达性分析)

理论上来讲 Sun 公司只定义了垃圾回收机制规则而不局限于其实现算法,因此不同厂商生产的虚拟机采用的算法也不尽相同。

GC(Garbage Collector)在回收对象前首先必须发现那些无用的对象,如何去发现定位这些无用的对象?常用的搜索算法如下:

  1. 引用计数器算法(废弃)

引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候, 计数器-1,当计数器为 0 的时候,JVM 就认为对象不再被使用,是“垃圾”了。该方法的优势就是高效,简单,但无法解决对象之间互相引用造成的问题。

但是不能解决循环引用问问题(A 对象引用 B 对象,B 对象又引用 A 对象,但是A,B 对象已不被任何其他对象引用),同时每次计数器的增加和减少都带来了很多额外的开销,所以在 JDK1.1 之后, 这个算法已经不再使用了。

     2.  根搜索算法(使用)

可达性分析:可达性分析就是通过一系列的被称为“GC Roots”的对象,从这些节点向下进行搜索,搜索走过的路径被称为引用链,当一个对象到GC Roots没有任何引用链时,证明这个对象是不可用的。JVM采用的就是这种方法。

根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被 GC Roots 的引用链连接的时候,说明这个对象是不可用的。

GC Roots 对象包括:

  1. 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
  2. 方法区域中的类静态属性引用的对象。
  3. 方法区域中常量引用的对象。
  4. 本地方法栈中 JNI(Native 方法)的引用的对象。

通过上面的算法搜索到无用对象之后,就是回收过程,回收算法如下:

 1)标记—清除算法(Mark-Sweep)(DVM 使用的算法

标记—清除算法包括两个阶段:“标记”和“清除”。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。

标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。

 2)复制算法(Copying)

复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,  然后把这块内存整个清理掉。

复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。现在的 JVM 用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是 1:1(大概是 8:1)。

3) 标记—整理算法(Mark-Compact)

标记—整理算法和标记—清除算法一样,不同的地方在于,标记清除算法只是把可回收的对象进行垃圾回收,不会对剩余的内存空间进行整理,而标记整理算法则会对存活的对象进行整理,把存活对象往内存的一端移动,然后直接回收边界以外的内存。标记—整理算法提高了内存的利用率,但是效率低,它适合收集对象存活时间较长的老年代。

4)分代收集(Generational Collection)

分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。

新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。

 

6. == 和 equals方法的区别

"=="比较的是栈上的值(基本数据类型比较值的方式)

equlas比较的是堆上的值(引用类型比较值的方式)

“==”如果前后比较的是对象,则比较的是对象的地址,如果前后是数字,则比较的数值。

“equals”如果前后是对象,比较的是对象的地址。如果比较的对象重写了equals方法,则有可能比较的是“值”。像八个基本数据类型的封装类、String、File、Date都比较的是值。

7. Java中的数据类型都有哪些?

Java中数据类型分为两种:基本数据类型和引用数据类型。

分别是8种基本数据类型:byte、short、int、long、float、double、char、boolean

除8种以外统称为引用类型,例如:String类、Object类、数组及自己创建的类等

在这里插入图片描述

基本数据类型:

数据类型

大小

byte(字节)

1(8位)

shot(短整型)

2(16位)

int(整型)

4(32位)

long(长整型)

8(32位)

float(浮点型)

4(32位)

double(双精度)

8(64位)

char(字符型)

2(16位)

boolean(布尔型)

1位

  1. Char型变量能不能存储一个汉字?

    char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。

  2. Integer和int的区别

    int是java提供的8种原始数据类型之一,意思整型,占用4字节。

    Integer是java为int提供的封装类,是引用数据类型。

    int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况。

  3.  int类型和String类型之间的互相转换?

    int -->String常用的方法:

    1. 字符串拼接

    2. String b = String.valueof(int a)方法

    String -->int常用的方法:

    int i = Integer.valueof(String a)方法

    int i = Integer.parseInt(String a)方法

  4. String相关知识

String是基本数据类型吗?(String不是基本数据类型)

String的长度是多少,有限制?(长度受内存大小的影响)

String、StringBuffer、StringBuilder区别

(1)String是字符串常量,一旦创建后就不可改变,String的拼接会创建新的字符串常量对象并回收之前的对象,频繁的String拼接对内存消耗较大,并且耗时。
(2)StringBuilder就是为了解决频繁String拼接造成的问题,StringBuilder是可变长度的,频繁的拼接直接对对象进行修改,无需重新创建和销毁对象。
(3)StringBuffer和StringBuilder类似,他俩的区别就是StringBuffer是线程安全的,无需考虑多个线程间数据同步问题,但效率会比StringBuilder慢一些。
(4)总结下来就是String适合少量字符串操作,StringBuilder适合单线程内大量字符串操作,StringBuffer适合多线程中字符操作。

String

字符串常量

不可变

使用字符串拼接时是不同的2个空间。

StringBuffer

字符串变量

可变

线程安全,字符串拼接直接在字符串后追加。

StringBuilder

字符串变量

可变

非线程安全,字符串拼接直接在字符串后追加。

  1. StringBuilder执行效率高于StringBuffer高于String。
  2. String是一个常量,是不可变的,所以对于每一次+=赋值都会创建一个新的对象,StringBuffer和StringBuilder都是可变的,当进行字符串拼接时采用append方法,在原来的基础上进行追加,所以性能比String要高,又因为StringBuffer是线程安全的而StringBuilder是线程非安全的,所以StringBuilder的效率高于StringBuffer。
  3. 对于大数据量的字符串的拼接,采用StringBuffer,StringBuilder。
  4. 另一种说法,JDK1.6做了优化,通过String声明的字符串在进行用“+”进行拼接时,底层调用的是StringBuffer,所以性能上基本和后两者没有什么区别。

2 .String 是基本数据类型吗?可以被继承吗?

String 是引用类型,底层用 char 数组实现的。因为 String 是 final 类,在 java 中被 final 修饰的类不能被继承, 因此 String 当然不可以被继承。

   5. Java中switch接受的几种数据类型

       short, int, byte,char

       enum(JDK1.5以后支持)

       String(JDK1.7以后支持)

 swich()语句中小括号能使用String类型数据么?

     只用JDK1.7版本以后才可以使用String类型数据

      long类型数据任何版本都不可以使用

8. 什么是值传递和引用传递?

答:值传递:传递的是实际参数的一个副本,这个值可能是基本类型,也可能是引用类型的地址。意思指的是在方法调用时,传递的参数是按值的拷贝传递。

       引用传递:传递的是实际参数的地址的一个副本。意思指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。

       在java中,只有值传递.引用传递其实也就是内存地址值的传递。

9. i++与++i的区别?

    i++为代码执行后自加1,++i为代码执行前自加1

10. final,finally,finalize 三者区别

Final是一个修饰符:

当final修饰一个变量的时候,变量变成一个常量,它不能被二次赋值

当final修饰的变量为静态变量(即由static修饰)时,必须在声明这个变 量的时候给它赋值

当final修饰方法时,该方法不能被重写

当final修饰类时,该类不能被继承

Final不能修饰抽象类,因为抽象类中会有需要子类实现的抽 象方法,(抽 象类中可以有抽象方法,也可以有普通方法,当一个抽象类中没有抽象方 法时,这个抽象类也就没有了它存在的必要)

Final不能修饰接口,因为接口中有需要其实现类来实现的方法

Finally:

Finally只能与try/catch语句结合使用,finally语句块中的语句一定会执行, 并且会在return,continue,break关键字之前执行

finalize:

Finalize是一个方法,属于java.lang.Object类,finalize()方法是GC (garbage collector垃圾回收)运行机制的一部分,finalize()方法是在 GC清理它所从 属的对象时被调用的

11. 静态变量和实例变量的区别

在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。

在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。

总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。  

12. Math.round()的使用

Math类中提供了三个与取整有关的方法:ceil、floor、round,这些方法的作用与它们的英文名称的含义相对应,例如,ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11;floor的英文意义是地板,该方法就表示向下取整,Math. floor (11.6)的结果为11,Math. floor (-11.6)的结果是-12;最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。

13. &和&&的区别

&和&&都可以用作逻辑与的运算符,&&为短路与,&不是短路与。

另外&可以做为整数的位运算符

例1:对于if(str != null&& !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。

例2:If(x==33 &++y>0) y会增长,if(x==33 && ++y>0)不会增长。

备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。

14. break和continue和return的区别?

break:跳出整个循环,continue:跳出本次循环,进入下一次循环,return:跳出方法,结束这个方法,并返回一个数据

15. 循环都有哪些?

for循环

for each循环

while循环

do while 循环 (先执行一次)

递归(方法自己调用自己)

 

16. 新建对象有几种方式

1. 使用new关键字

2. 使用反射,调用newInstance

3. 使用clone方法

4. 使用序列化与反序列化

5. 动态代理(Proxy类和CGLIB)

简述反射的作用

Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。

 Java序列化

序列化是把内存Java对象保存到存储介质中,反序列化就是把存储介质中的数据转化为Java对象。Java通过ObjectInputStream和ObjectOutputStream实现序列化和反序列化。需要进行序列化的对象的类必须实现Serializable接口,通常情况下需要满足以下条件:

1. 强烈建议手动生成serialVersionUID常量

2. 如果需要加解密的话,需要实现两个方法readObject和writeObject方法

3. 如果使用Hibernate二级缓存或其它缓存服务器的话,对象必须是可序列化的

4. 如果需要远程调用对象或传值的话,则对像需要序列化

5. 序列化类的可序列化成员必须也是可序列化的,不需要序列化的属性用transient修饰

17. 类的结构是什么?

成员变量

构造器(构造方法)(不可以被static修饰)

普通方法

代码块

内部类

18. 请分别实现深度和浅读的对象克隆?

原理:深度克隆和浅度克隆,Object中的克隆方法是浅度克隆。JDK规定了克隆需要满足的一些条件,简要总结一下就是:对某个对象进行克隆,对象的的成员变量如果包括引用类型或者数组,那么克隆的时候其实是不会把这些对象也带着复制到克隆出来的对象里面的,只是复制一个引用,这个引用指向被克隆对象的成员对象,但是基本数据类型是会跟着被带到克隆对象里面去的。而深度可能就是把对象的所有属性都统统复制一份新的到目标对象里面去。简单画个对比图:

深拷贝浅拷贝

 

 

实现方式

实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆;

实现Cloneable接口并重写Object类中的clone()方法,即可实现浅度克隆。 

代码

Car.java

public class Car implements Serializable {

private static final long serialVersionUID = 1L; private String brand;//品牌

private int maxSpeed;//最高时速

public Car(String brand, int maxSpeed) {

 this.brand = brand;

 this.maxSpeed = maxSpeed;

}

public String getBrand() { return brand;

}

public void setBrand(String brand) { this.brand = brand;

}

public int getMaxSpeed() { return maxSpeed;

}

public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed;

}

@Override

public String toString() { return "Car{" +

"brand='" + brand + '\'' + ", maxSpeed=" + maxSpeed + '}';

}

}

Person.java

public class Person implements Serializable {

private static final long serialVersionUID = 1L;

private String name;//姓名

private int age;//年龄

private Car car;//座驾

 

public Person(String name, int age, Car car) {

this.name = name;

this.age = age;

this.car = car;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

 

public void setAge(int age) { this.age = age;

}

 

public Car getCar() { return car;

}

 

public void setCar(Car car) { this.car = car;

}

 

@Override

public String toString() { return "Person{" +

"name='" + name + '\'' + ", age=" + age +

", car=" + car + '}';

}

}

Person2.java

public class Person2 implements Serializable {

private static final long serialVersionUID =

private String name;//姓名

private int age;//年龄

private Car car;//座驾

 

public Person2(String name, int age, Car car) { this.name = name;

this.age = age; this.car = car;

}

 

public String getName() { return name;

}

 

public void setName(String name) { this.name = name;

}

 

public int getAge() { return age;

}

 

public void setAge(int age) { this.age = age;

}

 

public Car getCar() { return car;

}

 

public void setCar(Car car) { this.car = car;

}

 

@Override

protected Object clone(){ Person2 s= null;

try {

s = (Person2) Person2.super.clone();

} catch (CloneNotSupportedException e) { e.printStackTrace();

}

return s;

}

 

@Override

public String toString() { return "Person{" +

"name='" + name + '\'' + ", age=" + age +

", car=" + car + '}';

}

}

CloneUtil.java

public class CloneUtil {

 

private CloneUtil() {

throw new AssertionError();

}

 

public static <T extends Serializable> T clone(T object) throws IOException, ClassNotFoundException {

// 说明:调用ByteArrayOutputStreamByteArrayInputStream对象的close方法没有任何意义

// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外资源(如文件流)的释放

ByteArrayOutputStream baos = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object);

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

ObjectInputStream ois = new ObjectInputStream(bais);

return (T) ois.readObject();

}

}

CloneTest

public class CloneTest {

public static void main(String[] args){ try {

// 深拷贝

Person p1 = new Person("xiaoming",18,new Car("Benz",300));

Person p2 = CloneUtil.clone(p1);

// 修改克隆的Person对象p2关联的汽车对象的品牌属性

// 原来的Person对象p1关联的汽车不会受到任何影响,还是Benz

// 因为在克隆Person对象时其关联的汽车对象也被克隆了

 

p2.setAge(25); p2.getCar().setBrand("BYD");

 

System.out.println(p1);

 

Person2 p3 = new Person2("xiaoming",18,new Car("Benz",300));

Person2 p4 = (Person2) p3.clone();

p4.setAge(25);

p4.getCar().setBrand("BYD");

 

System.out.println(p3);

} catch (Exception e) {

e.printStackTrace();

}

}

}

 

结果

2、如何理解clone 对象

  1. 为什么要用 clone?

首先,克隆对象是很有必要的,当一个对象需要被多人操作,但是又不想相互影响,需要保持原对象的状态,这时,就会克隆很多个相同的对象。

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

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

clone 在第一步是和 new 相似的, 都是分配内存,调用 clone 方法时,分配的内存和原对象(即调用 clone 方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone 方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

    3. clone 对象的使用

  1. 复制对象和复制引用的区别
  1. Person p = new Person(23, "zhang");
  2. Person p1 = p;
  3. System.out.println(p);
  4. System.out.println(p1);

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

1.com.itheima.Person@2f9ee1ac

2.com.itheima.Person@2f9ee1ac

 

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

 

而下面的代码是真真正正的克隆了一个对象。

3.Person p = new Person(23, "zhang");

4.Person p1 = (Person) p.clone();

5.System.out.println(p);

6.System.out.println(p1);

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

  1. com.itheima.Person@2f9ee1ac
  2. com.itheima.Person@67f1fba0

以上代码执行完成后, 内存中的情景如下图所示:

 

  2. 深拷贝和浅拷贝

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

  public class Person implements Cloneable{

    private int age ;

    private String name;

    public Person(int age, String name) {

      this.age = age;

      this.name = name; }

    public Person() {}

    public int getAge() {

      return age;

   }

    public String getName() {

      return name;

    }

   @Override

    protected Object clone() 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 对象, 也就是深拷贝。验证代码如下:

  1. Person p = new Person(23, "zhang");
  2. Person p1 = (Person) p.clone();
  3. String result = p.getName() == p1.getName()
  4. ? "clone 是浅拷贝的" : "clone 是深拷贝的";
  5. System.out.println(result);

打印结果为:

clone 是浅拷贝的

所以,clone 方法执行的是浅拷贝, 在编写程序时要注意这个细节。

如何进行深拷贝:

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

  1. static class Body implements Cloneable{
  2. public Head head;
  3. public Body() {}
  4. public Body(Head head) {this.head = head;}
  5. @Override
  6. protected Object clone() throws CloneNotSupportedException {
  7. Body newBody = (Body) super.clone();
  8. newBody.head = (Head) head.clone();
  9. return newBody;

         } }

  1. static class Head implements Cloneable{
  2. public Face face;
  3. public Head() {}
  4. @Override
  5. protected Object clone() throws CloneNotSupportedException {
  6. return super.clone();
  7.  } }
  8. public static void main(String[] args) throws CloneNotSupportedException {
  9. Body body = new Body(new Head(new Face()));
  10. Body body1 = (Body) body.clone();
  11. System.out.println("body == body1 : " + (body == body1) );
  12. System.out.println("body.head == body1.head : " + (body.head == body1.head));
  13. }

打印结果为:

  1. body == body1 : false
  2. body.head == body1.head : false

19.请详细讲述一下RandomAccess接口有什么作用

答:RandomAccess用来当标记的,是一种标记接口,接口的非典型用法。意思是,随机访问

任意下标元素都比较快。用处,当要实现某些算法时,会判断当前类是否实现了

RandomAccess接口,会根据结果选择不同的算法

20. 请详细讲述一下Java中volatile关键字的作用。

答:用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。

volatile很容易被误用,用来进行原子性操作。

主要用在多线程,同步变量。 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),

线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一

致的情况。volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,

直接访问主内存中的(也就是上面说的A)。在Java内存模型中,有main memory,每个线程也

有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量

的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个

线程memory中的值,或者main memory中的值不一致的情况。

一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache

在线程memory中。

  1. synchronized和volatile的区别
    (1)volatile仅能作用于变量,synchronized可以作用于变量、方法和类上
    (2)volatile仅能实现对变量的修改可见性,不能保证原子性,synchronized可以保证 原子性和可见性
    (3)volatile不会造成线程阻塞,synchronized可能会造成线程阻塞
    (4)volatile修饰的变量不会被编译器优化(在不同线程中不会出现副本),synchronized修饰的可以被编译器优化(但能保证原子性)。

  2. Java中锁的分类(synchronized用的是公平锁还是非公平锁)
    synchronized使用的是非公平锁,允许插队,提高性能。
    Java中锁的分类:参见该文章

21. 设计模式(单例、工厂、观察者、代理、适配器、建造者等常用模式)

  1. 单例的几种实现方式,为什么要使用单例

  2. 单例和全局变量的区别

  3. Android中你都知道用到了哪些设计模式,在哪里

  4. 什么时候用观察者模式

  5. 动态代理和静态代理的区别

单例

单例就是该类只能返回一个实例。

单例所具备的特点:

1.私有化的构造函数

2.私有的静态的全局变量

3.公有的静态的方法

单例分为懒汉式、饿汉式和双层锁式

饿汉式:

public class Singleton1 {

      private Singleton1() {};

      private static Singleton1 single = new Singleton1();

      public static Singleton1 getInstance() {

          return single;

     }

 }

 

懒汉式:

public class Singleton2 {

      private Singleton2() {}

      private static Singleton2 single=null;

      public tatic Singleton2 getInstance() {

           if (single == null) {  

              single = new Singleton2();

          }  

         return single;

     }

 }

线程安全:

public class Singleton3 {

  private Singleton3() {}

  private static Singleton3 single ;

  public static Singleton3 getInstance() {

             if(null == single){

                 synchronized(Singleton3.class){

                          if(null == single){

                                single = new Singleton3();

                             }

                  }

              }

             return single;

  }

}

 

22. 请谈谈Proxy模式,Adapter模式,Decorator模式以及Façade模式分别的使用场景和区别。

答:Proxy模式是设计模式中的代理模式,比如我去优衣库买衣服,那么我肯定不会直接去优

衣库工厂(生产衣服工厂)去买衣服吧,那么我们直接去门店啊或者代理店去买。这些地方

其实就是优衣库造衣工厂的代理。

什么时候开始使用呢?当我们需要使用的对象很复杂或者需要很长时间去构造,这时就可以

使用代理模式(Proxy)。例如:如果构建一个对象很耗费时间和计算机资源,代理模式(Proxy)

允许我们控制这种情况,直到我们需要使用实际的对象。一个代理(Proxy)通常包含和将要使

用的对象同样的方法,一旦开始使用这个对象,这些方法将通过代理(Proxy)传递给实际的对

象。 一些可以使用代理模式(Proxy)的情况:

一个对象,比如一幅很大的图像,需要载入的时间很长。   

一个需要很长时间才可以完成的计算结果,并且需要在它计算过程中显示中间结果

一个存在于远程计算机上的对象,需要通过网络载入这个远程对象则需要很长时间,特别是

在网络传输高峰期。

一个对象只有有限的访问权限,代理模式(Proxy)可以验证用户的权限

代理模式(Proxy)也可以被用来区别一个对象实例的请求和实际的访问,例如:在程序初始化

过程中可能建立多个对象,但并不都是马上使用,代理模式(Proxy)可以载入需要的真正的对

象。这是一个需要载入和显示一幅很大的图像的程序,当程序启动时,就必须确定要显示的

图像,但是实际的图像只能在完全载入后才可以显示!这时我们就可以使用代理模式

(Proxy)。

Adapter模式是设计模式的适配器模式,它主要是将一个类的接口转换成客户希望的另外一个接

口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

使用场景即系统需要使用现有的类,而这些类的接口不符合系统的接口。

想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在

将来引进的类一起工作。

两个类所做的事情相同或相似,但是具有不同接口的时候。

旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们

不希望手动更改原有类的时候。

使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第

三方组件接口的功能。

Decorator模式即设计模式中的装饰器模式。它能动态地给一个对象添加一些额外的职责或者

行为。就增加功能来说, Decorator模式相比生成子类更为灵活。它提供了改变子类的灵活方

案。装饰器模式在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它

是通过创建一个包装对象,也就是装饰来包裹真实的对象。

当用于一组子类时,装饰器模式更加有用。如果你拥有一族子类(从一个父类派生而来),

你需要在与子类独立使用情况下添加额外的特性,你可以使用装饰器模式,以避免代码重复

和具体子类数量的增加。

Façade模式即设计模式的外观设计模式。它为子系统中的一组接口提供一个一致的界面,

Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

应用场景是

当你要为一个复杂子系统提供一个简单接口时。

客户程序与抽象类的实现部分之间存在着很大的依赖性。

当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。仅通过

facade进行通讯。

23. Java泛型中的泛型参数能不能是基本类型,比如ArrayList中的T可不可以是int,为什么?

ArrayList中可不可以插入数字,如果可以,请用代码示例,如果不可以,请说明原因?

答:不能,泛型要求能包容的是对象类型,而基本类型在java里不属于对象。

ArrayList<String>中不可以插入数字,因为他的泛型参数规定是String类型,所以只能插入字符

串。但是如果非要插入数字,有三种方法可以解决。比如泛型参数改成Integer,或者不声明泛

型类型或者是数字和空字符串拼接。

24. 请详细解释一下Java泛型中<? super T>和<? extends T>的作用和区别。

答:<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类。

遵循一个原则则是,生产者(Producer)使用extends,消费者(Consumer)使用super。

生产者使用extends

如果你需要一个列表提供T类型的元素(即你想从列表中读取T类型的元素),你需要把这个

列表声明成< extends T>,比如List< extends Integer>,因此你不能往该列表中添加任何元素。

消费者使用super

如果需要一个列表使用T类型的元素(即你想把T类型的元素加入到列表中),你需要把这个

列表声明成< super T>,比如List< super Integer>,因此你不能保证从中读取到的元素的类型。

即是生产者,也是消费者

如果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List<Integer>。

 

25. Java异常的分类(exception(运行时异常和非运行时异常)、error)

java 异常是程序运行过程中出现的错误。Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。

在Java API中定义了许多异常类,分为两大类错误Error异常Exception。其中异常类Exception又分为运行时异常(RuntimeException)非运行时异常(非runtimeException),也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。

1.  Error与Exception

Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。

     error和exception的区别

     error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出,不可能指望程序能处理这样的情况。

     exception表示一种设计或实现问题,也就是说,它表示如果程序运行正常, 从不会发生的情况。

  1. 异常处理机制(抛出异常、处理异常)

  2. try-catch-finally代码块都是什么时候被执行的,什么情况下finally代码块不会被执行

 2.  java Exception体系结构 ( 运行时异常和非运行时异常 )     

      运行异常和检查异常区别

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。

  1. 运行时异常: 都是RuntimeException类及其子类异常:
    1. SecurityException
    2. IllegelArgumentException
    3. IndexOutOfBoundsException 索引越界异常
    4. ArithmeticException:数学计算异常
    5. NullPointerException:空指针异常
    6. ArrayIndexOutOfBoundsException:数组索引越界异常
    7. ClassNotFoundException:类文件未找到异常
    8. ClassCastException:造型异常(类型转换异常)

           这些异常是不检查异常(Unchecked Exception),程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的。

       2. 非运行时异常: 是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。

           如:

              IOException、文件读写异常

              FileNotFoundException:文件未找到异常

              EOFException:读写文件尾异常

              MalformedURLException:URL格式错误异常

              SocketException:Socket异常 

              SQLException:SQL数据库异常

 

1、Java 中异常分为哪些种类

  1. 按照异常需要处理的时机分为编译时异常也叫 CheckedException 和运行时异常也叫RuntimeException。只有 java 语言提供了 Checked 异常,Java 认为 Checked 异常都是可以被处理的异常, 所以 Java 程序必须显式处理 Checked 异常。如果程序没有处理 Checked 异常,该程序在编译时就会发生错误无法编译。这体现了 Java 的设计哲学:没有完善错误处理的代码根本没有机会被执行。对 Checked 异常处理方法有两种:
    1. 当前方法不知道如何处理,则在定义该方法是声明抛出该异常。
    2. 当前方法知道如何处理该异常,则用 try...catch 块来处理该异常。

运行时异常只有当代码在运行时才发行的异常,编译时不需要 try catch。Runtime 如除数是 0 和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。

2、调用下面的方法,得到的返回值是什么

  public int getNum(){

    try {

       int a = 1/0;

      return 1;

    } catch (Exception e) {

       return 2;

    }finally{

    }

代码在走到第 3 行的时候雨大了一个 MathException,这时第四行的代码就不会执行了,代码直接跳转到catch 语句中,走到第 6 行的时候,异常机制有这么一个原则如果在 catch 中遇到了 return 或者异常等能使该函数终止的话那么用 finally 就必须先执行完 finally 代码块里面的代码然后再返回值。因此代码又跳到第 8 行,可惜第 8 行是一个 return 语句,那么这个时候方法就结束了,因此第 6 行的返回结果就无法被真正返回。如果 finally 仅仅是处理了一个释放资源的操作,那么该道题最终返回的结果就是 2。

因此上面返回值是 3。

3、error 和exception 的区别(2017-2-23)

Error 类和 Exception 类的父类都是 Throwable 类,他们的区别是:

Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。

Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Exception 类又分为运行时异常( Runtime Exception) 和受检查的异常(Checked Exception ), 运行时异常;ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,      出现这类异常,程序会终止。而受检查的异常,要么用 try。。。catch 捕获,要么用 throws 字句声明抛出,交给它的父类处理,否则编译不会通过。

4、java 异常处理机制(2017-2-23)

Java 对异常进行了分类,不同类型的异常分别用不同的 Java 类表示,所有异常的根类为 java.lang.Throwable, Throwable 下面又派生了两个子类:Error 和 Exception,Error 表示应用程序本身无法克服和恢复的一种严重问题。Exception 表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常

(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,      是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。

java 为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须 try..catch 处理或用 throws 声明继续抛给上层调用方法处理,所以普通异常也称为 checked 异常,而系统异常可以处理也可以不处理,所以,编译器不强制用 try..catch 处理或用 throws 声明,所以系统异常也称为 unchecked 异常。

5、请写出你最常见的 5 个RuntimeException(2017-2-23)

NullPointerException,IndexOutOfBoundsException,ClassCastException,ClassNotFoundException , IllegalArgumentException,UnsupportedOperationException。

 

26. 集合

 ​​​​

1. Java集合类框架的基本接口有哪些?请说明各自的用途

   Collection:代表一组对象,每一个对象都是它的子元素。

   Set:不包含重复元素的Collection。

   List:有顺序的collection,并且可以包含重复元素。

   Map:可以把键(key)映射到值(value)的对象,键不能重复。

2. List, Set, Collection, Collections

  1. List和Set都是接口,他们都继承于接口Collection, List是一个有序的可重复的集合,而Set的无序的不可重复的集合。Collection是集合的顶层接口,Collections是一个封装了众多关于集合操作的静态方法的工具类,因为构造方法是私有的,所以不能实例化。
  2. List接口实现类有ArrayList,LinkedList,Vector。ArrayList和Vector是基于数组实现的,所以查询的时候速度快,而在进行增加和删除的时候速度较慢LinkedList是基于链式存储结构,所以在进行查询的时候速度较慢但在进行增加和删除的时候速度较快。又因为Vector是线程安全的,所以他和ArrayList相比而言,查询效率要低。

3.  List 和 Map的区别

一个是存储单列数据的集合,另一个是存储键和值的双列数据的集合,List中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。

  1. List有重复值,Map没有重复key,但可以有重复值
  2. List有序,Map不一定有序
  3. List只能存单列值,Map可以存双列值 

 4. 说出ArrayList, Vector, LinkedList的存储性能和特性

    1. ArrayList和Vector使用数组存储元素;LinkedList使用链表存储元素

    2. ArrayList和Vector插入删除数据时,需要搬运数据,效率较差;LinkedList使用链表,不需要搬运数据,效率高

    3. ArrayList和Vectory查询时,按数组下标查询,不需要遍历,效率高;LinkedList需要遍历,查询效率底

5. HashMap和Hashtable的区别 ( 注意:Hashtable中的“t”是小写的。)

  1. HashMap和Hashtable是Map接口下的两个实现类,因为Map对象是键值对的,所以此两类也是键值对的。
  2. HashMap是线程非安全的,Hashtable是线程安全的,所以HashMap的效率高于Hashtable。
  3. HashMap允许键或值为null,键最多只可以有一个为null,值不受限制。而Hashtable键或值都不许为null。

 

1、特点:存储对象;长度可变;存储对象的类型可不同;

2、集合框架

2)Collection

(1)List:有序的;元素可重复,有索引

(add(index, element)、add(index, Collection)、remove(index)、set(index,element)、get(index)、subList(from, to)、listIterator())

①ArrayList:底层是数组结构,查询快,增删慢,不同步。

②LinkedList:底层是链表结构,增删快,查询慢,不同步

addFist();addLast()  getFirst();getLast()

removeFirst();removeLast() 获取并删除元素,无元素将抛异常:NoSuchElementException

替代的方法(JDK1.6):

offerFirst();offerLast();

peekFirst();peekLast();无元素返回null

pollFirst();pollLast();删除并返回此元素,无元素返回null

③Vector:底层是数组结构,线程同步,被ArrayList取代了

注:了对于判断是否存在,以及删除等操作,以依赖的方法是元素的hashCode和equals方法

ArrayList判断是否存在和删除操作依赖的是equals方法

(2)Set:无序的,无索引,元素不可重复

①HashSet:底层是哈希表,线程不同步,无序、高效

保证元素唯一性:通过元素的hashCode和equals方法。若hashCode值相同,则会判断equals的结果是否为true;hashCode不同,不会调用equals方法

LinkedHashSet:有序,是HashSet的子类

②TreeSet:底层是二叉树,可对元素进行排序,默认是自然顺序

保证唯一性:Comparable接口的compareTo方法的返回值

===》TreeSet两种排序方式:两种方式都存在时,以比较器为主

第一种:自然排序(默认排序):

添加的对象需要实现Comparable接口,覆盖compareTo方法

第二种:比较器

添加的元素自身不具备比较性或不是想要的比较方式。将比较器作为参数传递进去。

定义一个类,实现Comparator接口,覆盖compare方法。当主要条件相同时,比较次要条件。

3)Map集合:

 HashMap和Hashtable的区别

  1. HashMap和Hashtable是Map接口下的两个实现类,因为Map对象是键值对的,所以此两类也是键值对的。
  2. HashMap是线程非安全的,Hashtable是线程安全的,所以HashMap的效率高于Hashtable。
  3. HashMap允许键或值为null,键最多只可以有一个为null,值不受限制。而Hashtable键或值都不许为null。

注意:Hashtable中的“t”是小写的。

(1)HashTable:底层数据结构是哈希表,不可存入null键和null值。同步的

Properties继承自HashTable,可保存在流中或从流中加载,是集合和IO流的结合产物

(2)HashMap:底层数据结构是哈希表;允许使用null键和null值,不同步,效率高

TreeMap:

底层数据结构时二叉树,不同步,可排序

与Set很像,Set底层就是使用了Map集合

方法:

V put(K key, V value) ;  void putAll(Map m)

void clear();  V remove(Object key)

boolean containsKey(Object key);  containsValue(Object key);  isEmpty()

V get(Object key); int size(); Collection<V> values()

Set<K> keySet();  Set<Map.Entry<K,V>> entrySet()

2.3、Map集合两种取出方式:

第一种:Set<K> keySet()

取出Map集合中的所有键放于Set集合中,然后再通过键取出对应的值

Set<String> keySet = map.keySet();

Iterator<String> it = keySet.iterator();

while(it.hasNext()){

String key = it.next();

String value = map.get(key);

//..

}

第二种:Set<Map.Entry<K,V>> entrySet()

取出Map集合中键值对的映射放于Set集合中,然后通过Map集合中的内部接口,然后通过其中的方法取出

Set<Map.Entry<String,String>> entrySet = map.entrySet();

Iterator<Map.Entry<String,String>> it = entrySet.iterator();

While(it.hasNext()){

Map.Entry<String,String> entry = it.next();

String key = entry.getKey();

String value = entry.getValue();

//……

}

 

2.4、Collection和Map的区别:

Collection:单列集合,一次存一个元素

Map:双列集合,一次存一对集合,两个元素(对象)存在着映射关系

 

2.5、集合工具类:

Collections:操作集合(一般是list集合)的工具类。方法全为静态的

sort(List list);对list集合进行排序; sort(List list, Comparator c) 按指定比较器排序

fill(List list, T obj);将集合元素替换为指定对象;

swap(List list, int I, int j)交换集合指定位置的元素

shuffle(List list); 随机对集合元素排序

reverseOrder() :返回比较器,强行逆转实现Comparable接口的对象自然顺序

reverseOrder(Comparator c):返回比较器,强行逆转指定比较器的顺序

 

2.6、Collection和Collections的区别:

Collections:java.util下的工具类,实现对集合的查找、排序、替换、线程安全化等操作。

Collection:是java.util下的接口,是各种单列集合的父接口,实现此接口的有List和Set集合,存储对象并对其进行操作。

 

3、Arrays:

用于操作数组对象的工具类,全为静态方法

asList():将数组转为list集合

好处:可通过list集合的方法操作数组中的元素:

isEmpty()、contains()、indexOf()、set()

弊端:数组长度固定,不可使用集合的增删操作。

如果数组中存储的是基本数据类型,asList会将数组整体作为一个元素存入集合

集合转为数组:Collection.toArray();

好处:限定了对集合中的元素进行增删操作,只需获取元素

 

下面的代码中list对象总共扩容了几次,请详细讲述一下ArrayList的扩容规则。

ArrayList<String> list = new ArrayList<>();

for (int i = 0; i < 20; i++) {

list.add(Integer.toString(i));

}

答:总共扩容了2次。它默认是大小(size)是10个,每次扩容都会比较现在的容量是否够

用,如果不够用就扩容1.5倍,调用Arrays.copyOf方法,所以每次扩容都是new一个新的数组!

如果一个list初始化容量为10,如果需要添加1000个对象,需要初始化11次,而每次都生成新

的数组对象,将原来的对象复制到新数组对象里,就是说每次都要丢弃原来的数组对象,这

样的操作确实特别费时又占用内存(虽说只是复制对象的引用,但数组对象本身也是需要占

用内存的)。看来如果元素很多的话,估计数量初始化一个容量还是很有必要的。

 

请详细讲述一下HashMap的内部实现原理。

答:HashMap的底层实现都是数组+链表结构实现的。添加、删除、获取元素时都是先计算

hash,根据hash和table.length计算index也就是table数组的下标,然后进行相应操作。HashMap

创建时会默认初始化时创建一个默认容量为16的Entry数组,默认加载因子为0.75,同时设置

临界值为16*0.75。

 

写一段代码在遍历 ArrayList 时移除一个元素。

答:我们知道ArrayList的底层是用数组实现的,如果你删除了其中一个元素,那么后边的元素都会向前移动。所以在遍历时如果删除元素,就要小心了。

第一种方法,用数组下标进行遍历,如果需要删除元素,我们从后向前遍历,这样不论有没有元素删除,我们都不会遗漏未被遍历的元素。

第二种方法,我们使用迭代器。

Iterator itr = list.iterator();

while(itr.hasNext()) {

     if(...) {

            itr.remove();

       }

}

总之,如果你的删除操作比较多的话,建议使用LinkedList。

请编写一段Java代码,对数组{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}进行随机排序。

答:代码如下

打印后的结果是:

8 0 9 2 3 5 4 7 6 1

 

27. Java中集合相关问题

        Java中集合都有哪些类型

在这里插入图片描述

  1.  ArrayList和LinkedList的实现和区别

    ArrayList是由数组实现的,数组结构插入和删除速度慢,访问速度快。
    LinkedList是由链表实现的,链表结构插入和删除速度快,访问速度慢。
  2. ArrayList初始长度、负载因子、增加倍数(同理其他的也有可能问到)

    ArrayList的初始长度为10,负载因子是1,扩容增量为0.5

  3. ArrayList、Vector、CopyOnWriteArrayList的区别

  4. HashMap的底层实现

    HashMap的实现原理是数组加链表或者红黑树来实现,当我们调用put方法时,先去获取key的hash值,然后根据hash值经过算法来确定bucket数组中的下标,如果该下标处没有值,则将node放置在这里,如果对应位置上有链表,则调用key的equals方法和链表上的每一个key进行比较,如果有重复的,那么对应的key位置将会被替换掉,如果没有,则添加到链表末尾。

    同理,调用put方法的时候,先调用key的hash值找到bucket对应下标,然后从链表中调用equals方法来获取我们需要找的value。

    HashMap中数组中某个下标处的链表大小超过8个时,将改为红黑树结构,小于8个时,重新改为链表结构。

  5. HashMap中bocket的下标到底是怎么算的

    Hashmap的初始容量为16,默认负载因子为0.75,超过后进行扩容,每次扩大一倍。

  6. HashMap的初始长度、负载因子、增加倍数

  7. HashMap是如何判断需要扩容的

  8. Hash碰撞的原因

  9. Hashcode、equals和==的区别

  10. 链表和红黑树相关问题

  11. hashMap、hashTable、concurrentHashMap的区别

1、HashMap 排序题

已知一个 HashMap<Integer,User>集合, User 有 name(String)和 age(int)属性。请写一个方法实现对 HashMap 的排序功能,该方法接收 HashMap<Integer,User>为形参,返回类型为 HashMap<Integer,User>, 要求对 HashMap 中的 User 的 age 倒序进行排序。排序时 key=value 键值对不得拆散。

tips:要做出这道题必须对集合的体系结构非常的熟悉。HashMap 本身就是不可排序的,但是该道题偏偏让给HashMap 排序,那我们就得想在 API 中有没有这样的 Map 结构是有序的,LinkedHashMap,对的,就是他,他是Map 结构,也是链表结构,有序的,更可喜的是他是 HashMap 的子类,我们返回 LinkedHashMap<Integer,User> 即可,还符合面向接口(父类编程的思想)。

但凡是对集合的操作,我们应该保持一个原则就是能用 JDK 中的 API 就有 JDK 中的 API,比如排序算法我们不应该 去 用 冒 泡 或 者 选 择 , 而 是 首 先 想 到 用 Collections 集 合 工 具 类 。

public class HashMapTest {

public static void main(String[] args) {

HashMap<Integer, User> users = new HashMap<>();

users.put(1, new User("张三", 25));

users.put(3, new User("李四", 22));

users.put(2, new User("王五", 28));

System.out.println(users);

HashMap<Integer,User> sortHashMap = sortHashMap(users);

System.out.println(sortHashMap);

/**

控制台输出内容

{1=User [name=张三, age=25], 2=User [name=王五, age=28], 3=User [name=李四, age=22]}

{2=User [name=王五, age=28], 1=User [name=张三, age=25], 3=User [name=李四,age=22]}

*/

}

public static HashMap<Integer, User> sortHashMap(HashMap<Integer, User> map) {

// 首先拿到 map 的键值对集合

Set<Entry<Integer, User>> entrySet = map.entrySet();

// set 集合转为 List 集合,为什么,为了使用工具类的排序方法

List<Entry<Integer, User>> list = new ArrayList<Entry<Integer, User>>(entrySet);

// 使用 Collections 集合工具类对 list 进行排序,排序规则使用匿名内部类来实现

Collections.sort(list, new Comparator<Entry<Integer, User>>() {

         @Override

         public int compare(Entry<Integer, User> o1, Entry<Integer, User> o2) {

               //按照要求根据 User age 的倒序进行排

                return o2.getValue().getAge()-o1.getValue().getAge();

         }

});

//创建一个新的有序的 HashMap 子类的集合

LinkedHashMap<Integer, User> linkedHashMap = new LinkedHashMap<Integer, User>();

//List 中的数据存储在 LinkedHashMap 

for(Entry<Integer, User> entry : list){

      linkedHashMap.put(entry.getKey(), entry.getValue());

}

//返回结果

return linkedHashMap;

}

}

 

2、集合的安全性问题

请问 ArrayList、HashSet、HashMap 是线程安全的吗?如果不是我想要线程安全的集合怎么办?

我们都看过上面那些集合的源码(如果没有那就看看吧),每个方法都没有加锁,显然都是线程不安全的。话又说过来如果他们安全了也就没第二问了。

在集合中 Vector 和 HashTable 倒是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了synchronized 关键字。

Collections 工具类提供了相关的 API,可以让上面那 3 个不安全的集合变为安全的。

Collections.synchronizedCollection(c)

Collections.synchronizedList(list)

Collections.synchronizedMap(m)

上面几个函数都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实实现原理非常简单,就是将集合的核心方法添加上了 synchronized 关键字。

 

3.  并发集合和普通集合如何区别?(2015-11-24)

并发集合常见的有 ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque 等。并发集合位  于 java.util.concurrent 包   下  ,  是 jdk1.5 之   后   才   有   的   ,   主   要   作 者  是 DougLea(http://baike.baidu.com/view/3141057.htm)完成的。

在 java 中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。

参考阅读:

ConcurrentHashMap 是线程安全的 HashMap 的实现,默认构造同样有 initialCapacity 和 loadFactor 属性, 不过还多了一个 concurrencyLevel 属性,三属性默认值分别为 16、0.75 及 16。其内部使用锁分段技术,维持这锁Segment 的数组,在 Segment 数组中又存放着 Entity[]数组,内部 hash 算法将数据较均匀分布在不同锁中。

put 操作:并没有在此方法上加上 synchronized,首先对 key.hashcode 进行 hash 操作,得到 key 的 hash 值。hash 操作的算法和 map 也不同, 根据此 hash 值计算并获取其对应的数组中的 Segment 对象( 继承自ReentrantLock),接着调用此 Segment 对象的 put 方法来完成当前操作。

ConcurrentHashMap 基于 concurrencyLevel 划分出了多个 Segment 来对 key-value 进行存储,从而避免每次put 操作都得锁住整个数组。在默认的情况下,最佳情况下可允许 16 个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。

get(key): 首先对 key.hashCode 进行 hash 操作,基于其值找到对应的 Segment 对象,调用其 get 方法完成当前操作。而 Segment 的 get 操作首先通过 hash 值和对象数组大小减 1 的值进行按位与操作来获取数组上对应位置的HashEntry。在这个步骤中,可能会因为对象数组大小的改变,以及数组上对应位置的 HashEntry 产生不一致性,那么 ConcurrentHashMap 是如何保证的?

对象数组大小的改变只有在put 操作时有可能发生,由于 HashEntry 对象数组对应的变量是volatile 类型的, 因此可以保证如 HashEntry 对象数组大小发生改变,读操作可看到最新的对象数组大小。

在获取到了 HashEntry 对象后, 怎么能保证它及其 next 属性构成的链表上的对象不会改变呢? 这点ConcurrentHashMap 采用了一个简单的方式,即 HashEntry 对象中的 hash、key、next 属性都是 final 的,这也就意味着没办法插入一个HashEntry 对象到基于next 属性构成的链表中间或末尾。这样就可以保证当获取到 HashEntry 对象后,其基于 next 属性构建的链表是不会发生变化的。

ConcurrentHashMap 默认情况下采用将数据分为 16 个段进行存储,并且 16 个段分别持有各自不同的锁Segment,锁仅用于 put 和 remove 等改变集合对象的操作,基于 volatile 及 HashEntry 链表的不变性实现了读取的不加锁。这些方式使得 ConcurrentHashMap 能够保持极好的并发支持,尤其是对于读远比插入和删除频繁的 Map 而言,而它采用的这些方法也可谓是对于 Java 内存模型、并发机制深刻掌握的体现。

推荐博客地址:http://m.oschina.net/blog/269037

4、List 的三个子类的特点(2017-2-23)

ArrayList 底层结构是数组,底层查询快,增删慢

LinkedList 底层结构是链表型的,增删快,查询慢

vector 底层结构是数组 线程安全的,增删慢,查询慢

5、List 和map 的区别(2017-2-23)

一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。

6、HashMap 和HashTable 有什么区别?(2017-2-23)

HashMap 是线程不安全的,HashMap 是一个接口,是Map 的一个子接口,是将键映射到值得对象,不允许键值重复, 允许空键和空值;由于非线程安全,HashMap 的效率要较 HashTable 的效率高一些.

HashTable 是线程安全的一个集合,不允许 null 值作为一个 key 值或者 Value 值;

HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步;

 

 

28、IO流

1、Java 中有几种类型的流

字节流和字符流。字节流继承于 InputStream 和 OutputStream , 字符流继承于 InputStreamReader 和OutputStreamWriter。

2、字节流如何转为字符流

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

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

3、如何将一个java 对象序列化到文件里

在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。

//对象输出流

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("D://obj")));

objectOutputStream.writeObject(new User("zhangsan", 100));

objectOutputStream.close();

//对象输入流

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj")));

User user = (User)objectInputStream.readObject();

System.out.println(user);

objectInputStream.close();

4、字节流和字符流的区别(2017-2-23)

要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为 IO 流,对应的抽象类为 OutputStream 和 InputStream,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。

在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?计算机中的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。

底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向 IO 设别写入或读取字符串提供了一点点方便。

字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,  其实是转成该字符的某种编码的字节形式,读取也是反之的道理。

5、结构:

字节流:InputStream,OutputStream

字符流:Reader,Writer

Reader:读取字符流的抽象类

             BufferedReader:将字符存入缓冲区,再读取

             LineNumberReader:带行号的字符缓冲输入流

            InputStreamReader:转换流,字节流和字符流的桥梁,多在编码的地方使用

            FileReader:读取字符文件的便捷类。

Writer:写入字符流的抽象类

            BufferedWriter:将字符存入缓冲区,再写入

            OutputStreamWriter:转换流,字节流和字符流的桥梁,多在编码的地方使用

             FileWriter:写入字符文件的便捷类。

InputStream:字节输入流的所有类的超类

                   ByteArrayInputStream:含缓冲数组,读取内存中字节数组的数据,未涉及流

                   FileInputStream:从文件中获取输入字节。媒体文件

                  BufferedInputStream:带有缓冲区的字节输入流

                  DataInputStream:数据输入流,读取基本数据类型的数据

                 ObjectInputStream:用于读取对象的输入流

                 PipedInputStream:管道流,线程间通信,与PipedOutputStream配合使用

                SequenceInputStream:合并流,将多个输入流逻辑串联。

OutputStream:此抽象类是表示输出字节流的所有类的超类

                 ByteArrayOutputStream:含缓冲数组,将数据写入内存中的字节数组,未涉及流

                 FileOutStream:文件输出流,将数据写入文件

                  BufferedOutputStream:带有缓冲区的字节输出流

                  PrintStream:打印流,作为输出打印

                 DataOutputStream:数据输出流,写入基本数据类型的数据

                 ObjectOutputStream:用于写入对象的输出流

                  PipedOutputStream:管道流,线程间通信,与PipedInputStream配合使用

2、流操作规律:

明确源和目的:

数据源:读取,InputStream和Reader

目的:写入:OutStream和Writer

数据是否是纯文本:

是:字符流,Reader,Writer

否:字节流,InputStream,OutStream

明确数据设备:

源设备:内存、硬盘、键盘

目的设备:内存、硬盘、控制台

是否提高效率:用BufferedXXX

3、转换流:将字节转换为字符,可通过相应的编码表获得

转换流都涉及到字节流和编码表

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

以stream结尾都是字节流,以reader和writer结尾都是字符流,两者的区别就是读写的时候一个是按字节读写,一个是按字符。在实际使用时差不多。

在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。只是读写文件,和文件内容无关的,一般选择字节流。

 

29、多线程

线程和进程的区别 :

1.线程(Thread)与进程(Process), 进程定义的是应用程序与应用程序之间的边界,通常来说一个进程就代表一个与之对应的应用程序。不同的进程之间不能共享代码和数据空间, 

   而同一进程的不同线程可以共享代码和数据空间。

2. 一个进程可以包括若干个线程,同时创建多个线程来完成某项任务,便是多线程。

3. 进程是静态的,其实就是指开启的一个程序;而线程是动态的,是真正执行的单元,执行的过程。其实我们平时看到的进程,是线程在执行着,因为线程是作为进程的一个单元存在的。

4. 同样作为基本的执行单元,线程是划分得比进程更小的执行单位。

5. 每个进程都有一段专用的内存区域。与此相反,线程却共享内存单元(包括代码和数据),通过共享的内存单元来实现数据交换、实时通信与必要的同步操作。

Android下 的进程:

1、进程的生命周期:

1)、进程的创建及回收: 进程是被系统创建的,当内存不足的时候,又会被系统回收

2)、进程的级别:

               Foreground Process 前台进程

               Visible Process 可视进程

               Service Process 服务进程:可以提高级别的

               Background Process 后台进程

               Empty Process 空进程(无组件启动,做进程缓存使用,恢复速度快)

 

1、创建线程的两种方式:

一是直接继承Thread类,二是实现Runnable接口。通常我们实现Runnabale接口方式去做。但不管怎样,当new了这个对象后,线程就已经进入了初始状态.

方式一:继承Thread

1:定义一个类继承Thread

2:覆盖Thread中的run方法(将线程运行的代码放入run方法中)。

3:直接创建Thread的子类对象

4:调用start方法(内部调用了线程的任务(run方法));作用:启动线程,调用run方法

方式二:实现Runnable

1:定义类实现Runnable接口

2:覆盖Runnable接口中的run方法,将线程的任务代码封装到run中

3:通过Thread类创建线程对象

4、并将Runnable接口的子类对象作为Thread类的构造函数参数进行传递

作为参数传递的原因是让线程对象明确要运行的run方法所属的对象。

区别:

继承方式:线程代码放在Thread子类的run方法中

实现方式:线程存放在接口的子类run方法中;避免了单继承的局限性,建议使用。

优势:

(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。

(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继

承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。

(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。 多个线程操作相同的数据,与它

们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数 实参传递进去,这个对象就是一个实现了Runnable

接口的类的实例.

2、线程状态:

Java中多线程的状态是,  线程的状态以及状态之间的相互转换:

1.新建状态(New):新创建了一个线程对象。

2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。比如: 通过sleep或者wait使线程不具备执行资格,

                                          需要notify唤醒,并处于临时状态。

阻塞的情况分三种:

等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,

                  线程重新转入就绪状态。

5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

 

启动一个线程用run还是start?

启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。

3  多线程安全问题

多个线程共享同一数据,当某一线程执行多条语句时,其他线程也执行进来,导致数据在某一语句上被多次修改,执行到下一语句时,导致错误数据的产生。

因素:多个线程操作共享数据;多条语句操作同一数据

原理:某一时间只让某一线程执行完操作共享数据的所有语句。

办法:使用锁机制:synchronized或lock对象

3.1 各种相关的锁

Java多线程锁有对象锁和类锁

对象级别锁 是一个机制,当你想同步一个非静态方法或者非静态代码块,让在给定的类实例中只有一个线程来执行这个代码块,这就可以使得实例级别的数据是线程安全的。

类级别锁 是在所有可变的实例或者运行环境中,类级别锁阻止多线程进入同步块,也就是说,如果运行环境中有DemoClass的100个实例,在任何时刻,只能有DemoClass的一个实例来执行它的demoMethod()方法,所有其他的DemoClass实例在其他线程中只能处于阻塞状态,这使得静态数据是线程安全的。

3.2  线程的同步:

同步的实现方面有五种,分别是synchronized、wait与notify、sleep、suspend、join

synchronized: 一直持有锁,直至执行结束

wait():使一个线程处于等待状态,并且释放所持有的对象的lock,需捕获异常。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,需捕获异常,不释放锁。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

当两个或两个以上的线程需要共享资源,他们需要某种方法来确定资源在某一刻仅被一个线程占用,达到此目的的过程叫做同步(synchronization)。

同步代码块:synchronized(对象){},将需要同步的代码放在大括号中,括号中的对象即为锁。

同步函数:放于函数上,修饰符之后,返回类型之前。

3.3  wait和sleep的区别:(执行权和锁区分)

(网上的答案:sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。)

sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。对于wait的讲解一定要配合例子代码来说明,才显得自己真明白。

wait:可指定等待的时间,不指定须由notify或notifyAll唤醒。

等待队列(释放资源)--->调用notify或者notifyall之后锁池状态--->( 等待锁释放)--->可运行状态--->运行状态---->访问代码

线程会释放执行权,且释放锁。

sleep:必须制定睡眠的时间,时间到了自动处于临时(阻塞)状态。

不释放资源-->结束后直接进入可运行状态--->运行状态---->访问代码

即使睡眠了,仍持有锁,不会释放执行权。

一个java控制台程序,默认运行两个线程,一个主线程,一个垃圾回收线程。

 

4.  Java线程池

Java线程池概述:

java线程池的工作原理和数据库连接池的差不多,因为每次重新创建线程都是很耗资源的操作,所以我们可以建立一个线程池,这样当需要用到线程进行某些操作时,

就可以直接去线程池里面找到空闲的线程,这样就可以直接使用,而不用等到用到的时候再去创建,用完之后可以把该线程重新放入线程池供其他请求使用从而提高应用程序的性能。

线程池的核心流程:

1.构建一个 ThreadPoolExecutor 并指定默认要创建的线程的数量

2.通过 threadPool.execute() 去添加一个个要执行的线程即实现了Runable接口的java类

3.在实现了Runable接口的java类的run方法中写入具体的业务代码

线程池的业务场景:

我在工作的时候,当时一个同事给我提了一个需求,目前有大量的图片需要处理生产缩略图并进行加水印,因为按照普通的处理方法一个个的进行处理太慢了,问我有没有好的解决方案,这个时候我就想到了java中的线程池,我构建了一个线程数为5个线程池,然后采用分段批量提取的方式每500条为一组数据进行图片信息的提取,然后再把这些通过Threadpool的execute方法交给线程池中的线程进行处理,即充分使用了CPU硬件资源又加快了大数据情况下程序的处理效率。

我当时在工作的过程中,认识一个做电商的朋友,他们当时公司才起步,很多技术都不成熟,所以就常常和我探讨一些技术问题,有次他向我请教一个问题,问我如何才能提高网站的性能,我根据自己在项目中的经验以及自己以前阅读的关于优化方面的资料给他提出了很多建议,如用lucene进行全文检索,用memcached进行分布式缓存,以及通过spring定时器结合freeMarker模板引擎来生成静态页面,由于要生成的页面的数量比较多,考虑到程序的性能,我建议他结合java的线程池进行工作,这样就可以充分使用了CPU硬件资源又加快了大数据情况下程序的处理效率。

线程池作用

1.减少了创建和销毁线程的次数,每个线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中线程的数目,防止因为消耗过多的内存,而导致服务器宕机(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后宕机)。

通常我们使用的线程池是实现了ExecutorService的ThreadPoolExecutor。

 

30 .  XML和Json的特点

1.   XML和Json的特点

Xml特点:

1、有且只有一个根节点;

2、数据传输的载体

3、所有的标签都需要自定义

4、是纯文本文件

Json(JavaScript Object Notation)特点:

json分为两种格式:

  1. json对象(就是在{}中存储键值对,键和值之间用冒号分隔,键 值 对之间用逗号分隔)。
  2. json数组(就是[]中存储多个json对象,json对象之间用逗号分隔),(两者间可以进行相互嵌套)数据传输的载体之一。

区别:

   传输同样格式的数据,xml需要使用更多的字符进行描述,

      流行的是基于json的数据传输。

      xml的层次结构比json更清晰。

共同点:

    xml和json都是数据传输的载体,并且具有跨平台跨语言的特性。

 

2. 解析xml文件的几种技术

  1. dom4j 、2.sax、3.jaxb、4.jdom、5.dom

   1. dom4j

dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。

   2.sax

SAX(simple API for XML)是一种XML解析的替代方法。相比于DOM,SAX是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。而且相比于DOM,SAX可以在解析文档的任意时刻停止解析,但任何事物都有其相反的一面,对于SAX来说就是操作复杂。

  3.  Jaxb

JAXB(Java Architecture for XML Binding) 是一个业界的标准,是一项可以根据XML Schema产生Java类的技术。该过程中,JAXB也提供了将XML实例文档反向生成Java对象树的方法,并能将Java对象树的内容重新写到XML实例文档。从另一方面来讲,JAXB提供了快速而简便的方法将XML模式绑定到Java表示,从而使得Java开发者在Java应用程序中能方便地结合XML数据和处理函数。

3. Dom4j与sax之间的对比

dom4j不适合大文件的解析,因为它是一下子将文件加载到内存中,所以有可能出现内存溢出,sax是基于事件来对xml进行解析的,所以他可以解析大文件的xml,也正是因为如此,所以dom4j可以对xml进行灵活的增删改查和导航,而sax没有这么强的灵活性,所以sax经常是用来解析大型xml文件,而要对xml文件进行一些灵活(crud)操作就用dom4j。

 

31. Http基础知识

1.  osi七层模型

     第一层:物理层

    第二层:数据链路层

    第三层:网络层

    第四层:传输层

    第五层:会话层

    第六层:表示层

    第七层:应用层

2. TCP 和 UDP

  1. TCP的三次握手和四次挥手

    • 第一次握手:客服端发送syn包到服务器,并进入SYN_SENT状态,等待服务器确认
      第二次握手:服务器收到syn包,确认客户端的syn,同时自己也发送一个syn包,服务器进入SYN_RECV状态
      第三次握手:客户端收到服务器发来的syn包后,再向服务器发送确认包,发送完成之后,完成三次握手,服务器和客户端开始进行数据传输

    • 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动方的数据传输,此时主动关闭方还可以接收数据
      第二次挥手:被动关闭方收到FIN包后,发送一个确认包给主动关闭方。
      第三次挥手:被动关闭方发送一个FIN包,用来关闭被动关闭方到主动关闭方的数据传输。
      第四次挥手:主动关闭方收到FIN包后,发送一个确认包给被动关闭方,此时四次挥手完成。

  2. TCP和UDP的区别及应用

    • TCP面向连接,UDP是无连接的,即发送数据之前不需要建立连接
    • TCP提供可靠服务(传输的数据无差错,不丢失,不重复且按序到达),UDP尽最大努力交付(不保证可靠交付)
    • TCP面向字节流,UDP面向报文
    • TCP连接是点对点的,UDP支持一对一,一对多,多对一和多对多交互
    • TCP适合需要可靠数据的传输,UDP则适合对通信可靠性要求不是那么高,同时要求传输速度尽可能的快(例如语音通话等)

3. HTTP与HTTPS的区别和原理

https://blog.csdn.net/xiaoming100001/article/details/81109617

https://www.runoob.com/w3cnote/http-vs-https.html

https://www.pianshen.com/article/8155326463/

https://cloud.tencent.com/developer/article/1381270

https://zhuanlan.zhihu.com/p/72616216

  1. HTTP1.0和2.0的区别
    HTTP1.0:浏览器每次请求都需要与服务器简历一个TCP连接,服务器处理完成之后立即断开TCP连接,服务器也不跟踪每个客户端也不记录过去的请求,即无连接无状态
    HTTP2.0:HTTP2.0引入了二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样服务器就可以并行的传输数据,浏览器接收到数据之后可以按照序列对数据进行合并,而不会出现数据错乱的情况。

  2. HTTP与HTTPS的区别及HTTPS是如何实现安全性的

    区别:

    1. HTTP是明文传输,传输内容容易被篡改或者窃取,HTTPS是密文传输
    2. HTTP在网络请求效率上会低于HTTP,因为采用了不同的请求协议和更复杂的安全验证
    3. HTTPS需要申请CA证书

    HTTPS主要通过非对称加密+对称加密+CA证书来保证请求安全
    HTTPS协议中使用了SSL协议加密,SSL的加密即用到了对称加密也用到了非对称加 密,对称加密速度快,非对称加密更加安全。 

  3. get是从服务器上获取数据,post是向服务器传送数据。
  4. get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。
  5. get安全性非常低,post安全性较高。但是执行效率却比Post方法好。
  6. 在进行文件上传时只能使用post而不能是get。

5.  Socket和WebSocket

  1. Socket和WebSocket的区别
    WebSocket相对于HTTP的优势是支持长连接,WebSocket的整个通信过程是建立在一次连接中的,避免了HTTP的无状态性,服务端会一直知道你的信息,直到连接关闭。

  2. Socket处于抽象层,是HTTP协议的底层协议,而WebSocket是类似于HTTP协议的Http应用层协议。
    HTTP协议的通信只能从客户端发起,服务器无法主动向客户端推送消息。WebSocket协议服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送消息。

  3. 使用WebSocket时需要注意什么

7.  session和cookie的区别

session是存储在服务器端,cookie是存储在客户端的,所以从安全来讲session的安全性要比cookie高,然后我们获取session里的信息是通过存放在会话cookie里的sessionid获取的。又由于session是存放在服务器的内存中,所以session里的东西不断增加会造成服务器的负担,所以需要把很重要的信息才存储在session中,而把一些次要东西存储在客户端的cookie里,然后cookie确切的说分为两大类分为会话cookie和持久化cookie,会话cookie确切的说是,存放在客户端浏览器的内存中,所以说他的生命周期和浏览器是一致的,浏览器关了会话cookie也就消失了,然而持久化cookie是存放在客户端硬盘中,而持久化cookie的生命周期就是我们在设置cookie时候设置的那个保存时间,然后我们考虑一问题当浏览器关闭时session会不会丢失,从上面叙述分析session的信息是通过会话cookie的sessionid获取的,当浏览器关闭的时候会话cookie消失所以我们的sessionid也就消失了,但是session的信息还存在服务器端,这时我们只是查不到所谓的session但它并不是不存在。那么,session在什么情况下丢失,就是在服务器关闭的时候,或者是session过期(默认时间是30分钟),再或者调用了invalidate()的或者是我们想要session中的某一条数据消失调用session.removeAttribute()方法,然后session在什么时候被创建呢,确切的说是通过调用getsession()来创建,这就是session与cookie的区别。

访问HTML页面是不会创建session,但是访问index.JSP时会创建session(JSP实际上是一个Servlet,Servlet中有getSession方法)。

8.  request和session的区别

  1. 他们的生命周期不同,request对应的是一次请求,session对应的是一次会话。
  2. request占用资源比较少,相对来说缺乏持续性,而session资源消耗比较大,所以通常使用request来保存信息。

9. 报错状态码

     301 永久重定向

     302 临时重定向

     304 服务端 未改变

     403 访问无权限***

     200 正常

     404 找不到访问的资源

     500 内部错误

     503 服务器承受不了压力报错

10. 常见协议及默认的端口号

       ftp    21   文件传输协议

       ssh    22   SSH服务协议,可以通过ssh来访问Linux系统

       Pop3  110  它是因特网电子邮件的第一个离线协议标准

       Smtp  25   简单邮件传输协议

        http   80   超文本传输协议

        https  443  即HTTP下加入SSL层,简单讲是HTTP的安全版。

        Svn   3690

        tomcat 8080

        Oracle  默认端口号1521

        Mysql 默认端口号 3306

        Sqlserver 默认端口1433

        Postgresql  默认端口 3690

 

33. 请用JDBC写一段访问数据库读取数据的代码,SL语句和驱动自己选择。

 

 

 

 37.线程相关

  1. 线程池

38. classLoader为什么要设计成双亲委派模式

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值