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集合的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)
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(2020年05月07日 19时30分50秒);
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)协议是网络层的主要协议,支持网间互连的数据通信。
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、发送消息
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)。