java学习笔记总结,持续更新中

java学习的笔记,目前正在学习中,持续更新。

标识符是直接copy下来的,命名一定要规范。

标识符规范:

1>标识符由大小写字母, 下划线, 数字, $符号组成.

2>开头可以是大小写字母, 下划线, 和$符号.(数字不能开头)

3>标识符长度没有限制

4>标识符不能是关键子和保留字

标识符的命名最好能反映出其作用 ,java语言对字母的大小写有严格的要求.所有自定义标识符需全部遵循标识符的命名规范.

变量: 

 1>如果是单个单词, 单词全部字母小写. 如:intcount; 

 2>如果是由多个单词组成的复合单词, 除第一个单词外,  其后所有单词首字母大写. 如: codeName;

 常量 :

 常量所有单词字母大写, 如果是由多个单词组成, 由下划线连接.如:String PERSON_NAME;

方法:

方法命名规范与变量相似, 如 count(); getSum();

类: 

 类名的所有单词首字母均大写. 如Person{}  , DataCenter{}; 

包:  

小写的倒置域名来命名. 格式: 前缀 + 项目名 + 模块名 + 层如: org.itfuture.domain.sort 

2>开头可以是大小写字母, 下划线, 和$符号.(数字不能开头) 

3>标识符长度没有限制 

4>标识符不能是关键子和保留字

1.成员变量是直接定义在类中的

2.局部变量具有“就近原则”
    例:static int aa=15;
            int aa=17;
    System.out.println(aa);

    // string aa; //在main方法中已经定义了aa    然后有从新定义了aa       就近原则  所以是  aa=17

3.java表达式:

                表达式:由数字,运算符,数字分组符号(括号),变量等已能够求得结果的有意义排列的组合
        直接使用运算符链接的变量/常量可以称为表达式
例如:
// a;
// a+b;
// 3.14+a;
// (x+y)

变量是有类型的,java中的数据类型有多少类型
* 分两种:
* 1)基本数据类型-》原生数据类型
* 2)引用数据类型-》对象数据类型
* --------------------
* 数据类型: 占位字节   数据分为   默认值
* 整数:byte     1 [-128,127]  0

*             int             4                                           0   

                                short            2                                 

                                 long     8                                           0L

* 小数:float                4                                          0.0F

                            double             8                                           0.0D

应用类型:
* 类-》String
* 接口->implements Runable

* 数组-》array

基本数据类型,仅仅是容量的大小不一样而已-》true

强制转化:

                byte b1=126;

byte b2=(byte)(b1+1);

//byte的包装类
byte bMax=Byte.MAX_VALUE;
System.out.println(bMax);//127
byte bMin=Byte.MIN_VALUE;
System.out.println(bMin);//-128
byte b3=(byte)(bMax+1);//127+1  超出范围[-128   127]  所以是-128

System.out.println(b3);//-128

在8大基本数据类型中,boolean不属于数据类型,不参与转换》true
*
基本数据类型
* 方式1:自动类型转换-》隐式类型转换
* 方式2:强制类型转换-》显示类型转换
* 遵循规则
* 1:小数据转大数据,系统可以完成自动转换
* 2:大数据转小数据,需要加强制转换符

* 溢出和精度损失:
* 例如:3公斤的苹果,放到一个1公斤容器里,动用保留   会造成缺失

赋值需要注意:

byte abc=2; //1
double bca=abc; //8
//int ddd=bca; //4
//小往大赋值 可以

//大往小赋值    不可以

//自动类型转换

byte b=17;
short s=b;
int i=s;
float f=i;
double d=f;

 

//byte b2=d; //错误:不兼容的类型,从double->byte,可能出现精度损失

数据过大和溢出
//当要表示的数据超出数据类型的临界范围时,称为溢出
//溢出清空发生时程序并没有做出数据范围检查处理,此时会出现    数据絮乱情况

//int 类型的取值范围【-2147483648,2147483647】

表达式的自动提升

/*

* 当一个算数表达式中包含多个基本数据类型(boolean)除外的值时,整个算数表达式类型将在数据类型运算出现数据自动提升    其规则是:
* 所有byte,short,char类型被自动提升到int类型
* 整个表达式的最终结果被提升到表达式中类型最高的类型

*/

++和--:

/*
* 自增++
* 自减--
*    ++在前 先++
*    ++在后 后++

*/

按位运算:

/*

* 位运算符
* & 按位与   同时为1则为1
* | 按位或   有1则为2
* ^ 异或
* ~ 去反
* <<左位移
* >>右位移
* >>>无符号右位移


*/

/*
* 二进制A  二进制B   与 或 异或
* 0 0 0 0 0
* 1 0 0 1 1
* 0 1 0 1 1
* 1 1 1 1 0

*/

/*
* a:0b00000101
* b:0b00000011 &
* ------------------
* 0b00000001

* 参与运算的两个数,若相应位数的值都为1,则该结果值是1,  否则都是为0

*/

运算顺序:

/*

* () [] 从左到右
* ! + - ~ ++ -- 从右向左 
* ? : 从右向左
* + - 从左向右

*/

注意:不推荐连续赋值,耦合度太高

例如:int a, b,c;

a=b=c=5;

三元运算:也叫三目运算符   :

                //常用的三元
int x1=30;
int y1=50;
int max=x1>=y1?x1:y1;  // 这个是算法

例://判断一个数是奇数还是偶数
int n=1;//0以下不做判断
String ret=n%2==1?"奇数":"偶数";

System.out.println(ret);

笔试题:

1.//forfor嵌套循环  九九乘法表

for(int line =1;line<=9;line++){
for(int i=1;i<=line;i++){
System.out.print(i+"*"+line+"="+i*line+"\t");
}
System.out.println();
}

2.//案例描述:请对以下代码进行优化:
//循环体代码执行的次数都是相同的-》没法优化
//优化只能从循环变量,i,j,k的实例、比较
//自增次数等方面的耗时上进行分析
for(int i=0;i<1000;i++){
for(int j=0;j<100;j++){
for(int k=0;k<10;k++){
//循环体代码
}
}

}

解:最优方案
* 该方案主要是将该方案循环数少的放在外面循环数多的放在里面,这样可以最大程度的减少相关循环遍历的实例化次数,初始化次数
* 将循环遍历的实例化放在循环外,这样可以进一步减少实例化次数

* 耗时减少

private static void testB() {
// TODO Auto-generated method stub
int i,j,k;

long start=System.nanoTime();
for( i=0;i<10;i++)
for( j=0;j<100;j++)
for( k=0;k<1000;k++)
;
System.out.println("TestB time>>"+(System.nanoTime()-start));
 

}

标签:

/* 
* 使用标签:
* 标签用来给某个循环起的别名:
* 有一个标准:
* 标签的命名得满足标识符的规范。

*/

方法/方法引入:

函数有四种     有参  无参   有反   无反

main方法专门由JVM来负责调用,我们只管启动JVM

/*

* 循环操作:
* while,do while ,for
* 目的-》解决代码重复的问题,重复的做某一件事情
* 循环解决的重复->要求是有规律的,语句格式是相同的
* 针对于某一种功能的重复操作,循环解决不了,此时使用方法。


* 开发遵循的原则之一:DRY原则
* Do not repeat yoursele, 不要重复你 自己的代码
* 原因:重复意味着维护成本的增大


* 语法结构:
* 方法的定义:
* 方法-》和c里面的函数一样-》属于特定功能的代码

* 定义格式:
* 【修饰符】 返回值类型  方法名称([形式参数....]){
* 方法体:
* 【return 值】;

* }

* 注意:
* 1)在类中定义,在java中最小的程序单元是类
* 2)方法定义在其他方法之外,方法和方法是兄弟关系
* 3)方法定义的先后顺序不影响


*/

方法的重载设计:

/*

* 方法重载设计:overload
* 定义:
* 在同一个类中,某方法允许存在一个以上同名方法
* 只要他们的参数列表不同即可。

* 如何判断方法重载设计
* 规则:两同一不同
* 两同:同一个类中。方法名相同
* 不同:方法参数不同(参数类型,个数,顺序)
* 注意点:方法重载和方法返回值类型无关,只是一般要求返回值类型一致
* 参数列表和参数的名称没关系,方法的重载和形参没关系


*/

/*
* 判断以下方法,在同一个类中,且方法名相同
* void doWork(int a,char b,boolean c){}
* 三个参数,分别是int,char boolean类型
* 下列哪个方法是上述方法的重载

*1)void doWork(char b,int a,boolean c){};//是
*2)int doWork(boolean a ,char c,int b){};//是
*3)void doWork(int a,char b,double c){};//是
*4)void doWork(int x.char b,boolean z){};//不是
*5)int doWork(int x,double y){};//是
*6)int doWork(int x,char y,boolean){};//不是

*/

方法中的术语:

        /*


* 方法中的术语:
* 修饰符:public  static等,static修饰的方法属于类
* 直接使用类名调用即可
* 返回值类型:方法就是为了完成一个功能,是否需要给调用者
* 返回一个结果。不需要使用关键字void,无返回的意思

* 方法的名称:遵循标识符规范,使用动词表示,要用 小 驼峰命名法 比如 getSumName 
*  

* 形式参数:方法原括号中,就是形参   只是占位符。

* 参数列表:参数列表==参数的类型+参数的个数+参数的顺序=》true

* 方法签名:==方法的名称+方法参数列表。
* 在同一个类中,方法签名是唯一的,否则编译报错-》true
* *方法名称  不是唯一的

* 方法体:【】中的代码,表示具体完成该功能的代码

* 实际参数:调用者实际传递参数

* 方法调用者:在哪里调用某一个方法,那么哪里就是该方法的调用者



*/

方法的特点-执行流程

数组和数组的定义:

为什么使用数组?
* 问题1:
* 声明变量的时候 ,要处理一组相同类型的数据,
* 比如:班上有100个人的年龄   int age1=17

* 只能表示一个年龄    表示很多人的年龄   需要用到数组

问题2:
* 求两个数的和,需要一个方法,求五个数的和,需要用到重载,
* 求100,1000,10000数的和?
* 方法参数列表特别的长,很不爽。
* 只是一个数组值得和--》数组


* 大师的建议:
方法的形参不要超过五个。

* 数组:就是一组数据
* 把相同类型的若干个变量按照有序的形式,组织起来的一种数据形式

* 数组的索引从0开始,步长为1

静态初始化数组

int arr[]=new int[]{3,4,5};//表示arr[]的一维数组

                int[] arr=new int[]{3,4,5,2,4,1,3,4,1,31};//尽量用这种数组

String[] bs=null;  //String引用数据类型

System.out.println(bs.length);//NullPointerException空指针异常

int[] arr2={};//int 基本数据类型
System.out.println(arr2.length);//0

//用null的时候  在内存中进行了释放

/*

* 数组操作:
* 初始化数组的两种操作方法:
* 1)静态初始化
* 2)动态初始化
* 一旦初始化完成,数组的长度就固定了,不能改变

* -----------***除非重新初始化,也就是说数组时定长的***-------------

* --------------------------
* 数组必须先初始化,才能使用,英文提出时候表示在内存中分配内存
* --------------------------
* 数组静态初始化的特点:
* 数组的长度由系统(JVM)决定,但是由我们来为每一个数组元素设置初始化
* ---------------------------
* 实例化一个数组分几步??
* 三步:
* 第一,声明什么类型的数组
* int[] nums
* 第二:初始化数组 (用到了new)
* new int[]{1,2,3}
* 第三,把初始化的值赋值给nums变量

* 注意:
* 1.不准动静结合创建数组
* 2.当知道需要存储那些数据的时候就使用静态
* 3.当不知道的时候,就用动态

* 语法结构:
* 数组元素类型[] 数组名=new 数组的元素类型[length];
* 数组元素类型[] 数组名=new 数组的元素类型[]{元素1......};




*/

//int[] arrays=new int[5]{};//动静结合是错的   规定!!!

//获取数组中元素最小/大元素  冒泡排序法
static int getMin(int[] nums){
int min=nums[0];
for (int index = 0; index < nums.length; index++) {
if (nums[index]<min) {
min=nums[index];
}
}
return min;

}

//递归操作的九九乘法表
public static void jiujiu(int i){
if (i==1) {
System.out.println("1*1=1");

}else{
jiujiu(i-1);
for (int j = 1; j <=i; j++) {
System.out.print(j+"*"+i+"="+(i*j)+" ");
}
System.out.println();
}
}
 

//打印数组   【A,B,C,D】
static void printArray(String[] arr){
if (arr==null) {
System.out.println("null");
return;

}

//左括号
String ret="[";

//遍历数组
for (int index = 0; index < arr.length; index++) {
//判断
ret=ret+arr[index];
if(index!=arr.length-1){

ret=ret+",";
}

}


//添加右括号
ret=ret+"]";

//输出
System.out.println(ret);
 

}

/*

* 线性搜索

* 查询key元素在数组中第一次出现的位置
* 参数:
* arr:从哪一个数组中去做查询
* key:当前查询的元素
* 返回值:若果key存在于arr数组中,则返回第一次出现的索引的位置
* 如果key不存在,返回-1

*/


static int indexOf(int[] arr,int key){
for (int index = 0; index < arr.length; index++) {
if (arr[index]==key) {
return index;
}
}
return -1;
}
 

//逆序打印数组元素
/*

* 原数组[a,b,c]
* 逆序操作
* 新数组[c,b,a]

*/

//不用返回值  一个参数

static void print(int[] arr){
String str="[";
for (int i = arr.length-1; i >=0; i--) {
str=str+arr[i];
if (i!=0) {
str=str+",";
}
}
str=str+"]";
System.out.println(str);

}

/*
* 必须掌握的
* 1.方法的定义和调用
* 2.方法中的术语
* 3.方法的特点-执行流程
* 4.方法的重载
* 5.数组的基本操作
* 6.数组常见的异常
* 7.获取,遍历,设置数组元素
* 8.获取最大,最小元素
* 9.打印数组元素
* 10.元素出现索引

* JVM内存模型图,面试前能自己画出来
* 堆:new在堆中开辟新空间
* 栈:每一个方法存在栈帧

*/

/*
* 方法参数的值传递机制:(by value)

* 方法被调用时,方法里的参数是以值传递的方式传递的。
* 所谓值传递,就是将实际参数的副本传入方法,而参数本身不受影响

*/

多维数组

/*

* 数组:用于存放数据,好比是容器
* 如何存放两个数组:
* 一维数组:每个元素都是一个值
* 二维数组:每一个元素都是一个一维数组
* 三维数组:每一个元素都是一个二维数组


*/

//2.java5对数组的支持
/*
* 1)增强for循环(for-each)
* 2)方法的可变参数

* 取出数组中元素值得时候,会使用什么
* for,while,do while   循环遍历

* 循环遍历-》但是循环变量是数组的索引

* 有时候,不关心数组的索引,只关心数组的元素是多少
* java5-》开始-》增强for循环(for-each),可以直接取出数组中的每一个元素
* 语法:
* for(元素类型  变量:数组名){
* 变量就表示每一个元素值

* }

*/

//for-in  for-each

for(int ele:array){
System.out.println(ele);
}

//语法糖

for (int i : array) {
System.out.println(i);
}

/*

* 语法糖:编译器级别的新特性
* 底层依然是for循环
* 什么时候使用:
* 如果需要去除数组元素的索引,就用for循环
* 只取出数组的元素,用foreach更简单
* 区别:
* for循环遍历元素+元素的索引
* foreach只遍历元素



*/

//写一个二维数组,用foreach遍历
int[][] arr6=new int[][]{
{1,2,3},{3,4,5},{5,6,7},
};

for (int[] is : arr6) {
for (int i : is) {
System.out.println(i);
}

}

数组的元素拷贝

/*
* 数组的元素拷贝
* 不用返回值
* 参数:
* src:源,从哪个数组中拷贝数据
* dest:目标,把数据拷贝到哪个数组中
* srcPos:从源数组中的哪一个位置开始拷贝
* destPos:在目标数组中开始存放的位置
* length:拷贝几个元素
*/

//copy操作
static void copy(int [] src,int srcPos,int[] dest,int destPos,int length){
//原理
// dest[destPos]=src[srcPos];
// srcPos++;destPos++;
// dest[destPos]=src[srcPos];
// srcPos++;destPos++;
// dest[destPos]=src[srcPos];
// srcPos++;destPos++;
//优化用for循环
for(int i=srcPos;i<srcPos+length;i++){
dest[destPos]=src[i];
destPos++;
}
}

封装——工具类(专门的工作交给一个工具类    存放各种方法   不用都去学习工作)

工具类——>设计模式之一——>工厂设计模式
封装——>专门存放操作某一类型的方法
封装可以提高代码的复用性

/*
* 为什么用排序
* 很多数据都是数组形式,元素无序——>乱
* 用数组中的数据就用到排序
* 排序分类
* 1)选择排序:直接选择排序  堆排序法
* 2)交换排序:冒泡排序  快速排序法
* 3)插入排序:直接插入排序  二分法排序  shell排序
* 4)归并排序
* 排序分为升序和降序:
* 常用:——>冒泡排序  快速排序  选择排序
* 做到:熟练掌握其中一种排序
* HR1   人事——>技术
* 笔试   机试——>技术面试——>人生
* 机试题:30秒敲
*  
冒泡排序:最简单
*  基本思路:对未排序的各元素从头到尾一次比较相邻的两个元素若较大则交换位置

*  

//冒泡  int[] arr={86,22,3,9,10}
//22 86 3 9 10    22 3 86 9 10   ..........
static void bubbleSort(int[] arr){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}

}

选择排序;
*  基本思路:选定某个索引位置 然后和后面元素依次比较  若大则交换位置 经过第一轮排序后可得出最小值
*  

*/

测试工具类java自带的Array类
Java.lang包下的类  不需要引入// int[] arr={10,20,30,40,50,30,10};
// ArrayUtil.getMax(arr);
// ArrayUtil.getMin(arr);

// ArrayUtil.printArray(arr);

方法的可变参数——>java5开始

可变参数就是方法的数组参数——>ture

为了避免歧义语法有规定 可变参数必须作为方法的最后一个参数。

/*
* 传统做法:在方法调用的时候,传递一个数组。
* 如果形参过多,就把多个数据先封装到数组中,再传递
* 但是 从专业角度不爽——>逼格高
* 不创建数组,直接传递
* 形参不超过五个  不要过多
* 注意:
* 可变参数就是方法的数组参数——>ture
* 为了避免歧义语法有规定 可变参数必须作为方法的最后一个参数。
*/

//需求:编写一个方法  统计使用数组传递过来的总和
// static double getSum(double[] arr){
// double sum =0.0;
// for(double price : arr){
// sum+=price;
// }
// return sum;

// }

构造器

构造器:
   主要用于创建并返回对象和初始化对象数据
   构造器的特点:
  类都有一个默认的构造器 当不显示的定义构造器的时候,编译器会在编译的时候,提供一个默认的构造器
   特点;
   1)构造器的名字必须和当前所在的类名相同
   2)构造器访问权限修饰符和当前类的修饰符相同
   3)一旦显示的提供了构造器,则默认的构造器就不在提供了
   4)默认构造器,无参数,无方法体
   5)不需要定义返回值类型,更不能使用void返回
   构造器的目的:创建并返回当前类的对象
 

构造方法:

特点:与类同名而没有返回值类型

作用是用于创建对象

当类中没有手动置顶构造方法的时候在编译的时候自动添加一个无参构造

 
   推论:
   一个类至少有一个构造器
   方法的重载:解决的目的:相同功能的方法,因为参数列表(个数 类型  顺序)不同 而带来的参数名不同的问题
  
   构造器的重载:
   不同类的构造器是不相同的  对于构造器重载来说  肯定是在同一个类中 并且构造器都是和类名相同的
   所以在同一个类中的多个构造器就必须参数列表不同

  (个数 类型  顺序)

其实系统默认提供一个构造器

类是用来描述多个对象共同的行为和状态

static 可以修饰成员变量(字段),可以修饰方法等,
 就表明该字段或者该方法属于类,而不是属于某一个对象。
 
类成员(static修饰的成员)的特点
1)随着类被加载进JVM而同时也初始化和在内存中分配空间
2)优先于对象存在(对象通过new出来的)
3)被该类的所有对象所共享
该状态/行为是整个人类的  那么每个人都有该状态/行为

4)直接使用类名调用即可 

this关键字:代表本类的在活动的对象,用于在本类中调用本类的方法和属性

this语句,表示调用本类中其他的对应形式的构造方法,必须放在首行

代码块:

构造在创建对象的时候先于构造方法执行一次---用于完成一些初始化操作

局部代码块:提高栈内存的利用率

4. 封装:

体现形式:方法、类(属性的私有化 -> 将属性设置为private,对外提供对应的get/set的方法,在方法中可以进行限定使数据更加符合场景要求)

优势:提高复用性,保证数据的合法性

5. 继承 :用extends关键字来完成继承关系 --- 支持的类与类之间的单继承 --- 单继承和多继承的优劣性 --- 提高复用性,避免方法调用产生歧义 --- 子类可以继承父类全部的数据域,但是只有一部分可见

6. 多态:

编译时多态:方法的重载

运行时多态:

向上造型:用父类来声明用子类来创建 --- 对象能干什么看的是父类,对象如何执行方法看的是子类。

方法的重写(Override)父子类中存在了方法签名完全相同的非静态方法。

--- 有两等两小一大.

两等:方法名相同;形参列表相同

两小:子类方法返回值类型应比父类方法返回值类型更小或相等;

           子类方法声明抛出的异常应比父类方法声明抛出的异常类更小或相等

一同:覆盖方法与被覆盖方法都是类方法或都是实例方法
 

 

7. 权限修饰符:public protected 默认 private --- protected在子类中使用的时候指的是在对应的子类中使用,不能跨子类使用

8. super关键字:在子类中表示父类对象的引用,用于调用父类中的方法和属性 --- super语句,表示在子类的构造方法中调用父类对应形式的构造方法。子类构造方法中如果没有手动指定super语句,那么默认调用父类无参构造(super();如果父类只提供了含参构造,那么子类的构造方法中必须手动提供对应形式的super语句

static - 静态

修饰符 - 用于修饰数据、方法、代码块以及内部类

静态变量

用static修饰变量,称之为静态变量,也叫类变量。在类加载的时候加载到了方法区,并且在方法区中被赋予了默认值。静态变量是先于对象出现的,所以习惯上是通过类名来调用静态变量。每一个对象存储的是这个静态变量在方法区中的地址,所以静态变量是被这个类的所有对象所共享的

静态变量能否定义到构造方法中?---不能。静态变量是在类加载的时候出现,先于对象出现。构造方法在创建对象的时候执行。

注意:

1. 类是加载到方法区中

2. 类是在第一次使用的时候才加载,加载之后就不会移除

 

静态方法

用static修饰的方法,称之为静态方法。静态方法随着类的加载而加载到方法区中,但是在方法区中不执行只存储,在方法被调用的时候到栈内存执行。静态方法先于对象存在,所以习惯上是通过类名来调用静态方法。

main Arrays.sort() System.arraycopy()

静态方法中可以定义静态变量吗?--- 不能 --- 静态方法在调用的时候执行,静态方法执行的时候里面的变量才能初始化;静态变量是在类加载的时候初始化

静态方法中能否使用this/super?--- 不行 --- this代表当前在活动的对象,静态方法先于对象存在

能否在静态方法中直接使用本类中的非静态方法/非静态属性?--- 不行

静态方法可以重载吗?---可以

静态方法可以被继承吗?---可以

静态方法可以重写吗?---不可以

静态方法虽然不能被重写,但是父子类中可以存在方法签名一致的静态方法 --- 静态方法的隐藏(hide)

注意:父子类中可以存在方法签名一致的方法,要么都是非静态(重写)要么都是静态(隐藏)

静态代码块

用static{}包起来的代码 --- 在类加载的时候执行一次

静态代码块:只在类加载的第一次时候执行一次 

执行顺序父类静态 -> 子类静态 -> 父类非静态 -> 父类的构造方法 -> 子类非静态 -> 子类的构造方法

 

final

修饰符 --- 修饰数据、方法以及类

final修饰数据的时候 --- 常量 -> 定义好之后不可改变。如果final修饰的是基本类型的数据,那么指的是实际值不可变;如果final修饰的引用类型的数据,那么指的是地址不可变,但是对象中的元素或者属性值可以改变 --- 对于成员常量要求在对象创建完成之前给值;对于静态常量而言要求在类加载完成之前给值

arr.length System.in System.out

注意:常量的存储和方法区中的运行时常量池有关

final修饰方法 --- 最终方法,能被继承但是不可被重写/隐藏,能被重载。

final修饰类 --- 最终类 --- 不能被继承

注意:能够执行Java程序的内存块只有栈内存。

abstract

如果一个类的所有子类都对这个类中的某个方法做了重写,那么这个时候这个类中的对应的方法可以不定义方法体,需要用abstract修饰方法,从而成为了一个抽象方法。抽象方法所在的类必须是抽象类。--- 抽象类中不一定有抽象方法

抽象类不能创建对象

抽象类一定不是最终类

注意:任何一个类都有构造方法

抽象方法没有方法体,一定要被重写。

抽象方法可以定义被static/final/private修饰吗?---不行

抽象方法一定不能定义在最终类中。

如果一个类中的抽象方法用的是默认权限,对子类有什么要求?--- 要求父子类要同包

interface --- 接口

接口中定义都是抽象方法(JDK1.8以前)。类和接口之间用的是implements关键字来产生关联 --- 实现。类在实现接口之后需要重写接口中所有的抽象方法

接口不允许被实例化,也没有构造方法

在Java中,支持的是类和接口之间的多实现 --- 一个类可以实现多个接口

在Java中,支持接口之间的继承,而且接口之间是多继承

从JDK1.8开始,接口中允许定义实体方法 -- 这个是实体方法必须用default修饰

如果一个接口中只定义了一个抽象方法,那么把这个接口声明为函数式接口,用@FunctionalInteface定义 --- 也是JDK1.8的特性之一

单继承,多实现原则

 

内部类

方法内部类

定义在方法中的类 --- 方法/局部内部类 --- 为了重复使用某段逻辑,并且使这段逻辑只从属于某一个方法使用

成员内部类

定义在类中类

Outer2.Inner2 oi2 = new Outer2().new Inner2();

静态内部类

用static修饰的内部类

Outer3.Inner3 oi3 = new Outer3.Inner3();

匿名内部类

匿名内部类本质上是实现了对应的接口或者是继承了对应的类

任何一个接口都可以存在匿名内部类形式

一个类只要可以被继承,那么就可以存在匿名内部类形式 --- 最终类不存在匿名内部类形式

扩展:类中可以定义类,类中也可以定义接口,接口中可以定义类,接口中也可以定义接口 --- 如果类中定义了接口或者是接口中定义了接口,那么称之为内部接口 --- 类中定义的接口,以及接口中定义的类和接口默认都是静态的

class A {

static interface A1 {}

interface B {

static class B1 {}

static interface B2{}

}

声明包用的是package --- 区分同名类,进行功能的划分

导入包用的是import --- 导包的作用是用于提示代码从哪儿去找这个类

* 表示导入当前包下的所有的类但是不包括子包下的类

java --- 原生包

javax --- 扩展包

org --- 第三方厂商提供的一些常用的包

java.lang - 核心/基本包,包含了Java程序运行需要的基本类。在Java程序启动的时候,包下的类就已经自动加载到内存中,所以使用的时候可以不用导包

java.util - 工具包

java.math - 数学运算

java.io - 数据传输

java.net - 网络通信

java.nio - 高并发

java.text - 格式化

总结:java.lang包下的类以及同包类在使用的时候可以不用导包

垃圾分代回收机制

针对的是堆内存。

Java中的每种数据类型大小都是确定的,所以所有的内存是由Java自己进行分配,意味着内存的管理和回收也是由JVM自己进行---在Java中一旦产生内存问题导致程序员无法处理。理论上在正常情况下Java中的堆内存是足够使用的 --- 当堆内存使用的负荷量(一般情况下70%)超过一定限度的时候,会启动垃圾回收器(Garbage Collector --- GC)进行堆内存的回收释放

垃圾回收器构成:

Heap space

     Young Generation

           eden

           survivor

               from space

                to space

Old Generation

 

扩展:eden:from:to = 8:1:1

对象刚创建的时候是先放入新生代中的伊甸园区;如果在伊甸园区经过一次回收依然存在,那么将这个对象挪到幸存区,在幸存区中经过多次回收这个对象依然存在则挪到老生代。在回收的时候先回收新生代,如果新生代回收之后的内存足够使用则不扫描老生代;如果不够则扫描老生代。老生代的扫描频率要低于新生代

发生在新生代的回收 --- 初代回收 minor gc

发生在老生代的回收 --- 完全回收 full gc

扩展:对象创建完成之后会先试图放入新生代;如果新生代经过回收之后也放不开,则直接试图将该对象放入老生代。老生代如果也放不开,则会出现错误 --- OutOfMemoryError

API --- Application Programming Interface --- 应用程序接口 --- 接口以及实现类

 

Object

顶级父类。任何一个类都将Object作为父类,也就意味着任何一个对象都可以赋值给Object对象。Object类也是Java中唯一的一个没有父类的类。

重要方法

clone() --- 如果一个对象要想被克隆,那么这个对象所对应的 类必须实现接口--- Cloneable --- 这个接口中没有任何的方法和属性仅仅起标志性的作用

finalize() --- 通知GC回收垃圾,GC不一定启动

 

String

最终类。代表字符串的类,所有的字符串都是String的对象。

字符串是一个常量,定义好之后不可改变。

因为字符串是一个常量,所以它是被共享的 --- 只要值相同,用的都是同一个字符串

字符串本质上是一个不可变的字符数组

String str = “abc”; --- 1

String str = new String(“abc”); --- 2

String str = “a”; --- 1

str = str + “b”; ->

str = new StringBuilder(str).append(“b”).toString(); - 4

String[] strs = {/* 100个元素 */};

// 将字符串数组中的所有元素进行拼接

// 使用+进行拼接 --- 整个过程要产生301个对象

String str = “”; // 1

for(String s : strs)

str += s; // 每拼接1次要额外产生3个对象,拼接100次要额外产生300个对象

 

// 使用StringBuilder进行拼接 --- 整个过程中要额外产生102个对象

StringBuilder sb = new StringBuilder(); // 1

for(String s : strs)

sb.append(s); // 每拼接1次要额外产生1个对象,拼接100次要额外产生100个对象

String str = sb.toString(); // 1

如果拼接的字符串个数比较多,建议使用StringBuilder;如果个数比较少的话可以使用+

 

String

提供了一系列的操作而不改变的原串。

西欧码表:1个字符1个字节

GBK:1个字符2个字节

UTF-8:1个字符3个字节

UTF-16:1个字符2个字节

练习:输入一个字符串和一个数字,这个数字表示的是字节个数,按照指定的字节个数来截取字符串---不允许出现半个汉字的情况

/*思路:
 * 1. 获取字符串以及数字
 * 2. 将字符串转化为字节数组
 * 3. 要判断字节个数是否合法
 * 4. 按照指定的字节对字符串进行截取,截取一个新的子字符串
 * 5. 要判断子字符串的最后一个字符合和原字符串的对应位置上的字符是否一致
 * 6. 如果不一致,说明最后一个字节的字符对应了半个汉字,应该舍弃最后一个字节
 */

public class StringExer1 {

	public static void main(String[] args) {

		Scanner s = new Scanner(System.in);
		String str = s.next();
		int num = s.nextInt();
		s.close();

		byte[] bs = str.getBytes();

		if (num < 0 || num > bs.length) {
			System.out.println("字节个数非法!");
			return;
		}

		String sub = new String(bs, 0, num);

		if (sub.charAt(sub.length() - 1) != str.charAt(sub.length() - 1)) {
			sub = new String(bs, 0, num - 1);
		}

		System.out.println(sub);

	}

}
public class StringDemo1 {

	public static void main(String[] args) {

		String str = "abcdefg";
		/*
		 * 1. 两个字符串在底层都是一个字符数组形式来存储 
		 * 2. 创建一个新的字符数组,长度是两个字符串的长度之和 char[] cs = new char[value1.length + value2.length]; 
		 * 3. 进行数组的复制
		 * System.arraycopy(value1,0,cs,0,value1.length);
		 * System.arraycopy(value2,0,cs,value1.length,value2.length); return new
		 * String(cs);
		 */
		// 拼接字符串
		// System.out.println(str.concat("def"));
		
		// 判断是否包含指定的子字符串
		// System.out.println(str.contains("cd"));
		
		// 判断字符串是否是指定的结尾
		// System.out.println(str.endsWith("efg"));
		
		// 判断字符串是否是指定的开头
		System.out.println(str.startsWith("ab"));

	}

}
package cn.tedu.string;

public class StringDemo2 {

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

		String str = "中文";

		// 实际上底层就是按位比较
		// System.out.println(str.equals(new String("ABC")));
		// System.out.println(str.equalsIgnoreCase(new String("ABC")));

		// 将字符串转化为一个字节数组
		// 在没有指定编码的情况下,在转换的时候会按照系统平台码的来执行的
		// byte[] bs = str.getBytes();
		byte[] bs = str.getBytes("utf-8");
		// for (byte b : bs) {
		// System.out.println(b);
		// }

		// byte[] bs = { -48, -50, -52 };
		// 将字节数组转化为了字符串
		// String str2 = new String(bs, "gbk");
		// 按照字节进行截取---表示从指定的下标开始,将指定个数的字节按照指定的编码转化为字符串
		String str2 = new String(bs, 2, 2, "gbk");
		System.out.println(str2);

	}

}
package cn.tedu.string;

public class StringDemo3 {

	public static void main(String[] args) {

		String str = "abcadea";
		
		// 查找最后一次出现的位置
		System.out.println(str.lastIndexOf("d",4));

		// 字符串在底层对hashCode方法做了重写
		// 保证了同一个字符串的哈希码在任意的环境下一定是相同的---从而保证字符串常量的唯一性
		// System.out.println(str.hashCode());

		// 获取指定字符在字符串中第一次出现的下标
		// 如果字符不存在,返回-1
		// System.out.println(str.indexOf('a'));
		// 从指定的下标开始寻找指定的字符出现的位置
		// System.out.println(str.indexOf('a', 2));
		// System.out.println(str.indexOf("ad", 2));

		// index(str, "a");
	}

	public static void index(String str, String sub) {

		// 记录位置
		int index = 0;

		// 判断index下标是否越界
		while (index < str.length()) {
			// 表示从index位继续往后找
			index = str.indexOf(sub, index);

			// 判断是否找到了这个元素
			if (index != -1) {
				System.out.println(index);
				index++;
			} else {
				break;
			}
		}

	}

}

REGEX --- Pattern

regular expression---正则表达式---本身表示规则,用正则表达式来指定规则,然后利用这个规则对其他事物做判断、匹配、筛选等

\\d 表示数字

\\. 表示 .

+ >= 1  ? <= 1  * >= 0

练习:输入字符串,判断这个字符串是否是一个小数

3.13 3.00  .5---false   00.21---false

System.out.println(str.matches("0\\.\\d+") || str.matches("[1-9]\\d*\\.\\d+"));

练习:

1. 匹配邮箱

@qq.com   @126.com  @sina.com.cn  @tedu.cn

字母/数字@数字/字母.com\.cn\.com.cn

	String email = "[email protected]";

		System.out.println(email.matches("[a-zA-Z0-9]{5,}@[a-zA-Z0-9]+\\.com")
				|| email.matches("[a-zA-Z0-9]{5,}@[a-zA-Z0-9]+(\\.com)?\\.cn"));

2. 匹配密码:8-12位  大写字母/小写字母/数字/空格中至少出现2种

package cn.tedu.regex;

public class PatternExer3 {

	public static void main(String[] args) {

		String password = "bdsgadgau";

		boolean b = checkPwd(password);
		System.out.println(b);

	}

	private static boolean checkPwd(String password) {

		// 判断参数是否为null
		if (password == null)
			return false;

		// 判断密码的长度
		if (!password.matches(".{8,12}"))
			return false;

		// 记录符号出现的种类
		int count = 0;

		// 判断密码中是否出现了大写字母
		if (password.matches(".*[A-Z].*"))
			count++;

		// 判断密码中是否出现了小写字母
		if (password.matches(".*[a-z].*"))
			count++;

		// 判断密码中是否出现了数字
		if (password.matches(".*\\d.*"))
			count++;

		// 判断密码中是否出现了空格
		if (password.matches(".* .*"))
			count++;

		return count >= 2;
	}

}

练习:

1. 替换叠字 --- 我我我我我我爱爱爱爱爱爱爱爱学学学学学学学学学学学习习习习习习习习习习习习习习习 -》 我爱学习    

String str = "我我我我我我爱爱爱爱爱爱爱爱学学学学学学学学学学学习习习习习习习习习习习习习习习";
		
		System.out.println(str.replaceAll("(.)\\1+", "$1"));

2. 输入一个字符串,求字符串的平均碎片长度

package cn.tedu.regex;

import java.util.Scanner;

public class PatternExer4 {

	public static void main(String[] args) {

		Scanner s = new Scanner(System.in);
		String str = s.next();
		s.close();

		while (str.length() != 0) {
			// 记录字符串长度
			int len = str.length();

			// 先来获取字符串的第一个字符
			char c = str.charAt(0);

			// 去掉这个字符串中所有的该字符		"\\+"
			str = str.replaceAll(c + "", "");

			System.out.println(c + ":" + (len - str.length()));
		}
	}

}

 

包装类:

针对每一个基本类型都提供了对应的类形式 int - Integer, char - Character;自动封箱 --- 指的是将基本类型的变量直接赋值给对应的引用类型的对象。自动封箱底层是调用了对应类身上的valueOf方法,对于整数在自动封箱过程中会有范围(-128~127)判断。自动拆箱是指将引用类型的对象直接赋值给对应的基本类型的变量。自动拆箱在底层是调用了对应对象身上的***Value方法。自动封箱/拆箱都是JDK1.5的特性之一。包装类的对象的实际值只要相同则它的哈希码就一定是相同的。

数学类:

Math:最终类。针对基本类型提供了初等数学运算。

BigDecimal:用于精确运算小数的类,但是要求参数以字符串形式传递。

BigInteger:用于运算任意大的整数,要求整数以字符串或者是对应的补码的字节数组形式来传递。

DecimalFormat:用于对数字进行格式化的类。

日期类

Date:表示日期的类。重点掌握字符串和日期之间的转换 --- SimpleDateFormat:parse将字符串转化为日期,format将日期转化为字符串。

Calendar:表示日历的类。从JDK1.2开始,Java推荐使用Calendar

时间包

在JDK1.8中对时间体系进行了全新的详细的划分,划分出来一个详细的时间体系的包 --- java.time

异常

异常是Java中一套用于问题的反馈和处理的机制

Throwable --- 异常的顶级父类

Error --- 错误表示合理(语法上还是逻辑上都是成立的)的应用程序中出现了严重的问题,而且这个问题不应该试图捕获 --- 意味着在程序中,错误一旦出现不能处理 --- StackOverflowError, OutOfMemoryError

Exception --- 表示合理的应用程序想要捕获的问题,也因此可以处理。处理方式:要么继续抛出,要么进行捕获处理。

编译时异常:在编译时期就已经出现要求必须处理。

CloneNotSupportedException

UnsupportedEncodingException

ParseException

运行时异常:在编译时期不出现到运行的时候才出现。可以处理可以不处理 --- RuntimeException

ArithmeticException

ArrayIndexOutOfBoundsException

NullPointerException

ClassCastException

StringIndexOutOfBoundsException

NumberFormatException

自定义异常:写一个类继承Exception或者是其子类。如果是继承了RuntimeException及其子类,那么这个时候定义的是一个运行时异常;如果继承的是其他的Exception,那么定义的就是编译时异常

异常的捕获方式

A. 如果多个异常的处理方式各不一样,可以使用多个catch分别捕获分别处理

B. 如果所有异常的处理方式都一样,可以捕获这些异常的父类进行统一的处理

C. 如果多个异常进行了分组,那么同一组的异常之间用 | 隔开进行分组处理 --- 从JDK1.7开始

总结:方法的重载和方法的重写

方法的重载和重写都是行为多态。

方法的重载指在同一个类中存在方法名一致而参数列表不同的方法,和修饰符、返回值类型以及异常没有关系。重载本身是一种编译时多态。

方法的重写指在父子类中存在方法签名一致的非静态方法。子类在重写父类方法的时候,子类权限修饰符的范围要大于等于父类权限修饰符的范围。如果父类中的方法的返回值类型是基本类型/void,那么子类重写的方法的返回值类型要与父类一致。如果父类的方法的返回值类型是引用类型,那么子类重写的方法的返回值类型要么和父类一致,要么是父类方法返回值类型的子类。另外,子类重写的方法抛出的编译时异常不能超过父类方法的编译时异常的范围。方法的重写是一种运行时多态。

注意:异常从抛出的地方开始,后续代码停止执行。

finally --- 无论出现异常与否都需要执行一次。

如果在项目的开发期间遇到异常,记录栈轨迹,找异常来源进行改正;如果项目已经上线,记录错误日志,往往跳转错误页面

集合 - Collection<E>

存储多个同一类型的数据的容器 --- 大小不固定

<E> - 泛型 - 在集合中的作用是用于表示元素类型。- 由于泛型的限制,集合中只能存储对象。

String[] arr; arr的数据类型是数组,元素类型是String

Collection<String> c; c的数据类型是集合,元素类型是String

5,7, 2 --- Collection<Integer>

Collection<int[]> c; --- 表示集合中存储的元素是数组

List - 列表

有序(保证元素的存入顺序)的集合 --- 存在了下标,因此能够通过下标来操作列表中的元素

ArrayList - 顺序表

底层是基于数组来存储数据。内存空间是连续的。默认初始容量是10,每次扩容默认增加一半,基于了右移。增删操作相对复杂,查询操作相对简单。是一个线程不安全的列表。

LinkedList - 链表

基于节点(Node)来实现的。利用节点来存储数据以及维系链表之间每一个节点的关系。内存空间不连续。增删操作相对简单,查询操作相对复杂。是一个线程不安全的列表

Vector - 向量

最早的列表,依靠数组来存储数据,初始默认是10,每次扩容默认增加一倍。是一个线程安全的列表

Stack - 栈

继承了Vector。遵循后进先出/先进后出的原则。最先放入栈中的元素 --- 栈底元素,最后放入栈中的元素 --- 栈顶元素。将元素放入栈中 --- 入栈/压栈,将元素从栈中取出 --- 出栈/弹栈

练习:使用数组/节点(Node)完成一个Stack --- empty peek pop push search

Set - 散列集合

包含的元素不重复。

HashSet

不包含重复的元素,不保证元素的存入顺序。底层基于HashMap来进行数据的存储。默认初始容量是16,默认加载因子是0.75f

加载因子过小,会导致频繁发生rehash操作而降低效率,同时还造成空间资源的浪费

加载因子过大,会导致每一个桶中的链的长度过长,从而降低增删效率。

从JDK1.8开始,对HashSet的存储做了调优:如果桶中的链的长度大于了8,会将这个链式结构扭转成一个二叉树结构

TreeSet --- 会对元素进行整体的自然排序,需要元素对应的类实现Comparable接口

Comparable --- 类在实现这个接口之后对这个类的所有的对象进行整体排序

Comparator --- 用于给某个对象单独指定规则

总结:如果需要给某个TreeSet对象单独指定比较规则,则使用Comparator;如果不指定比较规则,则使用Comparable进行整体的自然排序

 

Collection

List:保证元素的存入顺序,元素可以重复。

ArrayList:基于数组,内存空间是连续的,默认初始容量是10,每次默认增加一半。相对增删复杂但是查询简单。是一个线程不安全的列表

LinkedList:基于节点,内存空间是不连续的。相对增删简单但是查询复杂。是一个线程不安全的列表

考虑:如果在增删和查询的次数相差不多的情况下,使用ArrayList还是LinkedList? --- LinkedList

Vector:基于数组,内存空间一定是连续的。默认初始容量是10,默认标准容量增量为0,默认每次增加一倍。是一个线程安全的集合

Stack:栈。继承了Vector。遵循先进后出原则。--- 栈顶元素,栈底元素,入栈/压栈,出栈/弹栈

Set:保证元素的唯一性

HashSet:底层是基于了HashMap -> 基于了数组+链表结构。默认初始容量是16,默认加载因子是0.75f,默认增加一倍。--- 不保证元素的存储顺序

TreeSet:要求存入的元素对应的类必须实现Comparable的接口,然后利用接口中compareTo方法进行自然排序;如果需要对某一个TreeSet单独指定排序规则,则需要传入一个Comparator对象

迭代器

迭代器是通过指针的挪动来依次获取集合中的每一个元素。

Enumeration - 最早期的迭代器

Iterator - 通过对每一个元素进行标记确定每一个元素的存在与否。因此在迭代过程中不允许直接增删原集合

Collection中的iterator方法是从Iterable中继承过来的

实现了Iterable接口的类产生的对象可以被增强for循环进行遍历。 --- 增强for循环也是JDK1.5的特性之一。增强for循环本质上是一个迭代遍历。

泛型

参数化类型 - ParameterizedType --- JDK1.5的特性之一

用具体类型来替代泛型的过程 --- 泛型的擦除 --- 编译期

? extends 类/接口 表示传入这个类/接口或者是其子类/子接口对象 --- 上限

? super 类/接口 表示传入这个类/接口及其父类/父接口的对象 --- 下限

? 表示泛型的通配符

Map<K, V> - 映射

一个键对应一个值。键是唯一的,值可以重复。

每一个键和它所对应的值构成了键值对。--- 一个Map是由多个键值对来组成。

将每一个键值对看作一个对象,抽取出来一个代表键值对的接口 --- Entry,Entry是Map中的内部接口 --- 一个Map是由多个Entry对象来组成的

Map不是集合,但是Map是Java集合框架的成员。

Java集合框架(Java Collections Framework)包含:集合、数组、映射以及操作它们的工具类 --- Arrays、Collections、Interator、Comparable、Comparator

遍历映射

方式一:先获取映射中所有的键组成的集合,然后通过键获取对应的值

方式二:将所有的键值对放入集合中,然后遍历集合获取这个键值对的数据

HashMap:基于哈希码存储,然后允许键和值为null。默认初始容量是16,默认加载因子是0.75f,每次默认增加一倍。自定义初始容量x,这个x介于[2n, 2n+1],那么初始容量一定是2n+1 --- 底层保证HashMap的容量永远是2n的形式。本身是一个异步式线程不安全的映射

Hashtable:基于哈希码存储,然后不允许键和值为null。默认初始容量是11,默认加载因子是0.75f,每次默认增加一倍,然后再+1 --- 11 -> 23。本身是一个同步式线程安全的映射。

ConcurrentHashMap --- 异步式线程安全的映射

练习:输入一个字符串,统计其中每一个字符出现的次数

断言

对结果进行预测。

assert 断言条件 : 错误信息;

在Java中,断言不是默认开启的,需要利用参数手动指定开启 -> -ea -> -enableassertion

扩展:native修饰方法--- 本地方法,没有方法体但不是抽象方法,方法体是在JVM中用C语言完成的,在本地方法栈中执行

File

代表文件或者目录(文件夹)的类

练习:删除目录

思路:写一个单独的方法来删除目录或者文件:判断是一个目录还是还是一个文件;如果是文件(isFile())直接删除(delete()),如果是目录(isDirectory()),获取这个目录下的所有的子目录和子文件(listFiles()) --- 后续功能和当前方法要完成的事情是一致的,所以使用递归

package cn.tedu.file;

import java.io.File;

public class FileExer1 {

	public static void main(String[] args) {
		
		File file = new File("D:\\aaa");
		del(file);

	}

	public static void del(File file) {

		// 判断参数是否为空
		if (file == null)
			throw new NullPointerException("亲,文件不能为空哦~~~");

		// 判断file对象是文件还是目录
		if (file.isDirectory()) {

			// 如果是目录,需要获取这个目录下的所有的子文件和子目录
			File[] fs = file.listFiles();
			for (File f : fs) {
				// 逐个遍历每一个元素,判断这个元素是文件还是目录
				del(f);
			}

		}

		// 无论是文件还是目录都需要删除
		file.delete();
	}

}

练习:统计工作空间中Java文件和class文件的个数

思路:定义一个新的方法进行统计:先获取工作空间中所有的子文件和子目录。如果是子目录---递归;如果子文件,需要判断这个文件是否是一个Java文件还是一个class文件

package cn.tedu.file;

import java.io.File;

public class FileExer2 {

	static int javaCount = 0;
	static int classCount = 0;

	public static void main(String[] args) {

		File file = new File("F:\\workspace");
		count(file);

		System.out.println(javaCount);
		System.out.println(classCount);

	}

	public static void count(File file) {

		// 判断file对象是否为空
		if (file == null)
			throw new NullPointerException();

		// 判断是否是一个目录
		if (file.isDirectory()) {
			// 获取这个目录下的所有的子文件和子目录
			File[] fs = file.listFiles();
			for (File f : fs) {
				count(f);
			}
		} /* file如果不是目录就是文件 */ else if (file.getName().endsWith(".java"))
			javaCount++;
		else if (file.getName().endsWith(".class"))
			classCount++;

	}

}

 

路径

绝对路径:以盘符或者/开头的路径。指定的位置和文件当前错处的位置没有任何关系

相对路径:不以盘符或者/开头的路径。以当前路径为基准来计算指定的文件的所在路径

IO流

用于数据的传输的机制。IO -> Input Output Stream -> 输入输出流。数据从外部流向程序 - 输入流; 数据从程序流向外部 - 输出流。读取文件 -> 数据是从文件读到程序中 -> 输入流;向一个TXT文件中写入字符串 “abc” -> 数据是从程序写到文件中 -> 输出流

根据数据传输方向:输入流和输出流

根据数据传输形式:字符流和字节流

 

输入流

输出流

字符流

Reader

Writer

字节流

InputStream

OutputStream

四个基本的流都是抽象类。

数据的来源/目的地:存储设备、物理设备、内存、网络

向一个TXT文件中写入字符串 -> 输出流、字符流、和文件相关的流 -> FileWriter

流中的异常处理

1. 将流对象放在try之外声明并且赋值为null;放到try之内进行初始化

2. 在关流之前需要判断流对象是否为空

3. 为了防止关流失败导致流依然占用文件,所以需要将流对象强制置为null

4. 为了防止关流的时候自动冲刷缓冲区失败导致一部分数据产生丢失,需要在关流之前进行一次手动的冲刷

一、IO流
    InputOutputStream - 输入输出流,可以利用输入输出流向程序中写入写出数据。其中传输的是数据的流。
    按照数据流流动的方向,流分为输入流 和 输出流。流的方向由程序的角度来思考,将数据写入程序的流称之为输入流,从程序中写出数据的流,称之为输出流。
    按照数据流中传输的内容,流分为 字节流 和 字符流。

    按照两个维度,相乘,就得到了四大基本流:
        --------------------------------------
                    输入流            输出流
            字节流    字节输入流InputStream    字节输出流OutputStream
            字符流    字符输入流Reader        字符输出流Writer
        --------------------------------------
    这四大基本流是流的继承结构的根,都是抽象类,无法直接使用,一般用的都是他们的具有具体功能的子孙类。

二、字符流
    1.字符输入流Reader - FileReader
        构造方法:
            FileReader(File file) 
            FileReader(String fileName) 
        读取数据的方法:
            int read() 
            int read(char[] cbuf)
        关闭流:
            close()
    2.字符输出流Writer - FileWriter
        构造方法:
            FileWriter(File file) 
            FileWriter(String fileName) 
        输出数据的方法:
            void write(int c)
            write(char[] cbuf)
        刷新流:
            flush()
        关闭流:
            close()

       

package cn.tedu.io1;

import java.io.FileReader;
import java.io.Reader;

/**
 * 案例:编写一个流 来读取外部文件中的字符数据 	
 */
public class Demo01 {
	public static void main(String[] args) throws Exception {
		//1.创建文件字符输入流链接到 1.txt上
		Reader reader = new FileReader("1.txt");
		//2.通过流读取文件中的数据
		int i = 0;
		while((i=reader.read())!=-1){
			System.out.println((char)i);
		}
		//3.关闭流
		reader.close();
	}
}
package cn.tedu.io1;

import java.io.FileWriter;
import java.io.Writer;

/**
 * 案例:编写一个流 来将指定的字符写出到外部文件中
 */
public class Demo02 {
	public static void main(String[] args) throws Exception {
		//1.创建文件字符输出流
		Writer writer = new FileWriter("2.txt");
		//2.通过字符输出流输出数据
		writer.write((int)'a');
		writer.write((int)'b');
		writer.write((int)'c');
		writer.write((int)'d');
		writer.write((int)'e');
		//3.刷新流
		//writer.flush();
		//--关闭流,关闭流过程中会隐含的刷新一次流
		writer.close();
	}
}
package cn.tedu.io1;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

/**
 * 案例:利用字符流实现字符文件的拷贝 1.txt -> 3.txt
 */
public class Demo03 {
	public static void main(String[] args) {
		Reader reader = null;
		Writer writer = null;
		try {
			//1.创建字符输入流 连接1.txt 创建字符输出流 连接3.txt
			reader= new FileReader("1.txt");
			writer = new FileWriter("3.txt");
			//2.从1.txt读取数据
			int i = 0;
			while((i = reader.read())!=-1){
				writer.write(i);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//3.关闭流
			if(reader!=null){
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally{
					reader = null;
				}
			}
			if(writer!=null){
				try {
					writer.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally{
					writer = null;
				}
			}
		}
		
	}
}
package cn.tedu.io1;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

/**
 * 案例:利用字符流实现字符文件的拷贝 1.txt -> 3.txt
 */
public class Demo03 {
	public static void main(String[] args) {
		Reader reader = null;
		Writer writer = null;
		try {
			//1.创建字符输入流 连接1.txt 创建字符输出流 连接3.txt
			reader= new FileReader("1.txt");
			writer = new FileWriter("3.txt");
			//2.从1.txt读取数据
			int i = 0;
			while((i = reader.read())!=-1){
				writer.write(i);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//3.关闭流
			if(reader!=null){
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally{
					reader = null;
				}
			}
			if(writer!=null){
				try {
					writer.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally{
					writer = null;
				}
			}
		}
		
	}
}


    3.缓冲流
        java.io.BufferedReader
            构造方法:
                BufferedReader(Reader in) 
                BufferedReader(Reader in, int sz) 
                将一个Reader传入BufferedReader后 BufferedReader会在原来Reader的基础上增加缓冲区,从而实现高效的读取数据,这个过程中BufferedReader不会改变原有Reader的功能,只是为底层的流提供了缓冲的能力,用来提高读取效率。
            方法:
                int read()  
                int read(char[] cbuf)  
                int read(char[] cbuf, int off, int len)  
                String readLine()      
                close() 

        java.io.BufferedWriter
            构造方法:
                BufferedWriter(Writer out) 
                BufferedWriter(Writer out, int sz) 
                将一个Writer传入BufferedWriter后 BufferedWriter会在原来Writer的基础上增加缓冲区,从而实现高效的写出数据,这个过程中BufferedWriter不会改变原有Writer的功能,只是为底层的流提供了缓冲的能力,用来提高写出效率。
            方法:
                write(int c)
                write(String str)
                write(char[] cbuf)
                write(char[] cbuf, int off, int len)
                newLine()  
    4.装饰设计模式
        设计模式:前人对编写java代码的套路的总结,java一共有23种通用设计模式。
        装饰设计模式就起其中的一种。
        装饰设计模式主要的能力,是通过装饰者包装被装饰者,在被装饰者原有的功能的基础上,增加额外的能力。
        装饰设计模式实现方式:
            写一个类 使其具有和被装饰者相同的方法
            提供构造方法 将被装饰者 传入保存在类的内部
            对于不想改造的方法直接调用原有对象本来的方法
            对于想改造的方法 进行改造。
            对于想增加的方法 直接增加。
        这样可以将被装饰者传入,装饰,增加特定的功能。    

    5.StringReader
        数据来源是字符串的字符输入流,可以将一个字符串作为数据来源 以流的方式获取其中的数据

三、字节流
    1.InputStream - FileInputStream
        以字节为基本单位来操作数据
        构造方法:
            FileInputStream(File file) 
            FileInputStream(String name)
        方法:
            int read()
            int read(byte[] b) 
            int read(byte[] b, int off, int len)  
            int available() 
            void close()  
        案例:以字节为单位从文件中读取数据
        
    2.OutputStream - FileOutputStream
        以字节为基本单位来操作数据
        构造方法:
            FileOutputStream(File file) 
            FileOutputStream(String name) 
        方法:
            void write(int b) 
            void write(byte[] b) 
            void write(byte[] b, int off, int len) 
            void flush()
            void close() 

        案例:利用字节流实现文件的拷贝 文本文件 和 非文本文件都可以拷贝

package cn.tedu.io2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 利用字节流实现文件的复制
 */
public class Demo01 {
	public static void main(String[] args) throws Exception {
		//1.创建流
		InputStream in = new FileInputStream("1.wmv");
		OutputStream out = new FileOutputStream("2.wmv");
		//2.对接流 实现复制
		int i = 0;
		while((i = in.read())!=-1){
			out.write(i);
		}
		//3.关闭流
		in.close();
		out.close();
	}
}

 案例:利用字节流实现文件的拷贝 - 自定义缓冲区提升性能

  

package cn.tedu.io2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 利用字节流实现文件的复制 - 使用缓冲区
 */
public class Demo02 {
	public static void main(String[] args) throws Exception {
		//1.创建流
		InputStream in = new FileInputStream("1.wmv");
		OutputStream out = new FileOutputStream("2.wmv");
		//2.对接流 实现复制
		int i = 0;
		byte [] tmp = new byte[1024];
		while((i = in.read(tmp))!=-1){
			out.write(tmp,0,i);
		}
		//3.关闭流
		in.close();
		out.close();
	}
}

 3.转换流
        字符流的底层也是通过字节来操作数据的 只不过在字节流的基础上增加了 默认的转换器 可以经字节和字符进行映射 采用的编码集为系统码 且无法更改
        所以当使用字符流 操作 非系统码的文件时 可能产生乱码
        此时可以通过转换流 将自己构建的字节流 转换为字符流 并在这个过程中 指定需要的编码集 来解决问题
        
        InputStreamReader - 可以将字节输入流转换为字符输入流 在转换的过程中 其实内部就是多了一个将字节 映射为 字符的操作 而采用的编码集 可以由程序来指定
            构造方法:
                InputStreamReader(InputStream in) //将字节输入流转换为字符输入流 内部增加的转换器 采用默认的码表(系统码)
                InputStreamReader(InputStream in, String charsetName)  //将字节输入流转换为字符输入流 内部增加的转换器 采用的码表由charsetName来指定

            方法:
                int read() 
                int read(char[] cbuf)  
                int read(char[] cbuf, int offset, int length) 
                void close()  

        OutputStreamWriter - 可以将字节输出流转换wie字符输出流 在转换的过程中 其实内部就是多了一个将字符 映射为 字节的操作 而采用的编码集 可以由程序来指定
            构造方法:
                OutputStreamWriter(OutputStream out)//将字节输出流转换为字符输出流 内部增加的转换器 采用默认的码表(系统码)
                OutputStreamWriter(OutputStream out, String charsetName) //将字节输出流转换为字符输出流 内部增加的转换器 采用的码表由charsetName来指定

            方法:
                void write(int c)  
                void write(char[] cbuf)  
                void write(char[] cbuf, int off, int len)  
                void write(String str)

        案例5:利用转换流实现utf-8编码集的文本文件的拷贝    
        案例6(作业):请写程序将gbk的文本文件转换为utf-8的文本文件 实现一个转码的过程

package cn.tedu.io2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

/**
 * 案例:通过转换流 生成自定义码表的字符流 复制文件 解决乱码
 */
public class Demo04 {
	public static void main(String[] args) throws Exception {
		//1.创建字节流
		InputStream in = new FileInputStream("d://8.txt");
		OutputStream out = new FileOutputStream("d://9.txt");
	
		//2.创建转换流 将字节流转换为字符流 并显式指定码表为utf-8
		InputStreamReader reader = new InputStreamReader(in,"utf-8");
		OutputStreamWriter writer = new OutputStreamWriter(out,"utf-8");
		
		//3.对接流 复制文件
		char [] data = new char[1024];
		int i = 0;
		while((i=reader.read(data))!=-1){
			writer.write(data,0,i);
		}
		
		//4.关闭流
		reader.close();
		writer.close();
	}
}


        案例7(作业):请写程序将utf-8的文本文件转换为gbk的文本文件 实现一个转码的过程

package cn.tedu.io2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

/**
 * 案例:通过转换流 生成自定义码表的字符流 复制文件 将utf-8文件转换为gbk格式的文件
 */
public class Demo05 {
	public static void main(String[] args) throws Exception {
		//1.创建字节流
		InputStream in = new FileInputStream("d://8.txt");
		OutputStream out = new FileOutputStream("d://9.txt");
	
		//2.创建转换流 将字节流转换为字符流 并显式指定码表为utf-8
		InputStreamReader reader = new InputStreamReader(in,"utf-8");
		OutputStreamWriter writer = new OutputStreamWriter(out,"gbk");
		
		//3.对接流 复制文件
		char [] data = new char[1024];
		int i = 0;
		while((i=reader.read(data))!=-1){
			writer.write(data,0,i);
		}
		
		//4.关闭流
		reader.close();
		writer.close();
	}
}

    4.系统流
        System是java内置的代表当前系统的类 其上提供了很多和系统相关的属性和方法 方便我们对系统进行操作
        常用的属性:
            static InputStream in - 标准输入流
            static PrintStream out - 标准输出流 
            static PrintStream err - 标准错误输出流
        常用的方法:    
            static void exit(int status)  //退出虚拟机
            static void gc() //启动垃圾回收 虚拟机通常会根据自身的内存使用情况 自动决定什么时候来进行垃圾回收 但是也可以通过这个方法 通知虚拟机该垃圾回收 但是注意 这个方法调用并不会触发垃圾回收 只是一个通知告诉虚拟机该回收垃圾了 至于是否启动垃圾回收 仍然由虚拟机自己决定
            static void setIn(InputStream in) //设定系统标准输入流
            static void setOut(PrintStream out) //设定系统标准输出流  
            static void setErr(PrintStream err) //设定系统标准错误输出流
 
        其中System上提供的 in out err三个字节流 称之为系统流
        系统流其实也是字节流 只不过这个字节流不需要我们自己创建 是系统创建好 直接提供给我们用的 不要关 也不能关
        其中 in 是系统输入流 从系统标准输入流中获取数据 默认的系统标准输入流的来源 是控制台
        其中 out err 是系统输出流 向系统标准输出流中写出数据 默认的系统标准输出流的目的地 是控制台

        所以,平常大家写的 System.out.println("hello world~"); 起始就是通过System.out得到的了系统输出流 向这个流中 通过println方法写出了 数据 最终去往了控制台显示
        也可以用 System.err.println("xxx") 写出数据 和 System.out 唯一的不同是 打印出来将会是红色的文本 更加醒目 通常用来打印错误信息 平常大家见到的异常信息 就是通过System.err打印的
        系统输出流的默认位置是控制台 也可以通过System.setOut() 或System.setErr() 方法修改out或err指向的 系统输出流

        而 其中的System.in代表标准输入流 默认数据来源是控制台 所以可以利用它从控制台中读取数据
        所以平常从控制态中读取数据可以写成:
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
            简单写法
            Scanner scanner = new Scanner(System.in)
        系统标准输入流默认的位置是控制台 也可以通过System.setIn() 方法来修改系统默认输入流 从而从其他位置读取数据

    5.打印流
        采用了装饰设计模式 为普通输出流 增加打印功能
        
        PrintStream
            可以讲普通的字节流转换为打印字节流 这也是一个装饰设计模式 增加了打印的机制 有如下的三个好处
                打印流增加了打印各种类型数据的方法,省去了将各种类型的数据转换为字节再输出的过程。
                打印流会自动刷新流,不需要手动进行flush。    
                打印流永远不抛异常,它的内部自动解决异常。

        PrintWriter
            可以将普通的字符流转换为打印字符流 这也是一个装饰设计模式 增加了打印的机制 有如下的三个好处
                打印流增加了打印各种类型数据的方法,省去了将各种类型的数据转换为字符再输出的过程。
                打印流会自动刷新流,不需要手动进行flush。    
                打印流永远不抛异常,它的内部自动解决异常。

四、常见的具有特定功能的流
    1.序列化反序列化
        java通过创建对象代表现实中的一个具体的事物。
        而对象本身是存活在内存中的,内存中的对象的结构是很复杂的,没法直接将一个对象当作一个字节数组来操作。
        而在现实开发中,经常需要将内存中的对象 存储到磁盘 通过网络来传输 等等操作,此时需要将内存中的对象转换为可以直接操作的字节数组,这个过程称之为对象的序列化。
        而将之前序列化的对象的字节数组,再转回成对象的过程,就称之为反序列化。    
        
        java中的序列化反序列化 主要是靠如下两个流来实现的:
            ObjectOutputStream - 将对象序列化的流
                此类也是装饰设计模式的实现,可以在构造方法中将一个普通字节流传入,此类在传入的流的基础上增加了将对象转换为字节输出的方法。
                构造方法:
                    ObjectOutputStream(OutputStream out) 
                方法:
                    void writeObject(Object obj)  
                    void flush() 
                     void close()  

                 **想要用java序列化进行序列化操作的对象的类必须实现一个特定的java.io.Serializable接口。
                 **Serializable接口中并没有定义任何的方法,这个接口仅仅是一个标志,告知虚拟机当前类的对象可以进行序列化反序列化,这样使用接口的方式,称之为标记接口。
                
                案例:创建Person对象 并通过序列化将对象转为字节后输出到文件中 -- 这个过程也称之为将对象持久化
                 
            ObjectInputStream - 将对象反序列化的流    
                此类也是装饰设计模式的实现,可以在构造方法中将一个普通字节流传入,此类在传入的流的基础上增加了将之前序列化的字节信息转换为对象的方法。
                构造方法:
                    ObjectInputStream(InputStream in) 
                方法:
                    Object readObject() 
                    void close() 

                案例:将之前案例中序列化后持久化保存的Person对象反序列化恢复到内存中

                **java在序列化 和 反序列化时 会通过类中的SerialVersionUID来判断 序列化和反序列化时使用的类 是否是同一个 如果一致 则认为是同一个类 正常反序列化 如果不同则认为不是同一个类 抛出异常。 所以通常要在需要在实现了Serializable接口的类中 声明static final long SerialVersionUID 属性 来指定此编号,并且要保证,序列化和反序列时编号一致。如果不手动指定此编号,虚拟机会自动生成一个编号。
                **将对象进行序列化 转换为字节数组 保存在磁盘中的过程 也称之为将对象持久化了起来。将持久化的对象 再变回内存中对象的过程 也称之为反持久化。
                **在实现了Serializable接口的类中 如果部分属性不想被序列化 则可以加上transient关键字,则这个属性将会在序列化的过程中被忽略。
                **静态的属性也不会被序列化
                **集合/数组不能被序列化
                                    

    2.Properties - java属性对象
        java开发中往往涉及到一些配置型的参数,例如连接数据库时的用户名 密码等信息。。访问网络时的地址 端口等信息。。,这些参数经常会发生变化,不希望写死在程序中,此时,可以将这些参数写在外部的配置文件中,在程序中通过流来进行读取,解析出配置的信息,再基于这些信息工作。这样这些信息发生变化时,就不需要改代码,而只需要改配置文件中的信息就可以了,修改起来更便利,对于代码来说也更安全。
        这种使用配置文件 将程序中经常变化的配置信息 提取到程序外统一保存和管理的机制是非常常见的 在很多软件中都会这样去做 常见的配置文件的类型也有很多 比如 .ini .xml .json .yaml 等等 其中 java开发中常用的是.properties文件

        java中的.properties文件是java原生提供的配置信息存储方式,可以直接通过java提供的api来方便的操作。


        java为了方便解析.properties文件,专门提供了相应的解析类:
        java.util.Properties

            构造方法:
                Properties() 
            方法:
                void load(InputStream inStream)      
                void load(Reader reader)  
                String getProperty(String key)  
                Enumeration<?> propertyNames()
                Object setProperty(String key, String value)   
                void store(OutputStream out, String comments)  
                void store(Writer writer, String comments) 

          **.properties文件中只能支持iso8859-1码表的内容,只能写英文 数字 和常见符号,无法包含中文,如果想在properties文件中有中文,必须转换为\u表示的形式

一、多线程

线程

进程---计算机在执行的任务或者逻辑---服务(没有界面的进程)

线程---进程中任务的一个小任务---QQ,下载软件,JVM

记事本是单进程多线程


    1.多线程概念
        进程:操作系统中独立运行的程序或服务通常就是一个进程。计算机中可以同时运行多个进程。

        线程:一个进程内部,还可以分割为多个并发的线程,每个线程都可以独立的执行相关的任务。一个线程可以认为是程序执行的一条执行路径,在需要时可以在程序中开辟更多执行路径来并行的执行任务。当在一个进程中开启多个线程从而在并行的执行多个任务时,这就是一个多线程的程序。

        当程序开启多个线程时,多个线程抢夺cpu,哪个线程抢夺到哪个线程就可以执行,通过不停的抢夺cpu,多个线程都可以得到执行,而cpu处理的速度非常的快,切换线程也非常的快,所以在人看起来这些线程似乎都在并行的执行。

        java本身就支持多线程的开发。

        jvm虚拟机本身也是个进程,可以通过代码在jvm虚拟机进程中开启多个线程,从而开发多线程的程序。

自定义线程

1. 继承 Thread类,重写其中的run方法,将线程要执行的逻辑写到run方法。通过线程对象身上的start方法来启动线程

2. 实现Runnable接口,重写的run方法。通过Thread对象来启动这个线程

3. 实现Callable<T>接口,重写的call方法。---现阶段要求知道即可

    2.java中多线程的开发
        java中有代表线程的类,java.lang.Thread
        在这个类中,提供了创建线程、启动线程、操作线程、控制线程等相关的方法,是java多线程开发的核心类。
        
        java开发多线程程序
            方式1:
                写一个类继承Thread类 覆盖父类中的run方法 在run方法中实现线程要执行的代码 就可以开发出一个线程了
                线程开发完成后并不会自动执行,需要创建改类的对象后 调用该对象的start方法,启动线程。
            
                案例:用方式1开启多线程    

package cn.tedu.thread;
/**
 * 创建线程的方式一:继承Thread方式
 */

//--步骤1:写一个类继承Thread
class MyThread extends Thread{
	@Override
	public void run() {
		while(true){
			System.out.println("看电影。。。");
		}
	}
}

public class ThreadDemo02 {
	public static void main(String[] args) {
		//--步骤2:创建MyThread类的对象
		MyThread t1 = new MyThread();
		//--步骤3:调用t1的start()方法 启动线程
		t1.start();
		
		//--主线程并发执行其他任务
		while(true){
			System.out.println("写代码。。。");
		
	}
}
}

            方式2:
                写一个类实现Runnable接口,在实现类中实现接口中定义的run方法,在run方法中编写线程要执行的代码。
                new Thread(runnable)在参数中传入runnable实现类的对象,再调用start()方法 就可以启动线程

                案例:用方式2开启多线程

package cn.tedu.thread;

/**
 * 创建线程的方式一:实现Runnable接口的方式
 */
//--步骤1:写一个类实现Runnable接口
class MyRunnable01 implements Runnable{
	@Override
	public void run() {
		while(true){
			System.out.println("看电影。。。");
		}
	}
}

public class ThreadDemo01 {
	public static void main(String[] args) {
		//--步骤2:创建Thread对象,并传入Runnable接口实现类的对象
		Thread t1 = new Thread(new MyRunnable01());
		//--步骤3:调用Thread的start()方法 真正启动底层线程 运行指定线程代码
		t1.start();
		
		//--主线程并发执行其他任务
		while(true){
			System.out.println("写代码。。。");
		}
	}
}

            
        通过观察发现,多个线程的执行的先后顺序并没有任何规律,谁抢到cpu谁就执行。

    3.线程的状态切换
        线程由如下的几个状态
            未启动:线程对象被创建出来,但是还没有执行start(),并未执行起来
            冻结:线程启动了起来,有抢夺执行权限,参与抢夺cpu,但是未抢到cpu状态下,不能得到执行。
            执行:线程启动了起来,抢到了cpu,在执行
            挂起:线程启动了起来,但是通过sleep或wait进入挂起状态,没有执行权限,不再抢夺cpu,直到sleep结束或wait()被notify或notifyAll方法唤醒,重新进入冻结状态,开始抢夺cpu
            结束:线程启动了起来,执行结束或被中断,则线程结束,释放占用的内存和cpu。

        状态转换的过程:参考图

    4.线程中的常用方法
        static Thread currentThread() 获取当前正在执行的线程
        long getId()  获取线程的编号,每个线程在执行的过程中,jvm都会为其分配独一无二的编号来唯一的进行表示,可以通过这个方法获取线程的id
        String getName()  获取线程的名称,每个线程在执行的过程中,jvm都会为其分配一个名称,这个名称可以通过getName来获取
        void setName(String name)  设置线程的名字
        Thread.State getState() 获取线程的状态
         void interrupt() 中断线程
        static void sleep(long millis) 让当前线程进入休眠状态,被挂起,放弃执行权,不再抢夺cpu,持续指定的时间
        int getPriority()  获取当前线程的优先级
        void setPriority(int newPriority)  设置当前线程的优先级 优先级的取值范围为1~10 越大优先级越高 如果不设置 优先级为5
        **注意,Thread中虽然提供了stop方法,但是此方法已经被过时,不再有效,并且没有提供替代的方法,因为在线程执行的过程中强制退出,会具有固有的不安全性,可能造成很多意外的不符合预期的结果,所以Thread中现在没有一个明确的退出线程的方法,如果想要正常的退出线程,应该通过线程内代码的设计,来实现线程可以根据不同的状态,正常退出。或者当线程真的无法正常退出,需要强制退出时,可以选择interrupt方法中断执行。

package cn.tedu.thread;

/**
 * 线程类中的常用方法
 */

public class ThreadDemo04 {
	public static void main(String[] args) throws Exception {
//		//--getId() getName() setName()
//		Thread t1 = new Thread(new Runnable(){
//			@Override
//			public void run() {
//				
//			}
//		});
//		System.out.println(t1.getId());
//		System.out.println(t1.getName());
//		t1.setName("PQThread-01");
//		System.out.println(t1.getName());
		
//		//--setPriority(int newPriority)  getPriority()
//		Thread t1 = new Thread(new Runnable() {
//			private int i = 0;
//			@Override
//			public void run() {
//				while(true)
//					System.out.println(Thread.currentThread().getId()+":"+(++i));
//			}
//		});
//		Thread t2 = new Thread(new Runnable() {
//			private int i = 0;
//			@Override
//			public void run() {
//				while(true)
//					System.out.println(Thread.currentThread().getId()+":"+(++i));
//			}
//		});
//		t1.setPriority(10);
//		t2.setPriority(1);
//		System.out.println(t1.getPriority());
//		System.out.println(t2.getPriority());
//		t1.start();
//		t2.start();
		
		//--sleep
		System.out.println("aaaa");
		Thread.sleep(3000);
		System.out.println("bbbb");
	}
}

    5.多线程并发安全问题
        当多个线程并发执行 并且 操作了同一个共享资源时,由于线程的执行先后顺序是不确定的,产生了一些意外的结果,这样的现象就称之为产生了多线程并发安全问题。
        并发安全问题产生的条件:
            多个线程参与
            有共享资源
            同时操作 且 对共享资源的修改

package cn.tedu.thread;

/**
 * 多线程并发安全问题
 */
public class ThreadDemo05 {
	public static String name = "马冬梅";
	public static String gender = "女";
	
	public static void main(String[] args) {
		new Thread(new PrintThread()).start();
		new Thread(new ChangeThread()).start();
	}
}

class ChangeThread implements Runnable{
	@Override
	public void run() {
		while(true){
			synchronized (ThreadDemo05.class) {
				if("马冬梅".equals(ThreadDemo05.name)){
					ThreadDemo05.name = "夏洛";
					ThreadDemo05.gender = "男";
				}else{
					ThreadDemo05.name = "马冬梅";
					ThreadDemo05.gender = "女";
				}
			}
		}
	}
}
package cn.tedu.thread;

public class ThreadDemo06 {
	public static int tickets = 10;
	public static void main(String[] args) {
		new Thread(new Saler()).start();
		new Thread(new Saler()).start();
	}
}

class Saler implements Runnable{
	@Override
	public void run() {
		while(ThreadDemo06.tickets>0){
			synchronized (ThreadDemo06.class) {
				ThreadDemo06.tickets--;
				System.out.println(Thread.currentThread().getId() + "窗口,卖出去了一张票。。剩余票数为" + ThreadDemo06.tickets);
			}
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

线程的执行并不是有序的,是相互抢占,而且抢占并不是只在线程执行的开始而是发生在线程执行的每一步过程中---由于多个线程之间相互抢占资源导致出现了不符合常理的情况,称之为多线程的并发安全问题

同步代码块---将可能出现问题的代码放到了synchronized中,需要一个锁对象---必须是所有线程都认识的资源---共享资源,类的字节码,this

同步方法---synchronized修饰方法---锁对象是this

死锁---产生的原因:多个线程,共享资源过多,锁对象不统一,锁的嵌套---避免死锁:统一锁对象,减少锁的嵌套

package cn.tedu.thread;

/**
 * 死锁
 */
class DYJ{}
class SMY{}

public class Demo01 {
	public static DYJ dyj = new DYJ();
	public static SMY smy = new SMY();
	
	public static void main(String[] args) {
		new Thread(new T01_A()).start();
		new Thread(new T01_B()).start();
	}
}
class T01_B implements Runnable{
	@Override
	public void run() {
		try{
			synchronized (Demo01.smy) {
				System.out.println("B使用扫描仪"+Demo01.smy);
				Thread.sleep(3000);
			}
			synchronized (Demo01.dyj) {
				System.out.println("B使用打印机"+Demo01.dyj);
				Thread.sleep(2000);
			}
			
		}catch( Exception e){
			e.printStackTrace();
		}
	}
}
class T01_A implements Runnable{
	@Override
	public void run() {
		try {
			synchronized (Demo01.dyj) {
				System.out.println("A使用打印机"+Demo01.dyj);
				Thread.sleep(3000);
			}
			synchronized (Demo01.smy) {
				System.out.println("A使用扫描仪"+Demo01.smy);
				Thread.sleep(2000);
			} 
		}catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

同步/异步---如果一个对象在某个时间段内只允许一个线程操作--同步

同步一定是安全的,不安全一定是异步的。

HashMap---异步式线程不安全

Hashtable---同步式线程安全

ConcurrentHashMap---异步式线程安全

通过等待唤醒机制调节线程之间的执行顺序---线程之间的相互通信

线程在等待期间是位于线程池中的。---线程池本质上是一个存储线程的队列

package cn.tedu.thread;

/**
 * 等待唤醒机制 案例 - 改造改变打印案例
 */
public class Demo02 {
	public static String name = "马冬梅";
	public static String gender = "女";
	
	public static void main(String[] args) {
		new Thread(new PrintThread()).start();
		new Thread(new ChangeThread()).start();
	}
}

class ChangeThread implements Runnable{
	@Override
	public void run() {
		try {
			while(true){
				synchronized (Demo02.class) {
					if ("马冬梅".equals(Demo02.name)) {
						Demo02.name = "夏洛";
						Demo02.gender = "男";
					} else {
						Demo02.name = "马冬梅";
						Demo02.gender = "女";
					}
					//唤醒
					Demo02.class.notify();
					//等待
					Demo02.class.wait();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

class PrintThread implements Runnable{
	@Override
	public void run() {
		try {
			while(true){
				synchronized (Demo02.class) {
					System.out.println("姓名:" + Demo02.name + ",性别:" + Demo02.gender);
					Demo02.class.notify();
					Demo02.class.wait();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

a1,a2,c1,c2() -> a1 running -> a1,a2,c1,c2() -> a1 running -> a2,c1,c2(a1) -> a2 running -> c1,c2(a1,a2) -> c1 running -> a1,c1,c2(a2) -> c1 running -> a1,c2(a2,c1) -> c2 running -> a1(a2,c1,c2) -> a1 running -> a1,a2(c1,c2) -> a1 running -> a2(c1,c2,a1) -> a2 running -> (c1,c2,a1,a2)

总结:sleep和wait有什么区别?

sleep方法需要指定睡眠时间,到点自然醒。释放执行权,不释放锁。被设计在Thread类上,是一个静态方法

wait方法可以指定等待时间也可以不指定,如果不指定时间需要唤醒。释放执行权,释放锁。被设计在了Object类上,是一个普通的方法、wait方法必须结合锁来使用。

package cn.tedu.thread;

import java.util.LinkedList;

/**
 * 等待唤醒机制 案例 - 实现阻塞式队列
 * @author Administrator
 *
 */
class MyBlockingQueue{
	//--用LinkedList作为队列 存储数据
	private LinkedList queue = new LinkedList<>();
	
	//--限定阻塞式队列的容量
	private int MaxSize = 0;
	
	
	public MyBlockingQueue(int size) {
		this.MaxSize = size;
	}
	
	//--如果满了还要加 要阻塞当前操作线程
	public synchronized void add(Object obj){
		try {
			if(queue.size() >= this.MaxSize){
				//--不能往里加,阻塞当前线程,直到有人取走,队列变为非满
				this.wait();
			}
			queue.add(obj);
			this.notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	//--如果空了还要取 要阻塞当前操作线程
	public synchronized Object take(){
		try {
			if(queue.size() <=0 ){
				this.wait();
			}
			Object obj = queue.poll();
			this.notify();
			return obj;
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
}

public class Demo04 {
	public static void main(String[] args) {
		MyBlockingQueue bq = new MyBlockingQueue(3);
		new Thread(new Runnable() {
			@Override
			public void run() {
				bq.add("aaa");
				bq.add("bbb");
				bq.add("ccc");
				bq.add("ddd");
				bq.add("eee");
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println(bq.take());
				System.out.println(bq.take());
				System.out.println(bq.take());
				System.out.println(bq.take());
				System.out.println(bq.take());
			}
		}).start();
	}
}





线程的状态

守护线程

守护其他线程的执行。当被守护的线程结束之后,守护线程无论完成与否都会随之结束。

只要代码中出现了守护线程,要么这个线程是守护线程要么就是被守护的线程---如果出现了多个被守护的线程,那么以最后一个被守护的线程作为结束标志。

package cn.tedu.thread;

/**
 * 守护线程 - 王者荣耀 游戏开发
 */
public class Demo05 {
	public static void main(String[] args) throws Exception {
		new Thread(new ShuiJing()).start();
		
		Thread t1 = new Thread(new Hero("安其拉")); 
		Thread t2 = new Thread(new Hero("孙悟空")); 
		Thread t3 = new Thread(new Hero("诸葛亮")); 
		Thread t4 = new Thread(new Hero("吕布")); 
		Thread t5 = new Thread(new Hero("亚瑟"));
		
		t1.setDaemon(true);
		t2.setDaemon(true);
		t3.setDaemon(true);
		t4.setDaemon(true);
		t5.setDaemon(true);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
		
		while(ShuiJing.blood>0){
			Thread.sleep(1000);
			ShuiJing.blood -= 8;
		}
	}
}

class Hero implements Runnable{
	private String name = null;
	public Hero(String name) {
		this.name = name;
	}
	
	@Override
	public void run() {
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(name+"说:为了水晶,冲啊~~~碾碎他们。。");
		}
	}
	
}

class ShuiJing implements Runnable{
	public static int blood = 100;
	@Override
	public void run() {
		while(this.blood>0){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("全军出击,守卫水晶。。血量剩余"+blood);
		}
		System.out.println("水晶被打爆了。。游戏结束。。。");
	}
}

线程的优先级

线程有1-10这10个优先级。优先级越高,理论上线程抢到资源的概率越大。但是相邻两个优先级之间几乎看不出差别,至少相差5个优先级才会略有差别。

如果没有设置优先级,默认优先级为5

单例模式

保证全局过程中只存在一个唯一实例的模式

饿汉式相对懒汉式来说耗费内存。懒汉式相对饿汉式而言,存在线程安全问题。

package cn.tedu.thread;

/**
 * 线程间的通信 - 共享内存方式传递信息
 */
public class Demo03 {
	public static boolean canRun = true; 
	public static void main(String[] args) {
		new Thread(new Thread03_Master()).start();
		new Thread(new Thread03_Slave()).start();
	}
}

class Thread03_Master implements Runnable{
	@Override
	public void run() {
		try {
			Thread.sleep(2000);
			Demo03.canRun = false;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
class Thread03_Slave implements Runnable{
	@Override
	public void run() {
		while(Demo03.canRun){
			System.out.println("小弟在运行。。。");
		}
		System.out.println("小弟告辞了。。。");
	}
}
package cn.tedu.thread;

/**
 * join
 * @author Administrator
 *
 */
public class Demo06 {
	public static void main(String[] args) throws Exception {
		System.out.println("上数学课。。。");
		System.out.println("上数学课。。。");
		System.out.println("上数学课。。。");
		System.out.println("上数学课。。。");
		Thread t = new Thread(new Thread_06());
		t.start();
		t.join();//将进入阻塞模式 直到 t执行完成
		System.out.println("上数学课。。。");
		System.out.println("上数学课。。。");
		System.out.println("上数学课。。。");
		System.out.println("上数学课。。。");
	}
}

class Thread_06 implements Runnable{
	@Override
	public void run() {
		System.out.println("做广播体操开始。。。");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("做广播体操结束。。。");
	}
}

计算可以通过网络连接,组成计算机网络,计算机之间可以通过网络进行通信,传递信息。很多应用程序也都具有网络通信能力。而java也提供了开发网络程序的编程能力,这就称之为java的网络编程

 

OSI七层网络模型

物理层 数据链路层 网络层 传输层 会话层 表示层 应用层

网络中的计算机想要互相通信,必须遵循相同的沟通方式,需要提前约定,这样提前约定的沟通方式,称之为网络协议,由于网络是分层的,每层之间都有数据要传递,一般的协议都是为某一个层数据的通信来订立的,所以一般来说一个协议通常是归属于某一层的,每一个层也有若干的协议来约定通信规则

 

协议又可以分为公有协议 和 私有协议

 

公有协议是由国际化标准组织订立的,全世界的计算机都去遵循

应用层:HTTP HTTPS FTP SMTP POP3

传输层:TPC UDP

网络层:IP协议

私有协议是公司 组织 团队 个人 自己约定的协议 只在遵循该协议的小范围内起作用

 

IP协议目前有两个版本:

IPV4:

0~255 : 0~255 : 0~255 : 0~255

其中如下网段的地址比较特殊,是内网地址:

10.0.0.0 - 10.225.225.255

172.16.0.0 - 172.31.255.255

192.168.0.0 - 192.168.255.255

特殊的ip:

本地回环地址:

127.0.0.1

0.0.0.0

 

广播地址

xxx.xxx.xxx.255

 

IPV6:

128位的二进制表示的地址,可以表示2^128个地址

 

每个计算机 除了可以分配到一个IP以外 还会划分出2^16个端口

需要网络通信的软件 可以 来占用一个端口 通过 ip:端口 在指定ip的指定端口上进行通信

虽然ip只有一个,但是端口有很多,所以可以在一个ip上利用不同端口 实现同时进行多个通信的效果

 

0~65535

其中0~1024的端口是计算机预留的端口 普通程序不可以占用

其他端口应用程序随便占用,先到先得,同一时间一个端口只能有一个程序占用,所以用完后端口会被释放,其他程序才可以再次占用。

 

主机名

IP地址可以表示网络中的主机 但是ip不易记忆 所以一帮都会选择为当前主机 指定主机名

 

域名

主机名是可能重复的 为了防止在公网上主机名重复,有了域名的概念,域名需要统一到域名管理组织中注册,从而防止重复

DNS服务器

网络中有 DNS服务器中可以 帮我们将 主机名或域名翻译成对应ip

Hosts文件

可以在本地的Hosts文件中模拟DNS的功能

windows下:

C:\Windows\System32\drivers\etc\Hosts

Linux下:

/etc/hosts

 

为了能够使开发人员开发网络相关的程序,操作系统为开发者提供了网络编程的接口,通过这套接口可以开发基于网络层 和 传输层的 代码 从而实现网络通信。物理层 和 数据链路层 由操作系统负责,不需要开发人员关注,会话层 表示层 应用层 当中的需求 则需要开发人员根据需要自己来实现。

 

这套操作系统提供的网络编程的接口 称之为socket - 套接字编程

 

**套接字不是协议 只是一套编程接口 不要搞混

  • 套接字编程 socket编程
  • 主机名 域名 DNS服务器 Hosts文件
  • 网络编程基本概念 - 端口
  • 网络编程的基本概念 - IP
  • 网络编程的基本概念 - 协议
  • 网络编程的基本概念 - 网络模型
  • 网络编程概述

    2018年8月27日

    16:12

  • 代表IP地址的类
  • 继承结构:

    java.net

    InetAddress

    在java中代表IP地址

     

    重要方法:

     

    static InetAddress

    getLocalHost()

              返回本地主机。

    static InetAddress

    getByName(String host)

              在给定主机名的情况下确定主机的 IP 地址。

    static InetAddress

    getByAddress(byte[] addr)

              在给定原始 IP 地址的情况下,返回 InetAddress 对象。

     

    String

    getHostName()

              获取此 IP 地址的主机名。

    String

    getHostAddress()

              返回 IP 地址字符串(以文本表现形式)。

     byte[]

    getAddress()

              返回此 InetAddress 对象的原始 IP 地址。

     

  • InetSocketAddress
  • 继承结构

    java.net

    SocketAddress

    |

    java.net

    InetSocketAddress

    代表socket通信过程中的IP地址

     

    重要方法

    构造方法摘要

    InetSocketAddress(InetAddress addr, int port)

              根据 IP 地址和端口号创建套接字地址。

    InetSocketAddress(int port)

              创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。

    InetSocketAddress(String hostname, int port)

              根据主机名和端口号创建套接字地址。

     

     

    InetAddress

    getAddress()

              获取 InetAddress。

     String

    getHostName()

              获取 hostname。

     int

    getPort()

              获取端口号。

     

    2018年8月27日

    16:33

  • UDP协议概述
  • UDP是TCP协议中非常重要和常用的通信协议,可以实现不可靠的网络通信

     

    特点:

    不需要创建连接

    数据以独立的数据包的形式发送 每个数据包最大64KB

    传输过程中 不保证数据一定可以到达 也不保证接受的到的数据包的顺序和发送时一致

    速度比较快

     

    ~类似于飞鸽传书

    在速度要求比较高 可靠性要求比较低 的场景下优先使用

     

  • java中的udp实现
  • 继承结构

    java.net

    DatagramSocket

    代表UDP通信的一个端

     

    重要方法

    构造方法摘要

     

     

    DatagramSocket()

              构造数据报套接字并将其绑定到本地主机上任何可用的端口。

     

    DatagramSocket(int port)

              创建数据报套接字并将其绑定到本地主机上的指定端口。

     

    DatagramSocket(SocketAddress bindaddr)

              创建数据报套接字,将其绑定到指定的本地套接字地址。

     

    void

    send(DatagramPacket p)

              从此套接字发送数据报包。

     void

    receive(DatagramPacket p)

              从此套接字接收数据报包。

     

    继承结构

    java.net

    DatagramPacket

    重要方法

    构造方法摘要

    DatagramPacket(byte[] buf, int length)

              构造 DatagramPacket,用来接收长度为 length 的数据包。

     

     

    byte[]

    getData()

              返回数据缓冲区。

    int

    getLength()

              返回将要发送或接收到的数据的长度。

    SocketAddress

    getSocketAddress()

              获取要将此包发送到的或发出此数据报的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。

     void

    setSocketAddress(SocketAddress address)

              设置要将此数据报发往的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。

     void

    setData(byte[] buf)

              为此包设置数据缓冲区。

     void

    close()

              关闭此数据报套接字。

     

     

    案例:实现UDP聊天案例

    package cn.tedu.net.udp.chat;
    
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetSocketAddress;
    import java.util.Scanner;
    
    public class ChatClient {
    	public static void main(String[] args) {
    		new Thread(new Sender()).start();
    		new Thread(new Receiver()).start();
    	}
    }
    
    /**
     * 聊天消息接受者
     */
    class Receiver implements Runnable{
    	@Override
    	public void run() {
    		DatagramSocket ds = null;
    		try {
    			//1.创建接收端
    			ds = new DatagramSocket(44444);
    			while(true){
    				//2.接受数据包
    				byte [] data = new byte[1024];
    				DatagramPacket dp = new DatagramPacket(data, data.length);
    				ds.receive(dp);
    				//3.获取接收到的信息
    				String msg = new String(data,0,dp.getLength());
    				String ip = dp.getAddress().getHostAddress();
    				int port = dp.getPort();
    				System.err.println("==收到来自["+ip+":"+port+"]的消息,消息内容为:“"+msg+"”========");
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    			throw new RuntimeException(e);
    		} finally {
    			//4.关闭接收端
    			if(ds != null){
    				ds.close();
    			}
    		}
    	}
    }
    
    /**
     * 聊天消息发送者
     */
    class Sender implements Runnable{
    	@Override
    	public void run() {
    		Scanner scan = null;
    		DatagramSocket ds = null;
    		try {
    			//1.创建发送端
    			ds = new DatagramSocket();
    			//2.创建控制台扫描器
    			scan = new Scanner(System.in);
    			while(true){
    				//3.读取控制台 消息格式 [ip#端口#消息]
    				System.out.println("===请输入要发送的消息,格式为[ip#端口#消息]:=================");
    				String line = scan.nextLine();
    				String attrs [] = line.split("#");
    				String ip = attrs[0];
    				int port = Integer.parseInt(attrs[1]);
    				String msg = attrs[2];
    				//4.发送数据
    				DatagramPacket dp = new DatagramPacket(msg.getBytes(), msg.getBytes().length);
    				dp.setSocketAddress(new InetSocketAddress(ip, port));
    				ds.send(dp);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    			throw new RuntimeException(e);
    		} finally {
    			if(ds!=null){
    				ds.close();
    			}
    			if(scan!=null){
    				scan.close();
    			}
    		}
    	}
    }

     

  • TCP协议概述
  • TCP是TCP协议中非常重要和常用的通信协议,可以实现可靠的网络通信

     

    特点:

    需要创建连接 需要三次握手

    底层建立的连接流 数据包以流的方式传递 没有传输数据量大小的限制

    传输过程中 可以保证数据一定不会丢 也不会多 也可以保证 顺序的一致

    速度比较慢

     

    在可靠性要求比较高 速度要求比较低 的场景下优先使用

     

     

    client----server  例子:

    package cn.tedu.net;
    
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    
    /**
     * TCP通信的客户端
     */
    public class Demo01Client {
    	public static void main(String[] args) throws Exception {
    		//1.创建Socket
    		Socket socket = new Socket();
    		//2.连接服务器
    		socket.connect(new InetSocketAddress("127.0.0.1", 44444));
    		//3.从客户端发送消息给服务端
    		OutputStream out = socket.getOutputStream();
    		//4.向服务端发送数据
    		String msg = "hello world~ ";
    		out.write(msg.getBytes());
    		out.flush();
    		//5.从服务端接受数据
    		InputStream in = socket.getInputStream();
    		byte [] data = new byte[1024];
    		int len = in.read(data);
    		String msg2 = new String(data,0,len);
    		System.out.println(msg2);
    		//6.关闭套接字
    		socket.close();
    	}
    }
    

     

    package cn.tedu.net;
    
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * TCP通信的服务端
     */
    public class Demo01Server {
    	public static void main(String[] args) throws Exception {
    		//1.创建服务端
    		ServerSocket ss = new ServerSocket();
    		//2.绑定指定端口
    		ss.bind(new InetSocketAddress(44444));
    		//3.等待客户端连接
    		Socket socket = ss.accept();
    		//4.接受客户端的数据
    		InputStream in = socket.getInputStream();
    		byte [] data = new byte[1024];
    		int len = in.read(data);
    		String str = new String(data,0,len);
    		System.out.println(str);
    		//5.向客户端返回数据
    		String msg = "hello net~";
    		OutputStream out = socket.getOutputStream();
    		out.write(msg.getBytes());
    		out.flush();
    		//6.关闭套接字
    		socket.close();
    		ss.close();
    	}
    }
    

    例子2

    package cn.tedu.net;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * TCP案例:通过TCP实现文件上传 - 服务端代码
     */
    
    public class Demo02UploadServer {
    	public static void main(String[] args) throws Exception {
    		//1.创建服务端
    		ServerSocket ss = new ServerSocket();
    		ss.bind(new InetSocketAddress(44444));
    		System.out.println("###千度网盘开始运行了###");
    		//2.不停接受客户端连接,一旦连接成功,交给线程处理
    		while(true){
    			Socket socket = ss.accept();
    			new Thread(new UploadRunnable(socket)).start();
    		}
    	}
    }
    
    class UploadRunnable implements Runnable{
    	private Socket socket = null;
    	public UploadRunnable(Socket socket) {
    		this.socket  = socket;
    	}
    	
    	/**
    	 * 通过私有协议传输数据 协议的格式为 [文件名长度\r\n文件名 文件长度\r\n文件内容]
    	 */
    	@Override
    	public void run() {
    		OutputStream out = null;
    		try {
    			//1.获取socket输入流
    			InputStream in = socket.getInputStream();
    			//2.获取文件名 - 读到第一个回车换行之前 截取出文件名的长度 接着读取这个长度的字节 就是文件名
    			//--读取数据 直到遇到第一个回车换行
    			//----每次从流中读取一个字节 转成字符串 拼到line上 只要line还不是\r\n结尾 就重复这个过程
    			String line = "";
    			byte [] tmp = new byte[1];
    			while(!line.endsWith("\r\n")){
    				in.read(tmp);
    				line += new String(tmp);
    			}
    			//----读取到了 文件名长度\r\n 截掉\r\n 转成int 就是文件名的长度
    			int len = Integer.parseInt(line.substring(0, line.length()-2));
    			//----从流中接着读 len个字节 就是文件名
    			byte [] data = new byte[len];
    			in.read(data);
    			String fname = new String(data);
    			
    			//3.读取文件内容  - 读到下一个回车换行之前 截取出文件内容的长度 接着读取这个长度的字节 就是文件内容
    			String line2 = "";
    			byte [] tmp2 = new byte[1];
    			while(!line2.endsWith("\r\n")){
    				in.read(tmp2);
    				line2 += new String(tmp2);
    			}
    			//----读取到了 文件长度\r\n 截掉\r\n 转成int 就是文件的长度
    			int len2 = Integer.parseInt(line2.substring(0, line2.length()-2));
    			//4.从流中读取 文件长度个字节 就是文件内容 输出到文件中
    			byte data2 [] = new byte[len2];
    			in.read(data2);
    			
    			//5.创建文件输出流指向输出位置,将数据写出到流中
    			String path = "upload/"+fname;
    			out = new FileOutputStream(path);
    			out.write(data2);
    			System.out.println("接收到了来自["+socket.getInetAddress().getHostAddress()+"]的上传文件["+path+"]");
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			//6.关闭资源
    			if(out!=null){
    				try {
    					out.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				} finally {
    					out = null;
    				}
    			}
    			if(socket != null){
    				try {
    					socket.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				} finally {
    					socket = null;
    				}
    			}
    		}
    	}
    }
    
    package cn.tedu.net;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.util.Scanner;
    
    /**
     * TCP案例:通过TCP实现文件上传 - 客户端代码
     */
    public class Demo02UploadClient {
    	public static void main(String[] args) {
    		Scanner scanner = null;
    		InputStream in = null;
    		Socket socket = null;
    		try {
    			//1.要求用户输入文件路径
    			scanner = new Scanner(System.in);
    			System.out.println("--请输入要上传的文件的路径:");
    			String path = scanner.nextLine();
    			File file = new File(path);
    			//2.只有文件存在 且 是一个文件才上传
    			if(file.exists() && file.isFile()){
    				//2.创建连接文件的输入流
    				in = new FileInputStream(file);
    				//3.创建TCP客户端对象
    				socket = new Socket();
    				//4.连接TCP服务端
    				socket.connect(new InetSocketAddress("127.0.0.1",44444));
    				//5.获取到TCP服务端的输出流
    				OutputStream out = socket.getOutputStream();
    				//6.1向服务器发送[文件名字节长度\r\n]
    				out.write((file.getName().getBytes().length+"\r\n").getBytes());
    				//6.2向服务器发送[文件名字节]
    				out.write(file.getName().getBytes());
    				//6.3向服务器发送[文件内容字节长度\r\n]
    				out.write((file.length()+"\r\n").getBytes());
    				//6.4向服务器发送[文件内容字节]
    				byte [] data = new byte[1024];
    				int i = 0;
    				while((i = in.read(data))!=-1){
    					out.write(data,0,i);
    				}
    			}else{
    				throw new RuntimeException("文件不存在 或者是一个文件夹~~");
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally{
    			//7.关闭扫描器 关闭文件输入流 关闭套接字
    			if(scanner != null){
    				scanner.close();
    			}
    			if(in != null){
    				try {
    					in.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				} finally {
    					in = null;
    				}
    			}
    			if(socket!=null){
    				try {
    					socket.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				} finally {
    					socket = null;
    				}
    			}
    		}
    	}
    }
    

    一、反射
        剖析类 得到类内部信息 来实现特定功能

        反射的核心是代表类的字节码的类 Class类

        通过获取指定类的Class信息 剖析该类 具有的属性 方法 构造方法 等等信息 这个过程就是反射的过程。

        1.Class类
            获取指定类的Class
                类名.class
                    当只有类的时用这个方法
                类的对象.getClass()
                    当具有类的对象时用这个方法
                Class.forName("类的全路径名称")
                    当既没有类也没有对象,只直到类的全路径名称的时候调用这个方法
                    这个方法将会到内存中检查是否存在该类的字节码 如果存在 则直接返回代表该类的字节码的Class对象
                    如果不存在 则先加载类到内存 返回该类的字节码的Class对象
                    所以,这个方法也是最简单的加载类的方法,当需要明确的加载某个类到内存时,可以调用这个方法
                
                    *由于一个类的字节码在内存中只有一份,而在虚拟机中会有一个Class对象代表这段字节码,所以在一个程序中,对于同一个类获取多次Class得到的将是同一个对象    

            Class中提供的方法
                通用信息
                    T cast(Object obj)  //进行强制类型转换的方法
                    Class<?>[] getInterfaces() //获取这个类实现过的所有的接口
                    String getName()  //获取Class代表的字节码的类或接口的全路径名称
                    String getSimpleName()  //获取Class代表的字节码的类或接口的简单名称(不带包名的类名)
                    Package getPackage()  //获取Class代表的类或接口的包信息
                    Class<? super T> getSuperclass()  //获取Class代表的类或接口的超类(父类)
                    boolean isInterface()  //判断当前Class是否是一个接口
                    boolean isEnum() //判断当前Class是否是一个枚举类
                    T newInstance() //创建本类的一个对象 这个方法的调用有一个前提条件 这个类必须有共有的无参构造函数
     
                构造方法信息
                    Constructor<?>[] getConstructors()  
                    Constructor<T> getConstructor(Class<?>... parameterTypes)  

                    Constructor    
                        方法:
                            String getName() //获取构造方法的名字
                            Class<?>[] getParameterTypes() //获取构造方法参数们的类型  
                            T newInstance(Object... initargs) 

                    **在java中想要创建对象几种方式?
                        方式一:new对象
                        方式二:clz.newInstance() //必须有无参构造才可以使用
                        方式三: c = cz.getgetConstructor;c.newInstance(xxx) 
                     
                方法信息
                    Method[] getMethods()  //获取类中的所有方法
                    Method getMethod(String name, Class<?>... parameterTypes)  

                    Method方法
                        String getName()  //获取方法名
                        Class<?>[] getParameterTypes()  //获取方法的参数类型
                        Class<?> getReturnType()  //获取方法的返回值类型
                        Object invoke(Object obj, Object... args) //在传入的对象上 应用传入的参数 执行当前方法
                        
                属性的信息
                    Field[] getFields()  //获取所有属性
                    Field getField(String name)  //获取指定属性

                    Field方法
                        String getName()  //获取属性名称
                        Class<?> getType() //获取属性类型
                        Object get(Object obj) //传入一个对象 获取该对象上该属性的值
                        boolean getBoolean(Object obj)  
                        byte getByte(Object obj) 
                        char getChar(Object obj)  
                        double getDouble(Object obj)  
                        float getFloat(Object obj)  
                        int getInt(Object obj)  
                        long getLong(Object obj) 
                        short getShort(Object obj) //传入一个对象 获取该对象上该属性的值 
                        void set(Object obj, Object value)  //传入一个对象 和 一个值 在该对象上将该值设置为 传入的值
                        setBoolean(Object obj, boolean z) 
                        void setByte(Object obj, byte b) 
                        void setChar(Object obj, char c) 
                        void setDouble(Object obj, double d) 
                        void setFloat(Object obj, float f) 
                        void setInt(Object obj, int i) 
                        void setLong(Object obj, long l) 
                        void setShort(Object obj, short s) //传入一个对象 和 一个值 在该对象上将该值设置为 传入的值

                操作私有和保护成员
                    Constructor<?>[] getDeclaredConstructors()  
                    Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)  
                    Method[] getDeclaredMethods()  
                    Method getDeclaredMethod(String name, Class<?>... parameterTypes)  
                    Field[] getDeclaredFields()  
                    Field getDeclaredField(String name)  
                    以上方法可以帮我们获取到私有 保护 默认的成员 但是获取到后 如果访问权限不足 仍然无法使用
                    如果想要让没有访问权限的成员可以被访问 可以调用 setAccessible 设置为true 就可以强行访问 这种方式让我们反射可以访问类中的本来没有访问权限的成员 虽然方便 但是会破坏类的封装的特性 所以在使用时一定要谨慎 充分考虑是否可以承担这样的风险。

        2.反射的用处
            在正常的代码中是用不上反射技术的,更多的是在 设计框架的过程中 使用反射
            
    二、代码调试
        1.junit测试框架
            @Test //测试方法上标注的注解
            @Before //标注的方法将在@Test方法执行前执行
            @After //标注的方法将在@Test方法执行后执行
            @BeforeClass //在测试类加载后立即执行 -- 必须写在静态方法上
            @AfterClass //在测试类销毁前立即执行-- 必须写在静态方法上

            注意@Test只能用在 公有的 非静态 无返回值 无参数的方法上

        2.Debug调试模式
            打断点
            单步执行
            单步钻入
            单步跳出
            重新执行方法
            放行断点
        

java基础最后总结,java基础加强部分

一、增强for循环
    x必须实现过Iterable接口
    for(i in x){
        
    }
二、自动封箱拆箱
    byte - Byte
    short - Short
    int - Integer
    long - Long
    float - Float
    double - Double
    char - Character
    boolean - Boolean
    jdk5以后增加了自动封箱拆箱的机制,在必要时自动的进行 基本数据类型和其包装类的类型转换,方便了代码的开发。

三、静态导入
    import xxxxx
    import static xxxxx

四、可变参数
    public void mx(int ...nums)

    **一个函数中可变参数只能使用一次 且只能出现在函数的参数列表的最后一个位置
    **可变参数也可以直接接收一个数组 非常的方便和灵活

五、枚举
    枚举相当于是一个特殊的类 无法直接创建对象 只能有在枚举内部声明的固定个的值
    本质上是一个 私有化了构造方法 的 抽象类 在内部创建这个类被final的对象 供外界使用

    **枚举类可以包含属性
    **枚举类也可以自己声明构造方法 必须是私有的
    **枚举类也可以包含普通方法
    **枚举类也可以包含抽象方法

    所有的枚举类 都 继承自Enum类 所以自动就会具有Enum类中定义的方法
    String name() //获取当前枚举项的名称
    String ordinal() //获取当前枚举项在整个枚举中是第几个项 注意从0开始数
六、泛型
    泛型就是不确定的类型

    集合泛型
        在没有泛型时,集合类时面向Object开发的,所有类型的数据都可以存入,同时所有对象存入集合后类型信息也就丢失了,需要在获取数据时进行强制类型转换,非常的不安全!
        所有在jdk5版本中java引入了泛型机制,最早应用在集合类上,称之为集合泛型

        **泛型的使用:可以两边没有 也可以一边有一边没有 也可以两边都有 但是一旦两边都有 必须一模一样 泛型中没有继承关系的概念!
        List<String> list1 = new ArrayList<String>();    
        List list2 = new ArrayList();    
        List<String> list3 = new ArrayList();    
        List list4 = new ArrayList<String>();    
        //List<Object> list5 = new ArrayList<String>();//报错    
        //List<String> list = ArrayList<Object>();//报错

    自定义泛型
        方法上的泛型
            方法上的泛型的作用范围是当前方法
            方法上的泛型 需要先定义再使用
            方法上的泛型通过尖括号 在方法的返回值前进行声明
            泛型的名称可以随意的取 但是一般都取为一个大写字母 也可以在一个方法中定义多个泛型 只需要在尖括号中通过逗号分割依次声明就可以了
            方法上的泛型 的 具体类型 是在方法被调用的时启动推测而知

        类上的泛型
            类上定义的泛型的作用范围是当前类的内部
            类上定义的泛型 需要先定义再使用
            类上的泛型通过尖括号 在类名后进行声明
            泛型的名称可以随意的取 但是一般都取为一个大写字母 也可以在一个方法中定义多个泛型 只需要在尖括号中通过逗号分割依次声明就可以了
            类上定义的泛型 需要在创建类的对象或引用时 明确的指定泛型的具体类型        
            注意,类上定义的泛型,不能在静态方法中使用,如果非要在静态方法上使用泛型,可以在该方法上自己定义自己使用。

            **集合泛型其实一点都不特殊,本质上就是在集合类上定义的类上的泛型

        上边界
            泛型是一种不确定的类型 可以在使用时为其指定为任意类型 这种灵活的特性 使我们的程序更加的灵活 但是过于灵活可能也会带来问题 有时我们需要限定这种过于灵活的特点 此时可以使用上边界 在泛型定义时 通过extends关键字 限定 泛型 必须是指定类型 或其子孙类型
            而如果不指定泛型的上边界,泛型的上边界默认为Object    
    ==============
        下边界
            下边界通过super关键字来指定 
            限定泛型的取值为指定类型或其祖先类型
            但是super不是用来在泛型定义时使用的,而是用在泛型通配符中

        泛型通配符
            泛型的通配符 使用在 类上泛型指定具体类型时 如果无法确定 泛型的具体取值 可以使用?来替代
            而泛型通配符 可以通过 extends关键字声明?取值的上边界
    ==============

        泛型的原理 - 泛型擦除:
            .java -- 编译器 编译 --> .class --> jvm 运行    
            泛型只在编译阶段起作用 在编译的过程中 编译期 根据泛型的声明进行语法检查 在检查完成 真正将代码转换为.class文件的过程中 会将泛型相关的信息 进行擦除 所谓 的擦除 具体过程是 将所有的泛型替代为 其 上边界类型 并在必要的地方 加上必须的 类型强转 和 类型检查。所有 一个.java 编译为 .class之后 所有泛型的信息将会丢失 这个过程称之为 泛型的擦除。        

七、注解
    @Override
    @Deprecated
    @SuppressWarnings
    
    注释:给人看的提示信息
    注解:给程序看的提示信息

    注解的定义:
        声明一个注解的过程 和 声明一个接口非常的类似 通过@iterface来进行声明

        声明的注解 还可以使用 元注解 来进行修饰 控制注解的行为特性
        所谓的元注解 就是 用来修饰注解定义的注解
        @Target - 控制被修饰的注解 可以在什么位置使用 如果不声明@Target 则该注解可以用在任意位置
        @Retention - 控制注解被保留到什么时候
            RetentionPolicy.SOURCE -- 该注解被保留到源码阶段 在编译的过程中该注解被删除 -- 这种注解 是给编译器看的 用来控制 编译器的行为的
            RetentionPolicy.CLASS -- 该注解在源码中 和 编译后的.class中都有保留 但是 在.class被加载为字节码的过程中 被删除 -- 给类加载器看的 控制类加载器的行为
            RetentionPolicy.RUNTIME -- 该注解 在源码中 .class中 和 内存的字节码中 都可以找到 -- 这种注解 在内存的字节码中存在 也就意味着 程序员 可以通过反射技术 得到注解的信息 来控制程序的运行 这也是 我们最常用的级别
        @Documented -- 控制该注解是否会被文档提取工具提取为文档信息 如果不写这个注解 默认 在生成的文档中 是没有该注解信息的
        @Inherited -- 控制注解是否具有继承性 如果不写 默认注解不会继承

        注解中还可以声明属性
            注解中声明属性的过程 非常类似于 在接口中定义方法 只不过 注解中声明的属性 必须是public的或者不写访问权限修饰符 默认就是public的。
            注解中的属性 只能是 八种基本数据类型 Class类型 String类型 其他注解类型 及 这些类型的一维数组
            注解中声明的属性 需要在使用注解时 为其赋值 赋值的方式 就是使用注解时 在注解后跟一对小括号 在其中 通过 属性名=属性值 的方式 指定属性的值
            也可以在声明注解时 在注解的属性后 通过default关键字 声明属性的默认值 声明过默认值的属性 可以在使用注解时 不赋值 则默认采用默认值 也可以手动赋值 覆盖默认值
            如果属性是 一维数组类型 而 在传入的数组中 只有一个值 则包括数组的大括号可以省略
            如果注解的属性 只有一个需要赋值 且该属性的名称叫做value 则在使用注解时 value= 可以不写

    反射注解:
        在Class Method Field Constructor..等类上 都有获取注解信息的方法
            Annotation[] getAnnotations()  //获取所有注解
            <A extends Annotation> A getAnnotation(Class<A> annotationClass)  //获取指定注解
            boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)  //是否在该元素上 标注过指定注解
        可以通过这些方法 反射得到 类上 方法 属性 构造器..等位置上标注过的注解 从而 通过 是否有注解 或 注解上属性的取值不同控制程序的运行过程。

        **只有@Retention 是 RetentionPolicy.RUNTIME级别的注解才可以被反射

    **注解常用作 轻量级的配置 在后续学习的框架中 有非常广泛 而 灵活的运用

八、动态代理
    改变已有对象身上不喜欢的方法

    继承     
        只能在对象还未创建之前 就继承 直接创建的是子类才可以改变方法
        如果已经有对象创建出来了 继承并重写 对该对象无作用

    装饰
        装饰设计模式可以改变 已有对象身上的方法 但是编写的方式比较麻烦 特别是原来的类 方法比较多的情况下 冗余的代码非常的多

    动态代理
        java中为了实现代理 专门提供了一个 java.lang.reflect.Proxy类 帮我们生成代理者
        通过调用Proxy的 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 就可以为原有对象生成对应的代理对象了
        之后不允许直接使用被代理对象,所有的操作都找代理对象,而调用代理对象的任何方法都会走到InvocationHandler中的invoke方法中 可以在invoke方法中编写代码 处理代理的逻辑 对于不想改造的方法 调用原有对象身上的方法 对于想改造的方法 根据自己的意愿改造

九、单例设计模式
    单例设计模式 主要的目的是 实现某个类只有一个对象 效果

    懒汉式    
        将类的构造方法私有化
        在类内部提供静态的当前类的对象 最开始为null
        当有人调用获取对象的方法时 临时判断 保存在类的内部的对象是否为null 如果为null 就赋值后返回 如果不为null 直接返回
        这种方式在多线程场景下有多线程并发安全问题的风险 需要用synchronized来同步
        效率比较低下    
    饿汉式
        将类的构造方法私有化
        在类的内部提供静态的当前类的对象 直接就赋值
        当有人调用获取对象的方法时 直接返回该对象
        效率比较高 但是 在类加载 单对象还未被使用时 就创建了对象 比较占用控制
        推荐用这种方式
           

到了这里java基础部分就结束了,想要做一个好的程序员,这些还远远不够,这些只是带你入门,

努力吧,少年!!!希望能对你有所帮助!!

猜你喜欢

转载自blog.csdn.net/g5703129/article/details/79724084