=实战java:底层原理、数据结构、实战应用、设计思维 四个方面

JVM(java virtual machine):就是一个虚拟的用于执行字节码的“虚拟计算机”,它定义了指令集、寄存器集、结构栈、垃圾收集堆、内存区域。JVM负责解释运行java字节码,边解释边运行。java虚拟机是实现跨平台的核心机制。

JRE(java Runtime Environment):包含java虚拟机、库函数和运行java的应用程序所必需的文件。

JDK(java Development Kit):包含JRE以及编译器和调试器等用于程序开发的文件。

(JDK(JRE(JVM)java、 javaw、 libraries、 rt.jar)、javac、jar、bebugging、tools、javap)

JDK用于开发java程序,JRE是java运行环境,JVM是JRE的子集,JRE是JDK的子集。

浮点型float、double的数据不适合用于不容许舍入误差的金融计算领域。如果需要进行不产生舍入误差的精确数字计算、需要使用BigDecimal类和BigInteger类。可以处理任意长度的数值。(注意不要用浮点数进行比较)

java虚拟机的内存可以分为三个区域:栈(stack)、堆(heap)、方法区(method)

栈的特点:

  1. 栈描述的是方法执行的内存模型。每个方法被调用时都会创建一个栈帧(存储局部变量、操作数、方法出口等)。
  2. JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)。
  3. 栈属于线程私有,不能实现线程间的共享。
  4. 栈的存储特性是“先进后出,后进先出”。
  5. 栈是由系统自动分配的,运算速度快。栈是一个连续的内存空间。

堆的特点:

  1. 堆用于存储创建好的对象和数组)数组也是对象)。
  2. JVM只有一个堆,被所有线程共享。
  3. 堆是一个不连续的内存空间,分配灵活,但运算速度慢。

方法区(又叫静态区):

  1. 方法区实际也是堆,只是专门用来存储类、常量的相关信息。
  2. 用来存放程序中永远不变或唯一的内容,如类信息(class对象、静态变量、字符串常量等)。
  • 同一类的每个对象有不同的成员变量存储空间。
  • 同一类的每个对象共享该类的方法。

垃圾回收机制:

1、内存管理:内存管理很大程度上指的就是对象的管理,其中包括对象空间的分配和释放。

对象空间的分配:使用new关键字创建对象即可。

对象空间的释放:将对象赋值null即可。垃圾回收器将负责回收所有“不可达”对象的内存空间。

2、垃圾回收过程:

(1)发现无用的对象。

(2)回收无用对象占用的内存空间。

3、垃圾回收相关算法:

(1)引用计数法:堆中的每个对象都对应一个引用计数器,当有引用指向这个对象时,引用计数器加1,而当指向该对象的引用失效时(引用变为null),引用计数器减1,最后如果引用的计算器值为0 时,则java垃圾回收器会认为该对象时无用对象并对其进行回收,引用回收器的优点是算法简单,缺点是“循环引用的无用对象”无法识别。

(2)引用可达法(根搜索算法):程序吧所有的引用关系看做是一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点。但所有的引用节点寻找完之后,剩余的节点被认为是没有被引用到的节点,即无用节点。

通用的分代垃圾回收机制:

分代垃圾回收机制是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状态:年轻代、年老代和持久代。JVM将堆内存分为Eden、Survivor和Tenured/Old空间。

Java干实施 java实施是干什么的_java

1、年轻代:所有新生成的对象首先发在Eden区。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是Minor GC,每次Minor GC都会清理年轻代的内容,并采用效率较高的复制算法,频繁操作,但是会浪费内存空间。当"年轻代“区域存放满对象后,就将对象存放到年老代区域。

2、 年老代:在年轻代中经历了N(默认是15)次垃圾回收后仍然存活的对象,就会被放到年老代中,因此,可以认为年老代中存放的都是一些生命周期较长的对象。当年老代对象越来越多时,就需要启动Major GC和Full GC(全量回收)来一次大扫除,全面清理年轻代区域和年老代区域。

3、持久代:持久代用于存放静态文件,如java类、方法等。持久代对垃圾回收没有明显影响。

  1. Minor GC:用于清理年轻代区域。Eden区慢了就会触发一次Monor GC ,清理无用对象,将有用对象复制到“Survivor1”和“Survivor2”区中。
  2. Major GC:用于清理老年代区域。
  3. Full GC:用于清理年轻代、年老代区域,其成本较高,会对系统性能产生影响。

JVM调优和Full GC:

在对JVM调优的过程中,很大一部分工作就是对Full GC的调节。有如下原因:可能导致调整Full GC:

(1)年老代(Tenured)被写满。

(2)持久代(Perm)被写满。

(3)System.gc()被显式调用。

(4)上一次GC之后Help的各域分配策略动态变化。

在开发中容易造成内存泄漏的操作:

1、创建大量无用的对象:例如在需要大量拼接字符串时,使用了String而不是StringBullder

2、静态集合类的使用“像HashMap、Vector、List等最容易出现内存泄漏的问题。这些静态变量的生命周期和应用程序一致,所有的对象Object也不能释放。

3、各种连接对象(I/O流对象、数据库连接对象、网络连接对象)未关闭:I/O流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,它们和硬盘或者网络连接,不使用的时候一定要关闭。

4、监听器的使用:释放对象时,没有删除相应的监听器。

要点:

程序员无权调用垃圾回收器。

程序员可以调回System.gc(),该方法只是通知JVM,而不是运行垃圾回收器。应该尽量少用,因为会申请启动Full GC,成本高,影响系统性能。

finalize方法,是java提供给程序员用来释放对象或资源的方法,但应尽量少用。

创建一个对象分为以下四步:

(1)分配对象空间,并将对象成员变量初始化为0或空。

(2)执行属性值的显示初始化。

(3)执行构造器。

(4)返回对象的地址给相关的变量。

this的本质:“创建好的对象的地址”。由于在构造器调用前,对象已经创建,因此,在构造器中也可以使用this代表”当前对象“。

this的常见用法:

在程序中产生二义性之处,应使用this来指明当前对象。在普通方法中,this总是指向调用该方法的对象;在构造器中,this总是指向正要初始化的对象。

使用this关键字调用重载的构造器,避免相同的初始化代码。但只能在构造器中用,并且必须位于构造器的第一句。

this不能用于static方法中。

静态初始化块执行顺序:

上溯到Object类,先执行Object的静态初始化块,再向下执行子类的静态初始化块,直到类的静态初始化块为止。

构造器执行顺序和上面的顺序一样。

“ == “和equals方法:

“==”代表比较双方是否相同。如果是引用类型则表示地址相等,即是同一个对象。

Object类中定义有:public Boolean equals(Object obj)方法,提供定义“对象内容相等”的逻辑。Object的equals方法默认就是比较两个对象的hashCode,是同一个对象的引用时返回true否则返回false。也可根据自己的要求重写equals方法。

继承树追溯:

1、属性\方法查找顺序(例如查找变量h)

(1)在当前类中查找属性h。

(2)依次上溯每个父类,查看每个父类中是否有h,直到Object为止。

(3)如果没找到,则出现编译错误。

(4)只要找到h变量,则终止这个过程。

2、构造器调用顺序

构造器的第一句总是super(……),用来调用与父类对应的构造器。所以流程就是:先向上追溯到Object,然后再依次向下执行类的初始化块和构造器,直到当前子类为止。(注:静态初始化块调用顺序与构造器调用顺序一样)

关于多态要注意的3点:

(1)多态是方法的多态,不是属性的多态(多态与属性无关)

(2)多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。

(3)父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。

多态的主要优势是提高了代码的可扩展性,符合开闭原则。但是多态也有弊端,就是无法调用子类特有的功能。如果只想使用子类特有的功能,可以使用——对象的转型。

重写与重载之间的区别

区别点

重载方法

重写方法

参数列表

必须修改

一定不能修改

返回类型

可以修改

一定不能修改

异常

可以修改

可以减少或删除,一定不能抛出新的或者更广的异常

访问

可以修改

一定不能做更严格的限制(可以降低限制)

总结

方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
  • (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
  • (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

 

Java干实施 java实施是干什么的_构造器_02

 

1、抽象方法:使用abstract修饰的方法,没有方法体,只有声明。它定义的是一种规范,就是告诉子类必须要给抽象方法提供具体的实现。

2、抽象类:包含抽象方法的类就是抽象类。

抽象类的使用要点:

(1)有抽象方法的类只能定义成抽象类。

(2)抽象类不能实例化,即不能用new来实例化抽象类。

(3)抽象类可以包含属性、方法和构造器,但是构造器不能用new来实例化,只能用来被子类调用。

(4)抽象类只能用来被继承。

(5)抽象方法必须被子类实现。

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

抽象类和接口的区别

  • 1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

注意:JDK1.7之前的版本中,接口中只能包含静态常量和抽象方法,不能有普通属性、构造器和普通方法。在JDk1.8后的版本中,接口中包含普通的静态方法。

注意:内部类只是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类。对于一个名为Outer的外部类和其内部类定义的名为Inner的内部类。编译完成后会出现Outer。class和Outer$Inner.class两个类的字节码文件。所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类相同。

内部内的作用:

  1. 内部类提供了更好的封装,只能由外部类直接访问,而不允许同一个包中的其他类直接访问。
  2. 内部类可以直接访问外部类的私有属性,内部类被当做其外部类的成员。但外部类不能访问内部类的内部属性。
  3. 接口只是解决了多重继承的部分问题,而内部类使得多重继承的解决方案变得更加完整。

内部类主要分为:成员内部类(非静态内部类和静态内部类)、匿名内部类和局部内部类、【用法略】

String类和常量池:

1、全局字符串常量池(String Pool):

全局字符串常量池中存放的内容是在类加载完成后存到String Pool中,在每个VM中只有一份,存放的是字符串常量的引用值(在堆中生成字符串对象实例)。

2、class文件常量池(Class Constant Pool):

class常量池是在编译时每个class都有的,在编译阶段,它存放的常量(文本字符串、final常量等)和符号引用。

3、运行时常量池(Runtime Constant Pool):

运行时常量池是在类加载完成后,将每个class常量池中的符号引用值转存到运行时常量池中。也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

【字符串相等的判断:equals方法用来检测两个字符串的内容是否相等。如果字符串s和t内容相等,则s.equals(t)返回true,否则返回false。判断字符串是否相等不要使用“==”。】

异常的分类:

1、ERROR

2、Exception

(1)RuntimeException:(ArithmeticException、NullPointerException、ClassCastException、ArrayIndexOutOfBoundsException、NumberFormatException)

(2)CheckedException:(IOException、SQLException、FileNotFoundException)

异常处理:(1)捕获异常:try-catch-finally;(2)声明异常:throws;

冒泡算法的优化:定义一个标记,每一次判断数组元素是否发生了交换,如果没有发生,则说明此时数组已经有序,无需再进行后续比较了,此时可以终止比较了。

包装类的作用:

(1)作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如Object[]、集合等。

(2)包含每种基本数据类型的相关属性,如最大值和最小值等,以及相关的操作方法。这些操作方法的作用是在基本数据类型、包装类对象、字符串之间相互转化。

Integer intOne = new Integer(1);
Integer intTwo = Integer.valueOf(10); //官方推荐这种写法;
 
//字符串转化成Integer对象
Integer intThree = Integer.parseInt("334");
Integer intFour = new Integer("998");
 
//Integer对象转化成字符串
String str = intThree.toString();

自动装箱(autoboxing)和拆箱(unboxing)就是指在基本数据类型和包装类之间进行自动的转换。"编译器蜜糖(Compiler Sugar)”

1、自动装箱:JVM自动执行了Integer i = Integer.valueOf(5);

2、自动拆箱:“Integer i=5 ;int j = i.intValue();"

包装类的缓存问题:

整型、char类型所对应的包装类在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。

缓存处理的原理:如果数据在-128~ 127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256哥对象存放到名为cache的数组中。每当自动装箱过程发生时(或手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接回去数值中对应的保障类对象的引用;如果不在该区间,则会通过new调用包装类的构造器来创建对象。

(1)获得key对象的hashcode。

首先调用key对象的hashcode0方法,获得hashcode。

(2)根据hashcode计算出hash值(要求在[0,数组长度-1]区间)。

hashcode是一个整数,需要将它转化至[0,数组长度-1]的范围。转化后的hash值尽量均匀地分布在[0,数组长度-1]这个区间,以减少hash冲突。

一种极其简单和低下的算法:

hash值=hashcode/hashcode;

也就是说,hash值总是1。这意味着,键值对对象都会存储到数组索引1的位置,这样就形成了一个非常长的链表。相当于每存储一个对象都会发生hash冲突,HashMap也由此退化成了一个“链表”。

.一种简单和常用的算法(相除取余算法):

hash值=hashcode%数组长度

这种算法可以i让hash值均匀分布在[0,数组长度-1]区间。早期的HashTable就是采这种算法。由于这种算法使用了“除法”,所以效率低下。JDK后来改进了算法,首先约定数组长度必须为2的整数幂,这样采用位运算即可实现取余的效果:hash值=haslcode&(数组长度-1)。事实上,为了获得更好的散列效果,JDk对hashcode进行了两次散列处理(核心目的是为了使分布得更散,更均匀)

(3)生成Entry对象

如前所述,一个Entry对象包含四部分:key对象、value对象、hash值,以及指向下一个Eatry对象的引用。我们现在已算出了hash值,下一个Entry对象的引用为null。

(4)将Entry对象放到Table数组中

如果本Entry对象对应的数组索引位置还没有放Entry对象,则直接将Entry对象存储进数组:如果对应索引位置已经有Entry对象,则将已有entry对象的next指向本Entry对象,形成链表。

总结如上过程:当添加一个元素(key-value)时,首先计算key的hash值,以此确定插入数组中的位置。但是可能存在同一hash值的元素已经被放在数组同一位置的情况,这时就要将其添加到同一hash值的元素后面,它们在数组的同一位置就形成了链表,同一个链表上的hash值是相同的,所以说数组存放的是链表。JDK8中,当链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。

3.读取数据过程getkey)

通过key对象可获得“键值对”对象,进而返回value对象。这样,在理解了存储数据的过程后,读取数据就比较简单了,其步骤如下。

(1)获得key的hashcode,通过hash)散列算法得到hash值,进而定位到数组的位置。

(2)在链表上逐个比较key对象。调用equals0方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。

(3)返回eaquals0为true的节点对象的value对象。

明白了存取数据的过程,再来看一下hashcode)和equals方法的关系。java规定,两个内容相同(equals()为true)的对象必须具有相等的hashcode。因为如果equals()为true而两个对象的hashcode不同,那么在整个存储过程中就形成了悖论。

4.扩容问题

HashMap的位桶数组,初始大小为16。在实际使用中,其大小显然是可变的。如果位桶数组中的元素个数达到0.75*数组的length时,就调整数组大小至原来的2倍。扩容很耗时。扩容的本质是定义更大的新数组,并将日数组中的内容逐个复制到新数组中。

I/O流体系总结:

(1)InputStream & OutputStream:字节流的抽象
(2)Reader & Writer:字符流的抽象类
(3)FileInputStream & FileOutputStream:节点流,以字节为单位直接操作“文件。
(4)ByteArrayInputStream & ByteOutputStream:节点流,以字节为单位直接操作“字节数组对象”。
(5)ObjectInputStream & ObjectOutputStream :处理流,以字节为单位直接操作“对象”。
(6)DataInputStream & DataOutputStream:处理流,以字节为单位直接操作“基本数据类型和字符串类型”。
(7)FileReader & FileWriter :节点流,以字符为单位直接操作“文本文件”(注意只能读写文本文件)。
(8)BufferedReader & BufferedWriter 处理流,将Reader/Writer对象进行包装,增加缓存功能,提高读写效率。
(9)BufferedInputStream & BufferedOutputStream:处理流,将InputStream & OutputStream对象进行包装,增加缓存功能,提高读写效率。
(10)InputStreamReader & OutputStreamWriter:处理流,将字节对象转化成字符流对象。
(11)PrintStream:处理流,将OutputSream进行包装,它可以方便的输出字符,更加灵活。
InputStream & OutputStream 和 Reader & Writer类是所有I/O流类的抽象父类。

【菜鸟雷区】

文件字节流可以处理所有的文件,但是字节流不能很好地处理Unicode字符,经常会出现“乱码”现象,所以处理文本文件时,一般可以使用文件字符流,它以字符为单位进行操作。

【菜鸟雷区】

使用缓冲字节/字符流包装文件字节流,增加缓冲功能,提高效率,在关闭流时,应该先关闭最外层的包装流,即,“后开启的先关闭”

缓存区的默认大小是8192字节,也可以使用其他构造器来指定大小。(BufferedReader提供了更方便的readLine()方法,直接按行读取文本。将读取的一行字符串写入文件中后,下次写入前,要换行,否则会在上一行后边继续追加,而不是另起一行newLine();)

【菜鸟雷区】

在使用数据流时,读取顺序一定要和写入顺序一致,否则不能正确的读取数据。

【菜鸟雷区】

  • 对象流不仅可以读写对象,,还可以读写基本数据类型。
  • 使用对象流读写对象时,该对象必须经过序列化与反序列化
  • 系统提供的类(Date)已经实现了序列化接口,自定义类必须手动实现序列化接口。

序列化与反序列化:

将java对象转换为字节序列的过程称为对象的序列化。将字节序列恢复为java对象的过程称为对象的反序列化。

对象序列化的作用:

持久化:把对象的字节序列永久的保存到硬盘上,通常存放在一个文件中,例如休眠的实现,服务器session的持久化,hibernate持久化对象等。

网络通信:在网络上传送对象的字节序列,例如服务器之间的数据通信,对象传递等。

序列化涉及的类和接口:ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,吧得到的字节序列写到一个目标输出流中。

ObjectInputStream代表对象输入流,readObject()方法可以从一个源输入流中读取字节序列,再将其反序列化为一个对象并返回。

只有实现了Serializable接口的对象才能被序列化。Serializable接口是一个空接口,只是起到标记作用。

【注意】

  • static属性不参与序列化
  • 对象中的某些属性如果不想被序列化,不能使用static,而应使用transient修饰。
  • 为了防止读和写的序列化ID不一致,一般指定一个固定的序列化ID。

I/O流中大量使用了装饰器模式,让流具有更强大的功能以及更强的灵活性。

Apache IOUtils和FileUtils类库提供了更加简单、功能也更加强大的文件与I/O流操作功能。

【著名的java技术:commons,kafka,lucene,maven,shiro,struts等技术以及大数据技术中的Hadoop(大数据第一技术),hbase,spark,storm,mahout等0】

java实现多线程:

1、通过继承Thread类实现多线程:缺点:如果已经继承了一个类则无法再继承Thread类。

2、通过实现Runnable接口实现多线程。

Java干实施 java实施是干什么的_基础_03

终止线程的典型方式:终止线程一般不使用JDK提供的stop()/destroy()方法(它们本身已经被JDK废弃)。通常的做法是提供一个Boolean型的终止变量,当这个变量置false时,终止线程。

暂停线程执行的常用方法:sleep()和 yeild()

区别:

sleep()方法可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。

yield()方法可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。

联合线程的方法:线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕,才能继续执行。

实现线程的同步:可以通过private关键字来保证数据对象只能被方法访问,所以只需针对方法提出一套机制,这套机制就是使用sychronized关键字,它包括两种用法:sychronized方法和sychronized块。

quanz任务框架实现任务定时调度。