持续更新中
解析:
答案:A
A,一个类只能有一个直接父类,但是继承是有传递性的。一个类可以实现多的接口。一个接口可以继承多个类。
B,接口中没有普通变量(普通成员变量),接口中都是常量,默认修饰符:public static final
C,JDK8之前,接口中的方法都是默认public abstract的,JDK8之后,接口中可以有static、default的修饰的方法,一旦被修饰,方法必须有方法体(抽象方法是可以没有方法体的),接口中的方法都不能被private和protected修饰,同时外部接口、类只能被public修饰或者不写,但是内部接口、类可以被四个访问修饰符修饰。
D, 实现接口,其实就是需要重写接口中的abstract方法,一旦实现的类没有重写完,那么这个类必须是个抽象类(抽象类中可以没有抽象方法,但是有抽象方法的类必须是抽象类)。
解析:
答案:D
Applet 类是浏览器类库中最为重要的类,同时也是所有 JAVA 小应用程序的基本类。 一个 Applet 应用程序从开始运行到结束时所经历的过程被称为 Applet 的生命周期。 Applet 的生命周期涉及 init() 、 start() 、 stop() 和 destroy() 四种方法,这 4 种方法都是 Applet 类的成员,可以继承这些方法,也可以重写这些方法,覆盖原来定义的这些方法。除此之外,为了在 Applet 程序中实现输出功能,每个 Applet 程序中还需要重载 paint() 方法:
1、 public void init()
init()方法是 Applet 运行的起点。当启动 Applet 程序时,系统首先调用此方法,以执行初始化任务。
2、 public void start()
start()方法是表明 Applet 程序开始执行的方法。当含有此 Applet 程序的 Web 页被再次访问时调用此方法。因此,如果每次访问 Web 页都需要执行一些操作的话,就需要在 Applet 程序中重载该方法。在 Applet 程序中,系统总是先调用 init() 方法,后调用 start() 方法。
3、 public void stop()
stop()方法使 Applet 停止执行,当含有该 Applet 的 Web 页被其他页代替时也要调用该方法。
4、 public void destroy()
destroy()方法收回 Applet 程序的所有资源,即释放已分配给它的所有资源。在 Applet 程序中,系统总是先调用 stop() 方法,后调用 destroy() 方法。
5、 paint(Graphics g)
paint(Graphics g)方法可以使 Applet 程序在屏幕上显示某些信息,如文字、色彩、背景或图像等。参数 g 是 Graphics 类的一个对象实例,实际上可以把 g 理解为一个画笔。对象 g 中包含了许多绘制方法,如 drawstring() 方法就是输出字符串。
解析:
答案:B
复制的效率System.arraycopy>clone>Arrays.copyOf>for循环
这里面在System类源码中给出了arraycopy的方法,是native方法,也就是本地方法,肯定是最快的。而Arrays.copyOf(注意是Arrays类,不是Array)的实现,在源码中是调用System.copyOf的,多了一个步骤,肯定就不是最快的
解析:
答案:A B C
1.session用来表示用户会话,session对象在服务端维护,一般tomcat设定session生命周期为30分钟,超时将失效,也可以主动设置无效;
2.cookie存放在客户端,可以分为内存cookie和磁盘cookie。内存cookie在浏览器关闭后消失,磁盘cookie超时后消失。当浏览器发送请求时,将自动发送对应cookie信息,前提是请求url满足cookie路径;
3.可以将sessionId存放在cookie中,也可以通过重写url将sessionId拼接在url。因此可以查看浏览器cookie或地址栏url看到sessionId;
4.请求到服务端时,将根据请求中的sessionId查找session,如果可以获取到则返回,否则返回null或者返回新构建的session,老的session依旧存在
5.隐藏域在页面中对于用户是不可见的,在表单中插入隐藏域的目的在于收集或发送信息,以利于被处理表单的程序所使用。浏览者单击发送按钮发送表单的时候,隐藏域的信息也被一起发送到服务器。
解析:
答案:B C D
1.super和this都只能位于构造器的第一行,而且不能同时使用,这是因为会造成初始化两次,this用于调用重载的构造器,super用于调用父类被子类重写的方法,由于super()和this()必须在构造函数第一行,所以这一点也表明他俩不能在一个构造函数中
2.super()表示调用父类构造函数、this()调用自己的构造函数,而自己的构造函数第一行要使用super()调用父类的构造函数,所以这俩不能在一个构造函数中会出现重复引用的情况
3.this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块(里面不能使用非static类型的)。因为static修饰的方法不能存在this指针
解析:
答案:B C D
1.从地址栏显示来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
2.从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.
3.从运用地方来说
forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等.
4.从效率来说
forward:高.
redirect:低.
解析:
答案:B C D
1.从地址栏显示来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
2.从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.
3.从运用地方来说
forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等.
4.从效率来说
forward:高.
redirect:低.
public class Demo{
public static void main(String args[]){
int num = 10;
System.out.println(test(num));
}
public static int test(int b){
try
{
b += 10;
return b;
}
catch(RuntimeException e)
{
}
catch(Exception e2)
{
}
finally
{
b += 10;
return b;
}
}
}
解析:
答案:C
如果finally块中有return语句的话,它将覆盖掉函数中其他return语句。
解析:
答案:C
不同类型运算结果类型向右边靠齐。
char < short < int < float < double
解析:
答案:D
hashCode()方法和equals()方法的作用其实是一样的,在Java里都是用来对比两个对象是否相等一致。
那么equals()既然已经能实现对比的功能了,为什么还要hashCode()呢?因为重写的equals()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。
那么hashCode()既然效率这么高为什么还要equals()呢? 因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,
所以我们可以得出:
1.equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
所有对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
public class Demo {
public static String sRet = "";
public static void func(int i)
{
try
{
if (i%2==0)
{
throw new Exception();
}
}
catch (Exception e)
{
sRet += "0";
return;
}
finally
{
sRet += "1";
}
sRet += "2";
}
public static void main(String[] args)
{
func(1);
func(2);
System.out.println(sRet);
}
}
解析:
答案:B
- 调用func(1),if不符合,直接进入finally,sRet=“1"
- finally语句中没有返回值,故继续向下执行,sRet=“12”
- 调用func(2),if符合,sRet=“120”,此时有返回值!!!
- 调用finally语句,sRet=“1201”
- 因为已经有返回值了,finally之后的语句也不再执行,sRet=“1201”。
解析:
答案:D
switch语句后的控制表达式只能是short、char、int、String、long整数类型和枚举类型,不能是float,double和boolean类型。String类型是java7开始支持。
解析:
答案:C
- 接口中字段的修饰符:public static final(默认不写)
- 接口中方法的修饰符:public abstract(默认不写)
abstract只能修饰类和方法 不能修饰字段
一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职:
- Bootstrap ClassLoader 负责加载java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等
- Extension ClassLoader 负责加载java扩展类,主要是 %JRE_HOME/lib/ext 目录下的jar和class
- App ClassLoader负责加载当前java应用的classpath中的所有类。
classloader 加载类用的是全盘负责委托机制。 所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入。
所以,当我们自定义的classloader加载成功了 com.company.MyClass以后,MyClass里所有依赖的class都由这个classLoader来加载完成。
比较两个类是否相等,只有这两个类是由同一个类加载器加载才有意义。否则,即使这两个类是来源于同一个Class文件,只要加载它们的类加载器不同,那么这两个类必定不相等。
补充:
把类加载的过程放到Java虚拟机外部去实现,让应用程序决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
类加载器按照层次,从顶层到底层,分为以下三种:
(1)启动类加载器 : 它用来加载 Java 的核心库,比如String、System这些类
(2)扩展类加载器 : 它用来加载 Java 的扩展库。
(3) 应用程序类加载器 : 负责加载用户类路径上所指定的类库,一般来说,Java 应用的类都是由它来完成加载的。
我们应用程序都是由以上三种类加载器互相配合进行加载的,还可以加入自己定义的类加载器。称为 类加载器的双亲委派模型 ,这里类加载器之间的父子关系一般不会以继承的关系来实现,而是都使用 组合关系 来复用父加载器的。
是当一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法加载这个加载请求的时候,子加载器才会尝试自己去加载。
第一:可以避免重复加载,当父亲已经加载了该类的时候,子类不需要再次加载。
第二:考虑到安全因素,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的类装载器。
class X{
Y y=new Y();
public X(){
System.out.print("X");
}
}
class Y{
public Y(){
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();
public Z(){
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
解析:
答案:C
初始化过程:
- 初始化父类中的静态成员变量和静态代码块 ;
- 初始化子类中的静态成员变量和静态代码块 ;
- 初始化父类的普通成员变量和代码块,再执行父类的构造方法;
- 初始化子类的普通成员变量和代码块,再执行子类的构造方法;
(1)初始化父类的普通成员变量和代码块,执行 Y y=new Y(); 输出Y
(2)再执行父类的构造方法;输出X
(3) 初始化子类的普通成员变量和代码块,执行 Y y=new Y(); 输出Y
(4)再执行子类的构造方法;输出Z
所以输出YXYZ
均表示地址的是()
解析:
答案:D
- *p表示指针p
- &a表示取a的内存地址
- p = &a 表示p等于a的内存地址
- &*p表示获取指针p的内存地址
- *&p表示指向P内存地址的一个指针
解析:
答案:A
Arraylist默认数组大小是10,扩容后的大小是扩容前的1.5倍,最大值小于Integer 的最大值减8,如果新创建的集合有带初始值,默认就是传入的大小,也就不会扩容,由于本地在创建数组时直接分配了内存大小,所以不会扩充
解析:
答案:A
checked exception:指的是编译时异常,该类异常需要本函数必须处理的,用try和catch处理,或者用throws抛出异常,然后交给调用者去处理异常。
runtime exception:指的是运行时异常,该类异常不必须本函数必须处理,当然也可以处理。
Thread.sleep()抛出的InterruptException属于checked exception;IllegalArgumentException属于Runtime exception;
解析:
答案:C
String不可变!先来看看String的源码:
public final class String implements Serializable, Comparable<String>, CharSequence {
private final byte[] value;
private final byte coder;
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
.......}
1、String由final修饰,说明final不能被继承,不能被修改
2、String用来存储数据的是字节数组 byte[] ,同样也是由final修饰的。
3、虽然字节数组value是由final修饰,但是我们要清楚一个原则就是:String是引用型变量,要清楚在String的数组是放在堆中的,然后将栈中放的是数组在堆中的引用地址,而我们通常所用的就是一个指向堆中真实数组数据的一个引用地址,所以也称String为引用型对象。简而言之:引用地址不可变,但是地址指向的堆中的数组数据是可以改变的。一旦创建了一个String对象,就在内存中申请一片固定的地址空间存放数据,不管怎么变,地址是不会变的,但是地址所指向的空间真实存放的数据是可以改变的。再通俗点就是:你家的门牌号不会变,但是你家要住几口人是你说了算。
先来看下StringBuffer的源码:
public synchronized StringBuffer append(String str) {
this.toStringCache = null;
super.append(str);
return this;
}
由于有关键字synchronize,所以线程安全。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
解析:
答案:C
- 基本数据类型包括byte,short,int,long,float,double,char,boolean
- C语言当中String是以“\0”结尾的char类型的数组char[],java不是, String内部是用char[]数组实现的,不过结尾不用\0。
- char存储的unicode码,不仅可以存储ascII码,汉字也可以。
public class Test {
public static void main(String[] args) {
System.out.println(args[0]);
}
}
若采用命令行“java Test one two three”调用,则程序输出的结果为:
解析:
答案:B
采用命令行“ java Test one two three ”调用
其中Test为调用的方法,而one two three则为Test方法里面main函数的参数;
System.out.println(args[0]);表示输出第一个元素,故为one;
注:此处的继承不代表能调用
解析:
答案:A
在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者合起来形成一个子类的对象。所以所谓的继承使子类拥有父类所有的属性和方法其实可以这样理解,子类对象确实拥有父类对象中所有的属性和方法,但是父类对象中的私有属性和方法,子类是无法访问到的,只是拥有,但不能使用。就像有些东西你可能拥有,但是你并不能使用。所以子类对象是绝对大于父类对象的,所谓的子类对象只能继承父类非私有的属性及方法的说法是错误的。可以继承,只是无法访问到而已。
解析:
答案:ABC
- str += ‘a’ 和 str +="a"都是对的,但是如果a前面加一个空格,那么只能用双引号了。代表字符串
- str += ‘a’ 和 str +="a"都是对的,但是如果a前面加一个空格,那么只能用双引号了。代表字符串
- int 无法直接转成String类型
解析:
答案:ABD
- throw 用于方法块里面的 代码,比throws的层次要低,比如try…catch …语句块,表示它抛出异常,但它不会处理它
- 用于类 ---- 说明该类无法被继承,实例:String类
- 用于方法-----说明该方法无法被覆盖,实例:final不能与abstract关键字同时使用
- final用于变量-----说明属性不可变(可用于静态和非静态属性),但多和staic连用,表示常量
解析:
答案:B
把常用的tar解压命令总结下,当作备忘:tar
-c: 建立压缩档案
-x:解压
-t:查看内容
-r:向压缩归档文件末尾追加文件
-u:更新原压缩包中的文件
这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个。下面的参数是根据需要在压缩或解压档案时可选的。
-z:有gzip属性的
-j:有bz2属性的
-Z:有compress属性的
-v:显示所有过程
-O:将文件解开到标准输出
下面的参数-f是必须的
-f: 使用档案名字,切记,这个参数是最后一个参数,后面只能接档案名。压缩
- tar –cvf jpg.tar *.jpg 将目录里所有jpg文件打包成tar.jpg
- tar –czf jpg.tar.gz *.jpg 将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
- tar –cjf jpg.tar.bz2 *.jpg 将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
- tar –cZf jpg.tar.Z *.jpg 将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
- rar a jpg.rar *.jpg rar格式的压缩,需要先下载rar for linux
- zip jpg.zip *.jpg zip格式的压缩,需要先下载zip for linux
解压
- tar –xvf file.tar 解压 tar包
- tar -xzvf file.tar.gz 解压tar.gz
- tar -xjvf file.tar.bz2 解压 tar.bz2
- tar –xZvf file.tar.Z 解压tar.Z
- unrar e file.rar 解压rar
- unzip file.zip 解压zip
总结
- *.tar 用 tar –xvf 解压
- *.gz 用 gzip -d或者gunzip 解压
- .tar.gz和.tgz 用 tar –xzf 解压
- *.bz2 用 bzip2 -d或者用bunzip2 解压
- *.tar.bz2用tar –xjf 解压
- *.Z 用 uncompress 解压
- *.tar.Z 用tar –xZf 解压
- *.rar 用 unrar e解压
- *.zip 用 unzip 解压
答案:B
解析:
flush()函数强制将缓冲区中的字符流、字节流等输出,目的是如果输出流输出到缓冲区完成后,缓冲区并没有填满,那么缓冲区将会一直等待被填满。所以在关闭输出流之前要调用flush()。
答案:B
解析:
- 桥接模式:
定义 :将抽象部分与它的实现部分分离,使它们都可以独立地变化。
意图 :将抽象与实现解耦。
桥接模式所涉及的角色
- Abstraction :定义抽象接口,拥有一个Implementor类型的对象引用
- RefinedAbstraction :扩展Abstraction中的接口定义
- Implementor :是具体实现的接口,Implementor和RefinedAbstraction接口并不一定完全一致,实际上这两个接口可以完全不一样Implementor提供具体操作方法,而Abstraction提供更高层次的调用
- ConcreteImplementor :实现Implementor接口,给出具体实现
Jdk中的桥接模式:JDBC
JDBC连接 数据库 的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不动,原因就是JDBC提供了统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了
其中 AbstractProductA 和 AbstractProductB 就是两个产品族的抽象类 (或者接口),而 Product1 和 Product2 就是产品族下的具体产品类,AbstractCreator 就是工厂的抽象。
public class B
{
public static B t1 = new B();
public static B t2 = new B();
{
System.out.println("构造块");
}
static
{
System.out.println("静态块");
}
public static void main(String[] args)
{
B t = new B();
}
}
答案:C
解析:
大致的执行过程如下:
- 1.实例化t之后,先执行实例化t1,此时会默认执行构造函数,即执行
{ System.out.println("构造块"); }
当
public static B t1 = new B(); public static B t2 = new B();
执行完成之后接着就会按照顺序执行静态代码块
static { System.out.println("静态块"); }
答案:A,D
解析:
重载只要求参数列表不同,返回值无关。
java的数据类型分为两大类:基本类型和引用类型;
基本类型只能保存一些常量数据,引用类型除了可以保存数据,还能提供操作这些数据的功能;
为了操作基本类型的数据,java也对它们进行了封装, 得到八个类,就是java中的基本类型的封装类;他们分别是:
八种基本类型: byte short int long float double char boolean
对应的包装类 : Byte Short Integer Long Float Double Character Boolean
abstract class Animal{
abstract void say();
}
public class Cat extends Animal{
public Cat(){
System.out.printf("I am a cat");
}
public static void main(String[] args) {
Cat cat=new Cat();
}
}
运行后:
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
在其他方面,抽象类和普通的类并没有区别。
因为error是系统出错,catch是无法处理的,难以修复的,RuntimeException不需要程序员进行捕获处理,error和exception都是throwable的子类,我们只需要对exception的实例进行捕获即可
1、abstract类不能用来创建abstract类的对象;
2、final类不能用来派生子类,因为用final修饰的类不能被继承;
3、如2所述,final不能与abstract同时修饰一个类,abstract类就是被用来继承的;
4、类中有abstract方法必须用abstract修饰,但abstract类中可以没有抽象方法,接口中也可以有abstract方法。
- ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。 可以总结为一句话:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
举个例子,我出门需要先坐公交再做地铁,这里的坐公交和坐地铁就好比是同一个线程内的两个函数,我就是一个线程,我要完成这两个函数都需要同一个东西:公交卡(北京公交和地铁都使用公交卡),那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路),我可以这么做:将公交卡事先交给一个机构,当我需要刷卡的时候再向这个机构要公交卡(当然每次拿的都是同一张公交卡)。这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的。 有人要说了:你可以将公交卡设置为全局变量啊,这样不是也能何时何地都能取公交卡吗?但是如果有很多个人(很多个线程)呢?大家可不能都使用同一张公交卡吧(我们假设公交卡是实名认证的),这样不就乱套了嘛。现在明白了吧?这就是ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。- ThreadLocal类用于创建一个线程本地变量
在Thread中有一个成员变量ThreadLocals,该变量的类型是ThreadLocalMap,也就是一个Map,它的键是threadLocal,值为就是变量的副本。通过ThreadLocal的get()方法可以获取该线程变量的本地副本,在get方法之前要先set,否则就要重写initialValue()方法。
ThreadLocal的使用场景:
数据库连接:在多线程中,如果使用懒汉式的单例模式创建Connection对象,由于该对象是共享的,那么必须要使用同步方法保证线程安全,这样当一个线程在连接数据库时,那么另外一个线程只能等待。这样就造成性能降低。如果改为哪里要连接数据库就来进行连接,那么就会频繁的对数据库进行连接,性能还是不高。这时使用ThreadLocal就可以既可以保证线程安全又可以让性能不会太低。但是ThreadLocal的缺点时占用了较多的空间。
在java语言中,判断一块内存空间是否符合垃圾收集器收集标准的标准只有两个:
1.给对象赋值为null,以下没有调用过。
2.给对象赋了新的值,重新分配了内存空间。
执行顺序为
- 1.父类静态代码块、静态变量 ps:按声明顺序执行
- 2.子类静态代码块、静态变量 ps:按声明顺序执行
- 3.父类局部代码块、成员变量 ps:按声明顺序执行
- 4.父类构造函数
- 5.子类局部代码块、成员变量 ps:按声明顺序执行
- 6.子类构造函数
Integer a = 1;
Integer b = 1;
Integer c = 500;
Integer d = 500;
System.out.print(a == b);
System.out.print(c == d);
Integer a = 1;是自动装箱会调用Interger.valueOf(int)方法;该方法注释如下:
This method will always *** values in the range -128 to > 127 inclusive, and may *** other values outside of this range.
也就是说IntegerCache类缓存了-128到127的Integer实例,在这个区间内调用valueOf不会创建新的实例。
~是位运算符,意义是 按位非(NOT)
按位非也叫做补,一元运算符NOT“~”是对其运算数的每一位取反。
仅用于整数值
反转位,即0位变为1位,1变成0
在所有情况下〜x等于(-x)- 1
例如:
~ 0111 (7) = 1000 (8)
方法的重写(override)两同两小一大原则:
方法名相同,参数类型相同
子类返回类型小于等于父类方法返回类型,
子类抛出异常小于等于父类方法抛出异常,
子类访问权限大于等于父类方法访问权限。
Java有5种方式来创建对象:
ObjectName obj = new ObjectName();
ObjectName obj = ObjectName.class.newInstance();
ObjectName obj = ObjectName.class.getConstructor.newInstance();
ObjectName obj = obj.clone();
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) { ObjectName obj = ois.readObject(); }
在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。
sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
暂停当前正在执行的线程对象。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。
join()等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测
public class Example extends Thread{
@Override
public void run(){
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
System. out .print( "run" );
}
public static void main(String[] args){
Example example= new Example();
example.run();
System. out .print( "main" );
}
}
为啥是runmain而不是mainrun呢?
- 因为启动线程是调用start方法。
- 把线程的run方法当普通方法,就直接用实例.run()执行就好了。
- 没有看到start。所以是普通方法调用。
doget/dopost与Http协议有关,是在 javax.servlet.http.HttpServlet 中实现的
共享的资源有:
- 堆 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)
- 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的
- 静态变量 虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的
- 文件等公用资源 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。
独享的资源有- 栈 栈是独享的
- 寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC
public class Test {
public static class A {
private B ref;
public void setB(B b) {
ref = b;
}
}
public static Class B {
private A ref;
public void setA(A a) {
ref = a;
}
}
public static void main(String args[]) {
…
start();
….
}
public static void start() {
A a = new A();
B b = new B();
a.setB(b);
b = null; //
a = null;
…
}
}
- 首先在栈中 创建A,B两个对象,假设A对象所在地址为address1,B对象地址为address2
- 执行new A操作后会有一个指向address1的地址
- 执行new B之后也会有一个指向address2的地址
- 接下来执行a.setB(b) 此时会有b,以及ref两个变量指向address2
- 执行b=null操作那么b就不在会指向address2,但是ref仍然指向address2,所以此时还无法回收b
- 执行a=null操作,那么所有的变量都不在指向address2,且所有的变量也都不在指向address1,所以a,b所指向的地址均可被回收
局部内部类是放在代码块或方法中的,不能有访问控制修饰符,且不能用static修饰
线程的五大状态及其转换
- resume与suspended一起使用 wait与notify(notifyAll)一起使用 sleep会让线程暂时不执行 suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。
- 线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
- 1.新建状态(New): 当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
- 2.就绪状态(Runnable)
- 一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。
当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,
并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU
时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能
同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个
线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度
程序(thread scheduler)来调度的。- 3.运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.- 4.阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态: 1>线程通过调用sleep方法进入睡眠状态; 2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者; 3>线程试图得到一个锁,而该锁正被其他线程持有; 4>线程在等待某个触发条件; …
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间, 进入运行状态。- 5.死亡状态(Dead)
有两个原因会导致线程死亡:
run方法正常退出而自然死亡,
一个未捕获的异常终止了run方法而使线程猝死。 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是 可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了, 则返回false.
- 线程的启动方式只能通过start这种方式启动才能真正的实现多线程的效果
- 如果是手动调用run方法和普通方法调用没有区别,所以这个还是按照顺序执行首先执行run方法之后,执行输出语句所以最终得到结果foobar.
- 调用start()后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于可动行状态。然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体。
- ThreadLocalMap中使用开放地址法来处理散列冲突
- HashMap中使用的是分离链表法
之所以采用不同的方式主要是因为:在ThreadLocalMap中的散列值分散得十分均匀,很少会出现冲突。并且ThreadLocalMap经常需要清除无用的对象,使用纯数组更加方便。
- 含有abstract修饰符的class即为抽象类,abstract类不能创建的实例对象。
- 含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class
- 类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法,如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
两者之间的联系和区别:
抽象类 | 接口 |
---|---|
可以有构造方法 | 不能有构造方法 |
可以有普通成员变量 | 没有普通成员变量 |
可以包含非抽象的普通方法 | 所有方法必须都是抽象的,不能有非抽象的普通方法 |
访问类型可以是public,protected | 只能是public类型的,并且默认即为public abstract类型 |
可以包含静态方法 | 不能包含静态方法 |
可以包含静态成员变量,静态成员变量的访问类型可以任意 | 可以包含静态成员变量,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型 |
这里解释一下E和F选项
E:
final 的成员方法除了能读取类的成员变量,还能读取类变量
F:
String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象
又称为实例方法,非静态成员函数, 暗含this指针
又称为类方法,静态成员函数,由static修饰,与类对象无关,缺少this指针
两个最基本的java回收算法:复制算法和标记清理算法
复制算法:
两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
标记清理算法:
一块区域,标记可达对象(可达性分析),然后回收不可达对象,会出现碎片,那么引出标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
两个概念:新生代和年老代
Java一维数组有两种初始化方法:
int array[] = new int[]{1,2,3,4,5}
或者
int array[] = {1,2,3,4,5}
注意:写成一下形势也是错误的
int array[] = new int[5]{1,2,3,4,5}
array[0] = 1;
array[1] = 2;
array[2] = 3;
array[3] = 4;
array[4] = 5;
静态与动态初始化的区别就在于,前者是声明的时候就初始化,后者是先声明,再动态初始化。
java线程是一个运用很广泛的重点知识,我们很有必要了解java的daemon线程.
首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程
double d1=-0.5;
System.out.println("Ceil d1="+Math.ceil(d1));
System.out.println("floor d1="+Math.floor(d1));
Ceil d1=-0.0
floor d1=-1.0
Ceil d1=0.0
floor d1=-1.0
Ceil d1=-0.0
floor d1=-0.0
Ceil d1=0.0
floor d1=0.0
Ceil d1=0
floor d1=-1
public class Base
{
private String baseName = "base";
public Base()
{
callName();
}
public void callName()
{
System. out. println(baseName);
}
static class Sub extends Base
{
private String baseName = "sub";
public void callName()
{
System. out. println (baseName) ;
}
}
public static void main(String[] args)
{
Base b = new Sub();
}
}
这道题也是考察类加载顺序的题目.
首先,需要明白类的加载顺序:
其次,需要理解子类覆盖父类方法的问题,也就是方法重写实现多态问题。
Base b = new Sub();它为多态的一种表现形式,声明是Base,实现是Sub类,我们可以简单理解为 b 编译时表现为Base类特性,运行时表现为Sub类特性
当子类覆盖了父类的方法后,意思是父类的方法已经被重写,题中 父类初始化调用的方法为子类实现的方法,子类实现的方法中调用的baseName为子类中的私有属性。
所谓 volatile的措施,就是
一条进程的栈区、堆区、数据区和代码区在内存中的映射:
public boolean add(T e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 复制出新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 把新元素添加到新数组里
newElements[len] = e;
// 把原数组引用指向新数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
final void setArray(Object[] a) {
array = a;
}
读的时候不需要加锁,如果读的时候有多个线程正在向ArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的ArrayList public E get(int index) {
return get(getArray(), index);
}
CopyOnWriteArrayList适用于读多写少的并发场景前台线程和后台线程的区别和联系:
说明:
应用程序的主线程以及使用Thread构造的线程都默认为前台线程
使用Thread建立的线程默认情况下是前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序
jvm堆分为:新生代(一般是一个Eden区,两个Survivor区),老年代(old区)。
常量池属于 PermGen(方法区)
- 关于HashMap的一些说法:
- a) HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap的底层结构是一个数组,数组中的每一项是一条链表。
- b) HashMap的实例有俩个参数影响其性能: “初始容量” 和 装填因子。
- c) HashMap实现不同步,线程不安全。 HashTable线程安全
- d) HashMap中的key-value都是存储在Entry中的。
- e) HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性
- f) 解决冲突主要有三种方法:定址法,拉链法,再散列法。HashMap是采用拉链法解决哈希冲突的。
注:
* 链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位;用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。
* 拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0…m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。拉链法适合未规定元素的大小。
- Hashtable和HashMap的区别:
- a) 继承不同。 public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map
- b) Hashtable中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
- c) Hashtable 中, key 和 value 都不允许出现 null 值。 在 HashMap 中, null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null 。当 get() 方法返回 null 值时,即可以表示 HashMap 中没有该键,也可以表示该键所对应的值为 null 。因此,在 HashMap 中不能由 get() 方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey() 方法来判断。
- d) 两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
- e) 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
- f) Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
注: HashSet子类依靠hashCode()和equal()方法来区分重复元素。
HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值的,会去判断当前Map中是否含有该Key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。
- A Class类在java.lang包
- B 动态代理技术可以动态创建一个代理对象,反射不行
- C 反射访问私有成员时,Field调用setAccessible可解除访问符限制
- D CGLIB实现了字节码修改,反射不行
- E 反射会动态创建额外的对象,比如每个成员方法只有一个Method对象作为root,他不胡直接暴露给用户。调用时会返回一个Method的包装类
- F 反射带来的效率问题主要是动态解析类,JVM没法对反射代码优化。
- ^:起始符号,^x表示以x开头
- $:结束符号,x$表示以x结尾
- [n-m]:表示从n到m的数字
- \d:表示数字,等同于[0-9]
- X{m}:表示由m个X字符构成,\d{4}表示4位数字
15位身份证的构成:六位出生地区码+六位出身日期码+三位顺序码
18位身份证的构成:六位出生地区码+八位出生日期码+三位顺序码+一位校验码
此处考察与运算,做这种题只需要2步
1.将数值转为二进制:13(01101),17(10001)
2.将二进制数值进行相与运算,只有都为1的情况下才为1
最终的运算结果为00001即对应的10进制为1
作用域名 | 本类能不能访问访问 | 同一个包(Package)下的其他类能不能访问 | 不同包(Package),但是作为子类 | 不同包,不是子类 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
首先我先解释一下String的地址引用
接下来我接着解释一下整型的地址引用
Integer i1=128 表示Integer i1=Integer.valueOf(128)
Interger 赋予的int数值在-128 - 127的时候,直接从*** 中获取,这些***引用对Integer对象地址是不变的,但是不在这个范围内的数字,则new Integer(i) 这个地址是新的地址,不可能一样的。
static String str0="0123456789";
static String str1="0123456789";
String str2=str1.substring(5);
String str3=new String(str2);
String str4=new String(str3.toCharArray());
str0=null;
假定str0,…,str4后序代码都是只读引用。
Java 7中,以上述代码为基础,在发生过一次FullGC后,上述代码在Heap空间(不包括PermGen)保留的字符数为()
我先说明一下关于java的垃圾回收机制
垃圾回收主要针对的是堆区的回收,因为栈区的内存是随着线程而释放的。
堆区分为三个区:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。
而static String str0=“0123456789”; static String str1=“0123456789”;是属于static的静态变量,所以在持久代,但是我们的fullGc是针对老年代的回收,所以GC之后,只会回收str2,str3,str4,所以为5+5+5=15
public static void main(String args[])throws InterruptedException{
Thread t=new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.print("2");
}
});
t.start();
t.join();
System.out.print("1");
}
我先解释一下join函数的作用:等待该线程终止,简单来说也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
因为子线程的休眠时间太长,因此主线程很有可能在子线程之前结束也就是输出结果是12,但是子线程用了join函数,因此主线程必须等待子线程执行完毕才结束因此输出结果只能是21。
首先对于int和Integer类型
我们来分析一下具体的原因:
1.Integer与int比较时,Integer会有拆箱的过程,我们可以看看拆箱的代码:
@Override
public int intValue(){
return value;
}
直接返回的就是value,因此int与Integer以及new Integer()进行 ==比较时结果都是true。
3.Integer与Integer比较,需要看范围,如果在-128~127(包含-128,不包含127)范围内,会直接从IntegerCache中取,所以相等;若不在这个范围内,则都要去new一个对象,所以结果为false。
对于C选项:
int a =1会直接从常量池中取,只有值,没有引用地址,Integer b =new Integer(1);指向堆中地址为1的位置。所以C错
对于D选项:
原因和C一样,Integer b=1等价于Integer b=Integer.valueOf(1);
对于A选项:
String a = "hello"首先判断常量池中是否有hello,没有将会在常量池中创建,当String b="hello"时,此时常量池中已经有hello,无需重新创建,直接返回引用。
A:静态方法是一个属于类而不属于对象(实例)的方法。(√)
B:静态方法只能访问静态数据。无法访问非静态数据(实例变量)。(√)
C:静态方法只能调用其他静态方法,不能从中调用非静态方法。(√)
D:静态方法不能通过类名直接访问,也不需要任何对象。(×) 静态方法可以直接用类名访问。
我系统的解释一下static关键字修饰的内容特性
public class Example{
String str=new String("tarena");
char[]ch={'a','b','c'};
public static void main(String args[]){
Example ex=new Example();
ex.change(ex.str,ex.ch);
System.out.print(ex.str+" and ");
System.out.print(ex.ch);
}
public void change(String str,char ch[]){
//引用类型变量,传递的是地址,属于引用传递。
str="test ok";
ch[0]='g';
}
}
这道题的唯一考点 就在String声明的对象不可变。
string和char数组都是引用类型,引用类型是传地址的,会影响原变量的值,但是string是特殊引用类型,为什么呢?
因为string类型的值是不可变的,为了考虑一些内存,安全等综合原因,把它设置成不可变的; 不可变是怎么实现的?Java在内存中专门为string开辟了一个字符串常量池,用来锁定数据不被篡改,所以题目中函数中的str变量和原来的str已经不是一个东西了,它是一个局部引用,指向一个testok的字符串,随着函数结束,它也就什么都没了,但是char数组是会改变原值的。
public class Bground extends Thread{
public static void main(String argv[]){
Bground b = new Bground();
b.run();
}
public void start(){
for(int i=0;i<10;i++){
System.out.println("Value of i = "+i);
}
}
}
对于线程而言,start是让线程从new变成runnable。
run方法才是执行体的入口。
但是在Thread中,run方法是个空方法,没有具体实现。
Bground继承了Thread,但是没有重写run方法,那么调用run方法时将会调用父类的run()方法,肯定是无输出。
对于A选项,vector是线程安全的ArrayList,在内存中占用连续的空间。初始时有一个初始大小,当数据条数大于这个初始大小后会重写分配一个更大的连续空间。如果我们将Vector的泛型定义为Object类型,则可以存放任意类型,所以这种情况下就无需知道所存储对象的类型。
对于B选项,try{}catch{}会增加额外的开销
try{
int a=15;
if(a>10){
throw new RunTimeExcepton("a大于10");
}
}catch(Exception e){
}
上面这串代码意思就是大于10,我们就通过抛出异常的方式提示我们a大于10。因为函数调用是入栈出栈,栈是在寄存器之下的速度最快,且占的空间少,而自定义异常是存在堆中,肯定异常的内存开销大。
对于C选项,接口中方法默认是 abstract public,所以在接口只写函数声明是符合语法规则。但是变量默认是用public final static 修饰的,意思它是静态常量,常量不管在接口中还是类中必须在声明时初始化!所以C的后半句是错的,必须在声明时并给出初始化!
对于D选项,子类可以访问父类受保护的成员(protect)
持续更新中