JAVA 学习 笔记 2022/5/15

百度网盘链接:https://pan.baidu.com/s/1NQ48ZIilKBzxs0bffqpTTA?pwd=java
提取码:java

目录

JAVA基础JAVASE

简介

Java三大版本:JAVASE(标准版,桌面应用开发...),JAVAEE(E企业级开发,web应用,服务器端),JAVAME(手机,小家电开发)


什么是面向对象

  • OPP(Object-Oriented Programming)
  • 面向对象的本质是:以类的方式组织代码,以对象的组织(封装)数据。
  • 抽象
  • 三大特性:封装,继承,多态

JDK,JRE,JVM

JDK,JRE,JVM关系图

JDK: Java Development kit

JRE:Java Runtime Environment

JVM:Java Virtual Maching 真正实现一次编译处处运行

bin:存放一些可执行文件

include:有一些C语言程序

lib:库文件


Java程序运行机制

  • 编译型

C/C++,java...在真正执行前进行编译,后运行编译后的程序

  • 解释型

网页,python...不需要编译直接运行,但每次打开都要重新运行一次

  • ​ 随着硬件提升,两者差距不断减小;

    • img
  • 为什么JAVA文件中只能含有一个Public类?

    java 程序是从一个 public 类的 main 函数开始执行的,(其实是main线程),就像 C 程序 是从 main() 函数开始执行一样。 只能有一个 public 类是为了给类装载器提供方便。 一个 public 类只能定义在以它的类名为文件名的文件中。

    每个编译单元(文件)都只有一个 public 类。因为每个编译单元都只能有一个公共接口,用 public 类来表现。该接口可以按照要求包含众多的支持包访问权限的类。如果有一个以上的 public 类,编译器就会报错。 并且 public类的名称必须与文件名相同(严格区分大小写)。 当然一个编译单元内也可以没有 public 类。


Java命名规范

  • package的命名: package 的名字由全部小写的字母组成,例如:com.runoob。

  • class和interface的命名: class和interface的名字由大写字母开头而其他字母都小写的单词组成,例如:Person,RuntimeException。

  • class变量的命名: 变量的名字用一个小写字母开头,后面的单词用大写字母开头,例如:index,currentImage。

  • class 方法的命名: 方法的名字用一个小写字母开头,后面的单词用大写字母开头,例如:run(),getBalance()。

  • staticfinal变量的命名: static final变量的名字所有字母都大写,并且能表示完整含义。例如:PI,PASSWORD。

  • 参数的命名: 参数的名字和变量的命名规范一致。

  • 数组的命名: 数组应该总是用这样的方式来命名:byte[] buffer。

    养成良好的编程习惯,是一个合格程序员必备的条件!加油!


JavaDoc:文档注释

/**
*@Author conner
*@version 1.01
*@since 最低jdk版本
*@Description sad
*/
  • 可以导出为文档 html (与帮助文档格式相同)
    • DOC: javadoc -encoding utf-8 123.java
标签 描述
@author 标识一个类的作者
@deprecated 指名一个过期的类或成员
@docRoot 指明当前文档根目录的路径
@exception 标志一个类抛出的异常
@inheritDoc 从直接父类继承的注释
@link 插入一个到另一个主题的链接
@linkplain 插入一个到另一个主题的链接,但是该链接显示纯文本字体
@param 说明一个方法的参数
@return 说明返回值类型
@see 指定一个到另一个主题的链接
@serial 说明一个序列化属性
@serialData 说明通过writeObject( ) 和 writeExternal( )方法写的数据
@serialField 说明一个ObjectStreamField组件
@since 标记当引入一个特定的变化时
@throws 和 @exception标签一样.
显示常量的值,该常量必须是static属性。
@version 指定类的版本

标识符

类名,变量名与方法名都是标识符

关键字:(保留字)

abstract assert boolean break byte
case catch char class const
continue default do double else
enum extends final implements float
for goto if implements import
instanceof int interface long native
new package private protected public
return strictfp short static super
switch synchronized this throw throws
transient try void volatile while
  • 所有的标识符都应该以字母(A-Z或a-z),¥,_ 开始
  • 可接着(A-Z或a-z),¥,_ ,数字
  • 不能使用关键字
  • 大小写敏感

数据类型

  • Java是强类型语言,使用的变量会严格符合类型规定,所有的变量必须先定义后使用。(安全性高),Java中不存在无符号类型。

  • 数据类型分为:

    • 基本类型(primitive type)

      • 出现在代码里面的数字,若为声明都以默认类型处理

        int num1 = 5;
        long num2 = 54455456L//后面加上L后缀,表示该数字是long类型,若不加则类型错误
        double num3  =0.002       //小数默认为double
        float num4  = 0.0002f
        
        • 浮点数比较会存在误差,因为本身存在舍入误差,一般使用BigDecimal类(数学工具)比较。
        • 所有的字符都是以数字形式储存,可以强制转化为数字。
      • String as = "hello";//(as == ax) is true;
        String ax = "hello";//as与ax都指向常量池中的hello
        ----------------------------------------------------
        String sa = new String("hello");//(sa == sb) is false
        String sb = new String("hello");//
        
      • 优先级转换:byte,short ,char,int,long,float,double

      • 运算中自动向更高级转换为同一类型后运算。(自动类型转换)

      • 强行类型转换:

        1. 不能对布尔值进行转换
        2. 不能把对象转换为无关对象
        3. 转换中可能会造成内容溢出或精度问题,例:小数转整数舍去小数位
      • JDK7特性:数字可以用下划线分割: a = 100_000_000


String

  • String创建的字符串存储在公共池中,导致不可以改变,而new创建的字符串对象在堆上

    • String s = "Google";
      System.out.println("s = " + s);
      
      s = "Runoob";
      System.out.println("s = " + s)
      
    • 表面上s变了,但是这只是在常量池里又加入了Runoob,再将地址存入引用中,Google这串字符未发生变化且仍然存在内存中。

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

      java 中常量优化机制,编译时 s1 已经成为 abc 在常量池中查找创建,s2 不需要再创建。

      String s1="a"+"b"+"c";
      String s2="abc";
      System.out.println(s1==s2);    			//true
      System.out.println(s1.equals(s2));		//true
      

    ​ 先在常量池中创建 ab ,地址指向 s1, 再创建 abc ,指向 s2。对于 s3,先创建StringBuilder(或 StringBuffer)对象,通过 append 连接得到 abc ,再调用 toString() 转换得到的地址指向 s3。故 (s3==s2)false

    String s1="ab";
    String s2="abc";
    String s3=s1+"c";
    System.out.println(s3==s2);         // false
    System.out.println(s3.equals(s2));  // true
    

Java:String、StringBuffer和StringBuilder的区别_kingzone_2008的博客-CSDN博客_java string和stringbuffer的区别

正则表达式

  • 正则表达式定义了字符串的模式
  • 可以用来搜索,编辑,处理文本
  • 每种语言都有微妙差别

Java 正则表达式 | 菜鸟教程 (runoob.com)


数据类型的溢出

在 Java 中,只有数字类型才能参与运算。但是每个数据类型都有他的取值范围。

例如 byte 数据类型,它的取值范围为 -128 - 127

当我们使用 byte b = 128; 时,肯定会报错。

但是使用 byte b = 127+1; 并不会报错。

而且运算的结果为 -128

我们可以向数据类型的取值范围看作是一个圆,每多一个数据向前移动一个,当数据达到最大值时,我们再加 1,可以就会变成最小值,这就是数据的溢出。


但本质上是:Java 中,byte 占一个字节,取值范围为何是 -128127?(-2^72^7-1)

计算机是用二进制来表示数据的,一个字节也就是 8 个比特位,其中最高位表示符号位(0 正 1 负)。故 byte 的取值范围为 1000 00000111 1111

在 Java 中,是采用补码来表示数据的。正数的补码和原码相同,负数的补码是在原码的基础上各位取反然后加 1。1000 000 是补码,减一然后按位取反得到其原码 1000 0000。(减一得 0111 1111,再按位取反得 1000 0000)因为是负数,所以最小的 byte 值为 -2^7=-128。0111 1111 的十进制为 2^7-1=127(等比序列求和)。byte 是一个字节,共有 2^8=256 种可能性,也就是 -128~127。

其他基本数据类型同理:

char 没有负值,占两个字节,所以取值范围是 **02^16-1(65535)**。一个字节可以表示的范围为0255.但由于第一位是符号位,所以范围为-127~127。Java中不存在无符号类型。


  • 引用类型(reference type)

  • 出现在代码里面的数字,若为声明都以默认类型处理

字节

  • 位(bit ):计算及内部数据储存的最小单位,10010010是一个八位二进制数。
  • 字节(byte):计算机数据处理的基本单位,通常用B表示。(一个汉字占2个字节)
  • 1B = 8 bit
  • 32位电脑与64位电脑:大致上指寻址能力,32位极限内存4G,64位最高128G

进制

binary二进制0b octonary 八进制0 decimalism十进制 hexadecimal十六进制0x

int i = 10;
int i2 = 0b10;
int i3 = 010;
int i4 = 0x10AE;

数据结构

Java工具包提供了强大的数据结构。主要有:

JDK1.5

  • 枚举(Enumeration)
  • 位集合(BitSet)
  • 向量(Vector) //有一些部分已经很老,不推荐使用
  • 栈(Stack) //有一些部分已经很老,不推荐使用
  • 字典(Dictionary)
  • 哈希表(Hashtable)
  • 属性(Properties)
  • jdk1.5新增了很多多线程情况下使用的集合类.位于java.util.concurrent.
  • 迭代器可以代替增强for,并且有删除与检测下个元素的功能。

Java Iterator(迭代器) | 菜鸟教程 (runoob.com)

Java 数据结构 | 菜鸟教程 (runoob.com)

ArrayList和Vector的区别_冷丶的博客-CSDN博客_vector和arraylist的主要区别

Java集合(五)弃用的Vector和Stack - devin.ou - 博客园 (cnblogs.com)


JDK2 集合框架

​ 虽然在此之前已经有很有用的类,但是他们缺少一个核心与主题。故有了集合框架。

  • 该框架必须是高性能的。基本集合(动态数组,链表,数,哈希表)的实现也必须是高效的。
  • 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
  • 对一个集合的扩展和适应必须是简单的。

img

Java 集合框架 | 菜鸟教程 (runoob.com)


泛型generics,JDK5

本质时参数化类型,将操作的数据类型作参数处理(被广泛运用在集合类中),在定义时使用,可以接受不同种的数据类型如:泛型类,泛型方法,泛型接口。同时提供了编译时的安全检查机制如下:

经典案例

来看一个经典案例:
public static void main(String[] args) {
 //测试一下泛型的经典案例
 ArrayList arrayList = new ArrayList();
 arrayList.add("helloWorld");
 arrayList.add("taiziyenezha");
 arrayList.add(88);//由于集合没有做任何限定,任何类型都可以给其中存放

 for (int i = 0; i < arrayList.size(); i++) {
 //需求:打印每个字符串的长度,就要把对象转成String类型
 String str = (String) arrayList.get(i);
 System.out.println(str.length());
        }
    }

运行这段代码,程序在运行时发生了异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
发生了数据类型转换异常,这是为什么?

由于ArrayList可以存放任意类型的元素。例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,导致取出时强制转换为String类型后,引发了ClassCastException,因此程序崩溃了。

这显然不是我们所期望的,如果程序有潜在的错误,我们更期望在编译时被告知错误,而不是在运行时报异常。而为了解决类似这样的问题(在编译阶段就可以解决),在jdk1.5后,泛型应运而生。让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。

我们将第一行声明初始化ArrayList的代码更改一下,编译器就会在编译阶段就能够帮我们发现类似这样的问题。现在再看看效果。
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("helloWorld");
arrayList.add("taiziyenezha");
arrayList.add(88);// 在编译阶段,编译器就会报错
这样可以避免了我们类型强转时出现异常。

写法

  • 泛型方法声明为<>,与数据类型位置相同

    • Element 代指集合类型

    • Type 类

    • Key 键对

    • Value 值

    • Number 基本数据类型的包装类

    • 不确定类型
      • // ?代表可以接收任意类型
        // 泛型不存在继承、多态关系,泛型左右两边要一样
        //ArrayList<Object> list = new ArrayList<String>();这种是错误的
        
        //泛型通配符?:左边写<?> 右边的泛型可以是任意类型
        ArrayList<?> list1 = new ArrayList<Object>();
        ArrayList<?> list2 = new ArrayList<String>();
        ArrayList<?> list3 = new ArrayList<Integer>();
        
  • 限制类型:用于限制参数范围

    • 上限<? Extends 类> 只能接受该类型与子类
    • 下限<? super 类> 只能接受该类型与父类

注意

  • 被定义为泛型类,不一定需要传入泛型参数,Arrlist a = ...可以为Arrlist a =... 。此时检测机制失效,在泛型类与泛型方法可为任何类型,容易报错,如案例中。

  • 泛型不存在继承,多态, 左右两边类型应该一致。

    • public class a{
          public <? extends Object> void  right(){}
      	public <N> void false$(){}
      	public static void main(String[] args){
          right<?> a =new right<子类>();//正确
          false$<Integer> b = new false$<String>();//错误
      	}
      }
      

参考:还不知道泛型是什么?这一篇深入浅出泛型教学! - 知乎 (zhihu.com)


转义字符

\ \ 单个\ \b 退格
\f 换页 \n 换行
\r 回车 \t 水平制表符
\ ' 单引号 \ " 双引号

运算符

短路运算

在关系运算符中出现,&& 和 | | , 一旦判断出结果后面便不再执行。

位运算

0000_0000

  • & 对应位都是1,则为1,否则为0
  • | 对应位都为0,则为0,否则为1
  • ^ 对应位值相等,则为0,否则为1
  • ~ 取反运算符,每一位取反
  • << 左移运算符, 111100 -> 11110000
  • <<右移运算符 111100 ->1111
  • <<<按位右移补零操作符 111100 ->00001111

instanceof运算符

​ 检查对象是否是一个特定类型(类类型或者接口类型)是否有继承关系,返回boolean值。Object instanceof Object

运算符优先级

注意&&优先级大于||

字符串连接符

String + "都可以自动转换"

"" + 40 + 80 = "4080";

40 + 80 +"" ="120";

条件运算符

x ? a : n;

if(x){
	a
}else{
	b
}

命令行传参

  • 命令行可以给main(String[] args)运行时传参给args[]。

可变参数

  • JDK1.5开始,java支持传递同类型的可变参数给一个方法
  • 一个方法只有一个可变参数,且放在参数最后一个位置
main(String...args)

add(int i, double...a)

FOReach增强循环(JDK1.5)

//将arrys数组每个数逐个取出,存到arry中不断循环操作
fro(int arry : arrys){
		
}

稀疏数组

[0]记:数组总共 行 列 总共有多少个数字

[1]-[8]记: 数字位置 行 列 数字大小


N种内部类

  • 内部类

    • 可访问外部的私有属性(protect无法访问)

    • //创建一个外部类
         public class Outer {
             private int num = 10;
       
             public void out() {
                 System.out.println("这是外部类方法");
             }
       
             //创建一个内部类
             public class Inner {
                 public void in() {
                     System.out.println("这是内部方法" + num);
                 }
             }
       
             //实例化对象
             public static void main(String... args) {
                 //一种
                 Outer b = new Outer();
                 Outer.Inner a = b.new Inner();
             }
         }
      
    • 静态内部类

      • 无法访问非静态成员
    • 局部内部类(在方法中定义类)Java局部内部类 (biancheng.net)

      • 局部内部类与局部变量一样,不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。

      • 局部内部类只在当前方法中有效。

        1. public class Test {
              Inner i = new Inner();    // 编译出错
              Test.Inner ti = new Test.Inner();    // 编译出错
              Test.Inner ti2 = new Test().new Inner();    // 编译出错
              public void method() {
                  class Inner{
                  
                  }
                  Inner i = new Inner();
              }
          }
          
      • 局部内部类中不能定义 static 成员。

      • 局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。

      • 在局部内部类中可以访问外部类的所有成员。

    • 匿名内部类

      • 接口本身不可以直接new,但是可以通过匿名内部类重写。

      • public class Test{
            public static void main(String...args){
                new Runnale(){
                    @override
                    public void hello(){
                        
                    }
                };
            }
        }
        
      • Lamda表达式(匿名内部类的高级写法)Java8
        • 只能用于函数式接口(接口唯一包含一个抽象方法),且该方法只实现该接口

        • Runnable 1 = ()->{
              System.out.println("sda");
          };
          Thread a = new Thread(1);
          
          //Thread(Runnable target)
          Thread aa =new Thread(()->{
          	System.out.println("dsa");
          });
          
        • Lamda表达式通过前面的类型来确认重写目标,并且重写后立刻返回一个已经实例化后的对象。

        • ()内传参,由于接口已经规定参数类型,故在Lamda表达式还可以省略参数类型(要去掉都去掉)。

        • 一条语句还可以省去花括号。


  • 外部类

    • 一个java文件可以有多个class,但是只能有一个public class

继承

  • final定义的类不能被继承(断子绝孙),或者用于修饰方法,该方法不能被子类重写。
  • Java 不支持多继承,但支持多重继承。
  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。


重载

一定要有参数列表的变化才算重载,否则报错,别的都可以不变。

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

I/O流与File

基础学习:

  • I/O流
  • File类(略,可在菜鸟教程查看)

  • 字符流:仅用于语文本传输(读取数据以字符长度为单位,对应码表,故可以在文本输出时不出现乱码等情况。(相当于字节流的更高级实现)
  • 字节流:可以传输一切数据,图片,文本,视频,序列化.....以一个字节为单位。

反序列化(属于I/O流)

将一个对象表示为字节序列,其中包括了该对象的全部信息,可以通过序列化储存,与反序列化输出,使用ObjectInputStream与ObjectOutputStream

img

网上太多人写的太详细了,只能说感谢众多仙贝。

超硬核详细学习系列——深入浅出IO的知识点,值得你学习收藏必备 - 知乎 (zhihu.com)


异常

  • Erro JVM问题

  • Exception 程序问题

    • try尝试运行可能有异常的程序

    • 作者:Blueve
      链接:https://www.zhihu.com/question/27122172/answer/35335950

      通常情况下,使用者(包括用户、代码库的使用者)所引发的错误,需要通过异常机制来处理

      因为异常发生的时候,原订的执行流程就无法继续,但对于用户来讲,他们不能因为这样的错误就终止程序的使用,所以提供给程序设计者异常机制,让设计者决定发生意外的时候应该做些什么。而这种意外的产生原因是用户,用户的操作千千万万,导致的结果也可能千奇百怪,但是他们的操作若使得原有流程无法继续,那么就是异常。

      楼主说的判断文件先存在,再读写文件,其实就是这个问题,按照程序的流程,可以保证在判断是否存在的时候,文件的存在性,但是不能保证在真正操作文件的时候文件的存在性(例如判断的时候文件还在,真正操作之前却被用户自己删掉了)。因为流程上无法对流程外的用户行为(用户删文件)作出保证,所以需要异常机制。

      我认为,用户在程序运行时触发所导致的错误,需要异常机制来捕捉和处理。

      程序设计中还有一种叫断言(ASSERT)的东西,这种机制是用来约束程序设计者的,例如某些库的某些函数,在文档中约定了,这个函数的参数必须是>0,那么你在编程的时候愣是硬生生输入一个0,那么这时候就应该选择断言,用于帮助程序设计者及早的发现自己程序中的错误(这种错误是设计上的错误所引发的,而非用户的操作所导致的),而不是用异常机制去处理。

      所以,由程序员设计不足所导致的错误,需要用断言来捕捉和处理。

    • catch捕获异常(可以有多个catch),一旦捕获一个,剩余不执行

    • finally处理善后工作(关闭资源,IO)

    • throws可主动抛出异常,一般在方法抛出 (相当于自己不会做,就交给老板)

    • throw方法内抛出异常

    • 捕获异常,之后程序可以继续执行

      • try{
        	if(b = 0{
        		throw new ArithmeticException();
        	}
        	c=a/b;
        }cathc.............
        

  • 自定义异常
    • 通过继承Exception后重写,写出自己的异常

注解Java.Annotation

——框架的底层实现机制,给人和机器看的。JDK5.0后。

  • Annotation作用:
    • 不是程序本身,但可以对程序作出解释(与comment差不多)
    • 可以被其他程序(编译器...)读取,有功能

内置注解

  • @override 重写的注解,检查重写是否有误
  • @Deprecated 弃用的注解,不推荐使用,但可以使用
  • @SuppressWarning 镇压警告的注解,可以有参数

元注解

  • 元注解负责注解其他注解,Java定义了四个标准meta-annotation在java.lang.annotation
    • @Target :用于描述注解得使用范围(可以用在哪)
    • @Retention :表示需要在什么级别保存该注解信息,用于描述注解的生命周期
      • SOURCE(源代码)<CLASS<RUNTIME
    • @Document : 说明该注释被包括在javadoc中
    • @Inherited :说明子类可以继承父类的该注解
import java.lang.annotation;

@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Rerention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited

@interface MyAnnotation{
    
}

反射机制java.reflection

Java是静态语言,运行时结构不可变(代码固定),但是可以通过反射机制得到类似于动态语言的特性,但是安全性减小。

  • reflection允许程序在执行时借助于Reflection API取得任何类的内部信息,并且可以直接操作任意对象属性及方法。

    ​ Class c = Class.forName("java.lang.String")

  • 对性能有影响较慢。

类的加载与ClassLoader


java.lang.*

Java语言包(java.lang)简介_BB项目的博客

​ java.lang包是java语言的核心,它提供了java中的基础类,由Java语言自动调用,不需要显示声。包括基本Object类、Class类、String类、基本类型的包装类、基本的数学类等等最基本的类。我们介绍一下Java 8中的java.lang包。主要类如下图:这里写图片描述

java.lang.Object所有类的默认父类

​ 在java中所有类都会继承Object,如果一个类没声明继承类就会默认继承Object,如果声明类继承,该父类,或是父类的父类也一定会有一个继承Object形成多重继承。

方法 说明
Class<?> getClass() 获取对象运行时对象的类(反射)
Object clone() 创建与该对象的类相同的新对象
boolean equals(Object) 比较两对象是否相等
void finalize() 当垃圾回收器确定不存在对该对象的更多引用时,对象垃圾回收器调用该方法(GC)
int hashCode() 返回该对象的hash值
void notify() 激活等待在该对象的监视器上的一个线程
void notifyAll() 激活等待在该对象的监视器上的全部线程
String toString() 返回该对象的字符串表示
void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待(有三个重载)

java.lang.Math

Math类提供了常用的数学运算方法以及Math.PI和Math.E两个数学常量。 该类是final的,不能被继承,类中的方法和属性全部是静态,不允许在类的外部创建Math类的对象。因此,只能使用Math类的方法而不能对其作任何更改。

java.lang.String

字符串是字符的序列。在 Java 中,字符串无论是常量还是变量都是用类的对象来实现的。 java.lang 提供了两种字符串类:String 类和 StringBuffer 类。

String

按照 Java 语言的规定,String 类是 immutable 的 Unicode 字符序列,其作用是实现一种不能改变的静态字符串。例如,把两个字符串连接起来的结果是生成一个新的字符串,而不会使原来的字符串改变。实际上,所有改变字符串的结果都是生成新的字符串,而不是改变原来字符串。

StringBuffer

String 类不能改变字符串对象中的内容,只能通过建立一个新串来实现字符串的变化。如果字符串需要动态改变,就需要用 StringBuffer 类。StringBuffer 类主要用来实现字符串内容的添加、修改、删除,也就是说该类对象实体的内存空间可以自动改变大小,以便于存放一个可变的字符序列。


Bottun to Top && Acknowledgement

We would like to express our deep appreciation for the support offered by the following individuals and organizations.

Java 教程 | 菜鸟教程 (runoob.com)

【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili

什么情况下使用异常处理? - 知乎 (zhihu.com)


目录


多线程编程java.lang.Thread

【狂神说Java】多线程详解_哔哩哔哩_bilibili

​ 操作系统分配一个内存空间为进程,包含一个或多个线程(如一个视频能有声音,字幕,画面)。线程是进程的一部分,一个进程会一直执行,直到他的非守护线程结束。

  • ​ main函数是主线程,为系统入口。
  • 一个线程就是独立的执行路径,
  • 一个进程可能只有一个main线程,但是后台也会有多个线程如:gc线程....
  • 一个进程的多个线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不可认为干预。
  • 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制。
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销。
  • 每个线程都在自己的工作内存交互,内存控制不当会造成数据不一致。

很多线程都是模拟出来的(普通电脑中有许多别的应用在跑,所以大多数情况模拟多核),真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,同一个时间点cpu只能执行一个代码,因为切换的很快,就有同时执行的错觉。


线程的生命周期

线程是动态执行的过程,他有一个从产生到死亡的过程,并且一旦死亡无法在此start()

img

  • 就绪状态:当线程调用start()方法后,进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态:就绪状态的线程获取CPU资源,就可以执行run(),此时线程处于运行状态,十分复杂,可以变成阻塞状态,就绪状态和死亡状态。
  • 阻塞状态:如果一个线程执行了sleep,suspend(挂起)等方法,失去所占用的资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或者获得设备资源后可以重新进入就绪状态。可分为三种:
    • 等待阻塞:运行状态的线程执行wait()方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取synchronized(同步锁)失败(因为synchronized锁已被其他线程占用)
    • 其他阻塞:通过调用线程的sleep()或join()发出I/O请求是,线程就会进入阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态。

线程的优先级

Thread包中,已经定义好了优先级范围Thread.MIN_PRIORITY~Thread.MAX_PRIORITY 1-10。

默认情况下每个线程的优先级都是5,但是优先级大小只会影响系统分配资源,但是并不能决定系统分配资源,最后的效果还是由平台(系统)决定。但是还是大概率运行优先级高的线程,当运行优先级低的部分会产生叫做“性能倒置”的情况。


创建线程的三种方式

![屏幕截图 2022-05-10 091044](image/JAVAThread/屏幕截图 2022-05-10 091044.png)

调用start()开辟线程并且执行run(),直接执行run()只会执行里面的代码。

  • 继承Thread类,重写run()方法,可写外部类/内部类实现。

    public class Thread1 extends Thread{
        public static void main(String...args){
            Thread1 a = new Thread1();
            a.start();//同一个Thread1对象不能start两次。结束后死亡,无法复活。
        }//Thread有静态方法,Thread.currentThread.?可以获得信息
        @override
        public void run(){
        	System.out.printf("%3.2f",123.213);
        }
    }
    
  • 实现Runnable接口,重写run(),调用Thread创建线程(Thread 实现 Runnable接口)。由于java的单继承性,很多时候使用,并且一个对象可被多个Thread类使用。匿名内部类可简化代码。

    public class Thread2 implement Runnable{
        public static void main(String...args){
            //new Thread(name, Runnable)
            new Thread(a,name1);
            new Thread(new Thread2(),name2);
                
        Lambda a = new Lambda(){
            @override
            public void run(){
                System.out.printf("dsa/n");
            }
        }
            @override
       		 public void run(){
            	System.out.println("das");
       		 }    
        }
        //为简化接口实现,可以使用匿名内部类或Lambda表达式(只能实现函数式接口,一个方法)
    }
    
  • 实现callable接口(了解)

    Callable具有返回值,implemets Callable<>


线程不安全,并发问题

一个资源被多个线程同时操作

public class Test implements Runnable{
  private  int number = 10;
  public static void main(String...args){
      //一份资源
      Test h = new Test();//一个对象
      //多个代理
      Thread a = new Thread(h, "conner1");
      Thread b = new Thread(h, "conner2");
      Thread c = new Thread(h, "conner3");
      a.start();
      b.start();
      c.start();
  }

  @Override
  public void run() {
      while (true){
          System.out.println(Thread.currentThread().getName()+"拿到了"+ number--+"票");
          if(number == 0) break;
      }
  }
}

![屏幕截图 2022-05-10 0950352](image/JAVAThread/屏幕截图 2022-05-10 0950352.png)

出现一张票被多个人同时得到,逻辑上不可能,这就是线程安全问题。有一些数据结构不是线程安全指的就是此问题。


并发

并发:同一个对象被多个线程同时操控。

​ 此时应该用线程同步。线程同步其实是一种等待机制,多个需要同时访问这个对象的线程进入这个对象的等待池形成队列,前面的线程使用完毕,下一个线程再使用

线程同步(队列和锁)

  • 在访问时加入锁机制Synchronized,当一个线程获得对象的排它锁(锁有很多种),独占资源,其他线程必须等待,使用后释放锁即可

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题
    • 如果优先级高的线程等待优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
  • 同步方法:

    • 用synchronized关键字,可以为synchronized方法 与 synchronized块。

    • public synchronized void method() synchronized方法控制对“对象”的访问,synchronized方法都必须获得调用该对象的锁才能执行,否则线程就会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得锁,继续执行。(钥匙在桌上,谁先拿到是谁的,用了一次后又放回)锁住的默认为this对象,若修改的数据不在该同步块之中,仍会出现问题。

      • Synchronized方法
        • 缺陷:一个大的方法申明为synchronized将会影响效率,所以可以用synchronized块锁住修改数据的部分。

成功锁住:17行

public class Test implements Runnable{
    private  int number = 10;
    public static void main(String...args){
        
        Test h = new Test();//一个对象
        
        Thread a = new Thread(h, "conner1");
        Thread b = new Thread(h, "conner2");
        Thread c = new Thread(h, "conner3");
        a.start();
        b.start();
        c.start();
    }

    @Override
    //用synchronized锁住对象,相当于锁住this(就是main中 h ),让h的三个代理被锁
    public synchronized void run() {
        while (true){
	   	 if(number == 0) break;
            System.out.println(Thread.currentThread().getName()+"拿到了"+ number--+"票");
        }
    }
}

锁住失败:16行,42行
42行处锁this,锁住了Drawing对象,但是数据在Account对象中,仍出现问题。可以用synchronized块解决。

public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account("结婚基金",10000);

        Drawing you = new Drawing(account, 5000, "YOU");
        Drawing she = new Drawing(account, 6000, "SHE");
        you.start();
        she.start();

    }
}

class Account {
    String name;
    int money;

    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

class Drawing extends Thread {
    //账户
    private Account account;
    //取了多少钱
    private int drawingMoney;
    //现在手里有多少钱
    private int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);//extends Thread
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public synchronized void run() {
        //判断是否有钱
        if (account.money - drawingMoney < 0) {
            System.out.println(this.getName() + "余额不足");
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        nowMoney=account.money - drawingMoney;
        account.money=nowMoney;
        System.out.println(this.getName() + "成功取钱"+drawingMoney+"账户剩余"+nowMoney);
    }
}
Synchronized块
  • synchronized(Obj)
  • Obj被称为 同步监视器
    • Obj 可以是任何对象,但推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或是class(反射)
  • 同步监视器的执行过程
    • 第一个线程访问,锁定同步监视器,执行其中代码
    • 第二个线程访问,发现同步监视器被锁定,无法访问
    • 第一个线程访问完毕,解锁同步监视器
    • 第二个线程访问,发现同步监视器没有锁,然后锁定同步监视器

第42行锁住了Account,而不是this(Drawing),最后安全。

public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account("结婚基金",10000);

        Drawing you = new Drawing(account, 5000, "YOU");
        Drawing she = new Drawing(account, 6000, "SHE");
        you.start();
        she.start();

    }
}

class Account {
    String name;
    int money;

    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

class Drawing extends Thread {
    //账户
    private Account account;
    //取了多少钱
    private int drawingMoney;
    //现在手里有多少钱
    private int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);//extends Thread
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public synchronized void run() {
    	//同步块锁住account
        synchronized(account){
            //判断是否有钱
            if (account.money - drawingMoney < 0) {
                System.out.println(this.getName() + "余额不足");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            nowMoney=account.money - drawingMoney;
            account.money=nowMoney;
            System.out.println(this.getName() + "成功取钱"+drawingMoney+"账户剩余"+nowMoney);
        }
    }
}

Sychronized大佬讲解

synchronized详解 - 三分恶 - 博客园 (cnblogs.com)


死锁

  • 多个线程占有一些共享资源,并且互相等待其他线程占有的资源才能运行,导致两个或者多个线程都在等待对方释放资源,都停止执行的情况,一个同步块同时拥有两个以上对象的锁时,就可能发生死锁问题。
  • 产生死锁的四个必要条件:(只要破除以一个就可以避免死锁的发生)
    • 互斥条件:一个资源每次只能被一个进程使用
    • 请求与保持条件:一个进程因请求资源而被堵塞时,对已获得的资源保持不放
    • 不剥夺条件:进程已获得的资源,在未使用完前,不能强行剥夺
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待

Lock( 锁 JDK5.0)

  • JDK5.0后,java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。
  • java.until.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。提供了对共享资源的独占访问,每次只有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类(可重用锁)实现了Lock,他拥有与synchronized相通的并发性和内存语意,可显示加锁,释放锁。
  • ...........

生产者消费者问题(让线程高效协作)

根据应用场景解决:

  • 生产者生产产品,消费者拿产品,仓库存储产品。生产者和消费者共享同一个资源,并且相互依赖,互为条件。生产者生产完后消费者拿,消费者拿完后通知生产者生产。

  • 线程通信:

    • wait(), notify()只能在synchronized方法与synchronized块中使用。否则会抛出异常。

解决办法:

  • 管程法
    • 生产者和消费者之间加入缓冲区(I/O流有些也会因为节省系统资源而加大缓冲区,减少读取次数),消费者从缓冲区拿出数据,生产生产数据至缓冲区。
    • ![屏幕截图 2022-05-11 090230](image/JAVAThread/屏幕截图 2022-05-11 090230.png)
  • 信号灯法
    • 设置一个标记位,当消费者发出请求后wait()释放锁给生产者,生产者生产后释放锁给消费者。

线程方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定毫秒数内让当前执行的线程休眠
void join() 等待该线程终止(插队)
static void yield() 暂停当前执行的线程对象,执行其他线程(礼让)
void interruput() 中断线程(不推荐,已经废用)
boolean isAlive() 测试线程是否处于活动状态

中断线程

  • 建议线程正常停止(运行完整个代码块)--->利用循环次数,利用标志位
  • 官方不推荐使用stop或destroy等方法

线程休眠

  • sleep(long millis)阻塞的毫秒数。
  • 会抛出异常InterruptedException。
  • 时间到达后,线程进入就绪状态。
  • sleep可模拟网络延迟(放大问题的发生性),倒计时等。
  • 每个对象都有一把锁,sleep不会释放锁。

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功,因为转为就绪状态后又会与其他线程竞争,可能再次执行此线程。

join

  • 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。

线程状态Thread.State

.getState得到 Enum 类型常量:

  • NEW:尚未启动的线程
  • RUNNABLE:再java虚拟机执行的线程
  • WAITING:正在等待另一个线程执行特定动作的线程
  • TIMED_WATTING:正在等待另一个线程执行动作达到指定等待时间的线程
  • TERMINATED:已退出的线程

守护线程(Daemon Thread):

  • 线程分为用户线程(User Thread)和 守护线程(Daemon Thread)
  • GC为基础的守护线程,会在其他非守护线程存活情况下始终运行
  • 一个线程默认为用户线程,除非调用<?>.setDeamon(true)。
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如:后台记录操作日志,监控内存,垃圾回收..........

静态代理模式(线程的底部设计理念)(23种设计模式)

  • 真实对象和代理对象实现同一个接口

  • 代理对象代理真实对象(实现Runnable接口的多线程方式)

    public class Test{
        private int number = 10;
        public static void main(String...args){
            new weddingServices(new Me()).marry();
        }
    }
    //结婚这个事情接口
    interface Marry{
        public void marry();
    }
    //真实对象类,与专注于自己事情,相当于你实现的Runnable接口地类
    class Me implements Marry{
        @Override
        public void marry() {
            System.out.println("出席婚宴");
        }
    }
    //代理对象,做他无法做的事情,相当于实现了Runnable接口的Thread类
    class weddingServices  implements Marry{
        private Marry target = null;
        public weddingServices(Marry a){
            this.target = a;
        }
        @Override
        public void marry() {
            before();
            System.out.println("布置现场");
            target.marry();
            after();
        }
        public void before(){
            System.out.println("搭婚台");
        }
        public void after(){
            System.out.println("收拾清洁");
        }
    }
    
    
  • 好处:

    • 代理对象可以做很多真实对象做不了的事情
    • 真实对象专注自己的事情
    • 例如:重写Runnable接口,后用继承了Runnable的Thread用之前重写的部分。

线程池

1、线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。(是什么)

2、那么,我们为什么需要用到线程池呢?每次用的时候手动创建不行吗?

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。(为什么)

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快;另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。(什么用)

3、线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。

  • Executors:线程池创建工厂类,用于创建并返回不同类型的线程池
  • public static ExecutorServicenewFixedThreadPool(int nThreads):返回线程池对象
  • ExecutorService:线程池类
  • Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
  • Future 接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

4、这里介绍两种使用线程池创建线程的方法

1):使用Runnable接口创建线程池

使用线程池中线程对象的步骤:

  • 1、创建线程池对象
  • 2、创建 Runnable 接口子类对象
  • 3、提交 Runnable 接口子类对象
  • 4、关闭线程池

Test.java 代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
  public static void main(String[] args) {
      //创建线程池对象  参数5,代表有5个线程的线程池
      ExecutorService service = Executors.newFixedThreadPool(5);
      //创建Runnable线程任务对象
      TaskRunnable task = new TaskRunnable();
      //从线程池中获取线程对象
      service.submit(task);
      System.out.println("----------------------");
      //再获取一个线程对象
      service.submit(task);
      //关闭线程池
      service.shutdown();
  }
}

TaskRunnable.java 接口文件如下:

public class TaskRunnable implements Runnable{
  @Override
  public void run() {
      for (int i = 0; i < 1000; i++) {
          System.out.println("自定义线程任务在执行"+i);
      }
  }
}

2)使用Callable接口创建线程池

Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

ExecutorService:线程池类

Future submit(Callable task):获取线程池中的某一个线程对象,并执行线程中的 call() 方法

Future 接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

使用线程池中线程对象的步骤:

  • 1、创建线程池对象
  • 2、创建 Callable 接口子类对象
  • 3、提交 Callable 接口子类对象
  • 4、关闭线程池

Test.java 代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test{
  public static void main(String[] args) {
      ExecutorService service = Executors.newFixedThreadPool(3);
      TaskCallable c = new TaskCallable();
      //线程池中获取线程对象,调用run方法
      service.submit(c);
      //再获取一个
      service.submit(c);
      //关闭线程池
      service.shutdown();
  }
}

TaskCallable.java 接口文件如下:

import java.util.concurrent.Callable;

public class TaskCallable implements Callable<Object>{
  @Override
  public Object call() throws Exception {
      for (int i = 0; i < 1000; i++) {
          System.out.println("自定义线程任务在执行"+i);
      }
      return null;
  }
}

同步(Synchronous)异步(Asynchronous)

  1. 同步和异步区别在于等不等待。

  2. 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为

    • img
  3. 异步方法调用更像一个信息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续操作。而异步方法通常会在另一个线程中,“真实“地执行着不会阻碍调用者工作。

    • img

    同步(Synchronous)和异步(Asynchronous) - myCpC - 博客园 (cnblogs.com)


Bottun to Top && Acknowledgement

We would like to express our deep appreciation for the support offered by the following individuals and organizations.

【狂神说Java】多线程详解_哔哩哔哩_bilibili

Java 网络编程 | 菜鸟教程 (runoob.com)底部笔记处

同步(Synchronous)和异步(Asynchronous) - myCpC - 博客园 (cnblogs.com)


目录


网络化编程java.net


两种常见的网络协议支持:

  • TCP:Transmission Control Protocol, 传输控制协议。是一种面向连接,可靠的,基于字节流的传输层通信协议,TCP层位于IP层上,应用层下的中间层。被称为TCP/IP(协议簇)不止这两个。
  • UDP:UserDatagramProtocol,OSI模型的传输层。一个无连接的协议,提供了应用程序之间要发送数据的数据宝。缺乏可靠性且属于无连接协议,所以容易丢包,错误或重复数据包。
  • 简而言之,非通信专业不需要对此深入了解,TCP有著名的三次握手四次拒绝,两者之间有十分稳固的连接但传输速度慢,而UDP是管道流,连接不稳定,但是将数据一股脑全部注入,不确保数据的完整性,但速度快。例如:视频通话不追求高质量画面与语音,更加追求及时性,低延迟,故采用UDP,但若网络环境较差,会出现画面卡顿,语音丢失的情况。同时文字聊天追求信息的准确性(毕竟没有人看得懂乱码),不追求信息发送的速度,故一般采用TCP连接。
  • 关于OSI四层模型:9
  • 四层,五层,七层:10
  • 对此不需要深入了解,可以了解一下OSI模型有多层的原因:
  • 一开始,为了让网络运输有规范的格式,便定义了七层模型,然后设计相应的实现,可随着时代的发展,有人设计出来的协议,一个就可以实现多个层结构,于是有着各种层次,本质上都是对七层的不同实现。

基础只有两个主题:

  • Socket编程
  • URL(UniformRecourseLocator)处理

Socket编程java.net.Socket java.net.ServerSocket

​ Socket使用TCP通讯,将双方分为客户端Socket与服务器SeverSocket,

  1. 服务器端实例化一个SeverSocket对象,然后制定一个port(端口)打开。

  2. 服务器端SeverSocket调用accept()方法,等待客户端连接。

  3. 在服务器端等待时,客户端实例化Socket对象,凭URL和port连接上服务器端。

    public Socket(String host, int port) throws UnknownHostException, IOException.

    public Socket(InetAddress host, int port) throws IOException

    ......

    InetAddress用于处理URL

连接成功后通过I/O流传输数据,Sever输出流为客户输入流,客户输出流为Sever输入流。

  1. BIO(Blocking IO)同步阻塞的编程方式

java.swing

Swing简介:Swing是什么? (biancheng.net)


Bottun to Top && Acknowledgement

We would like to express our deep appreciation for the support offered by the following individuals and organizations.

更多具体知识可以参考:

菜鸟教程 (runoob.com)](https://www.runoob.com/java/java-networking.html)


目录

posted @ 2022-05-08 11:09  奇迹和魔法都是存在的  阅读(36)  评论(0编辑  收藏  举报