暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【Java】Java基础之数据结构

DevHome 2021-11-03
215

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的值放到栈顶:0
12: iinc 1, 1 //将局部变量j的值加1,j=1
15: 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 array
return new String(value, 0, count);
}


由字符串解析为基本数据类型,基本数据类型封装类的parse方法可能会报NumberFormatException,比如Integer.parseInt("era");

扩展点:





Integer a=Integer.parseInt(""); //NumberFormatException
JSONObject jsonObject=new JSONObject();
jsonObject.put("num", "");
Integer b = jsonObject.getInteger("num"); //null
int 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线程安全,但效率低。

线程安全:某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成


文章转载自DevHome,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论