青春有你-Java基础篇(2.1)

2、Java基础知识

→ 基本数据类型

8 种基本数据类型:整型4、浮点型2、布尔型1、字符型1

整型中 byte、short、int、long 的取值范围

byte: -128~127 -2(7)~2(7)-1 1字节

short:-32768 ~ 32767 -2(15)~2(15)-1 2字节

int:-2(31)~2(31)-1 4字节

long:-2(63)~2(63)-1 8字节

什么是浮点型?什么是单精度和双精度?为什么不能用浮点型表示金额?

简单说就是小数类型,小数点可以在相应的二进制的不同位置浮动

单精度 float , 4 个字节;双精度double用 8 个字节存储,这是他们最本质的区别。

单精度是1位符号,8位指数,23位小数。 32位

双精度是1位符号,11位指数,52位小数。 64位

浮点数的能表示的数据大小范围由阶码决定,但是能够表示的精度完全取决于尾数的长度 。 long的最大值是2的64次方减1,需要63个二进制位表示,即便是double,52位的尾数也无法完整的表示long的最大值。不能表示的部分也就只能被舍去了。对于金额,舍去不能表示的部分,损失也就产生了。 还有一个深刻的原因与进制转换有关。十进制的0.1在二进制下将是一个无限循环小数。 如果一个小数不是2的负整数次幂,用浮点数表示必然产生浮点误差。

→ 自动拆装箱

什么是包装类型、什么是基本类型、什么是自动拆装箱 ?

由于基本类型不面向对象,所以包装类就是将基本类型包装起来,使其具备对象的性质,可以添加属性和方法,位于java.lang包下

基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型 。 他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。

自动拆装箱

拆箱的过程就是通过Integer 实体调用intValue()方法;

装箱的过程就是调用了 Integer.valueOf(int i) 方法,帮你直接new了一个Integer对象

Integer 的缓存机制

Integer是对小数据(-128 ~ 127)是有缓存的,再jvm初始化的时候,数据-128 ~ 127之间的数字便被缓存到了本地内存中,这样,如果初始化-128~127之间的数字,便会直接从内存中取出,而不需要再新建一个对象 。

public class CompareExample {
	public static void main(String[] args) {
		
		Integer num3 = 100;
		Integer num4 = 100;
		System.out.println("num3==num4 " + (num3 == num4)); // true 符合缓存机制
		
		Integer num5 = 128;
		Integer num6 = 128;
		System.out.println("num5==num6 " + (num5 == num6));// false 不符合缓存机制
		
		int num9 = 100;
		Integer num10 = new Integer(100);
		System.out.println("num9==num10 " + (num9 == num10));// true 基本数据类型比较
	}
}

→ String

字符串的不可变性

不可变性:当你给一个字符串重新赋值之后,老值并没有在内存中销毁,而是重新开辟一块空间存储新值。

当代码中存在多个不同的字符串变量,它们存储的字符值都是相同的时候,这些变量存储的字符串不会每一个都单独去开辟空间,而是它们共用一个字符串对象。

JDK 6 和 JDK 7 中 substring 的原理及区别

JDK 6中的substring:String是通过字符数组实现的。在jdk 6 中,String类包含三个成员变量:char value[]int offsetint count。他们分别用来存储真正的字符数组,数组的第一个位置索引以及字符串中包含的字符个数。当调用substring方法的时候,会创建一个新的string对象,但是这个string的值仍然指向堆中的同一个字符数组。这两个对象中只有count和offset 的值是不同的。
在这里插入图片描述
jdk6的源码

//JDK 6
String(int offset, int count,  char value[]) {
 
    this.offset = offset;
    this.count = count;
    this.value = value;
}

public String substring(int beginIndex, int endIndex) {
    //check boundary
    return  new String(offset + beginIndex, endIndex - beginIndex, value);
}

如果你有一个很长很长的字符串,但是当你使用substring进行切割的时候你只需要很短的一段。这可能导致性能问题,因为你需要的只是一小段字符序列,但是你却引用了整个字符串(因为这个非常长的字符数组一直在被引用,所以无法被回收,就可能导致内存泄露)。 在JDK 6中,一般用以下方式来解决该问题,原理其实就是生成一个新的字符串并引用他。

x = x.substring(x, y) + ""

JDK 7 中的substring:
在这里插入图片描述

//JDK 7
public String(char value[], int offset, int count) {
    //check boundary
    this.value = Arrays.copyOfRange(value, offset, offset + count);
}

public String substring(int beginIndex, int endIndex) {
    //check boundary
    int subLen = endIndex - beginIndex;
    return new String(value, beginIndex, subLen);
}

【参考链接】 https://www.cnblogs.com/dsitn/p/7151624.html

replaceFirst、replaceAll、replace 区别

replace:参数为target和replaceent,也就是替换的目标对象和新对象

replaceAll:参数为regex和replacement,是正则表达式和新对象,也就是说replaceAll可以支持正则表达替换

replaceFirst:参数也是regex和replacement,不同的是它执行匹配的第一个结果

String s = "abcdefg1234hlqwa";
System.out.println(s.replace("a", "#"));
System.out.println(s.replaceAll("\\d", "#"));
System.out.println(s.replaceFirst("\\d", "#"));

// #bcdefg1234hlqw#
// abcdefg####hlqwa
// abcdefg#234hlqwa
String 对“+”的重载、字符串拼接的几种方式和区别

使用+拼接字符串 : 使用+拼接字符串不是运算符重载, 只是Java提供的一个语法糖。 字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。 也就是说,Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append

语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

concat方法 : 首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。 经过concat方法,其实是new了一个新的String,这也就呼应到前面我们说的字符串的不变性问题上了。

StringBuilderStringBuilder类也封装了一个字符数组,定义如下:char[] value; 它有一个实例变量,表示数组中已经使用的字符个数,定义如下: int count; append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。

StringBufferStringBuffer同上,区别是StringBuffer是线程安全的 ,使用synchronized进行了声明

StringUtils.join : 通过查看StringUtils.join的源码,可以发现其实它也是通过StringBuilder来实现的。

用时由短到长: StringBuilder < StringBuffer < concat < + < StringUtils.join
StringUtils.join更擅长处理字符串数组或者列表的拼接。

使用+拼接 :源码在for循环中,每次都是new一个StringBuilder,然后再把String转成StringBuilder, 再进行append。 所以,阿里巴巴Java开发手册建议:循环体内,字符串的连接方式,使用 StringBuilderappend 方法进行扩展。而不要使用+

String.valueOf 和 Integer.toString 的区别

String.valueOf(i)也是调用Integer.toString(i)来实现的。

java.lang.Object类里已有public方法.toString(),所以对任何严格意义上的java对象都可以调用此方法。但在使用时要注意,必须保证object不是null值,否则将抛出NullPointerException异常。

而valueOf(Object obj)对null值进行了处理,不会报任何异常。但当object为null 时,String.valueOf(object)的值是字符串”null”,而不是null。

switch 对 String 的支持

java中switch支持String,是利用String的hash值。

jvm是先调用String的hashCode方法得到hash值,然后将case中的常量换掉 ,本质上是switch-int结构。

并且String的Hash可能会冲突,即两个不同的String可能计算出相同的hash值, 因此 jvm又用String的equals方法来防止hash冲突的问题。最后利用switch-byte结构,精确匹配。

其实 switch中只能使用整型 ,其他数据类型都是转换成整型之后在使用switch的。

字符串池、常量池(Class 常量池、运行时常量池)、intern

jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。

全局字符串池(string pool):在类加载完成 ,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中 。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(用双引号括起来的字符串)的引用(而不是驻留字符串实例本身)

class常量池(class constant pool): 在java文件被编译成class文件后生成 。class文件中包含一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。 常量池的每一项常量都是一个表, 一共有11种各不相同的表结构数据,每种不同类型的常量类型具有不同的结构 。

运行时常量池(runtime constant pool): 当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。 class常量池中存的是字面量和符号引用, 经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

String.intern()是一个Native方法,作用是:如果字符串常量池存在字符串相等(equals() )的字符串对象,就返回此常量池中的String对象。否则将String对象添加到常量池中,并返回此String对象的引用。Jdk1.6及其之前的版本,字符串常量池分布在永久带(方法区)中,Intern()把首次遇到的字符串复制到常量池中,返回的也是常量池中实例的引用。jdk1.7以后字符串常量池移到了堆中,Intern()不再复制实例,只是在常量池中记录实例的引用,因此返回的引用和下面StringBuilder创建的实例是同一个。

总结:

  • 1.全局常量池在每个VM中只有一份,存放的是字符串常量的引用值。
  • 2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
  • 3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

JDK1.7为什么要把字符串常量池移动到堆里面
在Java 7之前,JVM 将Java字符串池放在永久代PermGen(方法区)中,该空间具有固定的大小 - 它不能在运行时扩展,也不在垃圾回收的范围内。在PermGen中实现字符串的风险是,如果我们定义太多字符串,JVM可能报OutOfMemory错误。
从Java 7开始,Java字符串池存储在堆空间中,这是 JVM的垃圾收集范围内。这种方法降低了OutOfMemory错误的风险,因为未引用的字符串将从池中删除,从而释放内存。

【参考链接】https://blog.csdn.net/xiao______xin/article/details/81985654
https://blog.csdn.net/h2453532874/article/details/84453801

→ 熟悉 Java 中各种关键字

transient、instanceof、final、static、volatile、synchronized、const 原理及用法

transient: 作用于变量上, 用transient关键字标记的成员变量不参与序列化过程 ,防止属性被序列化。

instanceof:用来在运行时指出对象是否是特定类的一个实例。

final: 修饰类则这个类不能被继承。修饰方法则这个方法不能被重写。修饰变量则这个变量只能初始化一次。

static: 创建独立于具体对象的域变量或者方法。即时没有创建对象,也能使用属性和方法。

volatile:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的(实现可见性)。 禁止进行指令重排序(实现有序性)。 volatile 只能保证对单次读/写的原子性。不保证操作的原子性,比如i++ 操作。

synchronized: 可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

const :Java没有这个关键字,是预留关键字,与其异曲同工的是final

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值