玩命加载中 . . .

JavaSE进阶学习笔记


学习视频链接:B站 动力节点 遇见狂神说

static关键字

  • static修饰的方法静态方法
  • static修饰的变量静态变量
  • static修饰的都用“类名.”访问。(都不需要new对象,就能访问)
  • static{} 是静态代码块,在类加载时执行,并执行一次。
  • {} 实例语句块,在构造方法执行前执行,构造一次执行一次。

static的也能用“引用.”的方式访问,不过运行时仍然和此对象无关,空指针异常充分的说明了以上的结论。(用空引用访问静态的不会出现空指针异常)

this关键字

this的用法this.this()

  • this不能用在静态方法中。只能使用在实例方法中。
  • this.大部分可以省略,区分局部变量和实例变量的时候不能省略。
  • this()只能出现在构造方法第一行,通过当前构造方法调用本类其它构造方法,代码复用。

super

super的用法super.super()

  • super.大部分可以省略,当父子都有相同的属性和方法时,在子类中访问父类的特征时,必须使用super.
  • 一个构造方法第一行啥也没有,自动会有一个super()
  • super不能出现在静态方法中,只能是实例方法。
  • super()是通过子类的构造方法调用父类的构造方法。

final关键字

  • final修饰的类无法继承。

  • final修饰的方法无法覆盖。

  • final修饰的变量只能赋一次值。

  • final修饰的引用一旦指向某个对象,则不能再重新指向其它对象,但该引用指向的对象内部的数据是可以修改的。

  • final修饰的实例变量必须手动初始化,不能采用系统默认值。

  • final修饰的实例变量一般和static联合使用,称为常量。

    public static final double PI = 3.1415926;

抽象类

怎样定义抽象类?

在class前添加abstract关键字即可。

  • 抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的。
  • final和abstract不能联合使用,这两个关键字是对立的。
  • 抽象类的子类可以是抽象类,也可以是非抽象类。
  • 抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。
  • 抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中。
  • 一个非抽象的类,继承抽象类,必须将抽象类的抽象方法进行覆盖/重写/实现。

怎样定义抽象方法?

public abstract void myMethod();

接口

[修饰符列表] interface 接口名{}
  • 接口是一种“引用数据类型”。
  • 接口是完全抽象的。
  • 接口支持多继承。
  • 接口中只有常量+抽象方法。
  • 接口中所有的元素都是public修饰的
  • 接口中抽象方法的public abstract可以省略。
  • 接口中常量的public static final可以省略。
  • 接口中方法不能有方法体。
  • 一个非抽象的类,实现接口的时候,必须将接口中所有方法加以实现。
  • 一个类可以实现多个接口。
  • extends和implements可以共存,extends在前,implements在后。
  • 使用接口,写代码的时候,可以使用多态(父类型引用指向子类型对象)。

接口在开发中的作用

接口在开发中的作用,类似于多态在开发中的作用。(多态:面向抽象编程,不要面向具体编程。降低程序的耦合度。提高程序的扩展力。)

面向接口编程,可以降低程序的耦合度,提高程序的扩展力。符合OCP(开闭原则:Open Closed Principle)开发原则。

  • 接口的使用离不开多态机制。(接口+多态才可以达到降低耦合度。)
  • 接口可以解耦合,解开的是谁和谁的耦合!!!
  • 任何一个接口都有调用者和实现者。
  • 接口可以将调用者和实现者解耦合。
  • 调用者面向接口调用。
  • 实现者面向接口编写实现。

进行大项目的开发,一般都是将项目分离成一个模块一个模块的,模块和模块之间采用接口衔接。降低耦合度。

抽象类与接口在语法上的区别

  • 抽象类是半抽象的,接口是完全抽象的。
  • 抽象类中有构造方法,接口中没有构造方法。
  • 接口和接口之间支持多继承,类和类之间只能单继承。
  • 一个类可以同时实现多个接口,一个抽象类只能继承一个类(单继承)。
  • 接口中只允许出现常量和抽象方法。

类之间的关系

  • 泛化关系,类和类之间的继承关系及接口与接口之间的继承关系
  • 实现关系,类对接口的实现
  • 关联关系,类与类之间的连接,一个类可以知道另一个类的属性和方法,在 java 语言中使用成员变量体现
  • 聚合关系,是关联关系的一种,是较强的关联关系,是整体和部分的关系,如:汽车和轮胎,它与关联关系不同,关联关系的类处在同一个层次上,而聚合关系的类处在不平等的层次上,一个代表整体,一个代表部分,在 java 语言中使用实例变量体现
  • 合成关系,是关系的一种,比聚合关系强的关联关系,如:人和四肢,整体对象决定部分对象的生命周期,部分对象每一时刻只与一个对象发生合成关系,在 java 语言中使用实例变量体现
  • 依赖关系,依赖关系是比关联关系弱的关系,在 java 语言中体现为返回值,参数,局部变量和静态方法调用

package

package出现在java源文件第一行。

带有包名的通过javac -d .xxx.java编译,通过java 完整类名运行。

如果带着包名描述,表示完整类名。如果没有带包,描述的话,表示简类名。

  • java.util.Scanner 完整类名。
  • Scanner 简类名

访问控制权限

Java 访问级别修饰符主要包括:private protected 和 public,可以限定其他类对该类、属性和方法的使用权限。

访问控制权限有4个,分别是private(私有)、public(公开)、protected(受保护)、default(默认)。

  • private 表示私有的,只能在本类中访问
  • public 表示公开的,在任何位置都可以访问
  • “默认”表示只能在本类,以及同包下访问
  • protected表示只能在本类、同包、子类中访问

以上对类的修饰只有:public 和 default,内部类除外。

访问控制权限可以修饰什么?

属性(4个都能用)
方法(4个都能用)
类(public和默认能用,其它不行。)
接口(public和默认能用,其它不行。)

Object类

  • Object 类是所有 Java 类的根基类
  • 如果在类的声明中未使用 extends 关键字指明其基类,则默认基类为 Object 类

任何一个类默认继承Object。就算没有直接继承,最终也会间接继承。

Object类中常用方法

protected Object clone()   // 负责对象克隆的。
int hashCode()    // 获取对象哈希值的一个方法。
boolean equals(Object obj)  // 判断两个对象是否相等
String toString()  // 将对象转换成字符串形式
protected void finalize()  // 垃圾回收器负责调用的方法

什么是API?

应用程序编程接口(Application Program Interface),整个JDK的类库就是一个javase的API。每一个API都会配置一套API帮助文档。SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。)

toString()

后续所有类的toString()方法是需要重写的。重写规则,越简单越明了就好。
System.out.println(引用);这里会自动调用“引用”的toString()方法。
String类是SUN写的,toString方法已经重写了。

equals()

后续所有类的equals方法也需要重写,因为Object中的equals方法比较的是两个对象的内存地址,需求应该比较内容,所以需要重写。

重写规则:自己定,主要看是什么和什么相等时表示两个对象相等。

  • 基本数据类型比较实用:==
  • 对象和对象比较:调用equals方法

String类是SUN编写的,所以String类的equals方法重写了。以后判断两个字符串是否相等,最好不要使用==,要调用字符串对象的equals方法。
注:重写equals方法的时候要彻底。

finalize

垃圾回收器(Garbage Collection),也叫 GC,垃圾回收器主要有以下特点:

  • 当对象不再被程序使用时,垃圾回收器将会将其回收
  • 垃圾回收是在后台运行的,我们无法命令垃圾回收器马上回收资源,但是我们可以告诉他,尽快回收资源(System.gc 和 Runtime.getRuntime().gc())
  • 垃圾回收器在回收某个对象的时候,首先会调用该对象的 finalize 方法
  • GC 主要针对堆内存
  • 单例模式的缺点

finalize方法是protected修饰的。

protected void finalize() throws Throwable { }

当垃圾收集器将要收集某个垃圾对象时将会调用 finalize,建议不要使用此方法,因为此方法的运行时间不确定,如果执行此方法出现错误,程序不会报告,仍然继续运行。

内部类

在一个类的内部定义的类,称为内部类。

内部类主要分为以下三类:

  • 实例内部类,类似于实例变量
  • 局部内部类,类似于局部变量
  • 静态内部类,类似于静态变量

匿名内部类

匿名内部类是局部内部类的一种。因为这个类没有名字而得名,叫做匿名内部类。

class Test{
    public static void main(String[] args){
        MyMath m = new MyMath();
        //使用匿名内部类
        m.mySum(new Conpute(){
            public int sum(int x,int y){
                return x+y;
            }
        },1,2)
    }
}

//负责计算的接口
interface Compute{
    //抽象方法
    int sum(int x,int y);
}

class MyMath{
    public void mySum(Compute c,int x,int y){
        int retValue = c.sum(x,y);
        System.out.println(x+"+"+y+"="+retValue);
    }
}

数组

数组是一种引用数据类型。

  • 空间存储上,内存地址是连续的。
  • 每个元素占用的空间大小相同。
  • 知道首元素的内存地址。
  • 通过下标可以计算出偏移量。

通过一个数学表达式,就可以快速计算出某个下标位置上元素的内存地址,直接通过内存地址定位,效率非常高。

优点:检索效率高。
缺点:随机增删效率较低,数组无法存储大数据量。
注:数组最后一个元素的增删效率不受影响。

一维数组

静态初始化

int[] array = {1,2,3,4};

Object[] objs = {new Object(), new Object(), new Object()};

动态初始化

int[] array = new int[4]; // 4个长度,每个元素默认值0

Object[] objs = new Object[4]; // 4个长度,每个元素默认值null

遍历

for(int i = 0; i < array.length; i++){
    System.out.println(array[i]);
}

二维数组

静态初始化

int[][] array = {
    {1,2,34},
    {54,4,34,3},
    {2,34,4,5}
};

Object[][] array = {
    {new Object(),new Object()},
    {new Object(),new Object()},
    {new Object(),new Object(),new Object()}
};

动态初始化

int[][] array = new int[3][4];
Object[][] array = new Object[4][4];
Animal[][] array = new Animal[3][4];
// Person类型数组,里面可以存储Person类型对象,以及Person类型的子类型都可以。
Person[][] array = new Person[2][2];

遍历

for(int i = 0; i < array.length; i++){ // 外层for循环负责遍历外面的一维数组。
    // 里面这个for循环负责遍历二维数组里面的一维数组。
    for(int j = 0; j < array[i].length; j++){
        System.out.print(array[i][j]);
    }
    // 换行。
    System.out.println();
}

数组的拷贝

使用System.arraycopy()方法。

数组有一个特点:长度一旦确定,不可变。所以数组长度不够的时候,需要扩容,扩容的机制是:新建一个大数组,将小数组中的数据拷贝到大数组,然后小数组对象被垃圾回收。

Arrays工具类

Java中提供了一个数组工具类:java.uril.Arrays

其中有一个sort()方法,可以进行排序,是静态方法,直接使用类名调用。

常见的算法

排序算法:

  • 冒泡排序算法
  • 选择排序算法

选择排序比冒泡排序的效率高。高在交换位置的次数上。选择排序的交换位置是有意义的。循环一次,然后找出参加比较的这堆数据中最小的,拿着这个最小的值和最前面的数据“交换位置”。

查找算法:

  • 二分法查找

二分法查找建立在排序的基础之上。

String

对String在内存存储方面的理解:

  • 字符串一旦创建不可变。
  • 双引号括起来的字符串存储在字符串常量池中。
  • 字符串的比较必须使用equals方法。
  • String已经重写了toString()和equals()方法。

StringBuffer/StringBuilder

StringBuffer 称为字符串缓冲区,它的工作原理是:预先申请一块内存,存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列。StringBuffer 是可变对象,这个是 String 最大的不同。

StringBuffer用法同 StringBuffer,StringBuilder 和 StringBuffer 的区别是 StringBuffer 中所有的方法都是同步的,是线程安全的,但速度慢,StringBuilder 的速度快,但不是线程安全的。

  • StringBuffer/StringBuilder可以看做可变长度字符串。
  • StringBuffer/StringBuilder初始化容量16。
  • StringBuffer/StringBuilder是完成字符串拼接操作的,方法名:append。
  • StringBuffer是线程安全的。StringBuilder是非线程安全的。
  • 频繁进行字符串拼接不建议使用+

基本类型对应的包装类

基本类型的包装类主要提供了更多的实用操作,这样更容易处理基本类型。所有的包装类都是final 的,所以不能创建其子类,包装类都是不可变对象。

基本类型 包装类
byte Byte
short Short
cahr Character
int Integer
long Long
float Float
double Double
boolean Boolean

自动装箱和拆箱

在 JDK5.0 以前,包装类和基本类型做运算时,必须将包装类转换成基本类型才可以,而 JDK5.0提供 Auto-boxing/unboxing(自动装箱和拆箱)。

  • 自动将基础类型转换为对象
  • 自动将对象转换为基础类型

自动装箱:把基本数据类型转换为包装类类型。
自动拆箱:把包装类类型转换为基本数据类型。

日期类

获取系统当前时间

Date d = new Date();

日期格式化

Date–>String

SimpleDateFormat sdf = new SimpleDate("yyyy-MM-dd HH:mm:ss SSS");
String s = sdf.format(new Date());

String–>Date

SimpleDateFormat sdf = new SimpleDate("yyyy-MM-dd HH-mm-ss");
Date d = sdf.parse("2020-02-20 02:20:20")

获取毫秒数

long timeMills = System.currentTimeMills();
Date d = new Date(timeMills - 1000*60*60*60*24);

数字类

DecimalFormat数字格式化
###,###.## 表示加入千分位,保留两个小数。
###,###.0000 表示加入千分位,保留4个小数,不够补0
BigDecimal
财务软件中通常使用BigDecimal

随机数

怎样产生int类型随机数?
Random r = new Random();
int i = r.nextInt();
怎样产生某个范围之内的int类型随机数?
Random r = new Random();
int i = r.nextInt(101); // 产生[0-100]的随机数。

枚举

枚举是一种引用数据类型。枚举编译之后也是class文件。使用枚举类型,能够限定取值的范围.

enum 枚举类型名{
    枚举值,枚举值2,枚举值3
}

当一个方法执行结果超过两种情况,并且是一枚一枚可以列举出来的时候,建议返回值类型设计为枚举类型。

异常

在程序运行过程中出现的错误,称为异常。Java中异常以类和对象的形式存在。

java中异常的作用是:增强程序健壮性。

异常的层次结构

异常的层次结构

Object
        Object下有Throwable(可抛出的)
        Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
        Exception下有两个分支:
            Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
            RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)

异常的分类

异常主要分为:错误、编译时异常(受控异常、受检异常CheckedException)、运行时异常(非受控异常、非受检异常UnCheckedException)

  • 错误:如果应用程序出现了error,那么将无法恢复,只能重新启动应用程序,最典型的error的异常是OutOfMemoryError。
  • 受控异常:出现了这种异常必须显式的处理,不显式处理Java程序将无法编译通过。
  • 非受控异常:此种异常可以不用显式的处理,例如被0除异常,Java并未要求一定要处理

编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的,编译时异常因为什么而得名?

因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new异常对象。

编译时异常和运行时异常的区别?

编译时异常一般发生的概率比较高。举个例子:你看到外面下雨了,倾盆大雨的。你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。“拿一把伞”就是对“生病异常”发生之前的一种处理方式。

对于一些发生概率较高的异常,需要在运行之前对其进行预处理。

运行时异常一般发生的概率比较低。举个例子:小明走在大街上,可能会被天上的飞机轮子砸到。被飞机轮子砸到也算一种异常。但是这种异常发生概率较低。在出门之前你没必要提前对这种发生概率较低的异常进行预处理。如果你预处理这种异常,你将活的很累。

假设你在出门之前,你把能够发生的异常都预先处理,你这个人会更加
的安全,但是你这个人活的很累。

假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果呢?

首先,如果这样的话,程序肯定是绝对的安全的。但是程序员编写程序太累,代码到处都是处理异常的代码。

Java语言中对异常的处理包括两种方式:

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。

第二种方式:使用try..catch语句进行异常的捕捉。

​ 这件事发生了,谁也不知道,因为我给抓住了。

:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。

UML

UML是一种统一建模语言。一种图标式语言(画图的)。

UML不是只有java中使用。只要是面向对象的编程语言,都有UML。

在UML图中可以描述类和类之间的关系,程序执行的流程,对象的状态等.

集合

集合不能直接存储基本数据类型,另外集合也不能直接存储Java对象,集合当中存储的都是Java对象的内存地址。(或者说集合中存储的是引用)

  • 集合在Java中本身是一个容器,是一个对象
  • 集合中任何时候存储的都是引用

在Java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。

  • 数据存储的结构就是数据结构。
  • 不同的数据结构,数据存储方式不同。

所有的集合类和集合接口都在java.util包下。

在Java中集合分为两大类

  • 单个方式存储元素,这一类集合中超级父接口是java.util.Collection
  • 以键值对的方式存储元素,这一类集合中超级父接口是java.util.Map

Collection

集合结构继承图

List集合存储元素的特点

  • 有序:存进去的顺序和取出的顺序相同,每一个元素都有下标
  • 可重复:存进去 一个1,还可以在存储1

Set集合存储元素的特点

  • 无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标
  • 不可重复:存进去1,就不能在存储1了

SortedSet集合存储元素的特点

  • 无序不可重复
  • 可排序:可以按照大小顺序排列

ArrayList集合初始化容量是10

自平衡二叉树

TreeSet:
TreeSet集合底层实际上是TreeMap。new TreeSet集合的时候,底层实际上new了一个TreeMap集合。
往TreeSet集合中放数据的时候,实际上是将数据放到TreeMap集合中了。
TreeMap集合底层采用了二叉树数据结构。

HashSet:
HashSet集合初始化容量16
初始化容量建议是2的倍数。
扩容:扩容之后是原容量2倍。

Set集合中的元素不能通过下标取元素(没有下标)。可以通过迭代器和``foreach`遍历。

//迭代器遍历
Iterator<E> it = set.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
//foreach遍历
for(E e : set){
    System.out.println(e);
}

Map

Map结构继承图

Map集合的key,就是一个Set集合。在Set集合中放数据,实际上放到了Map集合的key部分。

HashMap:
HashMap集合的key和value允许null
初始化容量16。默认加载因子0.75
扩容之后的容量是原容量的2倍。

Hashtable:
Hashtable集合底层也是哈希表数据结构,是线程安全的,其中所有的方法都带有synchronized关键字,效率较低,现在使用较少了,因为控制线程安全有其它更好的方案。
Hashtable的key和value不允许null
Hashtable集合初始化容量11
Hashtable集合扩容是:原容量*2 + 1

遍历map集合

Map<Integer,String> map = new HashMap<>();
map.put(1,"...");
...
/*
(一)
1、获取所有的key,所有的key是一个Set集合。
*/
Set<Integer> keys = map.keySet();
//2、遍历key,通过key获取value。
Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
    Integer key = it.next();
    String value = map.get(key);
    System.out.println(key + "=" + value);
}
//或者
for(Integer key : keys){
    System.out.ptintln(key + "=" + map.get(key));
}
/*
(二)Set<map.Entry<K,V>> entrySet()
1、把map集合直接全部转换成Set集合。Set集合中的元素的类型是:Map.Entry
*/
Set<Map.Entry<Integer,String>> set = map.entrySet();
//2、遍历Set集合,每一次取出一个Node
Iterator<Map.Entry<Integer,String>> it = keys.iterator();
while(it.hasNext()){
    Map.Entry<Integer,String> node = it.next();
    Integer key = Node.getKey();
    String value = Node.getValue();
    System.out.println(key + "=" + value);
}
//或者(这种效率比较高,因为key和value都是直接从node对象中获取的属性值,适合用于大数据量)
for(Map.Entry<Integer,String> node : set){
    System.out.println(node.getKey() + "--->" + node.getValue());
}

哈希表(散列表)

哈希表是一个数组和链表的结合体。【一维数组,这个数组中每一个元素是一个单向链表。】

在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。提高效率。

哈希表结构

同一个单向链表上的所有节点的hash相同,因为它们的数组下标是一样的。但同一个链表上的key和key的equals方法肯定返回的是false,都不相等。

因为哈希表的增删都是在链表上完成,查询也不需要扫描全部,只需部分扫描,所以哈希表的随即增删以及查询效率都很高。

HashMap【无序(不一定挂在哪个单向链表上)不可重复(equals方法保证HashMap集合的key不可重复)】,如果key重复,则value会被覆盖。

存放在HashMap集合key部分和HashSet集合中的元素需要同时重写hashCode和equals。
​    (1)放在HashMap集合的key部分的元素需要重写equals方法。(equals默认比较的是两个对象的内存地址,而我们应该比较内容)
​    (2)放在HashMap集合的key部分的元素其实就是放在HashSet集合中,所以HashSet集合中的元素也需要同时重写hashCode()和equals()方法。

properties

properties被称为属性类对象。是线程安全的。

泛型

JDK5.0之后推出的新特性。

为什么需要使用泛型?

  • 存储任意类型的数据在集合中 ,但是取出来都是Object类型的,此时就得强转
  • 约束存储到集合中的元素必须是相同的数据类型(相同的数据类型才能做比较,比如TreeSet类)

泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型是没用的)

使用了泛型的好处

  • 集合中存储的元素类型统一了
  • 从集合中去出来的元素类型是泛型指定的类型,不需要进行大量的向下转型

泛型的缺点

  • 导致集合中存储的元素缺乏多样性。

JDK8之后引入了自动类型推断机制(又称为钻石表达式)。

/*
    ArrayList<这里的类型会自动推断>(),JDK8之后才允许
*/
List<E> testList = new ArrayList<>();

IO流

InputStream和OutputStream

字节输入流(InputStream)和字节输出流(OutputStream)

InputStream和OutputStream继承结构图

Reader和Writer

字符输入流(Reader)和字符输出流(Writer)

Reader和Writer继承结构图

文件专属:
    *FileInputStream
    *FileOutputStream
    FileReader
    FileWriter
转换流:(将字节流转换成字符流)
    InputStreamReader
    OutputStreamWriter
缓冲流专属:
    BuffererReader
    BufferedWriter
    BufferedInputStream
    BufferedOutputStream
数据流专属:
    DataInputStream
    DateOutputStream
标准输出流:
    PrintWriter
    *PrintStream
*对象专属流:
    ObjectInputStrean
    ObjectOutputStream

PrintStream

PrintStream标准的字节输出流,默认输出到控制台。

标准输出流不需要手动close()关闭。

/*
日志工具(修改输出方向使用System.setOut())
 */
public class Logger {
    /*
    记录日志的方法。
     */
    public static void log(String msg) {
        try {
            // 指向一个日志文件
            PrintStream out = new PrintStream(new FileOutputStream("log.txt", true));
            // 改变输出方向
            System.setOut(out);
            // 日期当前时间
            Date nowTime = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String strTime = sdf.format(nowTime);

            System.out.println(strTime + ": " + msg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}



public class LogTest {
    public static void main(String[] args) {
        //测试工具类
        Logger.log("用户尝试进行登录,验证失败");
        Logger.log("用户尝试修改密码,修改失败");
    }
}

节点流与包装流

当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做节点流

外部负责包装的这个流叫做包装流(处理流)

FileReader reader = new FileReader("FileName");
BufferedReader br = new BufferedReader(reader);
/*
    FileReader就是一个节点流,BufferedReader就是一个包装流/处理流。

    关闭流:
        对于包装流来说,只要关闭最外层的流就行,里面的节点流会自动关闭。
*/

序列化与反序列化

序列化(Serialize):把Java对象转换为字节序列的过程(Java对象存储到文件中。将Java对象的状态保存下来的过程)

反序列化(DeSerialize):把字节序列恢复为Java对象的过程(将硬盘上的数据重新恢复到内存中,恢复成Java对象)

//参与序列化的类型必须实现java.io.Serializable接口。

private static final long serialVersionUID = 1L; //建议将序列化版本号手动的写出来。

IO+Properties联合应用

Properties是一种Map集合,key和value都是String类型。

/*
    将.properies文件中的数据加载到Properties对象中
*/
FileReader reader = new FileReader("fileName"); //创建输入流对象
Properties pro = new Properties();  //创建Map集合
//调用Properties对象的load方法将文件中的数据加载到Map集合中。
pro.load(reader); //文件中的数据顺着管道加载到Map集合中,其中key=value
//通过key获取value 
String value = pro.getProperty("key");
System.out.println(value);

文件拷贝

使用FileInputStream和FtongileOutputStream完成文件的拷贝。

/*
拷贝的过程,一边读一边写
 */
public class FileCopy {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("源文件路径");
            fos = new FileOutputStream("目标路径");

            //边读边写
            byte[] bytes = new byte[1024*1024];
            int readCount = 0;
            while((readCount = fis.read(bytes)) != -1){
                fos.write(bytes,0,readCount);
            }

            //刷新,输出流最后要刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //分开try,一起try的时候,其中一个出现异常,可能会影响另外的一个
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

目录拷贝

/*
    拷贝目录
*/
public class CopyDir{
    public static void main(String[] args){
        //源目录
        File srcFile = new File("源目录路径");
        //获取源目录所在路径的起始下标
        int index = srcFile.getAbsolutePath();
        //目标目录
        File destFile = new File("目标目录路径");
        //调用方法进行拷贝
        copyDir(srcFile,destFile,index);
    }

    public static void copyDir(File srcFile,File destFile,int index){
        /*
            如果是一个文件的话,递归结束,并且边读边写(拷贝文件)
        */
        if(srcFile.isFile()){
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try{
                fis = new FileInputStream(srcFile);
                String filePath = (destFile.getAbsolutePath().endwith("\\")?
                                   destFile.getAbsolutePath():destFile.getAbsolutePath() + "\\") 
                                   + srcFile.getAbsolutePath().substring(index);
                fos = new FileOutputStream(filePath);
                //边读边写
                byte[] bytes = new byte[1024*1024]; //一次读1M
                int readCount = 0;
                while((readCount =  fis.read(bytes)) != -1){
                    fos.weite(bytes,0,readCount);
                }
                //刷新
                fos.flush();
            }catch(FileNotFoundException e){
                e.printStackTrace();
            }catch(IOException e){
                e.ptintStackTrace();
            }finally{
                if(fos != null){
                    try{
                        fos.close();
                    }catch(IOException e){
                        e.ptintStackTrace();
                    }
                }
                if(fis != null){
                    try{
                        fis.close();
                    }catch(IOException e){
                        e.ptintStackTrace();
                    }
                }
            }
            //递归结束
            return;
        }
        //获取源目录下的子目录
        File[] files = srcFile.listFiles();
        for(File file : files){
            if(file.isDirectory){
                String destDir = (destFile.getAbsolutePath().endwith("\\") ? 
                                  destFile.getAbsolutePth() : destFile.getAbsolutePath() + "\\") 
                                     + file.getAbsolutePath().substring(index);
                File newFile = new File();
                if(!newFile.exists()){
                    newFile.mkdirs();
                }
            }
            //递归调用
            copyDir(file,destFile,index);
        }
    }
}

多线程

什么是进程?线程?

进程是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。

线程是指进程中的一个执行场景,也就是执行流程。

一个进程可以启动多个线程。

进程与线程的区别

每个进程是一个应用程序,都有独立的内存空间。

同一个进程中的线程共享其进程中的内存和资。

共享的内存是堆内存方法区内存,栈内存不共享。

栈内存独立,一个线程一个栈

一个线程一个栈

线程的创建与启动

Java虚拟机的主线程入口时main方法。

创建方式有两种:

继承Thread类(java.lang.Thread)

*实现Runnable接口(java.lang.Runnable)

//方法体中的代码都是按自上而下的顺序依次逐行执行的。
public class ThreadTest{
    public static void main(String[] args){
        //main方法,这里的代码属于主线程

        //创建分支线程对象
        MyThread myThread = new MyThread();
        //启动线程
        //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
        myThread.start(); //只是为了开启新的栈空间,只要栈空间开出来,start()方法就结束了,线程就启动了。
        /*
            myThread.run();  //不会启动线程,不会分配新的分支栈。(单线程)普通方法调用
        */

        //以下代码运行在主线程中
        for(int i = 0; i < 1000; i++){
            System.out.println("这是主线程--->" + i + "<---");
        }
    }

}

class MyThread extends Thread{
    //重写run()方法
    @Override
    public void run(){
        //以下代码运行在分支线程中
        for(int i = 0; i < 1000; i++){
            System.out.println("这是分支线程--->" + i + "<---");
        }
    }
}
public class ThreadTest{
    public static void main(String[] args){
        Thread t = new Thread(new MyRunnable());
        //启动线程
        t.start();

        //以下代码运行在主线程中
        for(int i = 0; i < 1000; i++){
            System.out.println("这是主线程--->" + i + "<---");
        }
    }

}
//这并不是一个线程类,是一个可运行的类。还不是一个线程。
class MyRunnable implements Runnable{
    //重写run()方法
    @Override
    public void run(){
        //以下代码运行在分支线程中
        for(int i = 0; i < 1000; i++){
            System.out.println("这是分支线程--->" + i + "<---");
     }
}
/*
    匿名内部类
    Thread t = new Thread(new Runnable(){
        //重写run()方法
        @Override
        public void run(){
            //以下代码运行在分支线程中
            for(int i = 0; i < 1000; i++){
                System.out.println("这是分支线程--->" + i + "<---");
            }
    });
    //启动线程
    t.start();

    //以下代码运行在主线程中
    for(int i = 0; i < 1000; i++){
        System.out.println("这是主线程--->" + i + "<---");
    }
*/

线程生命周期

线程生命周期.

新建:采用 new语句创建完成

就绪:执行 start 后

运行:占用 CPU 时间

阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合

终止:退出 run()

线程的调度

有两种调度模型:分时调度模型和抢占式调度模型,Java 使用抢占式调度模型。

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片(均分式调度)

抢占式调度模型:优先级高的线程获取 CPU 的时间片相对多一些(抢到的CPU时间片的概率就高一些),如果线程的优先级相同,那么会随机选择

线 程 优 先 级 主 要 分 三 种 :

​ MAX_PRIORITY( 最高级10 )

​ MIN_PRIORITY (最低级1)
​ NOM_PRIORITY (标准)默认5

//线程的sleep方法
Thread.sleep(1000*5); //睡眠5秒
//终止线程的睡眠
Thread.interruput();
//线程让位
Thread.yield();//它与 sleep()类似,只是不能由用户指定暂停多长时间,并且 yield()方法只能让同优先级的线程有执行的机会
/*
线程合并
    当前线程可以调用另一个线程的 join 方法,调用后当前线程会被阻塞不再执行,直到被调用的
线程执行完毕,当前线程才会执行
*/
t.join();  //t合并到当前线程,当前线程受阻塞,t线程执行直到结束

//如何正确的停止一个线程,通常定义一个标记,来判断标记的状态停止线程的执行

线程同步

线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁,如
果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问
题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后,再让
线程二来使用 s 变量。

数据在多线程并发的环境下会存在安全问题:

​ 多线程并发。
​ 有共享数据。
​ 共享数据有修改的行为。

解决线程安全问题:使用“线程同步机制”。

线程排队执行(不能并发)。用排队执行解决线程安全问题。这种机制被称为线程同步机制

线程同步就是线程排队了,线程排队了就会牺牲一部分效率。

异步编程模型:
            线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,
            谁也不需要等谁,这种编程模型叫做:异步编程模型。
            其实就是:多线程并发(效率较高。)

            异步就是并发。

同步编程模型:
            线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行
            结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
            两个线程之间发生了等待关系,这就是同步编程模型。
            效率较低。线程排队执行。

            同步就是排队。
/*
    线程同步同步机制语法:
        synchronized(共享对象){
            //线程同步代码块
        }
        synchornized后面小括号中的这个“数据”必须是多线程共享的数据,才能达到多线程排队。
*/
synchronized有三种写法:

        第一种:同步代码块
            灵活
            synchronized(线程共享对象){
                同步代码块;
            }

        第二种:在实例方法上使用synchronized
            表示共享对象一定是this
            并且同步代码块是整个方法体。

        第三种:在静态方法上使用synchronized
            表示找类锁。
            类锁永远只有1把。
            就算创建了100个对象,那类锁也只有一把。

        对象锁:1个对象1把锁,100个对象100把锁。
        类锁:100个对象,也可能只是1把类锁。
哪些变量存在线程安全问题?
    实例变量:在堆中。
    静态变量:在方法区。
    局部变量:在栈中。
以上三大变量中:
    局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。

    实例变量在堆中,堆只有1个。
    静态变量在方法区中,方法区只有1个。
    堆和方法区都是多线程共享的,所以可能存在线程安全问题。

    局部变量+常量:不会有线程安全问题。
    成员变量:可能会有线程安全问题。

用户线程和守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

守护线程:所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程
存在,那么守护线程就不会结束。

例如 java 中著名的垃圾回收器就是一个守护线程,只有应
用程序中所有的线程结束,它才会结束。(主线程main方法是一个用户线程。

守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

定时器

定时器的作用:间隔特定的时间,执行特定的程序。

在java的类库中已经写好了一个定时器:java.util.Timer

/*
    使用定时器指定定时任务
*/
public class TimerTest{
    public static void main(String[] args){
        Timer timer = new Timer(); //创建定时器对象
        //Timer timer = new Timer(true); //守护线程的方式
        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        SimplieDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        Date firstTime = sdf.parse(20200507193050);
        timer.schedule(new DataBackup(),firstTime,1000*60);
    }
}

class DataBackup exends TimerTask{
    public void run(){
        SimplieDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        String nowTime = sdf.format(new Date());
        System.out.println(nowTime + ":成功备份一次数据!");
    }
}

实现线程的第三种方式

实现Callable接口。(JDK8新特性。)这种方式实现的线程可以获取线程的返回值。

​ 继承Thread类(java.lang.Thread)和实现Runnable接口(java.lang.Runnable)无法获取线程返回值的,因为run方法返回void。

/*
    实现Callable接口
        FutureTask:java.util.concurrent.FutureTask;
    可以获取到线程的执行结果,但是效率较低。
*/
public class CallableTest{
    public static void main(String[] args){
        //创建一个“未来任务类”对象,参数需要给一个Callable接口实现类对象
        FutureTask task  = new FutureTask(new Callable(){
            @Override
            public Object call() throws Exception {
                System.out.println("sum方法开始执行!");
                Thread.sleep(1000*10);
                System.out.println("sum方法结束!");
                int a = 2020;
                int b = 2021;
                return a+b;
            }
        });
        //创建线程对象
        Thread t = new Thread(task);
        //启动线程
        t.start();
        //获取t线程的返回结果,get()方法的执行会导致当前线程阻塞
        Object obj = null;
        try {
            obj = task.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(t.getName()+"执行结果:"+obj);

        System.out.println(Thread.currentThread());
    }
}

关于Object类中的wait和notify方法

(生产者和消费者模式)

wait和notify方法不是线程对象的方法,是Java中任何一个Java对象都有的方法,因为这两个方式是Object类中自带的。wait方法和notify方法不是通过线程对象调用。

wait()方法:
    Object o = new Object();
    o.wait();

    表示:
        让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
        o.wait()方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。

notify()方法:
    Object o = new Object();
    o.notify();

    表示:
        唤醒正在o对象上等待的线程。
    还有一个notifyAll()方法:
        这个方法是唤醒o对象上处于等待的所有线程。

生产者和消费者模式

反射机制

通过java语言中的反射机制可以操作字节码文件(class文件)。(可以读和修改字节码文件,让程序更加灵活。)

java.lang.Class:代表整个字节码,代表一个类型,代表整个类

java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法

java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法

java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)

类加载器

类加载器(ClassLoader):专门负责加载类的命令/工具。

JDK中自带3个类加载器:

​ (父)启动类加载器:rt.jar

​ (母)扩展类加载器:ext/*.jar

​ 应用类加载器:classpath

代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以下代码类加载器会找String.class
件,找到就加载,那么是怎么进行加载的呢?
String s = "abc";

1、首先通过“启动类加载器”加载。
    注意:启动类加载器专门加载:...\jdk1.8.0_101\jre\lib\rt.jar
    rt.jar中都是JDK最核心的类库。

2、如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。
    注意:扩展类加载器专门加载:...\jdk1.8.0_101\jre\lib\ext\*.jar

3、如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。
    注意:应用类加载器专门加载:classpath中的类。

java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父”。“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。

​ 双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

获取Class

获取Class的三种方式

    第一种:多用于配置文件,将类名定义在配置文件中。读取文件,加载类     
        Class c = Class.forName("完整类名");

    第二种:多用于参数的传递
        Class c = 对象.getClass();

    第三种:多用于对象的获取字节码的方式
        Class c = int.class;
        Class c = String.class;
        ...
        Class c = 任何类型.class;
//package com.lskj.java.reflect;
public class Test{
    public Test(){
        System.out.println("构造方法被调用了...");
    }
    public Test(String s){
        System.out.println(s);
    }
}

/*
    通过反射实例化对象
*/
public class TestReflect01{
    public static void main(String[] args){
        /*
            通过Class的newInstance()方法来实例化对象。
                newInstance()方法内部实际上调用了无参数构造方法,所以保证无参构造的存在。
        */
        try{
            Class c = Class.forName("com.lskj.java.reflect.Test"); 
            Object obj = c.newInstance();

            System.out.println(obj);
        }catch(ClassNotFoundException e){
            e.ptintStackTrace();
        }catch(IllegalAccessException e){
            e.ptintStackTrace();
        }catch(InstantiationException e){
            e.ptintStackTrace();
        }
    }
}

/*
    通过读属性文件实例化对象
*/
public class TestReflect02{
    public static void main(String[] args) throws Exception{
        //通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("classinfo.properties");
        //创建属性类对象
        Properties pro = new Properties();
        //加载
        pro.load(reader);
        //关闭流
        reader.close();
        //通过key获取value
        String className = pro.getProperties("className");
        //通过反射机制实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);

    }
}
/*
假设配置文件在当前项目下
    classinfo.properties
        className=com.lskj.java.reflect.Test
*/

静态代码块在类加载时执行,并且只执行一次。

如果只是希望一个类的静态代码块执行,其他代码不执行,可以使用:

​ Class.forName(“完整类名”);

这个方法的执行会导致类加载,类加载时,静态代码块执行。

获取文件绝对路径

/*
    获取一个文件的绝对路径。(通用方式,不会受到环境移植的影响。)
    但是该文件要求放在类路径下。
*/
String path = Thread.currentThread().getContextClassLoader().getResource("文件相对路径").getPath();

资源绑定器

要求:

​ 文件必须在类路径下;

​ 文件以.properties结尾

//后缀名(扩展名)不能写
ResourceBundle bundle = ResourceBundle.getBundle("FileName");
String value = bundle.getString("key");

通过反射机制访问对象属性

//package com.lskj.java.reflect;

public class TestReflect{
    public static void main(String[] args) throws Exception{
        Class testClass = Class.forName("com.lskj.java.reflect.Test");
        Object obj = testClass.newInstance();
        //获取id属性
        Field idField = testClass.getDeclaredField("id");
        //给obj对象(Test对象)的属性id赋值
        idField.set(obj,2020);  //给obj对象的id属性赋值2020
        //读取属性的值
        System.out.println(idField.get(obj));
        //访问私有的属性
        Field nameField = testClass.getDeclaredField("name");
        //打破封装(反射机制的缺点)
        nameField.setAccessible(true);
        //给name属性赋值
        nameField.set(obj,"java");
        //获取name属性的值
        System.out.println(nameField.get(obj));
    }
}

public class Test{
    private String name;
    protected int age;
    boolean sex;
    public id;
    public static final double MATH_PI = 3.1415926;
}
  • getFields()获取所有public修饰的成员变量
  • getDeclaredFields()获取所有的成员变量,不考虑修饰符
  • getField(String name)获取指定的public修饰的成员变量
  • getDeclareField(String name)获取指定的成员变量

访问存在权限修饰符修饰的属性(方法)时(比如私有属性),需使用setAccessible(true)关闭安全检查,也就是所谓的暴力反射。

通过反射机制调用方法

    int...args    这就是可变长度
    语法:类型...
    1、可变长度参数要求的参数个数是0-N个;
    2、可变长度参数在参数列表中只能有一个并且必须在最后的位置。
//package com.lskj.java.reflect;

public class TestReflect{
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("com.lskj.java.reflect.Test");
        Object obj = c.newInstance();

        //获取Method
        Method test = c.getDeclearedMethod("test",String.class);
        //调用方法
        test.invoke(obj,"测试方法!");
    }
}

public class Test{
    public void test(String s){
        System.out.println("test()方法--->"+s);
    }
}

构造方法Constructor的反射

newInstance(Object… initargs)使用此Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。

网络编程

网络编程的目的:

​ 直接或间接地通过网络协议与其他计算机实现数据交换,进行通讯。

网络通信的要素

IP和端口号:

IP地址(InetAddress):唯一标识Internet上的计算机(通信实体)。

//获取本机IP
InetAddress inet = InetAddress.getLocalHost();
InetAddress inet2 = InetAddress.getHostName(); //获取本地域名

端口号:

​ 标识正在计算机上运行的进程(程序)。

不同的进程有不同的端口号

被规定为一个16位的整数0~65535

端口分类:
    公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,HTTPS占用端口443,FTP占用端口21,Telenet占用端口23)
    注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,Mysql占用端口3306,Oracle占用端口1521)
    动态/私有端口:49152~65535。

端口号与IP地址的组合得出一个网络套接字:Socket。

网络通信协议

计算机网络中实现通信必须有一些约定,即通信协议,对速率、代码传输、代码结构、传输控制步骤、出错控制等指定标准。

网络通信协议模型

        TCP:传输控制协议(Transmission Control Protocol)。
        UDP:用户数据报协议(User Datagram Protocol)。
        TCP/IP以其两个主要协议:传输控制协议(TCP)和网络互连协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
        IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。

TCP与UDP的区别

1)发送端首先发送一个带有SYN(synchronize)标志地数据包给接收方。
2)接收方接收后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了。
3)最后,发送方再回传一个带有ACK标志的数据包,代表我知道了,表示’握手‘结束。

1)Client:喂,听得到吗?

2)Server:听得到,你能听到我的吗?

3)Client:听到了,我们可以聊天了。

1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

1)Client:我要说的都说完了

2)Server:我已经全部听到了,但是等等我,我还没说完

3)Server:好了,我已经说完了

4)Client:好的,那我们的通信结束

SYN(synchronous建立联机)
ACK(acknowledgement 确认)
FIN(finish结束)

TCP编程

服务器:
​ 1、建立服务的端口 ServerSocket
​ 2、等待用户连接 accept
​ 3、接受用户消息

客户端:
​ 1、连接服务器 Socket
​ 2、发送消息

TCP实现聊天
public class TcpServer {
    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try{
            //有地址
            ss = new ServerSocket(9000);
            //等待客户端连接
            socket = ss.accept();
            //读取客户端的消息
            is = socket.getInputStream();
            /*
                byte[] buffer = new byte[1024];
                int len;
                while((len = is.read(buffer)) != -1){
                    String msg = new String(buffer,0,len);
                    System.out.ptintln(msg);
                }
             */
            //管道流
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[5];
            int len;
            while((len = is.read(buffer)) != -1){
                baos.write(buffer,0,len);
            }
            System.out.println(baos.toString());
            System.out.println("收到了来自于" + socket.getInetAddress().getHostAddress());
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(baos != null){
                try{
                    baos.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(is != null){
                try{
                    is.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try{
                    socket.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(ss != null){
                try{
                    ss.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}
public class TcpClient {
    public static void main(String[] args) {
        Socket socket = null;
        OutputStream os = null;
        try{
            //1、知道服务器的IP和端口
            InetAddress inet = InetAddress.getByName("127.0.0.1");
            //2、创建socket连接
            socket = new Socket(inet,9000);
            //3、发送消息 IO流
            os = socket.getOutputStream();
            os.write("你好,我是client!".getBytes());
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(os != null){
                try{
                    os.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try{
                    socket.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

UDP编程

类DatagramSocket和DatagramPacket实现了基于UDP协议网络程序。

UDP数据报通过数据报套接字DatagramSocket发送和接受,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。

DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接受端的IP地址和端口号。

UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。

//等待客户端的连接
public class UdpServer {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        try {
            //开放端口
            socket = new DatagramSocket(9000);
            //接收数据包
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

            socket.receive(packet); //阻塞接收

            System.out.println(packet.getAddress().getHostAddress());
            System.out.println(new String(packet.getData(),0,packet.getLength()));
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(socket != null){
                socket.close();
            }
        }
    }
}
//不需要连接服务器
public class UdpClient {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        try {
            //1、建立一个Socket
            socket = new DatagramSocket();
            //2、建包
            String msg = "你好呀!客户端。";
            InetAddress localhost = InetAddress.getByName("localhost");
            int port = 9000;
            //数据。数据的长度起始,要发给谁
            DatagramPacket packet = new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,localhost,port);
            //3、发送包
            socket.send(packet);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(socket != null){
                socket.close();
            }
        }

    }
}

URL编程

URL(Uniform Resource Locator)统一资源定位符,它表示Internet上某一资源的地址。

    传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
    参数列表格式:参数名=参数值&参数名=参数值......
public class URLTest {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://localhost:8080/example/test.txt?id=2020");

            System.out.println(url.getProtocol()); //协议
            System.out.println(url.getHost()); //主机IP
            System.out.println(url.getPort()); //端口
            System.out.println(url.getPath()); //文件路径
            System.out.println(url.getFile()); //文件名
            System.out.println(url.getQuery()); //参数
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}
/*
    下载网易云音乐中的音乐--->送给未来的你
*/
public class URLDown {
    public static void main(String[] args) throws Exception {
        //1、下载地址
        URL url = new URL("https://m10.music.126.net/20200509190336/27e12542a5f662f96dc2156811cb5687/yyaac/5158/540b/0e09/f2600c071aca2a507f0f9c9219348a3a.m4a");
        //2、连接到这个资源 HTTP
        HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();

        InputStream inputStream = urlConnection.getInputStream();
        FileOutputStream fos = new FileOutputStream("送给未来的你.mp3");
        byte[] buffer = new byte[1024];
        int len;
        while((len=inputStream.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
        fos.close();
        inputStream.close();
        urlConnection.disconnect();
    }
}

注解(Annotation)

注解,也叫元数据。一种代码级别的说明。它是JDK1.5以后版本一如的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的千米那,用来对这些元素进行说明,注释。

注解,或者叫做注释类型,是一种引用数据类型。编译之后也是生成xxx.class文件。

注解的定义:
        [修饰符列表] @interface 注解类型名{

         }
1、注解使用时的语法格式是:
            @注解类型名
2、注解可以出现在类上、属性上、方法上、变量上等....
        注解还可以出现在注解类型上。
1、Deprecated 用 @Deprecated 注释的程序元素,不建议使用这样的元素,通常是因为它很危险或存在更好的选择(@Deprecated该注解标注的内容,表示已过时)。 
2、Override 表示一个方法声明打算重写超类中的另一个方法声明。 
3、SuppressWarnings 指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。 一般传递参数all,@SuppressWarnings("all")

假设有这样一个注解,叫做:@Id
这个注解只能出现在类上面,当这个类上有这个注解的时候,要求这个类中必须有一个int类型的id属性。如果没有这个属性s就报异常。如果有这个属性则正常执行!

注解本质上就是一个接口,该接口默认继承Annotation接口

元注解

用来标注“注解类型”的“注解”,称为元注解

常见的元注解:
@Target:描述注解能够作用的位置
@Retention:描述注解被保留的阶段
@Documented:描述注解是否被抽取到api文档中
@Inherited:描述注解是否被子类继承

Target注解

Target注解是一个元注解,用来标注“注解类型”的“注解”

Target注解用来标注“被标注的注解”可以出现在:
    @Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
        表示该注解可以出现在:
            构造方法上
            字段上
            局部变量上
            方法上
            ....
            类上...    

Retention注解

Retention注解是一个元注解,用来标注“注解类型”的“注解”

这个Retention注解用来标注“被标注的注解”最终保存:
    @Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
    @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
    @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。

作用分类

编写文档

通过代码里标识的注解生成文档(生成Javadoc文档)。

/**
 * Test类
 * @author test
 * @version 0.1
 * @since 1.5
*/
public class Test {
    /**
     * 计算两个数的和
     * @param x 整数
     * @param y 整数
     * @return 两个整数之和
    */
    public int testAdd(int x,int y){
        return x+y;
    }
}

使用以下命令进行doc文档的生成:

javadoc Test.java

#防止中文乱码
javadoc -encoding UTF-8 -charset UTF-8 Test.java

代码分析

通过代码里标识的注解对代码进行分析(使用反射)。

编译检查

通过代码里标识的注解让编译器能够实现基本的编译检查(例如override)。


  目录