Java基础(学习笔记)

其他文章链接
Java基础
Java集合
多线程
JVM
MySQL
Redis
docker
计算机网络
操作系统


文章目录


前言

  由于之前的学习笔记全部都在ipad上,无法多端观看,所以现在把所有笔记添加到博客,方便手机pad电脑都可以查阅复习。

1.⾯向对象和⾯向过程的区别

  • ⾯向过程: ⾯向过程性能⽐⾯向对象⾼。因为类调⽤时需要实例化,开销⽐较⼤,⽐较消耗资源,所以当性能是最重要的考量因素的时候,⽐如单⽚机、嵌⼊式开发、Linux/Unix等⼀般采⽤⾯向过程开发。但是,⾯向过程没有⾯向对象易维护、易复⽤、易扩展。
  • ⾯向对象: ⾯向对象易维护、易复⽤、易扩展。因为⾯向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,⾯向对象性能⽐⾯向过程低。

⾯向过程性能都⽐⾯向对象⾼??
  这个并不是根本原因,⾯向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是⾯向对象语⾔,⽽是Java是半编译语⾔,最终的执⾏代码并不是可以直接被CPU执⾏的⼆进制机械码。⽽⾯向过程语⾔⼤多都是直接编译成机械码在电脑上执⾏,并且其它⼀些⾯向过程的脚本语⾔性能也并不⼀定⽐Java好。

2.Java 和 C++的共性与区别

  • 都是⾯向对象的语⾔,都⽀持封装、继承和多态
  • Java不提供指针来直接访问内存,程序内存更加安全
  • Java的类是单继承的,C++⽀持多重继承;虽然Java的类不可以多继承,但是接⼝可以多继承。
  • Java有⾃动内存管理机制,不需要程序员⼿动释放⽆⽤内存
  • Java不支持操作符重载
  • Java不提供goto语句

3.Java语言的特点

  1. 面向对象(封装、继承、多态)
  2. 平台无关性,一次编写,到处运行;
  3. 可靠性、安全性;
  4. ⽀持多线程(C++语⾔没有内置的多线程机制,因此必须调⽤操作系统的多线程功能来进⾏多线程程序设计,⽽Java语⾔却提供了多线程⽀持);
  5. ⽀持⽹络编程并且很⽅便(Java语⾔诞⽣本身就是为简化⽹络编程设计的,因此Java语⾔不仅⽀持⽹络编程⽽且很⽅便);
  6. 编译与解释并存。

4.Java⾯向对象编程三⼤特性:封装继承多态

4.1 封装

封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法,如果属性不想被外界访问,我们⼤可不必提供⽅法给外界访问。但是如果⼀个类没有提供给外界访问的⽅法,那么这个类也没有什么意义了。

4.2 继承

继承是使⽤已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功能,也可以⽤⽗类的功能,但不能选择性地继承⽗类。通过使⽤继承我们能够⾮常⽅便地复⽤
前的代码。

  1. 类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⼦类⽆法访问⽗类中的私有属性和⽅法,只是拥有。
  2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
  3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。

4.3 多态

多态就是编译时不确定究竟调用哪个具体方法,一直延迟到运行时才能确定。

在Java中有两种形式可以实现多态:继承父类(多个⼦类对同⼀⽅法的重写)和实现接口(实现接⼝并覆盖接⼝中同⼀⽅法)。

5.JVM、JDK、JRE

5.1 JVM

Java虚拟机(JVM)是运⾏Java字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果。

什么是字节码?采⽤字节码的好处是什么?

在Java中,JVM可以理解的代码就叫做字节码(即扩展名为.class的⽂件),它不⾯向任何特定的处理器,只⾯向虚拟机。Java语⾔通过字节码的⽅式,在⼀定程度上解决了传统解释型语⾔执⾏效率低的问题,同时⼜保留了解释型语⾔可移植的特点。所以Java程序运⾏时⽐较⾼效,⽽且,由于字节码并不针对⼀种特定的机器,因此,Java程序⽆须重新编译便可在多种不同操作系统的计算机上运⾏。

Java程序从源代码到运⾏⼀般有下⾯3步:

  我们需要格外注意的是.class=>机器码这⼀步。在这⼀步JVM类加载器⾸先加载字节码⽂件,然后通过解释器逐⾏解释执⾏,这种⽅式的执⾏速度会相对⽐较慢。⽽且,有些⽅法和代码块是经常需要被调⽤的(也就是所谓的热点代码),所以后⾯引进了JIT编译器,⽽JIT属于运⾏时编译。当JIT编译器完成第⼀次编译后,其会将字节码对应的机器码保存下来,下次可以直接使⽤。⽽我们知道,机器码的运⾏效率肯定是⾼于Java解释器的。这也解释了我们为什么经常会说Java是编译与解释共存的语⾔。

总结:
  Java虚拟机(JVM)是运⾏Java字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果。字节码和不同系统的JVM实现是Java语⾔“⼀次编译,随处可以运⾏”的关键所在。

5.2 JDK

JDK 是 Java Development Kit,它是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序。

5.3 JRE

JRE是Java运⾏时环境。它是运⾏已编译Java程序所需的所有内容的集合,包括Java虚拟机(JVM),Java类库,java命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。

如果你只是为了运⾏⼀下Java程序的话,那么你只需要安装JRE就可以了。如果你需要进⾏⼀些Java编程⽅⾯的⼯作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进⾏任何Java开发,仍然需要安装JDK。例如,如果要使⽤JSP部署Web应⽤程序,那么从技术上讲,您只是在应⽤程序服务器中运⾏Java程序。那你为什么需要JDK呢?因为应⽤程序服务器会将JSP转换为Javaservlet,并且需要使⽤JDK来编译servlet。

6.访问修饰符public、private、protected、default(默认)的区别

  • public:对所有类可见。使用对象:类、接口、变量、方法
  • protected:对同一包内的类和所有子类可见。使用对象:变量、方法。注意:不能修饰类(外部类)。
  • default(即默认):在同一包内可见。使用对象:类、接口、变量、方法。
  • private:在同一类内可见。使用对象:变量、方法。注意:不能修饰类(外部类)

在这里插入图片描述

7.Java数据类型

7.1 基本数据类型

  • 整数类型:byte、short、int、long
  • 浮点类型:float、double
  • 字符型:char
  • 布尔型:boolean

基本数据类型占字节数:byte 1, short 2, int 4, long 8, float 4, double 8,char 2。

7.2 引用数据类型

  • 接口
  • 数组

9.静态代码块、构造代码块、普通代码块代码块执行顺序

静态代码块——>普通代码块——>构造器

继承中代码块执行顺序:父类静态代码块——>子类静态代码块——>父类普通代码块——>父类构造器——>子类普通代码块——>子类构造器

10.final、finally、finalize

  • final修饰类,表明这个类不能被继承。final类中的所有成员⽅法都会被隐式地指定为final⽅法。
  • final修饰⽅法,表明把⽅法锁定,任何继承类不能重写它。类中所有的private⽅法都隐式地指定为final。
  • final修饰变量,如果是基本数据类型的变量,则其数值⼀旦在初始化之后便不能更改;如果是引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象

finally作为异常处理的一部分,它只能在try/catch语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit(0)可以阻断finally执行。

finalize是在java.lang.Object里定义的方法,也就是说每一个对象都有这么个方法,这个方法在gc启动,该对象被回收的时候被调用。
一个对象的finalize方法只会被调用一次,finalize被调用不一定会立即回收该对象,所以有可能调用finalize后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用finalize了,进而产生问题,因此不推荐使用finalize方法。

11.重载和重写的区别

重载就是同样的⼀个⽅法能够根据输⼊数据的不同,做出不同的处理
重写就是当⼦类继承⾃⽗类的相同⽅法,输⼊数据⼀样,但要做出有别于⽗类的响应时,你
就要覆盖⽗类⽅法

在这里插入图片描述

11.1 重载

  重载就是同⼀个类中多个同名⽅法根据不同的传参来执⾏不同的逻辑处理。
  发⽣在同⼀个类中,⽅法名必须相同,参数类型不同/个数不同/顺序不同,⽅法返回值和访问修饰符可以不同。

11.2 重写

  重写发⽣在运⾏期,是⼦类对⽗类的允许访问的⽅法的实现过程进⾏重新编写。

  1. ⽅法名、参数列表必须相同,返回值类型、抛出的异常范围⼩于等于⽗类,访问修饰符范围⼤于等于⽗类。
  2. 如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法,但是被static修饰的⽅法能够被再次声明。
  3. 构造⽅法⽆法被重写

11.3 构造器 Constructor 是否可被 override?

Constructor不能被override(重写),但是可以overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。

12.基本数据类型和包装类型

基本类型: boolean,char,byte,short,int,long,float,doubl
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

12.1 基本类型和包装类型的区别:

  1. 包装类型可以为null,而基本类型不可以(POJO中尽量用包装类型)
  2. 包装类型可用于泛型,而基本类型不可以
  3. 包装类型可用于泛型,而基本类型不可以

12.2 自动装箱和自动拆箱

装箱:将基本类型⽤它们对应的引⽤类型包装起来;
拆箱:将包装类型转换为基本数据类型。

12.3 int 和 Integer 区别

  • Integer是int的包装类;int是基本数据类型;
  • Integer变量必须实例化后才能使用;int变量不需要;
  • Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值;
  • Integer的默认值是null;int的默认值是0。

12.4 两个new生成的Integer变量的对比

Integer i = new Integer(10000);
Integer j = new Integer(10000);
System.out.print(i == j); //false

12.5 Integer变量和int变量的对比

自动拆箱

int a = 10000;
Integer b = new Integer(10000);
Integer c = 10000;
System.out.println(a == b); // true 
System.out.println(a == c); // true

12.6 非new生成的Integer变量和new Integer()生成变量的对比

非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同

Integer b = new Integer(10000);
Integer c = 10000;
System.out.println(b == c); // false

12.7 两个非new生成的Integer对象的对比

对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true

Integer m = 128;
Integer n = 128;
System.out.print(m == n); //false

当值在-128~127之间时,java会进行自动装箱,然后会对值进行缓存,如果下次再有相同的值,会直接在缓存中取出使用。缓存是通过Integer的内部类IntegerCache来完成的。当值超出此范围,会在堆中new出一个对象来存储。

13.在⼀个静态⽅法内调⽤⼀个⾮静态成员为什么是⾮法的?

由于静态⽅法可以直接通过类(不进行实例化)进⾏调⽤,而非静态成员变量必须实例化之后才能使用。

14.构造方法

14.1在Java中定义⼀个有时看似没用的空参构造⽅法的作⽤

  1. 便于子类继承此运行时类时,默认调用super(),即没有⽤super()来调⽤⽗类特定的构造⽅法时,保证父类有此构造器
  2. 便于通过反射,创建运行时类的对象,类.class.newInstance()

14.2 ⼀个类的构造⽅法的作⽤是什么?若⼀个类没有声明构造⽅法,该程序能正确执⾏吗?为什么?

主要作⽤是完成对类对象的初始化⼯作。
可以执⾏。因为⼀个类即使没有声明构造⽅法也会有默认的无参构造⽅法。

14.3 构造⽅法有哪些特性?

  1. 名字与类名相同;
  2. 没有返回值,但不能⽤ void 声明构造函数;
  3. ⽣成类的对象时⾃动执⾏,⽆需调⽤。

15.接⼝和抽象类的区别是什么?

1.语法层面上的区别
  1)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
  2)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法
  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
  4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
  1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
  2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。

jdk7~jdk9 Java 中接⼝概念的变化:

  1. 在jdk7或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实现接⼝的类实现。
  2. jdk8的时候接⼝可以有默认⽅法和静态⽅法功能,可以直接⽤接⼝名调⽤。实现类和实现是不可以调⽤的。如果同时实现两个接⼝,接⼝中定义了⼀样的默认⽅法,则必须重写,不然会报错。
  3. Jdk9在接⼝中引⼊了私有⽅法和私有静态⽅法。

注意:
1.实现多个接口一定要注意方法名不要重复,不然必须重写,不重写会报错
2.abstract不能与final并列修饰同一个类(因为继承)
3.abstract不能与private、static、final或native并列修饰同一个方法
4.abstract只能修饰类和方法

16.成员变量与局部变量的区别有哪些?

  1. 从语法形式上看:成员变量是属于的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被public,private,static等修饰符所修饰,⽽局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰。
  2. 从变量在内存中的存储⽅式来看:如果成员变量是使⽤static修饰的,那么这个成员变量是属于类的,如果没有使⽤static修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。
  3. 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
  4. 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被final修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。

17.创建对象的几种方式

  • new
  • 通过反射机制
  • 采用clone机制
  • 通过序列化机制

new 创建对象实例(对象实例在堆内存中),对象引⽤指向对象实例(对象引⽤存放在栈内存中)。

18.静态⽅法和实例⽅法有何不同

  1. 在外部调⽤静态⽅法时,可以使⽤"类名.⽅法名"的⽅式,也可以使⽤"对象名.⽅法名"的⽅式。⽽实例⽅法只有后⾯这种⽅式。也就是说,调⽤静态⽅法可以⽆需创建对象。
  2. 静态⽅法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态⽅法),⽽不允许访问实例成员变量和实例⽅法;实例⽅法则⽆此限制。

19.对象的相等与指向他们的引⽤相等,两者有什么不同?

对象相等,⽐的是内存中存放的内容是否相等。
引⽤相等,⽐较的是他们指向的内存地址是否相等。

20.在调⽤⼦类构造⽅法之前会先调⽤⽗类没有参数的构造⽅法,其⽬的是?

帮助⼦类做初始化⼯作。

21.动态语言和静态语言

动态语言:在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang(大多是脚本语言)。

静态语言:运行时结构不可变的语言就是静态语言。如Java、C、C++。

22.hashCode 与 equals、==

22.1 == 与 equals

  • == :它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型⽐较的是值,引⽤数据类型⽐较的是内存地址)。
  • equals():
    情况1:类没有覆盖equals()⽅法。则通过equals()⽐较该类的两个对象时,等价于通过“==”⽐较这两个对象。
    情况2:类覆盖了equals()⽅法。⼀般,我们都覆盖equals()⽅法来⽐较两个对象的内容是否相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。

说明:
  String中的equals⽅法是被重写过的,因为object的equals⽅法是⽐较的对象的内存地址,⽽String的equals⽅法⽐较的是对象的值。当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引⽤。如果没有就在常量池中重新创建⼀个String对象。

22.2 hashCode()

hashCode()的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个int整数这个哈希码的作⽤是确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object类中,这就意味着Java中的任何类都包含有hashCode()函数。

散列表存储(如HashMap,Hashtable,HashSet等)的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利⽤到了散列码。(可以快速找到所需要的对象)

22.3 为什么要有 hashCode

以“HashSet如何检查重复”为例⼦来说明为什么要有hashCode
  当你把对象加⼊HashSet时,HashSet会先计算对象的hashcode值来判断对象加⼊的位置,同时也会与其他已经加⼊的对象的hashcode值作⽐较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。
  但是如果发现有相同hashcode值的对象,这时会调⽤equals()⽅法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加⼊操作成功。如果不同的话,就会重新散列到其他位置。这样我们就⼤⼤减少了equals的次数,相应就⼤⼤提⾼了执⾏速度。

22.4 为什么重写 equals 时必须重写 hashCode ⽅法

  1. 使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
  2. 保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。

说明:
  hashCode()的默认⾏为是对堆上的对象产⽣独特值。如果没有重写hashCode(),则该class的两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)。

22.5 小节

  • hashCode主要用于提升查询效率,来确定在散列结构中对象的存储地址;
  • 重写equals()必须重写hashCode(),二者参与计算的自身属性字段应该相同;
  • hash类型的存储结构,添加元素重复性校验的标准就是先取hashCode值,后判断equals();
  • equals()相等的两个对象,hashcode()一定相等;
  • hashcode()不等,一定能推出equals()也不等;
  • hashcode()相等,equals()可能相等,也可能不等(即哈希冲突)。

哈希冲突解决办法:

  1. 开放地址方法
  • 线性探测
  • 平方探测:按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
  • 伪随机探测:按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。
  1. 再哈希法
  2. 链式地址法(HashMap的哈希冲突解决方法)
  3. 建立公共溢出区:将哈希表分为基本表和溢出表,将发生冲突的都存放在溢出表中。

23. Java 只有值传递

  • ⼀个⽅法不能修改⼀个基本数据类型的参数(即数值型或布尔型)。
  • ⼀个⽅法可以改变⼀个对象参数的状态。
  • ⼀个⽅法不能让对象参数引⽤⼀个新的对象。
  • 值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
  • 引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的是引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

24.深拷⻉ vs 浅拷⻉

  1. 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。
  2. 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此为深拷⻉。

25.i++原子性问题

原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个线程)。
(1)在单线程中,能够在单条指令中完成的操作都认为是"原子操作",因为中断只能发生于指令之间。
(2)在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。

Java中的i++是否是原子操作?否
i=0;两个线程分别对i进行++100次,值是多少? [2, 200]

26.读取properties配置文件中的内容

  • 使用流
Properties pros =  new Properties();
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);

String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password);
  • 使用ClassLoader
Properties pros =  new Properties();
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
pros.load(is);


String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password);

26.Java中的异常处理

26.1 Java异常类层次结构图

在这里插入图片描述
Exception:程序本身可以处理的异常,可以通过 catch 来进⾏捕获。 Exception ⼜可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
  受检查异常:受检查异常没有被catch/throw处理的话,就没办法通过编译。
  不受检查异常:不处理不受检查异常也可以正常通过编译。
Error:Error 属于程序⽆法处理的错误 ,我们没办法通过 catch 来进⾏捕获 。例如, Java 虚拟机运⾏错误(Virtual MachineError)、虚拟机内存不够错误 (OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发⽣时,Java 虚拟机(JVM)⼀般会选择线程终⽌。

26.2 Throwable 类常⽤⽅法

  • public string getMessage() :返回异常发⽣时的简要描述 。
  • public string toString() :返回异常发⽣时的详细信息。
  • public string getLocalizedMessage() :返回异常对象的本地化信息。使⽤ Throwable 的⼦类覆盖这个⽅法,可以⽣成本地化信息。如果⼦类没有覆盖该⽅法,则该⽅法返回的信息与 getMessage()返回的结果相同。
  • public void printStackTrace() :在控制台上打印 Throwable 对象封装的异常信息。

28.3 异常处理总结

  • try块:⽤于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟⼀个finally块。
  • catch块:⽤于处理try捕获到的异常。
  • finally块:⽆论是否捕获或处理异常,finally块⾥的语句都会被执⾏。当在try块或catch块中遇到return语句时,finally语句块将在⽅法返回之前被执⾏。

说明:在以下3种特殊情况下,finally块不会被执⾏:

  1. 在 try 或 finally块中⽤了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执⾏ 。
  2. 程序所在的线程死亡。
  3. 关闭 CPU。
    **注意:**当 try 语句和 finally 语句中都有 return 语句时,在⽅法返回之前,finally 语句的内容将被
    执⾏,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
public static void main(String[] args) {
        System.out.println(fun(1));  //输出0
    }

    public static int fun(int num) {
        try {
            return num;
        } finally {
            if (num == 1) {
                return 0;
            }
        }
    }

其他

  • switch(expr)中,expr 只能是 byte、short、char、int、String

较大模块知识点

一.String

String:字符串,使用一对 ”” 引起来表示

String s1 = "mogublog"; //字面量的定义方式
String s2 = new String("moxi");

1.String有哪些特性

  • 不变性:只要进行了修改,就会重新创建一个对象,这就是不可变性;
    • final:使用final来定义String类,表示String类不能被继承,提高了系统的安全性。
  • 常量池优化:String对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用;
  • 实现了Serializable接口:表示字符串是支持序列化的。
  • 实现了Comparable接口:表示 string可以比较大小
    • string在 jdk8及以前底层用final char[] value存储字符串数据。 JDK9时改为byte[]

2.JDK9时改为byte[]存储String的原因

JDK1.8中,String将字符存储在char数组中,每个字符使用两个字节(16位)。字符串是堆使用的主要组成部分,而且大多数字符串对象只包含拉丁字符,这些字符只需要一个字节的存储空间,因此这些字符串对象的内部char数组中有一半的空间将不会使用。造成了空间浪费。因此,JDK9时改为byte[]。

3.字符型常量和字符串常量的区别?

  1. 形式上:字符常量是单引号引起的⼀个字符;字符串常量是双引号引起的若⼲个字符
  2. 含义上:字符常量相当于⼀个整型值(ASCII值),可以参加表达式运算;字符串常量代表⼀个地址值(该字符串在内存中存放位置)
  3. 占内存⼤⼩:字符常量只占2个字节;字符串常量占若⼲个字节(注意:char在Java中占两个字节。)

4.String,StringBuffer 和 StringBuilder 的区别是什么?

  1. 可变性
    String 类中使⽤ final 关键字修饰字符数组来保存字符串, private final char/byte[] value,所以 String 对象是不可变的。
    StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类,也是使⽤字符数组保存字符串,但是没有⽤ final 关键字修饰,所以这两种对象都是可 变的。
  2. 线程安全性
    String 中的对象是不可变的,线程安全
    StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同步锁(synchronized),所以是线程安全的。StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的。
  3. 性能
    每次对String类型进⾏改变的时候,都会⽣成⼀个新的String对象,然后将指针指向新的String对象,性能最差。StringBuffer加了锁,效率比StringBuilder差一些。

使用情景:

  1. 操作少量的数据: 适⽤ String
  2. 单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
  3. 多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer

5.String为什么要设计成不可变的

  1. 便于实现字符串池(Stringpool)
    如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,Stringpool将不能够实现!
  2. 线程安全
    在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争。但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。
  3. 避免安全问题
    在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
  4. 加快字符串处理速度
    由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

6.在使用 HashMap 的时候,用 String 做 key 有什么好处

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

7.字符串常量池

java中常量池的概念主要有三个:全局字符串常量池,class文件常量池,运行时常量池。我们现在所说的就是全局字符串常量池。

常量池中不会存在相同内容的变量

String的string Pool是一个固定大小的Hashtable,默认值大小长度是 1009,1009也是JDK1.8可以设置的最小值。如果放进 string Pool的 string非常多,就会造成 Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用 string.intern时性能会大幅下降。

-XX:StringTablesize可设置 stringTable的长度

8.String的内存分配

常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,string类型的常量池比较特殊。它的主要使用方法有两种。

  1. 直接使用双引号声明出来的 String对象会直接存储在常量池中。比如:
    string info="atguigu.com";
  2. 如果不是用双引号声明的string对象,可以使用 string提供的 intern()方法。

9.字符串常量池存放位置

Java 6及以前,在永久代。
Java 7,在堆。
Java 8,在堆。

10.为什么 StringTable从永久代调整到堆中

  • 永久代的默认比较小
  • 永久代垃圾回收频率低

11.intern()

intern是一个 native方法,调用的是底层C的方法。

如果调用intern()方法,则会从字符串常量池中查询当前字符串是否存在。如果存在,则返回池中的字符串。否则,该字符串对象将被添加到池中,并返回对该字符串对象的引用。

12.字符串拼接

  • 常量与常量的拼接结果在常量池,原理是编译期优化。
  • 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder。
public static void test1() {
    String s1 = "a" + "b" + "c"; // 得到 abc的常量池 
    String s2 = "abc"; // abc存放在常量池,直接将常量池的地址返回 
    /** * 
     *最终 java编译成.class,再执行.class 
     */
    System.out.println(s1 == s2); // true,因为存放在字符串常量池 
    System.out.println(s1.equals(s2)); // true 
}
public static void test2() {
    String s1 = "javaEE";
    String s2 = "hadoop";
    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;
    System.out.println(s3 == s4); // true 
    System.out.println(s3 == s5); // false 
    System.out.println(s3 == s6); // false
    System.out.println(s3 == s7); // false 
    System.out.println(s5 == s6); // false
    System.out.println(s5 == s7); // false 
    System.out.println(s6 == s7); // false 
    String s8 = s6.intern();
    System.out.println(s3 == s8); // true
}

从上述的结果我们可以知道:
如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果。
而调用intern方法,则会判断字符串常量池中是否存在JavaEEhadoop值,如果存在则返回常量池中的值,否者就在常量池中创建。

public static void test4() {
    final String s1 = "a";
    final String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;
    System.out.println(s3 == s4);//true
}

从上述的结果我们可以知道:
即使左右两边如果存在变量,但是如果使用的是final修饰,则是从常量池中获取。所以说拼接符号左右两边都是字符串常量或常量引用则仍然使用编译器优化。
在开发中,能够使用final的时候,建议使用上。

13.字符串拼接的底层原理

拼接操作的底层其实使用了StringBuilder。

s1 + s2的执行细节:

  • StringBuilder s = new StringBuilder();
  • s.append(s1);
  • s.append(s2);
  • s.toString(); //类似于 new String(“ab”);

14.+和append()对比

通过StringBuilder的 append()方式添加字符串的效率,要远远高于String的字符串拼接方法。

  • StringBuilder的 append的方式,自始至终只创建一个 StringBuilder的对象。
  • 对于字符串拼接的方式,还需要创建很多 StringBuilder对象和调用toString()时候创建的String对象。
  • 内存中由于创建了较多的StringBuilder和String对象,内存占用过大,如果进行GC那么将会耗费更多的时间。

15.字符串拼接改进

  • 我们使用的是StringBuilder的空参构造器,默认的字符串容量是16,然后将原来的字符串拷贝到新的字符串中,我们也可以默认初始化更大的长度,减少扩容的次数。
  • 因此在实际开发中,我们能够确定,前前后后需要添加的字符串不高于某个限定值,那么建议使用构造器创建一个阈值的长度。

16.new String(“aaa”)创建了几个字符串对象

两个对象:

  • 一个对象:new关键字在堆空间中创建
  • 另一个对象:字符串常量池中的对象

17.new String(“a”) + new String(“b”) 会创建几个对象

6个对象:

  • 对象 1:new StringBuilder()
  • 对象 2:new String(“a”)
  • 对象 3:常量池的a
  • 对象 4:new String(“b”)
  • 对象 5:常量池的b
  • 对象 6:toString中会创建一个new String(“ab”)
    - 调用toString方法,不会在常量池中生成ab

18.JDK6和JDK7 intern()的区别

JDK1.6中,将这个字符串对象尝试放入串池。

  • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址。
  • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址。

JDK1.7起,将这个字符串对象尝试放入串池。

  • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址。
  • 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址。
public static void main(String[] args) {
    String s = new String("2");
    s.intern();
    String s2 = "2";
    System.out.println(s == s2);//JDK6 false,JDK7 false

    String s3 = new String("3") + new String("3");
    s3.intern();
    String s4 = "33";
    System.out.println(s3 == s4);//JDK6 false,JDK7 true
}

JDK 1.6
在这里插入图片描述
Strings=new String(“2”);创建了两个对象,一个在堆中的StringObject对象,一个是在常量池中的“2”对象。
s.intern();在常量池中寻找与s变量内容相同的对象,发现已经存在内容相同对象“2”,返回对象2的地址。
Strings2=“2”;使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象"2"的地址。
System.out.println(s==s2);从上面可以分析出,s变量和s2变量地址指向的是不同的对象,所以返回false

String s3 = new String(“3”) + new String(“3”);创建了两个对象,一个在堆中的StringObject 对象,一个是在常量池中的“3”对象。中间还有2个匿名的new String(“3”)我们不去讨论它们。
s3.intern();在常量池中寻找与s3变量内容相同的对象,没有发现“33”对象,在常量池中创建“33”对 象,返回“33”对象的地址。
String s4 = “33”;使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象"33"的 地址。
System.out.println(s3 == s4);从上面可以分析出,s3变量和s4变量地址指向的是不同的对象,所 以返回false

JDK 1.7
在这里插入图片描述
Strings=new String(“2”);创建了两个对象,一个在堆中的StringObject对象,一个是在堆中的“2”对象,并在常量池中保存“2”对象的引用地址。
s.intern();在常量池中寻找与s变量内容相同的对象,发现已经存在内容相同对象“2”,返回对象“2”的引用地址。
Strings2=“2”;使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回对象“2”的引用地址。
System.out.println(s==s2);从上面可以分析出,s变量和s2变量地址指向的是不同的对象,所以返回false

Strings3=newString(“3”)+newString(“3”);创建了两个对象,一个在堆中的StringObject对象,一个是在堆中的“3”对象,并在常量池中保存“3”对象的引用地址。中间还有2个匿名的new String(“3”)我们不去讨论它们。
s3.intern();在常量池中寻找与s3变量内容相同的对象,没有发现“33”对象,将s3对应的StringObject对象的地址保存到常量池中,返回StringObject对象的地址。
Strings4=“33”;使用字面量创建,在常量池寻找是否有相同内容的对象,发现有,返回其地址,也就是StringObject对象的引用地址。
System.out.println(s3 == s4);从上面可以分析出,s3变量和s4变量地址指向的是相同的对象,所以返回true。

二.异常处理

三.泛型

四.Java 中 IO 流

1.File

java .io.File 类: 文件 和文件目录路径 的 抽象表示形式 ,与平台无关

File类的重要方法

构造器:

  • public File(String pathname) pathname 可以是绝对路径或者相对路径
  • public File(String parent,String child)
  • public File(File parent,String child)

获取:

  • public String getAbsolutePath() 获取绝对路径
  • public String getPath() 获取路径
  • public String getName() 获取名称
  • public String getParent() 获取上层文件目录路径 。 若无返回 null
  • public long length() 获取文件长度 即:字节数 。不能获取目录的长度 。
  • public long lastModified() 获取最后一次的修改时间毫秒值

  • public String[] list() 获取 指定目录下的所有文件或者文件目录的名称数组
  • public File[] listFiles() 获取 指定目录下的所有文件或者 文件 目录 的 File 数组

重命名:

  • public boolean renameTo(File dest) 把文件重命名为指定的文件路径

判断:

  • public boolean isDirectory() 判断是否是文件目录
  • public boolean isFile() 判断是否是文件
  • public boolean exists() 判断是否存在
  • public boolean canRead() 判断是否可读
  • public boolean canWrite() 判断是否可写
  • public boolean isHidden() 判断是否隐藏

创建:

  • public boolean createNewFile() 创建文件 。若文件存在则不创建返回 false
  • public boolean mkdir() 创建文件目录 。如果此文件目录存在就不创建 了。如果此文件目录的上层目录不存在也不创建 。
  • public boolean mkdirs() 创建文件目录 。 如果上层文件目录不存在一并创建

删除:

  • public boolean delete 删除文件或者文件夹(要删除一个文件目录请注意该文件目录内不能包含文件或者文件目录)

2.Java中IO流分为⼏种?

  • 按照流的流向分,可以分为输⼊流和输出流
  • 按照操作单元划分,可以分为字节流和字符流
  • 按照流的⻆⾊划分,可以分为节点流(文件流)和处理流

Java IO流的40多个类都是从如下4个抽象基类中派⽣出来的。
  InputStream/Reader:所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
  OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
在这里插入图片描述

3.字符流与字节流的区别

  • 读写的时候字节流是按字节读写,字符流按字符读写。
  • 字节流适合所有类型文件的数据传输,因为计算机字节(Byte)是电脑中表示信息含义的最小单位。字符流只能够处理纯文本数据,其他类型数据不行,但是字符流处理文本要比字节流处理文本要方便。
  • 在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。
  • 只是读写文件,和文件内容无关时,一般选择字节流。

4.节点流(文件流)

FileReader、FileWriter、FileInputStream 和 FileOutputStream
是直接用来操作文件的,被称为文件流(节点流)。

以下内容以字符流为例。

4.1 读取文件

在读取文件时,必须保证该文件已存在 ,否则报异常 。

  1. 建立一个流对象,将已存在的一个文件加载进流。
    FileReader fr = new FileReader(new File(“Test.txt”));
  2. 创建一个临时存放数据的数组。
    char[] ch = new char[1024];
  3. 调用流对象的读取方法将流中的数据读入到数组中。
    fr.read ch;
  4. 关闭资源。
    fr.close();

代码:

FileReader fr = null;
try {
    fr = new FileReader(new File("c:\\text.txt"));
    char[] buffer = new char[1024];
    int len;
    while ((len = fr.read(buffer)) != -1) {
        System.out.println(new String(buffer, 0, len));
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fr != null) {
        try {
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.2 写入文件

在写入一个文件时 ,如果使用构造器 FileOutputStream(file),则目录下有同名文件将被覆盖
如果使用构造器 FileOutputStream(file,true),则目录下的同名文件不会被覆盖在文件内容末尾追加内容

  1. 创建流对象,建立数据存放文件
    FileWriter fw = new FileWriter(new File(“Test.txt”));
  2. 调用流对象的写入方法,将数据写入流
    fw.write (“pearz”);
  3. 关闭流资源,并将流中的数据清空到文件中。
    fw.close();

代码:

FileWriter fw = null;
try {
    fw = new FileWriter(new File("c:\\text.txt"));
    fw.write("pearz");
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fw != null) {
        try {
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.缓冲流

BufferedInputStream 和 BufferedOutputStream
BufferedReader 和 BufferedWriter

为了提高数据读写的速度,Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用 8192 个 字节 8Kb) 的缓冲区 。

  • 缓冲流要“套接”在相应的节点流之上,所以缓冲流也是处理流。
  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区。
  • 当使用 BufferedInputStream 读取字节文件时,BufferedInputStream 会一次性从文件中读取 8192 个(8Kb),存在缓冲区中,直到缓冲区装满了才重新从文件中读取下一个 8192 个字节数组 。
  • 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满BufferedOutputStream才会把缓冲区中的数据一次性写到文件里 。使用方法flush() 可以强制将缓冲区的内容全部写入输出流。
  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。
  • flush() 方法的使用:手动将 buffer 中内容写入文件。
  • 如果是带缓冲区的流对象的 close() 方法,不但会关闭流还会在关闭流之前刷新缓冲区 关闭后不能再写出。

代码:

BufferedReader br = null;
BufferedWriter bw = null;
try {
    //创建缓冲流对象:它是处理流,是对节点流的包装
    br = new BufferedReader(new FileReader("d:\\pearz\\source.txt"));
    bw = new BufferedWriter(new FileWriter("d:\\pearz\\target.txt"));
    String str;
    while ((str = br.readLine()) != null) {//一次读取字符文本文件的一行字符
        bw.write(str);//一次写入一行字符串
        bw.newLine();//写入行分隔符
    }
    bw.flush();//刷新缓冲区
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //关闭 IO 流对象
    if (br != null) {
        try {
            br.close();//关闭过滤流时 会自动关闭它所包装的底层节点流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if (bw != null) {
        try {
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6.转换流

转换流提供了在字节流和字符流之间的转换。字节流中的数据都是字符时,转成字符流操作更高效 。Java API 提供了两个转换流:

  • InputStreamReader:将 InputStream 转换为 Reader
  • OutputStreamWriter:将 Writer 转换为 OutputStream
    在这里插入图片描述

构造器:
public InputStreamReader Input(Stream in)
public InputSreamReader Input(Stream in,String charsetName)
public OutputStreamWriter(OutputStream out)
public OutputSreamWriter(OutputStream out,String charsetName)
charsetName代表所用字符集,如gbk,utf-8等。

代码(为了便于理解,没有捕获异常而直接抛出了,使用时一定要捕获异常):

FileInputStream fis = new FileInputStream("source.txt");
FileOutputStream fos = new FileOutputStream("target.txt");

InputStreamReader isr = new InputStreamReader(fis);
OutputStreamWriter osw = new OutputStreamWriter(fos);

BufferedReader br = new BufferedReader(isr);
BufferedWriter bw = new BufferedWriter(osw);

String str;
while ((str = br.readLine()) != null) {
    bw.write(str);
    bw.newLine();
    bw.flush();
}
bw.close();
br.close();

7.对象流

ObjectInputStream 和 OjbectOutputSteam

用于存储和读取 基本数据类型数据或对象的处理流。它的强大之处就是可以把Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。

7.1 什么是序列化

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的好处在于可将任何实现了 Serializable 接口的对象转化为 字节数据使其在保存和传输时可被还原。

7.2 如何实现java序列化

序列化: 用 ObjectOutputStream 类 保存 基本类型数据或对象的机制。
反序列化: 用 ObjectInputStream 类 读取 基本类型数据或对象的机制。

将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

ObjectOutputStream 和 ObjectInputStream 不能序列化 static 和 transient 修饰的成员变量

7.3 Java 序列化中如果有些字段不想进⾏序列化,怎么办?

使⽤transient关键字修饰。
transient关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和⽅法。

8.获取⽤键盘输⼊常⽤的两种⽅法

⽅法1:通过Scanner

	Scanner sc = new Scanner(System.in);
	String s = sc.nextLine();
    sc.close();

⽅法2:通过BufferedReader

	BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
    String s = bufferedReader.readLine();

9.既然有了字节流,为什么还要有字符流?

字符流是由Java虚拟机将字节转换得到的,问题就出在这个过程还算是⾮常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以,I/O流就⼲脆提供了⼀个直接操作字符的接⼝,⽅便我们平时对字符进⾏流操作。如果⾳频⽂件、图⽚等媒体⽂件⽤字节流⽐较好,如果涉及到字符的话使⽤字符流⽐较好。

字节流操作字节,比如:.mp3 ,.avi ,.rmvb,mp4 ,.jpg ,.doc,.ppt
字符流操作字符,只能操作普通文本文件 。 最常见的文本文件:.txt ,.java ,.c ,.cpp 等语言的源代码。尤其注意 doc,excel,ppt 这些不是文本文件

10.BIO,NIO,AIO

  • BIO(BlockingI/O):同步阻塞I/O模式,数据的读取写⼊必须阻塞在⼀个线程内等待其完成。在活动连接数不是特别⾼(⼩于单机1000)的情况下,这种模型是⽐较不错的,可以让每⼀个连接专注于⾃⼰的I/O并且编程模型简单,也不⽤过多考虑系统的过载、限流等问题。线程池本身就是⼀个天然的漏⽃,可以缓冲⼀些系统处理不了的连接或请求。但是,当⾯对⼗万甚⾄百万级连接的时候,传统的BIO模型是⽆能为⼒的。因此,我们需要⼀种更⾼效的I/O处理模型来应对更⾼的并发量。
  • NIO(Non-blocking/NewI/O):NIO是Java1.4引入的⼀种同步⾮阻塞的I/O模型。NIO中的N可以理解为Non-blocking,不单纯是New。它⽀持⾯向缓冲的,基于通道的I/O操作⽅法。NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现,两种通道都⽀持阻塞和⾮阻塞两种模式。阻塞模式使⽤就像传统中的⽀持⼀样,⽐较简单,但是性能和可靠性都不好;⾮阻塞模式正好与之相反。对于低负载、低并发的应⽤程序,可以使⽤同步阻塞I/O来提升开发速率和更好的维护性;对于⾼负载、⾼并发的(⽹络)应⽤,应使⽤NIO的⾮阻塞模式来开发 。
  • AIO(AsynchronousI/O):AIO也就是在Java7中引⼊了NIO的改进版NIO2,它是异步⾮阻塞的IO模型。异步IO是基于事件和回调机制实现的,也就是应⽤操作之后会直接返回,不会堵塞在那⾥,当后台处理完成,操作系统会通知相应的线程进⾏后续的操作。AIO是异步IO的缩写,虽然NIO在⽹络操作中,提供了⾮阻塞的⽅法,但是NIO的IO⾏为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程⾃⾏进⾏IO操作,IO操作本身是同步的。⽬前来说AIO的应⽤还不是很⼴泛。

五.网络编程

1.InetAdress类

在Java中使用InetAdress类代表IP,该对象里有两个字段:主机名(String)和 IP地址(int)

InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例:
- public static InetAddress getLocalHost()
- public static InetAddress getByName(String host)

InetAddress常用方法:
- public String getHostAddress():返回 IP地址字符串(以文本表现形式)
- public String getHostName():获取此 IP地址的主机名
- public boolean isReachable(int timeout):测试是否可以达到该地址

2.套接字(Socket)

IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。

Socket分类:
流套接字(streamsocket):使用TCP提供字节流服务
数据报套接字(datagramsocket):使用UDP提供数据报服务

  • 网络通信其实就是Socket间的通信。
  • 信的两端都要有Socket,是两台机器间通信的端点。
  • Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
  • 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。

3.TCP网络编程

必须先开启服务端,再开启客户端

3.1 客户端Socket的工作过程

  1. 创建Socket:根据指定服务端的IP地址或端口号构造Socket类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
  2. 打开连接到Socket的输入/出流:使用getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输
  3. 按照一定的协议对Socket进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
  4. 关闭Socket:断开客户端到服务器的连接,释放线路

3.2 服务端Socket的工作过程

  1. 调用ServerSocket(intport):创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
  2. 调用accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
  3. 调用该Socket类对象的getOutputStream()和getInputStream():获取输出流和输入流,开始网络数据的发送和接收。
  4. 关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字。

3.3 TCP网络编程代码实例

从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。

客户端:

public void client() throws IOException {
    //1.创建Socket对象,指明服务器端的ip和端口号
    Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
    //2.获取输出流,用于输出数据
    OutputStream os = socket.getOutputStream();
    //3. 获取输入流,用于输入数据
    FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
    //4.写数据
    byte[] buffer = new byte[1024];
    int len;
    while((len = fis.read(buffer)) != -1){
        os.write(buffer,0,len);
    }
    //关闭数据的输出(很关键的一步,不然程序无法向下进行不,阻塞I/O)
    socket.shutdownOutput();

    //5.接收来自于服务器端的数据,并显示到控制台上
    InputStream is = socket.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer1 = new byte[20];
    int len1;
    while((len1 = is.read(buffer)) != -1){
        baos.write(buffer1,0,len1);
    }

    System.out.println(baos.toString());

    //6.资源的关闭(这里为了方便读代码简化了,应该使用try-catch-finally以及判断是否为空)
    fis.close();
    os.close();
    socket.close();
    baos.close();
}

服务端:

public void server() throws IOException {
    //1.创建服务器端的ServerSocket,指明自己的端口号
    ServerSocket ss = new ServerSocket(9090);
    //2.调用accept()表示接收来自于客户端的socket
    Socket socket = ss.accept();
    //3.获取输入流
    InputStream is = socket.getInputStream();
    //4.获取输出流
    FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
    //5..读取输入流中的数据
    byte[] buffer = new byte[1024];
    int len;
    while((len = is.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

    System.out.println("图片传输完成");

    //6.服务器端给予客户端反馈
    OutputStream os = socket.getOutputStream();
    os.write("照片已收到,谢谢!".getBytes());

    //7.关闭资源(这里为了方便读代码简化了,应该使用try-catch-finally以及判断是否为空)
    fos.close();
    is.close();
    socket.close();
    ss.close();
    os.close();
}

4.UDP网络编程

发送端、接收端开启顺序无要求

4.1 流程

  1. DatagramSocket与DatagramPacket
  2. 建立发送端,接收端
  3. 建立数据包
  4. 调用Socket的发送、接收方法
  5. 关闭Socket

4.2 UDP网络编程代码实例

发送方:

public void sender() throws IOException {
    DatagramSocket socket = new DatagramSocket();

    String str = "UDP发送方";
    byte[] data = str.getBytes();
    InetAddress inet = InetAddress.getLocalHost();
    DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);

    socket.send(packet);

	//这里为了方便读代码简化了,应该使用try-catch-finally以及判断是否为空
    socket.close();

}

接收方:

public void receiver() throws IOException {
    DatagramSocket socket = new DatagramSocket(9090);

    byte[] buffer = new byte[100];
    DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

    socket.receive(packet);

    System.out.println(new String(packet.getData(),0,packet.getLength()));
    //这里为了方便读代码简化了,应该使用try-catch-finally以及判断是否为空
    socket.close();
}

5.URL编程

URL(Uniform Resource Locator):统一资源定位符,它表示 Internet上某一 资源的地址。
URL对象生成后,其属性是不能被改变。

5.1 URL的基本结构

由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
如:http://192.168.10.8:8080/helloworld/index.vue?id=8

5.2 URL的常用方法

  • public String getProtocol() 获取该URL的协议名
  • public String getHost() 获取该URL的主机名
  • public String getPort() 获取该URL的端口号
  • public String getPath() 获取该URL的文件路径
  • public String getFile() 获取该URL的文件名
  • public String getQuery() 获取该URL的查询名

5.3 URLConnection类

表示到URL所引用的远程对象的连接。当与一个URL建立连接时, 首先要在一个 URL对象上通过方法 openConnection()生成对应的 URLConnection 对象。如果连接过程失败,将产生IOException。

5.4 URI、URL和URN

URI是以一种抽象的,高层 次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL 和URN都是一种URI。

  • URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
  • URL是uniform resource locator,统一资源定位符,它是一种具体 的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
  • URN,uniform resource name,统一资源命名,是通过名字来标识资源, 比如mailto:java-net@java.sun.com。

5.5 URL编程代码实例

public static void main(String[] args) throws IOException {

    URL url = new URL("http://localhost:8080/images/image1.jpg");

    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

    urlConnection.connect();

    InputStream is = urlConnection.getInputStream();
    FileOutputStream fos = new FileOutputStream("image1.jpg");

    byte[] buffer = new byte[1024];
    int len;
    while ((len = is.read(buffer)) != -1) {
        fos.write(buffer, 0, len);
    }

    System.out.println("下载完成");

	//这里为了方便读代码简化了,应该使用try-catch-finally以及判断是否为空
    is.close();
    fos.close();
    urlConnection.disconnect();
}

六.反射

反射机制允许程序在执行期借助于反射相关API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

Java是静态语言,不是动态语言,但Java可以称之为准动态语言。 即Java有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性。

1.Java反射机制提供的功能

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

2.反射相关的主要API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器
  • ··· ···

3.获取Class类

  1. 前提:已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
    实例:Class clazz = String.class;
  2. 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
    实例:Class clazz =“pearz”.getClass();
  3. 前提:已知一个类的全类名,可能抛出ClassNotFoundException(体现反射的动态性
    实例:Class clazz = Class.forName(“java.lang.String”);
  4. 通过类的加载器
    ClassLoader cl = this.getClass().getClassLoader();
    Class clazz4 = cl.loadClass(“类的全类名”);

4.哪些类型可以有Class对象

class、interface、数组、枚举、注解、基本数据类型、void

5.获取运行时类的对象

  1. 调用Class对象的newInstance()方法
    要求:1)类必须有一个无参数的构造器。2)类的构造器的访问权限需要足够。
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
  1. 调用有参构造器
//1.根据全类名获取对应的Class对象
Class clazz = Class.forName("pearz.java.Person");
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);//姓名,年龄
//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p = (Person) con.newInstance("Peter",20);

6.通过反射获取运行时类的完整结构

  • 实现的接口
    getInterfaces(),不含父类实现的接口
  • 所继承的父类
    getSuperclass()
  • 构造器
    getConstructors(),public构造方法,不含父类
    getDeclaredConstructors(),所有构造方法,不含父类

    Constructor类中:
    取得修饰符:getModifiers();
    取得方法名称:getName();
    取得参数的类型:getParameterTypes();
  • 方法
    getDeclaredMethods(),全部方法,不含父类
    getMethods(),public方法,包含父类

    Method类中:
    取得全部的返回值:getReturnType()
    取得全部的参数:getParameterTypes()
    取得修饰符:getModifiers()
    取得异常信息:getExceptionTypes()
  • Field
    getFields(),public的Field,包含父类
    getDeclaredFields(),全部Field,不含父类

    Field方法中:
    以整数形式返回此Field的修饰符:getModifiers()
    得到Field的属性类型:getType()
    返回Field的名称:getName()
  • Annotation注解相关
    getAnnotation()
    getDeclaredAnnotations()
  • 泛型相关
    获取父类泛型类型:Type getGenericSuperclass()
    泛型类型:ParameterizedType
    获取实际的泛型类型参数数组:getActualTypeArguments()
  • 类所在的包
    getPackage()

7.setAccessible()

启动和禁用访问安全检查的开关
Method和Field、Constructor对象都有setAccessible()方法。

  • 参数值为true,指示反射的对象在使用时应该取消Java语言访问检查。
    -提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    -使得原本无法访问的私有成员也可以访问
  • 参数值为false,指示反射的对象应该实施Java语言访问检查。

8.操作运行时类中的指定的属性

Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();

//1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");
//2.保证当前属性是可访问的
name.setAccessible(true);
//3.获取、设置指定对象的此属性值
name.set(p,"Tom");

9.操作运行时类中的指定的方法

非静态方法

Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();

//1.获取指定的某个方法
//getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);
//3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参。invoke()的返回值即为对应类中调用的方法的返回值。
Object returnValue = show.invoke(p,"CHN"); //等价于String nation = p.show("CHN");

静态方法

Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();

Method showSta = clazz.getDeclaredMethod("showSta");
showSta.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
Object returnVal = showSta.invoke(null);
Object returnVal = showSta.invoke(Person.class);
//由于showSta()方法为static方法,所以以上两句等价
System.out.println(returnVal);//null

10.调用运行时类中的指定的构造器

如:private Person(String name)

Class clazz = Person.class;

//1.获取指定的构造器
//getDeclaredConstructor():参数:指明构造器的参数列表
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保证此构造器是可访问的
constructor.setAccessible(true);
//3.调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值