Java基础知识梳理(详细)
1. Java中的数据类型
基本数据类型特征表
类型 位数 最小值 最大值 默认值 其他
byte 8 -128(-2^7) 127(2^7-1) 0 有符号、二进制补码表示short 16 -32768(-2^15) 32767(2^15-1) 0 有符号、二进制补码表示int 32 -2^31 2^31-1 0 有符号、二进制补码表示long 64 -2^63 2^63-1 0L(0l) 有符号、二进制补码表示float 32 2^(-149) 2^128-1 0.0f 单精度、IEEE754标准double 64 2^(-1074) 2^1024-1 0.0d 双精度、IEEE754标准char 16 \u0000(0) \uffff(65535) \u0000(0) 单一的、Unicode字符
字符类型涉及到编码问题。
char类型固定为16位2字节长,因为java内码表示字符按照UTF-16存储。而一旦转化为字节就要根据特定的编码格式来看了,例如UTF-8占用1-4字节。出现乱码问题需要分析编码,在windows系统下默认的编码是GBK,使用gradle命令行可能出现乱码(还没发现啥解决办法)。关于字符的一个问题是char是否能表示所有的汉字,我觉得是不能的,可能可以表示常用的所有汉字,但生僻字16位肯定不够的,不然也不至于需要UTF-8多达4个字节来表示汉字。
String内部是通过char数组实现的,生僻字可能需要两个char来表示。
Unicode是一种编码规范,有Unicode编码表,其具体实现有UTF-8, UTF-16, UTF-32等。除此之外还有GBK,GB2312等。
ASCII编码占8位,一个字节就可以表示英文环境所有字符。
引申到MySQL建表过程中需要指定编码,如果是utf8编码可以留意下,一个字符占据可变字节长度,varchar(10)表示的是10个字符而非字节。
GB2312收录了约7000常用汉字,GBK收录了20000+汉字。相关的文章很多,也不是很容易分辨,这里我只列一下结论:GB2312和GBK都继承自ASCII编码,即英文和符号编码和ASCII一致;对于汉字的编码则一律用2字节,能够表示的范围有所不同。而UTF-8编码是1-4字节,汉字绝大多数用3字节表示。
浮点数内存结构
类型 位数 符号位 指数位 尾数位
float 32 1 8 23
double 64 1 11 52
原始数据类型对应的封装对象
(byte, Byte), (short, Short), (long, Long), (float,Float), (double, Double), (boolean, Boolean)
(int, Integer), (char, Character)
【小题】
Integer i = null;int j = i.intValue();
【答案】编译通过,但运行时报错NullPointerException。
自动装箱和拆箱
Integer i = 100; //自动装箱,编译器执行Integer.valueOf(100)int j = i; //自动拆箱,编译器执行i.intValue()
由于自动拆装箱的存在,要小心留意i为null。
【小题】
Integer i1 =200;Integer i2 =200;System.out.println("i1==i2: "+(i1==i2));Integer i3 =100;Integer i4 =100;System.out.println("i3==i4: "+(i3==i4));
【答案】运行结果为false,true.
首先,==和equals()的区别:
==比较的是两个对象的引用是否相同,或者是比较原始数据类型是否相等;
equals()比较的是两个对象的内容是否相同,可以通过重写equals添加自己的比较逻辑。
其次,-128~127的Integer值可以从缓存中取得,即Integer类型的常用数字缓存IntegerCache.cache。其他情况要重新创建。
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
关于基本数据类型的几点补充
short s1=1; s1=s1+1;
这一句编译错误,因为执行s1+1返回的结果是int类型(执行隐式类型转换)。修改的话要强制转换为short型才可以。
short s1=1; s1+=4;
这一句没有任何问题。
switch语句不能作用于浮点数类型中,可以作用于char, byte, short, int, Character, Byte, Short, Integer, String or an enum.
封装类型+String都是final的,不可被扩展,数字类型的公共父类是Number类,都实现了Comparable接口。
【小题】
public static void main(String[] args) throws Throwable {int j=0;for(int i=0;i<1000;i++) {j=j++;}System.out.println(j);}
【答案】运行结果为0。
解释一(未十分确信):Java使用中间缓存变量机制,j=j++语句会执行为:
temp=j;j=j+1;j=temp;
解释二(靠谱):使用javap反汇编命令进行反汇编,其中j=j++对应的结果是(j对应的变量编号是1):
11: iload_1 //将局部变量j的值放到栈顶:012: iinc 1, 1 //将局部变量j的值加1,j=115: istore_1 //将栈顶的值放到局部变量j中,j=0
所以从底层实现看,j=j++这一句中的自增操作只是对局部变量的操作,局部变量变化后没有存储到栈顶,反而被之前栈顶的值覆盖了,所以相当于不起作用。
数组
3种创建方式
int[] arr1 = {1,2,3,4}; //正确int[] arr2 = new int[4]; //正确int[] arr3 = new int[]{1,2,3,4}; //正确int[] arr4 = new int[4]{1,2,3,4};s //错误,编译不通过
数组越界,抛出ArrayIndexOutOfBoundsException
数组具有length属性
如果不对数组指定初值,默认初始化为相应数据类型的默认值。
多维数组,嵌套中括号即可。
数组是一种特殊的结构,在数组对象的对象头中需要记录数组长度的信息。JVM中的对象包括三部分,即对象头(Mark Word)、实例数据和对齐填充;而对象头(Mark Word)中又分为三部分,包括运行时信息(gc信息,锁标志位等)、类型指针、如果是数组还需要记录长度。
String
不属于基本类型,内部实际值是一个char[]数组
JDK1.6之前,方法区包括运行时常量池在永久代中;
JDK1.7,方法区和运行时常量池在永久代,字符串常量池在堆中;
JDK1.8,永久代被废弃,方法区在元空间,运行时常量池和字符串常量池在堆中。原文
创建
String s1="ss"; //先将"ss"存储在池中,然后返回引用String s2=new String("ss"); //创建新对象,使用的"ss"已经在池中String s3="Prog"+"gramming"; //创建3个对象,均存储在池中
字符串常量池和不可变(Immutable)字符串
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享。原文
查看源码可以发现,String的定义是public final class String implements java.io.Serializable, Comparable<String>, CharSequence,经过final修饰,无法被继承。
扩展点:封装类型Short, Integer均被final修饰,继承自Number类。
扩展点2: 封装类型Short, Integer等也是不可变类型,内部实际值是对应基本类型的名为value的final变量。
【小题】
String str1 ="abc";String str2 ="abc";System.out.println(str2==str1);System.out.println(str2.equals(str1));String str3 =new String("abc");String str4 =new String("abc");System.out.println(str3==str4);System.out.println(str3.equals(str4));
【答案】结果是true,true, false, true.前两个String对象从String池中获取,后两个对象是新创建的,内容相同但引用不同。
【小题】
String d ="2";String e ="23";e = e.substring(0, 1);System.out.println(e.equals(d));System.out.println(e==d);
【答案】结果是true,false.因为substring方法的实现会重新创建String对象。
public String substring(int beginIndex, int endIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > value.length) {throw new StringIndexOutOfBoundsException(endIndex);}int subLen = endIndex - beginIndex;if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);}return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen);}
【小题】
String str1 = "str";String str2 = "ing";String str3 = "str" + "ing";String str4 = str1 + str2;System.out.println(str3 == str4);String str5 = "string";System.out.println(str3 == str5);
【答案】结果是false,true。后一个结果应该很好理解,因为第3行代码执行时已经将"string"存储在常量池中,第6行代码返回的是常量池中同一字符串"string"的引用,所以结果为true。前一个结果可能有点疑惑,后来把这段代码编译反汇编后发现执行第3行代码时是直接生成的String对象,内容为"string";而执行第四行代码时是借助new StringBuilder().append(“str”).append(“ing”).toString(),关键在于StringBuilder中定义的toString()方法,返回的是一个重新创建的String对象,并没有存在池中,所以前一个结果为false。
//StringBuilder.toString()源代码public String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);}
由字符串解析为基本数据类型,基本数据类型封装类的parse方法可能会报NumberFormatException,比如Integer.parseInt("era");
扩展点:
Integer a=Integer.parseInt(""); //NumberFormatExceptionJSONObject jsonObject=new JSONObject();jsonObject.put("num", "");Integer b = jsonObject.getInteger("num"); //nullint bV = jsonObject.getIntValue("num"); //0
intern()方法:若由equals()判断池中包含相同的对象则返回池中该对象的引用,否则将新对象加入池中并返回引用。注意不管是怎么创建的,都先从池里找。
对于字符串拼接,考虑性能,如果循环进行拼接操作,生成的字符串都会存在池里,并且每次拼接都要重新构造StringBuilder对象,影响性能。因此可以使用StringBuilder优化:
StringBuilder builder...append(String.valueOf("fsdasf"));
1
StringBuilder为什么性能高:StringBuilder内部有一个char数组,初始容量16,每次扩容old*2+2,会先和需要的长度比较,如果不够那么直接扩容到所需的长度。拼接字符串不会有中间结果,因为直接拼接在数组里了,但同时可能造成空间浪费。注意:java中的加号拼字符串已经被jvm优化为StringBuilder来实现。仅当多次拼接(例如循环)时StringBuilder效率更高,否则效率是差不多的。不过这种说法也不完全正确,参见一个例子:
String s = "hello ";String s2 = s + "world";String s3 = "hello " + "world";System.out.println(s2 == "hello world");System.out.println(s3 == "hello world");
结果是false,true。可见,两个字符串直接拼接有可能还有特殊的优化方式,拼接后的结果和常量池结果相同,而不是生成新的String。
StringBuilder扩容时有个最大值是Integer.MAX_VALUE-8,如果所需容量超过这个值,那么仍然按照用户给定的值来分配空间,但有可能会超过VM的限制(VM limit exceeded)。
StringBuilder/StringBuffer
StringBuilder线程不安全,但效率高;
StringBuffer线程安全,但效率低。
线程安全:某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成




