Java基础全面解析(实用干货)

发表时间: 2018-11-11 18:34

一.hashMap与hashTable与ConcurrentHashMap:

1.HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。<Dictionary类是一个已经被废弃的类>

2.Hashtable既不支持Null key也不支持Null value。HashMap中,null可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为null。

3.Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现 同步,HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。如果想要线程安全的 HashMap,可以通过Collections类的静态方法synchronize dMap获得线程安全的HashMap。 <Map map =
Collections.synchronizedMap(new HashMap())>;

4.hashMap的数据结构:HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。

5.ConcurrentHashMap:底层采用分段的数组+链表实现,线程安全ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,此时锁加在key上,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本。

二.(String)、toString、String.valueOf的区别

1.(String):使用这种方法时,需要注意的是类型必须能转成String类型。因此最好用instanceof做个类型检查,以判断是否可以转换。instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例,

父类parent,子类son, 此时 flag= son instanceof parent,flag=true。

2.toString:String s = Object.toString(),在使用时要注意,必须保证object不是null值,否则将抛出NullPointerException异常。

3.String.valueOf:

内部实现:

public static String valueOf(Object obj){

return (obj==null) ? "null" : obj.toString()

};

当Object不为空时,调用toString()方法,当Object为null时,则返回一个字符串"null"!!!!

三.字节流与字符流的区别

字节流:InputStream,OutputStream,程序→文件

字符流:BufferedRead,BufferedWrite,程序→缓存区→文件

字符流需要关闭字符流,缓存区的数据才会被写入文件,否则会一直堆在缓存区。或者可以用flush()方法,将数据强行写入文件。

利用BufferedRead读取文件:

public static void main(String[] args) throws Exception {

String s= "F:/456.txt";

File f =new File(s);

FileReader fr = new FileReader(f);

BufferedReader br = new BufferedReader(fr);

String temp="";

while((temp=br.readLine())!=null) {

System.out.println(temp);

}

}

四.Integer

源代码:

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) {

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);

}

high = h;

cache = new Integer[(high - low) + 1];

int j = low;

for(int k = 0; k < cache.length; k++)

cache[k] = new Integer(j++);

} private IntegerCache() {}

}

这个类就是在Integer类装入内存中时,会执行其内部类中静态代码块进行其初始化工作,做的主要工作就是把一字节的整型数据(-128,127)装包成Integer类并把其对应的引用存入cache数组中,这样在方法区中开辟空间存放这些静态Integer变量,同时静态cache数组也存放在这里,供线程享用,这也称静态缓存。

所以当用Integer声明初始化变量时,会先判断所赋值的大小是否在-128到127之间,若在,则利用静态缓存中的空间并且返回对应cache数组中对应引用,存放到运行栈中,而不再重新开辟内存。若不在则new 一个新的对象放入堆中。

五.类的初始化过程

/*父类*/

public class Person {

public Person() {

System.out.println("im person_1");

}

{

System.out.println("im person_2");

}

static {

System.out.println("im person_3");

}

}

/*子类*/

public class test extends Person {

public test() {

System.out.println("im test_1");

}

{

System.out.println("im test_2");

}

static {

System.out.println("im test_3");

}

public static void main(String[] args) throws Exception {

new test();

}

}

输出:

im person_3

im test_3

im person_2

im person_1

im test_2

im test_1

解释:在类中变量初始化时,顺序为 static→变量→构造方法。

六.值传递,引用传递

public class test {

String s="hello";

char[] ch={'a','b','c'};

Character ck='k';

public static void main(String[] args) throws Exception {

test tt = new test();

tt.change(tt.s,tt.ch,tt.ck);

System.out.println("--------");

System.out.println("s+"+tt.s.hashCode());

System.out.println("ch+"+tt.ch.hashCode());

System.out.println("ck+"+tt.ck.hashCode());

System.out.println("--------");

System.out.println(tt.s);

System.out.println(tt.ch);

System.out.println(tt.ck);

}

public void change(String str,char[] ch,Character ck){

str="world";

ch[0]='d';

ck='c';

System.out.println("str+"+str.hashCode());

System.out.println("ch+"+ch.hashCode());

System.out.println("ckl+"+ck.hashCode());

}

}

输出:

str+113318802

ch+1828682968

ckl+99

--------

s+99162322

ch+1828682968

ck+107

--------

hello

dbc

k

可见,String类型是不会被修改的,在编译时,方法栈里有world,如果是输入赋值给String应该会变,char数组传递的是数组的引用,Character传递的是值

传值不会修改原来的,传引用会修改原来的。

七.i++与++i

public static void main(String[] args) throws Exception {

int a=1;

int b=a++; //先执行b=a,再执行a++

System.out.println(b++); //先执行print(b),再执行b++

}

输出:1

八.==与equals的区别

==:

1.在==中,如果比较的是int,long,short这种基本数据类型,那么==比较的是它们的值

2.若比较的引用数据类型,如类,String,那么比较的是它们的内存地址,除非是同一个new一个出来的对象,此时地址相同,返回ture,否则返回false

如:

String a= new String("abc");

String b=a;

sout(a==b); //ture

若:

String c= new String("c");

String c1= "c";

sout(c==c1);//false

equals:

1.所有类都继承自Object,若不重写equals()方法,那么调用Object类中的equals()方法,源代码:

public boolean equals(Object obj) {

return (this == obj);

}

也就是仍然是比较其地址。

2.若重写其方法:

在String中:

源代码:

public boolean equals(Object anObject) {

if (this == anObject) {

return true;

}

if (anObject instanceof String) {

String anotherString = (String) anObject;

int n = value.length;

if (n == anotherString.value.length) {

char v1[] = value;

char v2[] = anotherString.value;

int i = 0;

while (n-- != 0) {

if (v1[i] != v2[i])

return false;

i++;

}

return true;

}

}

return false;

}

可以看出equals()是会先用==方法,然后比较两个String的值是否相等。

九.final,static关键字

final:

当用final修饰一个类时,表明这个类不能被继承,比如出于安全的考虑,可修饰为final。

如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

static:

修饰类的成员变量时,每个类只有一个这个成员,而不是每个类的实例化对象都有一个这个变量。

用static修饰后,类名.方法名,类名.属性名,可直接调用,不用实例化对象,避免了先要new出对象的繁琐和资源消耗。

在创建对象时,static修饰的成员会首先被初始化。

十.sleep() 和 wait() 有什么区别?

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

十一.得到文件下的文件

public static void FileRead(String path)throws Exception{

File f= new File(path);

if(!f.exists())

System.out.println("file not exist");

if (f.isDirectory()) {

File[] ss = f.listFiles();

for (File s : ss) {

System.out.println(s.getPath()); // F:\txt.txt

}

}else{

System.out.println(f.getName());

}

}

十二.实现多线程的三种方法

1.继承thread类创建线程

public class MyThread extends Thread {

public void run() {

System.out.println("MyThread.run()");

}

}

MyThread myThread1 = new MyThread();

myThread1.start();

2.实现runnable接口创建线程

public class MyThread extends OtherClass implements Runnable {

public void run() {

System.out.println("MyThread.run()");

}

}

3.实现Callable接口通过FutureTask包装器来创建Thread线程

PS:别说四种,第四种无法理解

十三.抽象类与接口的区别 

主要是:单继承(抽象类),多实现(接口)。

抽象类不能直接实例化对象,需要继承抽象类才能实例化其子类。

从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。

接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。

接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

十四.String Pool

String pool 指字符串常量池,保存着所有在编译时就已经确定的String变量。调用String.intern()方法,可以将此String变量加入常量池中。

String pool在堆中。

String a= new String("a");

String a1= new String("a");

sout(a==a1);//false

String b="b";

String b1="b";

sout(b==b1);//true

String c= new String("c");

String c1= "c";

sout(c==c1);//false

sout(c.equals(c1));//true

详情见 八. ==与equals的区别

十五.ArrayList和Vector的区别

这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的。

(1)同步性:

Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最 好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。

备注:对于Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住Vector与Hashtable是旧的,是java一诞生就提供了的,它们是线程安全的,ArrayList与HashMap是java2时才提供的,它们是线程不安全的。所以,我们讲课时先讲老的。

(2)数据增长:

ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。

总结:即Vector增长原来的一倍,ArrayList增加原来的0.5倍。

十六.Java 中Collections类里的reverse (反转方法)

public static void reverse(List<?> list) {

int size = list.size();

if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {

for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)

swap(list, i, j);

} else {

ListIterator fwd = list.listIterator();

ListIterator rev = list.listIterator(size);

for (int i=0, mid=list.size()>>1; i<mid; i++) {

Object tmp = fwd.next();

fwd.set(rev.previous());

rev.set(tmp);

}

}

}

此方法可反转数组的值.