Java 基础知识 02

Java 基础知识 02

计算机基础

位运算符

  • Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。
  • 位运算符作用在所有的位上,并且按位运算。假设A = 60,B = 13;它们的二进制格式表示将如下:
A = 0011 1100
B = 0000 1101
----------------------
A & B = 0000 1100
A | B = 0011 1101
A ^ B = 0011 0001
~A = 1100 0011
操作符描述
&如果相对应位都是1,则结果为1,否则为0
|如果相对应位都是0,则结果为0,否则为1
^如果相对应位值相同,则结果为0,否则为1
~按位取反运算符翻转操作数的每一位,即0变成1,1变成0。
<<按位左移运算符。左操作数按位左移右操作数指定的位数。
>>按位右移运算符。左操作数按位右移右操作数指定的位数。
>>>按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。

注:

  • 在实际编程中,位移运算符仅作用于整型(32位)和长整型(64位)数上。
  • 移动的位数是一个mod的结果(32位:mod 32,64位:mod 64)。即对应来说,35 >> 135 >> 33是一样的结果,35 << 1和35 << 65是一个结果。
  • 负数在无符号右移63位时,除最右边为1外,其余均为0,达到最小值1。如果>>> 64,则为其原数值本身。

|和||,&和&&区别

&和&&,按位与和逻辑与,两者都可以作用于条件表达式,但是后者有短路的作用,表达式如下:

boolean a = true;
boolean b = true;
boolean c = (a=(1==2)) && (b=(1==2)); // 最后a的值为false, b的值为true
boolean c = (a=(1==2)) & (b=(1==2)); // 最后a的值为false, b的值为false

同样,|和||,按位或与逻辑或,后者具有短路的功能,表达式如下:

boolean a = true;
boolean b = true;
boolean c = (a=(1==1)) || (b=(1==1)); // 最后a的值为true, b的值为false
boolean c = (a=(1==1)) | (b=(1==1)); // 最后a的值为true, b的值为true

浮点数

思考:精度丢失的原因

float a = 1F;
float b = 0.9F;

// 结果为:0.100000024
float c = a - b;
  1. 这是编程时经常会出现的问题,小数的精度丢失。首先要明确一点的是,在Java中,小数也就是浮点数采用的是科学计数法:a × 10n,其中 1≤|a|<10

  2. 以单精度float为例,它占用4个字节,也就是32个bit,分为三个部分

    1. 符号位: 在最高位处,0表示整数,1表示负数;

    2. 阶码位: 存储的其实是指数对应的移码,而不是指数的原码或补码。移码:将一个真值在数轴上正向平移一个偏移量之后得到的,所以移码大的真值也大。这样做的好处就是可以更直观地反映两个真值的大小。

      假设指数的真值为e,阶码位E,则有:E = e + (2n-1 - 1),以单精度为例,这里的n = 8。

    3. 尾数位:最右侧分配连续的23位用来存储有效数字。这里为了节约存储空间,将符合规格化尾数的首个1省略,所以尾数表面上是23位,其实表示了24位二进制数。

  3. 在进行加减运算时会进行如下操作:

    1. 零值检测。检查参加运算的两个数中是否存在为0的数。(浮点数是无法表示0的,所以0在浮点数是一种规定,即阶码与尾数全为0)。所以,如果其中一个数为0,则直接得出结果。
    2. 对阶操作。判断阶码是否相等,跟数学中的同位相加减是一个道理。而计算机中移位会导致精度丢失,无论是左移还是右移,但是右移可能会将高位移除。所以,对阶时移动方向为右移,即选择阶码小的数进行操作。
    3. 尾数求和。对阶完成后,直接按位相加减即可。
    4. 结果规格化。要满足科学技术的规范,结果有可能需要进行阶码移位。
    5. 结果舍入。对阶和规格化时,会右移导致精度丢失,即被移出的位数会被丢弃,从而导致结果精度丢失。为了减少这种精度的损失,先将移出的这部分数据保存起来,称为保护位,等到规格化后再根据保护位进行舍入处理。

通过上文的叙述,下面说一下为何1.0f - 0.9f会有精度丢失:

浮点数符号阶码尾数尾数补码
1.001271000-0000-0000-0000-0000-00001000-0000-0000-0000-0000-0000
-0.911261110-0110-0110-0110-0110-01100001-1001-1001-10001-1001-1010
  • 首先两个数都不为0。但是,由于阶码不同,所以要进行对阶操作。根据规定将126移动为127,同时高位补1,则移动后-0.9的尾数补码为:1000-1100-1100-1100-1100-1101
  • 此时进行同位加减。尾数位计算结果为:0000-1100-1100-1100-1100-1101
  • 规格化。尾数的最高位必须为1,所以结果需要向左移4位,同时阶码需要减4,再隐藏最高位,而低位则用0进行补齐。最终尾数结果为:100-1100-1100-1100-1101-0000
  • 综上,最终结果的符号为0,阶码位1111011,尾数为100-1100-1100-1100-1101-0000。三部分组合起来就是最终结果,对应的十进制就是0.100000024

CPU与内存

CPU内部结构:由控制器和运算器组成,内部寄存器来使这两者的协同更加高效。

控制器

控制器有点像编程语言的编译器。由控制单元、指令编码器、指令寄存器组成。控制单元是CPU的大脑,由时序控制和指令控制组成;指令编码器是在控制单元的协调下完成指令读取、分析并交由运算器执行等操作;指令寄存器是存储指令集。

运算器

它的核心是算术逻辑运算元,即ALU,能执行算术运算或逻辑运算等各种指令,运算单元会从寄存器中提取或存储数据。

寄存器

最著名的寄存器是CPU的高级缓存L1、L2,CPU的运行速度远大于内存的读写速度。并且CPU会缓存部分指令和数据,以提升性能。

TCP/IP

  • Transmission Control Protocol/Internet Protocol中文译为传输控制协议/因特尔互联网协议,我们熟知的协议有:HTTP、HTTPS、FTP、SMTP、UDP等。详细的讲解这里就不叙述了,类似的文章也有狠多。这里详解一下三次握手四次挥手

TCP建立连接

  • 首先TCP是一种面向连接,确保数据在端到端间可靠传输的协议。TCP报文格式如下图所示:
    在这里插入图片描述

  • 这里着重介绍TCP的FLAG位,由6个bit组成,分别代表SYN、ACK、FIN、URG、PSH、RST,都以置1表示有效。重点关注SYN、ACK和FIN

    • SYN(Synchronize Sequence Numbers)用作建立连接时的同步信号;
    • ACK(Acknowledgement)用于对收到的数据进行确认,所确认的数据由确认序列号表示;
    • FIN(Finish) 表示后面没有数据需要发送,通常意味着所建立的连接需要关闭了。
  • 三次握手指的就是TCP建立连接的三个步骤:

    1. A机器(客户端)发出一个数据包并将SYN置为1,表示希望建立连接。包中携带的序列号假设为x。
    2. B机器(服务端)收到A机器发过来的数据包后,通过SYN得知这是一个建立连接的请求,于是发送一个响应包并将SYN和ACK标记都置为1。假设这个包中的序列号是y,而确认序列号必须是x+1,表示收到了A发送过来的SYN。在TCP中,SYN被当作数据部分一个字节。
    3. A机器收到B机器的响应后需要进行确认,确认包中将ACK置1,并将序列号设置为y+1,表示收到了来自B的SYN
      在这里插入图片描述
  • 三次握手有两个目的:信息对等和防止超时。三次握手,分别确认的信息如下图所示:
    在这里插入图片描述

TCP断开连接

TCP是全双工通信,这与数据库建立的连接时不同的。断开连接,则需要进行四步操作:

  1. A机器想要关闭连接,则待本方数据发送完毕后,传递FIN信号给B机器;
  2. B机器应答ACK,告诉A机器可以断开,但是需要等B机器处理完数据,再主动给A机器发送FIN信号。这时,A机器处于半关闭状态(FIN_WAIT_2),无法再发送新的数据。
  3. B机器做好连接关闭前的准备工作后,发送FIN给A机器,此时B机器也进入半关闭状态(CLOSE_WAIT)
  4. A机器发送针对B机器FIN的ACK后,进入TIME_WAIT状态,经过2MSL(Maximum Segment Lifetime)后,没有收到B机器的报文,则确定B机器已经收到A机器最后发送的ACK指令,此时TCP连接正式释放。
    在这里插入图片描述

HTTPS

  • HTTPS的全称是HTTP over SSL,简单理解就是在http传输的基础上增加了SSL协议的加密能力。
  • SSL(Secure Socket Layer, SSL)安全套接字层,SSL协议工作于传输层与应用层之间,为应用提供数据的加密传输。
  • 访问一个HTTPS的网站的大致流程如下:
    1. 浏览器向服务器发送请求,请求中包括浏览器支持的协议,并附带一个随机数。
    2. 服务器收到请求后,选择某种非对称加密算法,把数字证书签名公钥、身份信息发送给浏览器,同时也附带一个随机数。
    3. 浏览器收到后,验证证书的真实性,用服务器的公钥发送握手信息给服务器。
    4. 服务器解密后,使用之前的随机数计算出一个对称加密的密钥,以此作为加密信息并发送
    5. 后续所有的信息发送都是以对称加密方式进行的。
      在这里插入图片描述

面向对象

  • 面向对象的四大特征:抽象、封装、继承和多态。
    • 抽象是程序员的核心要素之一,体现出程序员对业务的建模能力,以及对架构的宏观掌控力。
    • 封装是一种对象功能内聚的表现形式,是模块之间的耦合度便利,更具有维护性
    • 继承使子类能够继承父类,获得父类的部分属性和行为,使模块更有复用性
    • 多态使模块在复用性的基础上更加有扩展性,使系统运行更有想象空间。

访问权限控制

  • Java访问权限分为四个等级,权限控制及可见范围如下表所示:
访问权限控制符任何地方包外子类包 内类 内
public
protected×
××
private×××
  • 在定义类时,推荐访问控制级别从严处理:
    1. 如果不允许外部直接通过new创建对象,构造方法必须是private
    2. 工具类不允许有publicdefault构造方法;
    3. 类非static成员变量并且与子类共享,必须是protected
    4. 类非static成员变量并且仅在本类中使用,必须是private
    5. static成员变量如果仅在本类中使用,必须是private
    6. 若是static成员变量,必须考虑是否为final
    7. 类成员变量只供类内部调用,必须是private
    8. 类成员方法只对继承类公开,那么限制为protected

序列化

  • 将数据对象转换为二进制流的过程称为对象的序列化(Serialization)。反之,将二进制流恢复为数据对象的过程称之为反序列化(Deserialization)。常见的序列化方式有三种:
    1. Java原生序列化。保留了对象的元数据信息,兼容性最好,但不支持跨语言,而且性能一般。
    2. Hessian序列化。一种支持动态类型、跨语言、基于对象传输的网络协议。具有如下特性
      1. 自描述序列化类型。极大缩短了二进制流。
      2. 语言无关。支持脚本语言;
      3. 协议简单,比Java原生序列化高效。
      4. 注意:Hessian序列化时,先序列化子类,然后序列化父类,因此反序列化结果会导致子类同名成员变量被父类的值覆盖。
    3. JSON序列化。一种轻量级的数据交换格式。JSON序列化就是将数据对象转换为JSON字符串。可读性比较好,方便调试。

覆写

  • 概念就不多说了。简单的说就是子类重写父类中可以被重写的方法。通过父类引用执行子类方法是需要主要以下两点
    • 无法调用到子类中存在而父类本身不存在的方法;
    • 可以调用到子类覆写父类的方法,这是一种多态的实现。
  • 覆写父类方法,需要满足以下几个条件:
    1. 访问权限不能变小。即方法的访问权限限制不能更小,但可以更大;
    2. 返回类型能够向上转型成为父类方法的返回类型。也就是说,返回类型是父类方法返回类型本身或其子类;
    3. 异常也要能向上转型成为父类的异常。也就是说,子类只能抛出父类方法的异常及其子类异常;
    4. 方法名、参数类型及个数必须严格一致。
  • 覆写只能针对非静态、非final、非构造方法。如果想在子类覆写的方法中调用父类方法,则可以使用super关键字。

重载

  • 先介绍一个概念:方法签名
    • 方法名称+参数类型+参数个数,组成一个唯一键,称为方法签名。注意:方法的返回值并不是方法签名的一员。
    • JVM就是通过这个唯一键决定调用哪种重载的方法。
  • JVM在重载方法时,选择合适的目标顺序方法如下:
    1. 精确匹配;
    2. 如果是基本数据类型,自动转换成更大表示范围的基本类型;
    3. 通过自动拆箱与装箱;
    4. 通过子类向上转型继承了路线依次匹配;
    5. 通过可变参数匹配。
  • 23的区别,举例说:传入int值为1的参数,那么形参类型为long类型的方法优先于形参类型为Integer类型的方法。
  • 可以看出,可变参数的方法是在匹配规则中优先级最低了,并且实际中也极不推荐这种方式。

泛型

  • 泛型定义时,任何字母都可以作为泛型符号。也就说在泛型符号位中使用String,此时并不是表示String类型,而只是一个符号的作用。约定俗称的符号包括:
    • E代表Element,用于集合中的元素;
    • T代表the Type of Object,表示某个类;
    • K代表Key、V代表Vlaue,用于键值对元素。
  • 泛型定义时的规范如下:
    1. 尖括号里的每一个元素都指代一种未知类型;
    2. 尖括号的未知非常讲究,必须在类名之后或方法返回值之前。
    3. 泛型在定义处只具备执行Object方法的能力。
    4. 对于编译之后的字节码指令,并没有这些花里胡哨的方法签名,这也就说明泛型只是一种编写代码时的语法检查
  • 泛型的好处包括:
    • 类型安全,将运行时的异常转到了编译时。
    • 提升可读性;
    • 代码重用。

JVM

字节码

  • Java之所以可以一次编写,到处执行。便是运用了中间码,这种中间码就是字节码。JVM将字节码解释执行,屏蔽对底层操作系统的依赖。如果是热点代码,会通过JIT动态地编译为机器码,提高执行效率。

字节码主要指令如下

  • 加载或存储指令
    1. 将局部变量加载到操作栈中。如ILOAD、ALOAD
    2. 从操作栈顶存储到局部变量表。如ISTORE、ASTORE
    3. 将常亮加载到操作栈顶。如ICONST、BIPUSH、SIPUSH、LDC
      1. ICONST加载的是 -1 ~ 5的数;
      2. BIPUSH,即Byte Immediate PUSH,加载-128 ~ 127之间的数;
      3. SIPUSH,即Short Immediate PUSH,加载-32768 ~ 32767之间的数;
      4. LDC,即Load Constant,在-2147483648 ~ 2147483647或者是字符串时,JVM采用LDC指令压入栈中。
  • 运算指令
    • 将两个操作栈帧的值进行计算,并把结果写入到操作栈顶,如IADD、IMUL等;
  • 类型转换指令
    • 显示转换两种不同的数值类型。I2L、D2F等;
  • 对象创建与访问指令。
    1. 创建对象指令。如NEW、NEWARRAY等;
    2. 访问属性指令。如GETFIELD、PUTFIELD、GETSTATIC等;
    3. 检查实例类型指令。如INSTANCEOF、CHECKCAST等。
  • 操作栈管理指令
    • 出栈操作。如POP即一个元素,POP2即两个元素。
    • 复制栈顶元素并压入栈。如DUP
  • 方法调用与返回指令
    1. INVOKEVIRTUAL指令:调用对象的实例方法;
    2. INVOKESPECIAL指令:调用实例初始化方法、私有方法、父类方法等。
    3. INVOKESTATIC指令:调用类静态方法;
    4. RETURN指令:返回VOID类型。
  • 同步指令
    • JVM使用方法结构中的ACC_SYNCHRONIZED标志同步方法,指令集中有MONITORENTERMONITOREXIT支持synchroinze语义。

类加载过程

  • 具体过程,可以参见另外一篇博客 《JVM的内部结构与详述》

  • 这里补充说一下,为什么需要在自定义类加载器,也就是为何要打破双亲委托机制。

    • 主要是为了防止在实际工程中,引入多个jar包导致某些类存在包路径和类名相同的情况,就会引起冲突,导致应用程序出现异常。主流的容器类框架都会自定义类加载器,实现不同中间件之间的类隔离,有效避免了类冲突。
  • 以下情况下需要自定义类加载器:

    1. 隔离加载类;
    2. 修改类加载方式;动态加载的情况下,会有这种场景。
    3. 扩展加载源。比如从数据库,网络中进行加载。
    4. 防止源码泄露。

内存布局和垃圾回收

详见 《JVM的内部结构与详述》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值