Java知识点概要

Java知识点

一、java和Scala

1、scala中声明变量的方式有几种?

  • 1)scala中的变量分为:不可变变量 val ( value) 和 可变变量 var( variable)
    • val:定义的是不可重新赋值的变量(值不可修改),使用val声明变量,相当于java 中的final修饰,不能在指向其他的数据了
    • var:定义的是可重新赋值的变量(值可以修改),后期可以被修改重新赋值
  • 2)scala中声明变量是:变量名称 在前,变量类型 在后,跟java是正好相反
  • 3)scala中定义变量可以不写类型,让scala编译器自动推断

2、scala与Java的区别

scala是运行在 JVM 上的多范式编程语言,同时支持面向对象和面向函数编程,scala最终被编译为.class文件,运行在JVM虚拟机中,其实本质上还是java, 所以在scala和java可以互调双方的api,兼容Java,可以访问庞大的Java类库。

  • Scala是函数式和面向对象编程的结合,而Java是面向对象编程语言。

  • 用Scala编写的代码更短、更紧凑。在Java中,代码是长格式的。

  • (1)变量

    • scala中声明变量,名称在前,类型在后,java正好相反;
    • scala定义变量可不写类型,自动推断,语句最后不需要分号;
    • scala中val声明不可变变量,相当于final,用var 声明可变变量;
    • scala用关键字lazy来定义惰性变量,调用时,才实例化;
    • scala所有类型,都使用大写字母开头,整形使用Int而不是Integer
  • (2)方法函数

    • scala通过def 语句定义方法,通过val 语句定义函数。函数没有返回值用Unit,相当于java的void 。
    • scala的return是可选的,方法调用会自动返回最后求值的表达式。如果scala使用了return则需要显示指定方法的返回值
  • (3)接口

    • scala不支持接口interface,采用trait(类似于Java中的抽象类)。 java支持接口
    • 类和方法修饰符的默认值 scala默认是public,java默认是protected.
  • (4)对字符串的支持 :scala采用三个双引号“”“支持换行字符串,Java需要采用“+”进行字符串的连接。

3、Scala方法函数

  • 1、def 定义方法,参数列表的类型不可省,返回类型自动推断可省(递归方法除外), 返回值为最后一行可不写return ;
  • 2、val定义函数,函数是一个对象(变量),不需返回类型
  • 3、方法是类的一部分,隶属于类或者对象,运行时加载到 JVM 的方法区;
  • 4、函数是一个完整对象,可赋值给一个变量,运行时加载到 JVM的堆内存中。函数对象有apply,curried,toString,tupled这些方法,而方法则没有。继承 Trait(Trait(特征) 相当于 Java 的接口。
  • 5、方法转换为函数,val a = add _

方法 描述

  • 遍历 foreach: 传入一个函数对象,函数输入为集合元素,返回为空。list.foreach(println)
  • 映射 map:传入一个函数对象,函数输入为类型A,返回为类型B。list.map(_*10)
  • 扁平化映射 flatmap :传入一个函数对象,函数的输入是集合的元素,返回一个集合 list.flatMap(_.split(" "))
  • 过滤 filter: 传入一个函数对象,接收一个集合类型的参数,返回布尔类型
  • 排序 sort :传入一个函数对象,接收一个集合类型的元素参数,返回排序后的列表
  • 分组 groupBy: 传入一个函数对象,接收集合元素类型的参数,返回一个K类型的key,这个key会用来进行分组,相同的key放在一组中
  • 聚合 reduce:传入函数对象,用来不断进行聚合操作,第一个A1类型参数为当前聚合后的变量,第二个A1类型参数为:当前要进行聚合的元素,列表最终聚合为一个元素
    // 第一个下划线表示第一个参数,就是历史的聚合数据结果
    // 第二个下划线表示第二个参数,就是当前要聚合的数据元素 scala> a.reduce(_ + _)

4、方法和函数的区别?

(1)定义方式:val 语句 定义函数;def 语句 定义方法。

(2)属性不同

  • 方法是类的一部分,隶属于类或者对象的。运行时加载到 JVM 的方法区。Scala 中的方法跟 Java 的类似,方法是组成类的一部分。
  • 函数是一个完整的对象,可以赋值给一个变量。运行时加载到 JVM的堆内存中。函数对象有apply,curried,toString,tupled这些方法,而方法则没有。
  • Scala 中的函数其实就是继承了 Trait(Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大的类的对象。
  • 换句话来说,在类中定义的函数,即是方法。方法转换为函数:使用_即可将方法转换为函数,作为变量传递,就需要将方法转换为函数

1、定义方式:

//方法的定义
scala> def m1(x: Int) = x + 1
m1: (x: Int)Int
​
#方法是一个以def开头的带有参数列表(可以无参数列表)的一个逻辑操块,
#这正如object或者class中的成员方法一样。(上面有定义说明)
​
//函数的定义
scala> val f1 = (x : Int) => x + 1
f1: Int => Int = $$Lambda$1036/961708482@1573e8a5
​
#函数是一个赋值给一个变量(或者常量)的匿名方法(带或者不带参数列表),
#并且,通过 =>转换符号 跟上逻辑代码块的一个表达式。
# =>转换符号 后面的逻辑代码块的写法与method的body部分相同。


2、相互转换:

scala> def add(x:Int,y:Int)=x+y
add: (x: Int, y: Int)Int
​
scala> val a = add _
a: (Int, Int) => Int = <function2>

5、集合

·(1)元组,val a = (1, “张三”, 20, “北京市”) :可包含一组不同类型的值,_1表 示访问第一个元素

·(2)变长数组,scala.collection.mutable.ArrayBuffer:增+=、删-=、追加++=, array.sum/max/min/sorted

· (3)可变Map,scala.collection.mutable.Map: 增map+=(“w” ->35),删map -=“w”,改map(“li”)=50,查map(“zh”)

·(4)可变Set,scala.collection.mutable.Set:不重复 无顺序,增+=、删-=、追 加++=,

·(5)可变列表,scala.collection.mutable.ListBuffer,可重复 有顺序,增+=、删 -=、++=加list,

Scala元组将固定数量的项目组合在一起,以便它们可以作为一个整体传递。
元组可以容纳不同类型的对象( 与数组或列表不同),但它们也是不可变类型。

6、匿名函数:一个没有名称的函数

//定义一个数组
scala> val array=Array(1,2,3,4,5)
array: Array[Int] = Array(1, 2, 3, 4, 5)//定义一个没有名称的函数----匿名函数
scala> array.map(x=>x*10)
res1: Array[Int] = Array(10, 20, 30, 40, 50)

7、柯里化

含义:方法可以定义多个参数列表,用较少的参数列表,调用较多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化。

  • 柯里化(Currying)是一种,将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

  • 柯里化,是把接受多个参数的函数,变换成,接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

  • 柯里化,是函数式编程的一个重要概念。它既能减少代码冗余,也能增加可读性。另外,附带着还能用来装逼。

curry化最大的意义在于, 把多个参数的function等价转化成, 多个单参数function的级联,这样所有的函数就都统一了,方便做lambda演算。

在scala里,curry化对类型推演也有帮助,scala的类型推演是局部的,在同一个参数列表中后面的参数不能借助前面的参数类型进行推演,curry化以后,放在两个参数列表里,后面一个参数列表里的参数可以借助前面一个参数列表里的参数类型进行推演。这就是为什么 foldLeft这种函数的定义都是curry的形式

# 定义2个整数相乘运算
def multiplie2par(x:Int,y:Int)=x*y
​
# 使用柯里化技术,
# 将上述2个整数的乘法函数,改修为接受一个参数的函数,
# 该函数 返回 是一个以原有第二个参数为参数的函数。def multiplie1par(x:Int)=(y:Int)=>x*y
​
# 说明:
# multiplie1par(x:Int) 为接收一个参数的新等价函数,
# (y:Int)=>x*y 则是新等价函数的返回体,它本身就是一个函数(严格来说应该是一个匿名函数),
# 参数为 除等价新函数的参数外原函数剩余的参数。
​
进一步简化为:
def multiplie1par1(x:Int)(y:Int)=x*y
​
    
三个整数乘法的函数
def multiplie3par(x:Int,y:Int,z:Int)=x*y*z
def multiplie3par1(x:Int)=(y:Int,z:Int)=>x*y*z
def multiplie3par2(x:Int)(y:Int)(z:Int)=x*y*z
def multiplie3par3(x:Int)(y:Int)=(z:Int)=>x*y*z
​
# 编译执行的结果都是一样,调用不同形式参数过程略有不同,
# 直接参入三个参数的一步到位就可以得到运算结果,而
# 传入1或者2个参数的需要分步骤再传入第2/3或者第3个参数才能求出三个整数相乘的结果,
# 很好地体现了延迟执行或者固定易变因素等方面能力。def getAddress(a:String):(String,String)=>String={
       (b:String,c:String)=>a+"-"+b+"-"+c
       }
​
​
scala> val f1=getAddress("china")
f1: (String, String) => String = <function2>
​
scala> f1("beijing","tiananmen")
res5: String = china-beijing-tiananmen
​
​
​
//这里就可以这样去定义方法
​
def getAddress(a:String)(b:String,c:String):String={ 
        a+"-"+b+"-"+c 
        }
//调用
scala> getAddress("china")("beijing","tiananmen")
res0: String = china-beijing-tiananmen
​
//之前学习使用的下面这些操作就是使用到了柯里化
List(1,2,3,4).fold(0)(_+_)
List(1,2,3,4).foldLeft(0)(_+_)
List(1,2,3,4).foldRight(0)(_+_)

8、闭包

函数里面,引用外面类成员变量叫作闭包

var factor=10val f1=(x:Int) => x*factor
​
//定义的函数f1,它的返回值是依赖于不在函数作用域的一个变量//后期必须要要获取到这个变量才能执行
//spark和flink程序的开发中大量的使用到函数,函数的返回值依赖的变量可能都需要进行大量的网络传输获取得到。
//这里就需要这些变量实现序列化进行网络传输。

scala中是没有Java中的静态成员的。如果将来我们需要用到static变量、static方法,就要用到scala中的单例对象object

7、守卫

在for表达式中可以添加if判断语句,这个if判断就称为守卫
scala> for(i <- 1 to 10 if i >5) println(i) 6 7 8 9 10

8、隐士转换

Scala提供的隐式转换隐式参数功能,是Java等编程语言所没有的功能。
scala允许开发人员自定义类型转换规则,将两个无关的类型通过编程手段让他们自动转换。

隐式转换:在 Scala 编译器进行类型匹配时,如果找不到合适的类型就会编译失败,此时会在当前的环境中自动推导出合适的类型,从而完成编译。

核心:就是定义一个使用 implicit 关键字修饰的方法 ,实现把一个原始类转换成目标类,进而可以调用目标类中的方法。

作用:简化编程,调用方法时,不需要向隐式参数传参,Scala 会自动在其作用域范围内寻找隐式值,并自动传入。通过隐式转换,可以在不改变代码的情况下,扩展某个类的功能。

隐式函数:使用implicit关键字声明的函数称之为隐式函数

object ScalaImplicit {
    def main(args: Array[String]): Unit = {
      //定义隐式函数 让Double 类型的变量自动转换为int类型
      implicit def transform( d : Double ): Int = {
          d.toInt
      }
      var d : Double = 2.0
      val i : Int = d
      println(i)
    }
}
 

隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数, 此时Scala会尝试找到一个指定类型的用implicit修饰的参数,即隐式值,并注入参数。
所有的隐式转换和隐式参数必须定义在一个object中

object ImplicitConversion {
  def main(args: Array[String]): Unit = {
    //隐式值/变量
    implicit val dd : Double = 2.0

    //隐式参数
    def transform( implicit  d : Double = 3.0 ) = {
      d.toInt
    }
    
	//值调用顺序:隐式值 -> 隐式参数默认值 -> 前两者都没有 报错
    println(transform()) //结果:3  
    //方法调用时,使用小括号会导致隐式值无法转递 所以这里调用的是隐式参数的值
    
    println(transform)//结果:2
    //不使用小括号可以传递隐式值
  }
}
}

在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用。
其所带的构造参数有且只能有一个
隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。

object ScalaImplicit {
    def main(args: Array[String]): Unit = {
        val emp = new Emp()
        emp.insertUser()
    }
    class Emp {
    }
    //将一个类Emp 变成另外一个类 User
    implicit class User( emp : Emp) {
        def insertUser(): Unit = {
            println("insert user...")
        }
    }
}

二、JVM

1、JVM是什么?

JVM(java虚拟机Java Virtual Machine), 是一种抽象化的计算机, 在运行时负责将Java程序的.class文件解释成特定的机器码进行运行,有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。具有跨平台、跨语言的特性。

JVM可以将Java语言通过各种不同的解释器翻译成各个平台(windows、linux等)能读懂的语言因此可以跨平台。

JVM ,用以把Java语言编译后的.class文件翻译成系统能读懂的机器码, 如果别的语言也翻译成了.class二进制字节码文件,也可以在JVM里运行,如Scala。

2、类加载机制:

类加载器负责加载class文件,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且ClassLoader只负责文件的加载,至于它是否可以运行,则由Execution Engine决定

从类被加载到虚拟机内存中开始,到卸御出内存为止,它的整个生命周期分为7个阶段:加载--验证--准备--解析--初始化--使用--卸载。其中验证、准备、解析三个部分统称为连接。

  • 1)加载

    • 将class文件加载在内存中。
    • 将静态数据结构(数据存在于class文件的结构)转化成方法区中运行时的数据结构。
    • 在堆中生成一个代表这个类的java.lang.Class对象,作为数据访问的入口
  • 2)验证:确保加载的类符合JVM规范与安全。保证被校验类的方法在运行时不会做出危害虚拟机安全的事件

  • 3)准备:为static变量在方法区中分配空间,设置变量的初始值。例如static int a=3,在此阶段会a被初始化为0;

  • 4)解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 5)初始化:为类的静态变量赋予正确的初始值

  • 6)使用:正常使用

  • 7)卸载:GC把无用的对象从内存中卸载

Java知识点概要_第1张图片
Java知识点概要_第2张图片

3、类的加载顺序:

1)Bootstrap ClassLoader:负责加载JAVA_HOME中jre/lib/rt.jar里所有的 class,由 C++ 实现,不是 ClassLoader 子类。

2)Extension ClassLoader:负责加载Java平台中扩展功能的一些 jar 包,包括JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的 jar 包。

3)App ClassLoader:负责加载 classpath 中指定的 jar 包及目录中 class。

4)Custom ClassLoader:属于应用程序根据自身需要自定义的 ClassLoader,如 Tomcat、jboss 都会根据 J2EE 规范自行实现 ClassLoader。

先检查,再加载

  • 检查类的顺序:是自底向上,从 Custom ClassLoaderBootStrap ClassLoader 逐层检查,只要某个 Classloader 已加载就视为已加载此类,保证此类只所有 ClassLoader 加载一次。

  • 加载类的顺序:是自顶向下,也就是由上层来逐层尝试加载此类。
    Java知识点概要_第3张图片

4、双亲委派机制

  • JVM在加载类时默认采用的是双亲委派机制:

  • 就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
    Java知识点概要_第4张图片

5、JVM中内存区域的划分​

Java知识点概要_第5张图片

JVM运行时数据区域:由5块部分组成,分别是堆,方法区,虚拟机栈,本地方法栈,以及程序计数器组成。
可以根据内存是否线程共享划分成:线程私有区域/ 线程共享区域

  • 1.java文件经过编译之后变成class字节码文件
  • 2.字节码文件通过类加载器被搬运到jvm虚拟机当中来
  • 3.虚拟机当中主要有五大块
    • 1)方法区: 存储已经被虚拟机加载的类结构信息、常量、静态变量等;方法区的大小决定了系统可以保存多少个类
    • 2)堆: new的对象,对象实例和数组等。线程共享区域
    • 3)虚拟机栈: 主管Java方法的执行,存储方法的局部变量 、部分中间结果,并参与方法的调用和返回。
    • 4)本地方法栈:调用一些底层的C程序实现。线程私有区域
    • 5)程序计数器: 存储下—条即将要执行的字节码指令地址,由执行引擎读取下一条指令,程私有区域

Java知识点概要_第6张图片

  • 一个进程中存在 多个线程 ,
  • 每个线程中,都存在自己的栈和程序程序计数器,
  • 一个进程中,共用一个堆和一个方法区 。
  • 局部变量在栈上,静态变量在方法区中、成员变量在堆上

Java知识点概要_第7张图片
Java知识点概要_第8张图片

Java知识点概要_第9张图片
Java知识点概要_第10张图片

5.1、堆
  • 堆内存:存放的是new的对象,对象实例和数组等,垃圾收集器就是收集这些对象,然后根据GC算法回收。

  • 一个JVM实例只存在一个堆内存,堆内存也是Java内存管理的核心区域,所有的线程共享Java堆内存

  • JVM启动的时候即被创建,堆内存的大小是可调节的。

  • 方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除

  • 堆内存是Gc执行垃圾回收的重点区域

  • 新生代:老年代 = 1:2

  • 新生代中,suvivor(幸存者):eden0(from):eden1(to 伊甸园)= 8:1:1

堆内存GC流程 ,对象如何晋升到老年代

  • 当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。

    • 如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
    • 大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;
  • 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。

  • Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。

堆内存什么要分成新生代,老年代,持久代

  • 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。

  • Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

  • 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)

Java知识点概要_第11张图片
在jdk1.8当中已经不存在永久代这一说了,取而代之的是元数据区,

Java知识点概要_第12张图片

5.2、方法区
  • 方法区:存储已经被虚拟机加载的类结构信息、常量、静态变量等;

  • 各个线程共享的内存区域,在JVM启动的时候被创建, 关闭JVM就会释放这个区域的内存。

  • 方法区的大小决定了系统可以保存多少个类,如果定义太多类,加载大量的第三方的Jar包,Tomcat部署过多工程,导致方法区溢出,虚拟机同样会抛出内存溢出OOM:PermGenspace或者Metaspace

Java知识点概要_第13张图片
Java知识点概要_第14张图片

5.3、虚拟机栈

虚拟机栈,主管Java程序的运行(主要是方法的执行),保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分中间结果,并参与方法的调用和返回。

  • 每个线程创建时都会创建一个虚拟机栈,

  • 内部保存一个个栈帧,对应着一次次的Java方法调用

  • 生命周期和线程的—致,线程被销毁,虚拟机栈也就随之销毁,不存在垃圾回收问题,

  • 栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,每个方法执行的同时都会创建一个栈帧,用于存储局部变量表等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

  • 栈的大小和具体JVM的实现有关,通常在256K~756K之间,与等于1Mb左右

栈内存溢出

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。

  • 如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)

  • 参数 -Xss 去调整JVM栈的大小

Java知识点概要_第15张图片

  • 栈管运行,堆管存储
    在这里插入图片描述
5.4、本地方法栈
  • 虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,
  • 本地方法栈,为虚拟机使用到的Native方法服务(比如C语言写的程序和C++写的程序)
  • Java虚拟机栈管理Java方法的调用,而本地方法栈用于管理本地方法的调用

5.5、程序计数器

  • 程序计数器是内存区域中唯一 一块不存在OutOfMemoryError的区域
  • 每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
    Java知识点概要_第16张图片
    在这里插入图片描述

5.6、本地方法接口

作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++程序,目前该方法使用的越来越少了,除非是与硬件有关的应用
Java知识点概要_第17张图片

9、垃圾回收

Java知识点概要_第18张图片

Java知识点概要_第19张图片

Java知识点概要_第20张图片
在这里插入图片描述

Java知识点概要_第21张图片

Java知识点概要_第22张图片

10 、STW

Java知识点概要_第23张图片

11. 垃圾收集器,各自的优缺点,重点 cms和G1

Java知识点概要_第24张图片
Java知识点概要_第25张图片Java知识点概要_第26张图片

(1)几种垃圾收集器:

  • 1)Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。
  • 2)ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法
  • 3)Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
  • 4)Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
  • 5)Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
  • 6)CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
  • 7)G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。

(2)CMS收集器和G1收集器的区别:

  • 1) CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
    G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
  • 2)CMS收集器以最小的停顿时间为目标的收集器;
    G1收集器可预测垃圾回收的停顿时间
  • 3)CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
    G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
    在这里插入图片描述

Java知识点概要_第27张图片
Java知识点概要_第28张图片

12、调优

Java知识点概要_第29张图片
Java知识点概要_第30张图片
Java知识点概要_第31张图片

Java知识点概要_第32张图片
Java知识点概要_第33张图片
在这里插入图片描述

Java知识点概要_第34张图片
Java知识点概要_第35张图片

三、Java基础

1、数据类型

Java知识点概要_第36张图片
Java知识点概要_第37张图片

自动转换:char–> byte–>short–>int–>long–>float–>double

Java知识点概要_第38张图片

所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。

String s1 = "abc";            // 常量池
String s2 = new String("abc");     // 堆内存中
System.out.println(s1==s2);        // false两个对象的地址值不一样。
System.out.println(s1.equals(s2)); // true

Java 中int 和 Integer 的区别

  • 1)int:是基本数据类型,int 变量存储的是数值。Integer 是引用类型,实际是一个对象,Integer 存储的是引用对象的地址。
  • 2)Integer:是一个对象,需要存储对象的元数据,占用更多的内存。但是 int 是一个原始类型的数据,所以占用的空间更少。
  • 3) new Integer() :生成的变量指向堆中新建的对象,非 new 生成的 Integer 变量指向的是 java 常量池中的对象,两者在内存中的地址不同。

Java中String、StringBuffer 和 StringBuilder 的区别
Java知识点概要_第39张图片
在这里插入图片描述

  • 1)String:字符串常量,字符串长度不可变。Java中String 是immutable(不可变)的。用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。
  • 2)StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用 StringBuffer,如果想转成 String 类型,可以调用 StringBuffer 的 toString() 方法。Java.lang.StringBuffer 线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。
  • 3)StringBuilder:字符串变量(非线程安全)。在内部 StringBuilder 对象被当作是一个包含字符序列的变长数组。
    -4)基本原则:如果要操作少量的数据String单线程操作大量数据StringBuilder多线程操作大量数据,用StringBuffer

ArrayList 和 LinkedList 的区别

  • ArrayList 是 List 接口的一种实现,它是使用数组来实现的。

  • LinkedList 是 List 接口的一种实现,它是使用链表来实现的。

  • ArrayList 遍历和查找元素比较快。LinkedList 遍历和查找元素比较慢。

  • LinkedList 添加、删除元素比较快。ArrayList 添加、删除元素比较慢。

    浅拷贝、深拷贝
    Java知识点概要_第40张图片
    基本数据类型的特点:直接存储在栈(stack)中的数据
    引用数据类型的特点: 存储的是该对象在栈中引用,真实的数据存放在堆内存

引用数据类型,在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

深拷贝和浅拷贝,针对引用数据类型

  • 浅拷贝,只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
  • 深拷贝,会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

2、变量类型

public class Variable{
 	static int allClicks=0; // 类变量
 	String str="hello world"; // 实例变量 
public void method(){ 
int i =0; // 局部变量
 	}
 }
  • 1)类变量 (静态变量):声明在类中方法体之外,用 static 修饰,属于整个类。可通过对象名或类名来调用。

  • 2)实例变量(成员变量):声明在类中方法体之外,没有 static 修饰,在创建对象的时候实例化。可以被类中方法、构造方法和特定类的语句块访问。

  • 3)局部变量:在方法、构造方法或语句块中定义的变量 。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。

  • 类变量在方法区中、实例变量在堆上 ,局部变量在栈上

Java知识点概要_第41张图片
Java知识点概要_第42张图片

3、面向对象编程

OOP: object -oriented-proramming 面向对象编程

面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据。

  • 1)(class):数据类型,是一个模板,它描述一类对象的行为和状态。 它是对某一类事物整体描述定义,但是并不能代表某一个具体的事物.
  • 2)对象:对象是类的一个具体实例,有状态和行为。
  • 3)实例(instance):数据
  • 3)属性(field):变量名.字段名,访问field
    Java知识点概要_第43张图片

三大特性:

  • 1)封装:表示一个class包含多个field,用private修饰field,可以拒绝外部访问。
  • 2)继承:表示一个class使用extends继承父类,可获得父类所有功能,只需编写新功能
  • 3)多态:表示方法的调用,总是作用于对象的实际类型,对象能执行的方法,主要看左边,和右边的关系不大

Java知识点概要_第44张图片
Java知识点概要_第45张图片
1.父类引用指向子类的对象
2.把子类转换为父类,向上转型;
3.把父类转换为于类,向下转型;强制转换,((sudent)s2).eat()

Java知识点概要_第46张图片

方法:

  • 1)构造方法:方法名为类名,参数无限制,无返回值,用于初始化实例,可定义多个构造方法,未定义时编译器创建默认构造方法,
  • 2)方法重载:同一类中,多个方法的方法名与返回值类型相同,但参数(个数、类型、位置)不同,目的在于相同功能的方法使用一个名字,便于调用。
  • 3)方法重写@override,子类重写父类的方法,方法签名(方法名称、参数和返回类型) 要一样,重写必须要有继承
  • 同一类重载方法,在子类重写方法,都可以用相同的方法名来实现不同的功能。

高级:

  • 1)抽象方法: 定义了方法,但没有具体的执行代码,那么这个方法为抽象方法, 用abstract修饰,定义了子类必须实现的接口规范,实现代码的重用
  • 2)抽象类包含抽象方法的类,也可以有非抽象方法。定义接口规范,不允许被实例化,只能使用一次继承关系
  • 3)接口(interface)一个抽象类没有实例字段,所有的方法都是抽象方法一个类却可以实现多个接口,但只能继承一个抽象类
  • 4)JavaBean:是一种符合命名规范的class,方便IDE工具读写属性,传递数据,枚举属性
  • 5)内部类:定义在一个class内部的class,可访问外部类私有属性,破坏原有类的程序结构(属性、构造方法、普通方法、内部类)。

4、修饰符

<1> 访问权限:public、default、protected、private

类(外部类),有 2 种访问权限:public、default
方法和变量,有 4 种访问权限:public、default、protected、private

  • 1) public : 表示任何地方的其他类都能访问。
  • 2) private : 表示只有自己类能访问。
  • 3) default :同一个包的类可以访问。
  • 4) protected :表示同一个包的类可以访问,其他的包的该类的子类也可以访问。

<2> 修饰符:abstract、static、final

  • 1) abstract: 表示是抽象类。 使用对象:类、接口、方法
  • 2) static: 可以当做普通类使用,而不用先实例化一个外部类。(用他修饰后,就成了静 态内部类了)。 使用对象:类、变量、方法、初始化函数(注意:修饰类时只 能修饰 内部类 )
  • 3)final: 表示类不可以被继承。 使用对象:类、变量、方法

this、super

  • this :指向对象本身的指针,形参与成员名字重名,用 this 来区分。
  • super :父类对象的一个指针。
    Java知识点概要_第47张图片

5、运算符

1)算术运算符、2)关系运算符、3)位运算符、4)逻辑运算符、5)赋值运算符、6)其他运算符

循环结构 : for, while 及 do…while

Math.floor :向下取整。Math.ceil :向上取整。Math.round :四舍五入取整

装箱和拆箱的概念有点特别。
Java知识点概要_第48张图片

6、反射与注解

Java知识点概要_第49张图片
在这里插入图片描述
Java知识点概要_第50张图片
Java反射机制提供的功能

  • 在运行时,判断任 一个对象所属的类,判断一个类所具有的成员变量和方法
  • 在运行时,构造一个类的对象,获取泛型信息,调用任意一个对象的成员变量和方法, 处理注解生成动态代理,

Java知识点概要_第51张图片
Java知识点概要_第52张图片
Java知识点概要_第53张图片

Java知识点概要_第54张图片

Java知识点概要_第55张图片
Java知识点概要_第56张图片

7、泛型

Java知识点概要_第57张图片

8、Java集合

Java知识点概要_第58张图片

Java知识点概要_第59张图片
Map接口和Collection接口是所有集合框架的父接口:

  • Collection接口的子接口包括:Set接口和List接口
  • Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  • Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

集合和数组的区别
数组是固定长度的;集合可变长度的。

数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

数组: int[] ns={1,2,3,4} ; For(int n :ns) ; int[][] ns={{1,2,3,4},{5,6,7,8}}

9、lamda表达式

Java知识点概要_第60张图片

Java知识点概要_第61张图片
实现接口的方法:(只用一个方法的接口,也称函数式接口)

  • 1)实现类,重写方法;
  • 2)静态内部类,重写方法;
  • 3)局部内部类,重写方法;
  • 4)匿名内部类,重写方法;
  • 5)lamda表达式

Java知识点概要_第62张图片
Java知识点概要_第63张图片

10、异常

Java知识点概要_第64张图片
Java知识点概要_第65张图片

四、数据结构与算法

1、数据结构

Java知识点概要_第66张图片

(1)数组(array)
  • 特点:在内存中顺序存储,是有限个相同类型的变量所组成的有序集合,下标从0开始,一直到数组长度-1。

  • 时间复杂度:读取元素、更新元素:O(1);插入操作、删除操作:O(n)

  • 优点:按照索引查询元素的速度很快;按照索引遍历数组也很方便。

  • 劣势:数组大小在创建后就确定了,无法扩容;数组只能存储一种类型的数据;添加删除元素的操作很耗时间,因为要移动其他元素。

  • 应用场景:读操作多、写操作少的场景

(2)链表(linked list)
  • 链表: 是一种在物理上非连续、非顺序的数据结构,由若干节点所组成。每一个节点又包含两部分,一部分是存放数据的变量data;另一部分是指向下一个节点的指针next。

  • 双向链表:每一个节点除了拥有datanext指针,还拥有指向前置节点的prev指针。

  • 插入删除时, O(1) 的时间复杂度

  • 优点:不需要初始化容量;可以添加任意元素;插入和删除的时候只需要更新引用。

  • 缺点:含有大量的引用,占用的内存空间大;查找元素需要遍历整个链表,耗时。

  • 应用场景:尾部频繁插入、删除元素

数组与链表区别:

  • 数组顺序存储,在内存中占用了连续完整的存储空间。

  • 链表随机存储,采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠next指 针关联起来。这样可以灵活有效地利用零散的碎片空间。

  • 查找元素:链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向
    后一个一个节点逐一查找。O(n)。

  • 数组的优势,在于能够快速定位元素,适用于读多写少的场景

  • 链表的优势,在于能够灵活插入删除操作,如果需要在尾部频繁插入、删除元素,用链表更合适一些

(3)栈(stack)
  • 弹夹很相似,先进后出,是一种线性数据结构,可以用数组来实现,也可以用链表来实现。

  • 入栈和出栈,只影响最后一个元素,不涉及其他元素的整体移动,所以无论是以数组还是以链表实现,入栈出栈的时间复杂度都是O(1)

  • 应用场景:逆流而上追溯“历史”,实现递归的逻辑,就可以用栈来代替,因为栈可以回溯方法的调用链;面包屑导航,用户在浏览页面时,轻松地回溯到上一级或更上一级页面。

(4)队列(queue)
  • 单行隧道很相似,先进先出,是一种线性数据结构,可以用数组来实现,也可以用链表来实现

  • 应用场景:按照“历史”顺序,把“历史”重演一遍

  • 多线程中,争夺公平锁的等待队列,就是按照访问顺序来决定线程在队列中的次序的。

  • 双端队列(deque):把栈和队列的特点结合起来,既可以先入先出,也可以先入后出.

(5)树(tree)
  • ,是一种非线性结构,由n(n>0)个有限结点组成有层次关系的集合。树的最大层级数,被称为树的高度或深度。

  • 二叉树:每个结点最多含有2个子树。可能只有1个,或者没有孩子节点。

  • 满二叉树: 每一个分支都是满的,都有两个子结点的二叉树。

  • -完全二叉树: 条件没有满二叉树 苛刻, 完全二叉树只需保证,最后一个节点之前的节点都齐全即可。

  • 二叉查找树:用以进行查找操作,要求左子树小于父节点,右子树大于父节点,正是这样保证了二叉树的有序性。 性质:

    • 1.如果左子树不为空,则左子树上所有节点的值均小于根节点的值
    • 2.如果右子树不为空,则右子树上所有节点的值均大于根节点的值
    • 3.左、右子树也都是二叉查找树
    • 二叉查找树还有另一个名字——二叉排序树(binary sort tree)。
  • 平衡二叉树:也称AVL树,当且仅当任何结点的两棵子树的高度差不大于1的二叉树。Java中HashMap的红黑树就是平衡二叉树!!!

  • B树:一种对读写优化的自平衡二叉树,在数据库的索引中常见的BTREE就是自平衡二叉树。

  • B+树:B+树是应文件系统所需而产生的B树的变形树。

    • 所有的非终端结点,可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字
    • 所有的叶子结点中,包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。
    • 有m个子树的中间节点包含有m个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引。

遍历:

从更宏观的角度来看,二叉树的遍历归结为两大类。

  1. 深度优先遍历(前序遍历、中序遍历、后序遍历)。
  2. 广度优先遍历(层序遍历)。

深度优先遍历: 偏向于纵深, 一头扎到底 的访问方式。可

  • 前序遍历, 根左右,输出顺序是根节点、左子树、右子树。
  • 中序遍历,左根右,输出顺序是左子树、根节点、右子树。
  • 后序遍历,左右根,输出顺序是左子树、右子树、根节点。

广度优先遍历: 就是二叉树按照从根节点到叶子节点的层次关系一层一层横向遍历各个节点

二叉堆

  • 本质上是一种完全二叉树,它分为两个类型。最大堆、最小堆。
  • 最大堆的任何一个父节点的值,都大于或等于它左、右孩子节点的值
  • 最小堆的任何一个父节点的值,都小于或等于它左、右孩子节点的值。
  • 二叉堆虽然是一个完全二叉树,但它的存储方式并不是链式存储,而是顺序存储。换句话说,二叉堆的所有节点都存储在数组中。
  • 二叉堆是实现堆排序及优先队列的基础。
  • 二叉堆节点“上浮”和“下沉”的时间复杂度都是O(logn),所 以优先队列入队和出队的时间复杂度也是O(logn)!
  • 二叉堆的特性:
  1. 最大堆的堆顶是整个堆中的最大元素。
  2. 最小堆的堆顶是整个堆中的最小元素。
  3. 可以用最大堆来实现最大优先队列,这样的话,每一次入队操作就是堆的插入操作,每一次出队操作就是删除堆顶节点

优先队列

  • 不再遵循先入先出的原则,而是分为两种情况。
  • 最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队
  • 最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队
(6)堆(Heap)
  • 堆可以被看成一个树的数组对象,具有如下特点:
  • 堆是一颗完全二叉树
  • 最大堆/大根堆:某个结点的值不大于父结点的值。
  • 最小堆/小根堆:某个结点的值不小于父结点的值。
(7)图(Graph)
  • 一个图就是一些顶点的集合,这些顶点通过一系列边结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。

  • 节点之间的关系是任意的,图中任意两个数据元素之间都有可能相关。

(8)哈希表(hash table)
  • 散列表,提供了键(Key)和值(Value) 的映射关系。只要给出一个Key,就可以高效查找到它所匹配的Value,时间复杂度接近于O(1)

  • jdk8中,Java中经典的HashMap,以 数组+链表+红黑树 构成。

哈希函数

  • 数组只能根据下标,像a[0]、a[1]、a[2]这样来访问,而散列表的Key则是以字符串类型为主的。
  • 需要一个“中转站”,通过某种方式,把Key和数组下标进行转换。这个中转站就叫作哈希函数。
  • 最简单的转化方式是:按照数组长度进行取模运算
  • 通过哈希函数,可以把字符串或其他类型的Key,转化成数组的下标index。

写操作

  • 就是在散列表中插入新的键值对,如调用hashMap.put(“31”, “王五”),具体做法:
    • 第1步,通过哈希函数,把Key转化成数组下标5。
    • 第2步,如果数组下标5对应的位置没有元素,就把这个Entry填充到数组下标5的位置。

哈希冲突

  • 由于数组的长度是有限的,当插入的Entry越来越多时,不同的Key通过哈希函数获得的下标有可能是相同的。例如002936这个Key对应的数组下标是2;002947这个Key 对应的数组下标也是2。

解决哈希冲突的方法

  • 主要有两种,一种是开放寻址法,一种是链表法。
  • 开放寻址法:当一个Key通过哈希函数获得对应的数组下标已被占用时,我们可以“另谋高就”,寻找下一个空档位置。
  • 链表法,应用在Java的集合类HashMap当中。 HashMap数组的每一个元素不仅是一个Entry对象,还是一个链表的头节点。每一个 Entry对象通过next指针指向它的下一个Entry节点。当新来的Entry映射到与之冲突的数组 位置时,只需要插入到对应的链表中即可。

为了提升插入和查找的效率,HashMap会把Entry链表转化为红黑树这种数据结构。

Java知识点概要_第67张图片
Java知识点概要_第68张图片

2、排序算法

根据时间复杂度的不同,主流的排序算法可以分为3大类。
https://www.processon.com/view/609b2f875653bb147747da16?fromnew=1

1、冒泡排序:从第一个起,相邻两个比较,大的依次往后移动。第一次排序,找到最大的放最后,第二次次排序,找到次大的放倒数第二位置,一直放到第一个位置。

2、选择排序:先选择全部中的最小的,放第一个位置;再选择从第二个到最后中最小的,放第二个位置;依次从后面选择最小的放在前面。

3、插入排序:摸牌,一个有序表和一个无序表,每次从无序表中取第一个元素,依次与有序表元素比较,插入到有序表中的适当位置。

4、希尔排序:插入排序的改进,缩小增量排序。先设置初始增量gap=length/2,依次从头开始,将0号元素与加上增量的元素分为一组;对分好组的每一组数据完成插入排序;减小增量为原来的一半,继续分组后排序,直到增量最小减为1

如10个数: 第一轮,增量gap=length/2=5,分为5组,1、6元素一组,2、7一组,3、8一组插入排序。
第二轮,增量gap=gap/2=2,分为2组,1、3、5、7、9元素一组,2、4、6、10一组进行插入排序
第三轮,增量gap=gap/2=1,分为2组,

5、快速排序:冒泡排序的改进,先设一个分界值,将大于分界值的放到到数组右边,小于分界值的数据放到数组的左边;然后,左边数组和右边数组又各自设一个分界值,递归分成左右两大小组, 直到每组不能划分。当左侧和右侧两个部分的数摇排完序后整个数姐的排序也就完成了。

6、基数排序(桶排序):将每个元素按照其个十百千等位数中的数字,放入对应1到9号桶(数组);从最低位开始,依次进行一轮排序,最多进行元素最高位轮排序,数列就变成一个有序序列.

Java知识点概要_第69张图片
快速排序:

public static void quickSort(int[] arr,int left, int right) {
        
        int l = left; //左下标
        int r = right; //右下标
        int pivot = arr[(left + right) / 2]; //pivot 中轴值
        int temp = 0; //临时变量,作为交换时使用

        // todo 1、交换左右两边的数,让比pivot的值小放到左边,比pivot 值大放到右边
        while( l < r) { 
            while( arr[l] < pivot) {//在pivot的左边一直找,找到大于等于pivot值,才退出
                l += 1;
            }
            while(arr[r] > pivot) { //在pivot的右边一直找,找到小于等于pivot值,才退出
                r -= 1;
            }
            if( l >= r) { //如果l >= r说明pivot 的左右两的值,
                break;   // 说明已经按照左边全部是小于等于pivot值,右边全部是大于等于pivot值
            }
            //交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            
            //如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
            if(arr[l] == pivot) {
                r -= 1;
            }
            //如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
            if(arr[r] == pivot) {
                l += 1;
            }
        }

        
        
        // todo 2、交换到l==r, 防止栈溢出,设置l++, r--, 交叉,就不会执行上面的while否
        if (l == r) {
            l += 1;
            r -= 1;
        }
        
        // todo 3、 左右两边递归快速排序
        if(left < r) {   //向左递归
            quickSort(arr, left, r);
        }
        if(right > l) {  //向右递归
            quickSort(arr, l, right);
        }

    }

二分查找:
Java知识点概要_第70张图片


public static int binarySearch(int[] arr, int left, int right, int findVal) {
		// 当 left > right 时,说明递归整个数组,但是没有找到
		if (left > right) {
			return -1;
		}
		int mid = (left + right) / 2;
		int midVal = arr[mid];

		if (findVal > midVal) { // 向 右递归
			return binarySearch(arr, mid + 1, right, findVal);
		} else if (findVal < midVal) { // 向左递归
			return binarySearch(arr, left, mid - 1, findVal);
		} else {
			
			return mid;
		}

	}

Java知识点概要_第71张图片
Java知识点概要_第72张图片
Java知识点概要_第73张图片
Java知识点概要_第74张图片

Java知识点概要_第75张图片

Java知识点概要_第76张图片

五、Java 多线程编程

https://www.processon.com/view/5f3f16a85653bb06f2ddd549?fromnew=1

1、 线程和进程

Java知识点概要_第77张图片

  • 进程:在操作系统中能够独立运行,并且作为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从创建、运行到消亡的过程。

  • 线程:是一个比进程更小的执行单位,能够完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程中可以产生多个线程。

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
进程要想执行任务,必须得有线程,进程至少要有一条线程
Java知识点概要_第78张图片

2、进程通信

Java知识点概要_第79张图片
在这里插入图片描述
Java知识点概要_第80张图片

并发编程三要素

  • 1)原子性:指一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行

  • 2)可见性:指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果

3)有序性:程序的执行顺序按照代码的先后顺序来执行。

3、多线程有什么用?

  • 1)发挥多核CPU的优势,多线程可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程的方式去同时完成几件事情而不互相干扰。

  • 2)防止阻塞,从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。

  • 3)便于建模,假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

4、线程生命周期,五中基本状态

  • 1)新建(new Thread):当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1=new Thread();

  • 2)就绪(runnable):线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();

  • 3)运行(running):线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

  • 4)堵塞(blocked):由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

    • 正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
    • 正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
    • 被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
  • 5)死亡(dead):当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

    • 自然终止:正常运行run()方法后终止

    • 异常终止:调用stop()方法让一个线程终止运行

根据阻塞产生的原因不同,阻塞状态又可以分为三种:

  • a.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
  • b.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
  • c.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    Java知识点概要_第81张图片

Java知识点概要_第82张图片

5、创建线程方式

  • 1)继承Thread类,重写run()方法

  • 2)实现Runnable接口,重写run()方法

  • 3)实现Callable接口,重写call()方法,有返回值

  • 4)线程池方式创建:Java通过Executors提供四种线程池:

    • 1 newCachedThreadPool 创建⼀个可缓存线程池, 如果线程池⻓度超过处理需要,可灵活回收空闲线程,若⽆可 回收,则新建线程。

    • 2 newFixedThreadPool 创建⼀个定⻓线程池, 可控制线程最⼤并发数,超出的线程会在队列中等待。

    • 3 newScheduledThreadPool 创建⼀个定⻓线程池, ⽀持定时及周期性任务执⾏。

    • 4 newSingleThreadExecutor 创建⼀个单线程化的线程池, 它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有任务 按照指定顺序(FIFO, LIFO, 优先级)执⾏。

Java知识点概要_第83张图片
Java知识点概要_第84张图片
Java知识点概要_第85张图片
Java知识点概要_第86张图片

Java知识点概要_第87张图片

6、创建线程的三种方式的对比?

1)采用实现Runnable、Callable接口的方式创建多线程。

  • 优势是:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类
  • 在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  • 劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

2)使用继承Thread类的方式创建多线程

  • 优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
  • 劣势是:线程类已经继承了Thread类,所以不能再继承其他父类

3)Runnable和Callable的区别

  • Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()
  • Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
  • Call方法可以抛出异常,run方法不可以。
  • 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
    Java知识点概要_第88张图片

7、start() 和 run() 方法区别

在这里插入图片描述

  • start()方法,被用来启动新创建的线程,而且start()**内部调用了run()**方法,这和直接调用run()方法 的效果不一样。
  • run()方法 ,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
  • start()方法会将新创建的线程交给CPU去调度,CPU可以通过 轮转时间片去执行这个线程,至于是否分给该线程时间片那就看CPU了。

8、sleep()和wait()方法区别

sleep方法和wait方法都可以用来放弃CPU一定的时间,

  • 类:sleep()来自thread,wait()来自object();

  • 锁:sleep()不释放锁,wait()释放锁;sleep()时间到了会自动恢复,wait()可以使用notify()直接唤醒

  • 使用范围:sleep可以在任何地方使用;wait,notify和notifyAll,只能在同步控制方法或者同步控制块里面使用,

① 来自不同的类,分别是,sleep来自Thread类,wait来自Object类。 sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。

② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 sleep不让出系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用, 要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来, 如果时间不到只能调用interrupt()强行打断。 Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。

③ 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。 synchronized(x){ x.notify() //或者wait() } 两者的线程状态都从运行—> 阻塞,不同的是sleep方法在阻塞的同时还带着锁,wait方法在阻塞

9、线程安全问题

线程安全问题:指的是在某一线程从开始访问到结束访问某一数据期间,该数据被其他的线程所修改,那么对于当前线程而言,该线程就发生了线程安全问题,表现形式为数据的缺失,数据不一致等。

在这里插入图片描述

线程安全问题发生的条件:

  • 1)多线程环境下,即存在包括自己在内存在有多个线程
  • 2)多线程环境下存在共享资源,且多线程操作该共享资源
  • 3)多个线程必须对该共享资源有非原子性操作

线程安全问题的解决思路:

  • 1)尽量不使用共享变量,将不必要的共享变量变成局部变量来使用。
  • 2)使用synchronized关键字同步代码块,或使用jdk包中提供的Lock为操作进行加锁。
  • 3)使用ThreadLocal为每一个线程建立一个变量的副本,各个线程间独立操作互不影响。
  • 在这里插入图片描述

10、 乐观锁、悲观锁、可重入锁

Java知识点概要_第89张图片

  • 1)乐观锁: 对于并发间操作产生的线程安全问题,持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

  • 2)悲观锁: 对于并发间操作产生的线程安全问题,持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

可重入锁STFW得到以下两种主流解释

  • 解释一:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

  • 解释二:可重入锁又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。

11、 死锁、饥饿、活锁

Java知识点概要_第90张图片

1)死锁

  • 假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。多个线程环形占用资源也是一样的会产生死锁问题。
  • 解决方法:
    • 避免一个线程同时获取多个锁
    • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
    • 尝试使用定时锁,使用 lock.tryLock(timeout) 来代替使用内部锁机制。
    • 想要避免死锁,可以使用无锁函数(cas)或者使用重入锁(ReentrantLock),通过重入锁使线程中断或限时等待可以有效的规避死锁问题。

2)饥饿,

  • 饥饿指的是某一线程或多个线程因为某些原因一直获取不到资源,导致程序一直无法执行。如某一线程优先级太低导致一直分配不到资源,或者是某一线程一直占着某种资源不放,导致该线程无法执行等。
  • 解决方法:
    • 与死锁相比,饥饿现象还是有可能在一段时间之后恢复执行的。可以设置合适的线程优先级来尽量避免饥饿的产生。

3)活锁,

  • 活锁体现了一种谦让的美德,每个线程都想把资源让给对方,但是由于机器“智商”不够,可能会产生一直将资源让来让去,导致资源在两个线程间跳动而无法使某一线程真正的到资源并执行,这就是活锁的问题。
    Java知识点概要_第91张图片

12、同步、异步、阻塞、非阻塞

在这里插入图片描述
在这里插入图片描述

阻塞是用来形容多线程的问题,几个线程之间共享临界区资源,那么当一个线程占用了临界区资源后,所有需要使用该资源的线程都需要进入该临界区等待,等待会导致线程挂起,一直不能工作,这种情况就是阻塞。如果某一线程一直都不释放资源,将会导致其他所有等待在这个临界区的线程都不能工作。

当我们使用synchronized或重入锁时,我们得到的就是阻塞线程,如论是synchronized或者重入锁,都会在试图执行代码前,得到临界区的锁,如果得不到锁,线程将会被挂起等待,知道其他线程执行完成并释放锁且拿到锁为止。

解决方法:
可以通过减少锁持有时间,读写锁分离,减小锁的粒度,锁分离,锁粗化等方式来优化锁的性能。

13、synchronized关键字

参考

  • synchronized,中文意思是同步,也称之为”同步锁“。
  • synchronized的作用,是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。
  • synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
    Java知识点概要_第92张图片

Java知识点概要_第93张图片
Java知识点概要_第94张图片

14、Lock类

JVM提供了synchronized关键字来实现对变量的同步访问,以及用wait和notify来实现线程间通信。

在jdk1.5以后,JAVA提供了Lock类来实现和synchronized一样的功能,并且还提供了Condition来显示线程间通信。

Lock类是Java类来提供的功能,丰富的api使得Lock类的同步功能比synchronized的同步更强大。

  • Lock类实际上是一个接口,我们在实例化的时候实际上是实例化实现了该接口的类Lock lock = new ReentrantLock();
  • 用synchronized的时候,synchronized可以修饰方法,或者对一段代码块进行同步处理。前面讲过,针对需要同步处理的代码设置对象监视器,比整个方法用synchronized修饰要好。
  • Lock类的用法也是这样,通过Lock对象lock,用lock.lock来加锁,用lock.unlock来释放锁。在两者中间放置需要同步处理的代码。
    Java知识点概要_第95张图片
    Java知识点概要_第96张图片
    参考

15、synchronized、Lock

Java知识点概要_第97张图片

Java知识点概要_第98张图片

Java知识点概要_第99张图片

Java知识点概要_第100张图片

Java知识点概要_第101张图片

16、volatile关键字

volatile是Java中的关键字,用来修饰会被不同线程访问和修改的变量。JMM(Java内存模型)是围绕并发过程中如何处理可见性、原子性和有序性这3个特征建立起来的,而volatile可以保证其中的两个特性。

Java提供了volatile关键字保证可见性

  • 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
  • 从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
  • volatile是Java虚拟机提供的轻量级同步机制

Java知识点概要_第102张图片
资料

17、synchronized、volatile

在这里插入图片描述

Java知识点概要_第103张图片

18、CAS 机制

在这里插入图片描述

CAS(compare and swap)是解决多线程并行情况下使用锁造成性能损耗的一种机制。

  • 多线程CAS操作包含3个操作数,分别是内存位置V、预期原值A和新值B。
  • 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
  • 无论哪种情况,它都会在CAS指令之前返回该位置的值。多线程CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
  • 当多个线程尝试使用CAS同时更新同一个变量的时候,只有其中一个线程能够更新变量的值。

19、ThreadLocal类

在这里插入图片描述
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
  
我们可以得知ThreadLocal的作用是∶提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。

  • 线程并发:ThreadLocal只在多线程的环境下起作用
  • 传递数据:我们可以通过ThreadLocal在同一线程不同组件中传递公共数据
  • 线程隔离:每一个线程的变量都是独立的,不会相互影响

20、如何实现线程同步?

Java知识点概要_第104张图片
线程同步有5种方法:

  • 1、同步方法,使用 synchronized关键字,可以修饰普通方法、静态方法,以及语句块。

  • 2、同步代码块,用synchronized关键字修饰语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

  • 3、使用特殊域变量(volatile)实现线程同步。

  • 4、使用重入锁实现线程同步,在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。

  • 5、使用局部变量实现线程同步,如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

21、线程B怎么知道线程A修改了变量

  • 1)volatile 修饰变量
  • 2)synchronized 修饰修改变量的方法
  • 3)wait/notify
  • 4)while 轮询

22、性能问题,利用线程池

线程的生命周期开销是非常大的,一个线程的创建到销毁都会占用大量的内存。同时如果不合理的创建了多个线程,cup的处理器数量小于了线程数量,那么将会有很多的线程被闲置,闲置的线程将会占用大量的内存,为垃圾回收带来很大压力,同时cup在分配线程时还会消耗其性能。

解决思路:
利用线程池,模拟一个池,预先创建有限合理个数的线程放入池中,当需要执行任务时从池中取出空闲的先去执行任务,执行完成后将线程归还到池中,这样就减少了线程的频繁创建和销毁,节省内存开销和减小了垃圾回收的压力。同时因为任务到来时本身线程已经存在,减少了创建线程时间,提高了执行效率,而且合理的创建线程池数量还会使各个线程都处于忙碌状态,提高任务执行效率,线程池还提供了拒绝策略,当任务数量到达某一临界区时,线程池将拒绝任务的进入,保持现有任务的顺利执行,减少池的压力。

22、为什么要用线程池

在这里插入图片描述

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

他的主要特点为:线程复用;控制最大并发数:;管理线程。

  • 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 第三:提髙线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

java中的线程池是通过 Executor框架实现的,该框架中用到了 Executor, Executors,ExecutorService, ThreadPoolExecutor这几个类。

Java知识点概要_第105张图片
Java知识点概要_第106张图片

23、什么是Future?

Future模式

  • 是多线程开发中非常常见的一种设计模式。
  • 它的核心思想是异步调用。当我们需要调用一个函数方法时。如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调用者立即返回,让他在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获取需要的数据。

场景比如:外卖。

  • 比如在午休之前我们可以提前叫外卖,只需要点好食物,下个单。然后我们可以继续工作。到了中午下班的时候外卖也就到了,然后就可以吃个午餐,再美滋滋的睡个午觉。而如果你在下班的时候才叫外卖,那就只能坐在那里干等着外卖小哥,最后拿到外卖吃完午饭,午休时间也差不多结束了。

使用Future模式

  • 获取数据的时候无法立即得到需要的数据。而是先拿到一个契约,你可以再将来需要的时候再用这个契约去获取需要的数据,这个契约就好比叫外卖的例子里的外卖订单。
    https://www.jianshu.com/p/949d44f3d9e3

24、 线程通信

Java知识点概要_第107张图片
Java知识点概要_第108张图片
Java知识点概要_第109张图片

六、Java IO

网络编程是指:编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中提供了两种常见的网络协议的支持:

  • TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。

  • UDP:UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。

  • Socket 编程:套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
    java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

同步和异步:同步和异步是针对应用程序和内核的交互而言的。

  • 同步:指用户进程触发IO 操作,并等待或者轮询的去查看IO 操作是否就绪;
  • 异步:指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。

以银行取款为例:

  • 同步 : 自己亲自出马持银行卡到银行取钱(使用同步 IO 时,Java 自己处理IO 读写);
  • 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO 时,Java 将 IO 读写委托给OS 处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS 需要支持异步IO操作API);

阻塞和非阻塞:阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式。

  • 阻塞方式下读取或者写入函数将一直等待;
  • 非阻塞方式下,读取或者写入方法会立即返回一个状态值。

以银行取款为例:

  • 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
  • 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器通知可读写时再继续进行读写,不断循环直到读写完成)

1、IO 里面的常见类,字节流、字符流、接口、实现类、方法阻塞?

输入流就是从外部文件输入到内存,输出流主要是从内存输出到文件。
Java知识点概要_第110张图片

IO 流主要分为字符流字节流

  • 字节流 中 有 抽 象 类 InputStreamOutputStream,它们子类 FileInputStreamFileOutputStream,BufferedOutputStream 等。
  • 字符流 Reader 和 Writer 等。都实现了 Closeable, Flushable, Appendable 这些接口。

程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件

java 中的阻塞式方法是指在程序调用改方法时,必须等待输入数据可用或者检测到输入结束或者抛出异常,否则程序会一直停留在该语句上,不会执行下面的语句。比如 read()和readLine()方法。

2、什么是 IO 流?

是一种数据的流从源头流到目的地。比如文件拷贝,输入流和输出流都包括了。输入流从 文件中读取数据存储到进程(process)中,输出流从进程中读取数据然后写入到目标文件。

3、Java 中有几种类型的流?

按照流的方向输入流inputStream、输出流outputStream

按照实现功能分

  • 节点流,可以从或向一个特定的地方(节点)读写数据。如 FileReader。 直接与数据源相连,用于输入或者输出处理流:
  • 处理流,是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。)

按照处理数据的单位: 字节流字符流

  • 字节流继承于 InputStream 和 OutputStream, 字符流继承于 InputStreamReader 和 OutputStreamWriter 。

  • 字节流的操作,不会经过缓冲区(内存),而是直接操作文本本身的;字符流的操作,会先经过缓冲区(内存),然后通过缓冲区再操作文件以字节为单位输入输出数据,

  • 字节流按照 8 位传输 以字符为单位输入输出数据,字符流按照 16 位传输。

4、阻塞 IO、非阻塞 IO?

IO 操作包括:对硬盘的读写、对 socket 的读写以及外设的读写。 当用户线程发起一个 IO 请求操作,内核会去查看要读取的数据是否就绪

  • 对于阻塞 IO 来说,如果数据没有就绪,则会一直在那等待,直到数据就绪;
  • 对于非阻塞 IO 来说,如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程,这样才完成了一个完整的 IO 读请求操作,

也就是说 一个完整的 IO 读请求操作包括两个阶段:
1)查看数据是否就绪;
2)进行数据拷贝(内核将数据拷贝到用户线程)。

那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别

  • 就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息

Java 中传统的 IO 都是阻塞 IO,比如通过 socket 来读数据,调用 read()方法之后,如果数据没有就绪,当前线程就会一直阻塞在 read 方法调用那里,直到有数据才返回;而如果是非阻塞 IO 的话,当数据没有就绪,read()方法应该返回一个标志信息,告知当前线程数据没有就绪,而不是一直在那里等待。

5、谈谈对 NIO 的认知?

NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。NIO可以理解为非阻塞IO,

传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,而NIO中可以配置socket为非阻塞模式。

对于 NIO,它是非阻塞式,核心类:
(1)Buffer 为所有的原始类型提供 (Buffer)缓存支持。
(2)Charset 字符集编码解码解决方案 、
(3)Channel 一个新的原始 I/O 抽象,用于读写 Buffer 类型,通道可以认为是一种连接,可以是到特定设备,程序或者是网络的连接。

6、NIO 和传统的 IO 有什么区别?

  • 1)传统 IO 一般是一个线程等待连接,连接过来之后分配给processor线程,processor 线程与通道连接后,如果通道没有数据过来就会阻塞(线程被动挂起)不能做别的事情。

  • NIO 则不同,首先,在 selector 线程轮询的过程中就已经过滤掉了不感兴趣的事件,其次,在 processor处理感兴趣事件的 read 和 write 都是非阻塞操作即直接返回的,线程没有被挂起。

  • 2)传统 io 的管道是单向的,nio 的管道是双向的。

  • 3)不管传统 io 还是 nio 都需要read 和 write 方法,这些都是 java 程序调用的而不是系统帮我们调用的,nio2.0 里这点得到了改观,即使用异步非阻塞 AsynchronousXXX 四个类来处理。

7、BIO 和 NIO 和 AIO 的区别以及应用场景?

同步:java 自己去处理 io。
异步:java 将 io 交给操作系统去处理,告诉缓存区大小,处理完成回调。
阻塞:使用阻塞 IO 时,Java 调用会一直阻塞到读写完成才返回。
非阻塞:使用非阻塞 IO 时,如果不能立马读写,Java 调用会马上返回,当 IO 事件分发器通知可读写时在进行读写,不断循环直到读写完成。

BIO同步并阻塞

  • 服务器的实现模式是一个连接一个线程
  • 缺陷是:由于客户端连接数与服务器线程数成正比关系,可能造成不必要的线程开销,严重的还将导致服务器内存溢出。当然,这种情况可以通过线程池机制改善,但并不能从本质上消除这个弊端。

NIO:同步非阻塞

  • 服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器 Selector 上,多路复用器轮询到连接有 IO 请求时才启动一个线程
  • JDK1.4 以前, 一直是 BIO, 之后 NIO,

AIO:异步非阻塞

  • 服务器的实现模式为多个有效请求一个线程,客户端的 IO 请求都是由 OS 先完成再通知服务器应用去启动线程处理(回调)。
  • JDK1.7 发布了 NIO2.0,这就是真正意义上的异步非阻塞,

应用场景:并发连接数不多时采用 BIO,因为它编程和调试都非常简单,但如果涉及到高并发的情况,应选择 NIO 或 AIO,更好的建议是采用成熟的网络通信框架 Netty。

8、什么是缓冲区?有什么作用?

  • 缓冲区就是一段特殊的内存区域,很多情况下当程序需要频繁地操作一个资源(如文件或数据库)则性能会很低,所以为了提升性能就可以将一部分数据暂时读写到缓存区,以后直接从此区域中读写数据即可,这样就可以显著的提升性能。
  • 对于 Java 字符流的操作都是在缓冲区操作的,所以如果我们想在字符流操作中主动将缓冲区刷新到文件则可以使用 flush() 方法操作。

9、什么是 Java 序列化,如何实现 Java 序列化?

序列化就是一种用来处理对象流的机制,将对象的内容进行流化。可以对流化后的对象进行读写操作,可以将流化后的对象传输于网络之间。

序列化是为了解决,在对象流读写操作时所引发的问题

序列化的实现:

  • 将需要被序列化的类实现 Serialize 接口,没有需要实现的方法,此接口只是为了标注对象可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,再使用 ObjectOutputStream 对象的 write(Object obj)方法就可以将参数 obj 的对象写出

10、PrintStream、BufferedWriter、PrintWriter 的比较?

  • 1、 PrintStream 类的输出功能非常强大,通常如果需要输出文本内容,都应该将输出流包装成PrintStream 后进行输出。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream

  • 2、BufferedWriter:将文本写入字符输出流,缓冲各个字符从而提供单个字符,数组和字符串的高效写入。通过 write()方法可以将获取到的字符输出,然后通过 newLine()进行换行操作。BufferedWriter 中的字符流必须通过调用 flush 方法才能将其刷出去。并且 BufferedWriter 只能对字符流进行操作。如果要对字节流操作,则使用 BufferedInputStream

  • 3、PrintWriter 的 println 方法自动添加换行,不会抛异常,若关心异常,需要调用 checkError方法看是否有异常发生,PrintWriter 构造方法可指定参数,实现自动刷新缓存(autoflush)

11、有哪些可用的 Filter 流?

在 java.io 包中主要由 4 个可用的 filter Stream。两个字节 filter stream,两个字符 filter stream.分别是 FilterInputStream, FilterOutputStream, FilterReader and FilterWriter.这些类是抽象类,不能被实例化的。

12、如何实现对象克隆? 有两种方式:

1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法;

2). 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克 隆

七、网络基础

Java知识点概要_第111张图片
Java知识点概要_第112张图片

Java知识点概要_第113张图片

Java知识点概要_第114张图片
在这里插入图片描述
Java知识点概要_第115张图片

Java知识点概要_第116张图片
在这里插入图片描述
在这里插入图片描述

Java知识点概要_第117张图片

你可能感兴趣的:(大数据知识总结,java)