JAVA从入门到实战

本文是一篇关于Java SE的自学笔记,涵盖了从Java简介、数据类型、流程控制语句到面向对象、异常处理、多线程编程等多个章节。详细介绍了Java的特性,如跨平台性、安全性,并讨论了JVM的运行原理。还涉及了数据类型的运算符、数组、方法、面向对象概念、异常处理、工具类、集合、文件与流、多线程以及网络编程基础。
摘要由CSDN通过智能技术生成

# JAVA SE
前言:本文是我的自学Java笔记,仅记录回顾所用,还未完善勿喷,图片未添加。文章是2020年写的,忘记发了,如有不对的地方请各位师傅指出,感谢!!!(尽快完善尽快完善)

事事难上难,举足常虞失坠。件件想一想,浑身都是过差。——清·金缨

第一章-java的简介

1、java是什么?

一种编程语言
高级编程语言:c/c++,java,php,javaScript,python…
因为计算机只能认识二进数,不能识别高级语言,所以所有的高级语言都需要转变为机器语言

  • 高级编程语言分为二种:
    • 编译型语言(c/c++):

      • 需要先将源代码进行编译,转换为特定的可执行文件(平台能直接执行的二进制文件)
      • 优点:编译一次,永久执行,当你发布可执行文件给客户时不需要提供源代码, 只需要提供一个可执行的二进制文件即可,体现了安全性、执行速度快
      • 缺点:不能跨平台
    • 解释型语言(javaScript,python):

      • 需要一个解释器,在源代码执行的时候被解释器翻译为一个与平台无关的中间代码,之后再翻译成各平台的机器语言
      • 特点:夸平台
      • 缺点:运行时需要源代码,运行速度慢

2、java的发展历程

开始研究1990
Oak1991.6
HotJava1995.5
Java1.01995.5.23
Java1.11997.2.18
Java 1.21998.12.4
Java1.32000.5.8
Java1.42002.2.13
Java5.02004.9
Java6.02006.4
Oracle74亿美元收购sun2009.4.20
Java72011.7.28
Java82014.3.19
Java92017.9.21

3、java的特征

  1. java的特征
    • 半编译半解释
    • java会将代码编译成一个.class文件(不完全编译),再将.class文件解释称各平台能够识别的机器语言
    • 跨平台
    • 速度快
    • 安全
    • 健壮
    • 分布式:垂直分布式、水平分布式
    • 多线程
  2. [面试题]jvm是什么?jvm的运行原理?jvm的生命周期
    • jvm:java虚拟机(相当于一个翻译官)
    • 运行原理:java编译器先将代码编译成.class,java虚拟机再将.class文件解释成各个平台能够识别的机器语言,每个平台都有自己的jvm因为有不同系统版本的jdk。
    • 生命周期:当遇到main时候jvm开始运行,main方法运行完成的时候jvm结束;如果说有10个main同时执行,10java虚拟机在运行,他们互不影响。

4、java语言的版本

 javaSE(java核心技术)、javaEE(java企业级开发技术,javaWEB)、javaME(小型版本)

5、安装JDK

  • 无论使用什么样的编程语言开发,都要安装SDK,在java中称为JDK。JDK:JAVA开发工具包。JRE:JAVA运行环境

  • [面试题] JRE和JVM的关系?

    答:JRE=JVM+基本的数据包+JAVA依赖包

  • JDK目录结构:bin:存放可执行文件 java.exe javac.exe,lib:存放一些二进制文件和所依赖的包

6、关键字,保留字,标识符

关键字:java中特殊的英文单词,内部编译器自动识别
保留字:暂定为java使用,还没有真正成为关键字,以后用不用还不确定
标识符:包、类、方法、参数和变量的名称总称为标识符

  • 命名规则:
    规则:
    必须以字母、下划线(_)或美元符号($)开头;
    余下的字符可以是下划线、美元符号或任何的字母或数字,长度不限;
    标识符中不能有空格;
    不能使用Java中的关键字或者保留字做为标识符;

  • 规范:
    1、包:域名.公司名.项目名.种族 com.nesoft.java150219.test
    2、类名:驼峰命名法 /_/_/\ 首字母一定要大写Student , HelloWorld ,Hello_world
    3、方法名:驼峰命名法 /_/_/\ 首字母一般要小写 getName(),get(), set_sex()
    4、参数和变量名:驼峰命名法 /_/_/\ 首字母一般要小写 int maxAge
    5、常量名全部大写

7、String[] args java虚拟机默认传入的字符数组参数

第二章-数据类型

1、基本数据类型

数值型:
byte, short, int, long     一个字节8位   1G=1024m 1m=1024k 1k=1024b
byte 1字节8位   -2^7-2^7-1  ,即-127到128
short 2字节16 -2^15-2^15-1
int 4字节32位  -2^31-2^31-1  java中默认的整型
long 8字节64位 -2^63-2^63-1
long b4=12312312;  //发生了隐式转换 12312312是int类型,自动转换long
long b5=12312312L;  //一般声明Long类型数据时,在数据的后面加上L
Java中不同进制整型的表示形式
十进制 第一位不是0   例:10等
十六进制A:10 F:15     例:0xF     运算5*16^0+4*16^1=690x45
八进制   例:0125     运算:5*8^0+2*8^1+1*8^2  第一位是0
二进制 必须以ob开头  例:0b10101001                         
浮点型: double,float
double: 8字节 64位  -2^63-2^63-1     java中默认的浮点型
float: 4字节 32位  -2^31-2^31-1    float和long一样需要在变量值后加首字母
字符型:char
char 是字符时只能表示单个字符
char c2=49;  //是整数时,是一个16位无符号整数 ,jvm会找到acsii表找到整数对应acsii值

转义字符:   \    ,\n代表的是回车(换行)
             System.out.println("你好,\"中国\"");
布尔型: boolean -->false=0 true=1 一般用于判断比较

2、变量

变量 :临时存放数据的场所
局部变量:在方法中或则代码块中声明的变量,一定是初始化之后变量才能使用

数据类型的相互转换
   byte:1个字节 8位 -128~127
   short :2个字节 16位 (-2^15~2^15-1)
   int :4个字节 32位 (-2^31~2^31-1)
   long:8个字节 64位 (-2^63~2^63-1)
   浮点型:
   float:4个字节 32 位
   double :8个字节 64位
   注:默认的是double类型,如3.14是double类型的,加后缀F(3.14F)则为float类型的。
   char类型:
   char:2个字节。
   Boolean 类型:
   boolean: (true or false)(并未指明是多少字节  1字节  1位 4字节)

   1.隐式转换:就是把小精度的数值直接赋值给大精度数据类型,会发生隐式转换    
   		例:  short a1=10; int a2=a1;
   2.显示转换:就是需要使用强制转换,当大精度的类型的数值要变小精度的类型数值时需要实现强制转换;
   		但可能会导致精度缺失,发生后果自负 例:int a=10;short b=(short)a;
  3.byte、short、char之间不会相互转换,他们三者在计算时首先会转换为int类型
  4.int接收的是一个字符,jvm会去找acsii表,char接收的是一个整型是,jvm会去找acsii表

3、赋值运算符

赋值运算符: + - * /  %(求余)
1.赋值的时候从右往左计算,例将20赋给a int a=20;
2.a=a+10,可以写成a+=10; 加减乘除同理

4、算术运算符

1.++a先自增1,后运算
2.a++先运算后,后自增1

5、比较运算符

  比较运算符>、<、>=、 <=、= =、!=、instanceof
    1.比较运算符,返回boolean, if(){}的条件返回的也是boolean    
    		boolean temp=1>2;    
    		boolean t1=1!=2;
    2.==用于数值类型比较  比较就是两个数值是否相同
    		boolean t2=1==2;
    3.//instanceof 比较是否是同一个对象 用于引用类型数据
        Date d=new Date();
        Date d1=new Date();
        boolean t3=d instanceof Date;
        if(d instanceof Date) {
             System.out.println("d是日期类型,可以转为字符串类");
        }

6、逻辑运算符

 &&逻辑与,||逻辑或(),逻辑非 !
  1.&&逻辑与 表达式两边都为true才为true  表达式一&&表达式二
  2.逻辑或 || 达式一边都为true就为true  表达式一||表达式二
  3.逻辑非  对结果取反
     if(!false) {
            System.out.println("true");
       }

7、按位运算符

按位运算符byte short char int long有效
按位运算符:     &      |     ^     ~    >>     <<      >>>
1. & 按位与 & 只有参加运算的两位都为1,&运算的结果才为1,否则为0
       int i=5&2;
    /*   0101 ---5
         0010 ---2
         0000 ---0*/
2. | 按位或 | 只有参加运算的两位都为0,|运算的结果才为0,否则为1
       int j=29|5;
    /*011101  --29
      000101 --5
      011101 --29*/
3. ^ 异或 只有参加运算的两位不同,^运算的结果才为1,否则为0
       int h=29^5;
    /*0001 1101  --29
      0000 0101   --5
      0001 1000--24*/
4. << 左移 << n<<m n*2^m   符号位:0代表正数  1代表负数
       int k=5<<2;
    /*0000 0101
      0001 0100*/
5. >> 右移 >>  
       int g=5>>2;
    /*0000 0101
      0000 0001*/
6. >>>  >>>:无符号右移。无论是正数还是负数,高位通通补0

7.左移的正负数,右移是正数,都可以套用公式  n>>m n/2^m
8.注意:负数的左移右移,将原码转为补码再移动,最后将得到的结果转为原码。
	反码时原码取反, 补码是反码+1;
   	int num=-9<<2;
    /*1000 1001 --原码
     *1111 0110 --反码
     *1111 0111 --补码
     *
     *1111 011100 --左移结果
     *1111 011011 补码反反码
     *1000 100100 //36   原码
     */
9.正数右移左边空余部分补0  负数右移左边空余部分补1
    int num2=-9>>2;
    /*1000 1001 --原码
     *1111 0110 --反码
     *1111 0111 --补码
     *
     *1111 1101 --右移结果
     *1111 1100 补码转反码
     *1000 0011  //-3   原码
     */

8、按位运算和逻辑运算的区别

1.表达式一&&表达式二  ,表达式一&表达式二
2.按位与& 逻辑与&&的区别:
	&&具有短路功能,若果表达式一为false,不执行表达式二   
	&不具有短路功能 表达式一和表达式二无论如何都执行
3.表达式一||表达式二  ,表达式一|表达式二
4.按位或| 逻辑或||的区别:   
	||具有短路功能,若果表达式一为true,不执行表达式二   
	|不具有短路功能 表达式一和表达式二无论如何都执行

9、三元运算符

三元表达式:表达式1?表达式2:表达式3; 
	表达式1的结果为true时,就为第二个表达式,如果为false时,就为第三个表达式
技巧:从右往左计算
例如:boolean t1=false?true:false?false:false?true:false?true:false;结果为false

10、String类

String类: 引用类型(对象类型)和基本数据类型的区别
         只要是变量的声明都放在栈区
         基本数据类型数据存在栈区
         引用类型数据类型存在堆区
new 内存空间分配符(在堆中)
1.声明变量初始化的时候就已经是在内存中创建了一个对象
2.==用于引用数据类型比较的时候,比较的时两个对象的地址
3.但内存中有对象了,就不会重新创建,例内存中已经有zhangsan了,如果再想创建String s4=new String("zhangsan"); //这样声明在内存中创了多少个对象 1个
4.若没有,String s4=new String("lisi"); //这样声明在内存中创了多少个对象 2个
5.System.out.println("y"+y); //任何数据类型和字符串拼接都会变成字符串

第三章-流程控制语句

1、if语句

if语句 :表达式为true那么执行if语句体;if(表达式){}
(1).单分支 if(表达式){}
(2).多分支
   if(表达式){
       }else if(){
           }else{}

2、Switch语句

(1).switch (表达式){
   case 取值1:
   语句块1                      
   …
   case 取值n:
   语句块n                      
   default:          
   语句块n+1  
   }
(2).case的取值一定要和switch中的表达式的数据类型一致,case值必须是常量,不能跟变量。
(3).break:跳出switch语句
(4).switch语句的结束:
	遇到break语句或者如果满足条件执行语句块,但是没有遇到break语句会继续往下执行直到遇到下一个break语句为止,如果没有break语句一直执行到switch语句的最后

3、for循环

  (1).for(初始化表达式;循环条件表达式;循环后的操作表达式){
               执行语句块
        }
  (2).死循环产生的条件:没有循环终止条件
  (3).注意:死循环之后的代码都不能通过编译
   死循环有:
   for(;;){死循环1};
   for(int i=0;;){死循环2};
   for(int i=-1;i<=0;){死循环3};
   for(int i=0;;i++){死循环4}

4、while和do…while

  where(表达式){  表达式做条件判断--boolean  }
  do{}while() 无论如何都会执行一次语句块
  (1).while的死循环
       while(true) {死循环1};while(i<100) {死循环2};

5、变量的作用域

  (1).变量的作用域:变量要初始化之后才能使用;
       在同一个作用域范围内,变量不能重新被定义;
  (2).若变量在main方法声明,则在if或者其它语句中,不可重复声明;
       若变量在if或者其它语句中声明,则只能在其语句中可使用,离开此语句不能使用;

6、控制台输入和随机数

  (1).控制台输入:Scanner sc=new Scanner(System.in);//需要scanner包
       sc.next();//读取一个字符串,以空格结束
       sc.nextInt();//读取整数
       sc.Double();//浮点型
       sc.nextLine();// 读取控制台输入的一整行字符串数据
  (2).随机数:Randow r=new Random();
       r.nextInt(10)//表示随机获取0-9的数
       r.nextInt(10)+1//表示1-10的数

7、循环嵌套和循环中断

(1)练习循环嵌套
//  (1)循环嵌套:九九乘法表
 for(int i=1;i<=9;i++) {//行数
               for(int j=1;j<=i;j++) {//列数
                      System.out.print(j+"x"+i+"="+j*i+" ");
                }
               System.out.println();
           }
(2)循环中断(break,continue)

① break

for(int i=1;i<=9;i++) {//行数
               if(i==5){break;}//跳出整个循环
               for(int j=1;j<=i;j++) {//列数
                      System.out.print(j+"x"+i+"="+j*i+" ");
                }
               System.out.println();
           }

②continue

for(int i=1;i<=9;i++) {//行数
               if(i==5){continue;}//跳出整个循环
               for(int j=1;j<=i;j++) {//列数
                      System.out.print(j+"x"+i+"="+j*i+" ");
                }
               System.out.println();
           }

③outer

  outer:for(int i=1;i<=9;i++) {//行数
                   inner:for(int j=1;j<=i;j++) {//列数
                       if(i==5){
                           break outer;    
                       }
                          System.out.print(j+"x"+i+"="+j*i+" ");
                    }
                   System.out.println();
               }

二、数组(第四章)

1、数组:int[] arr1= {元素1,元素2…} 就一个容器

(1).特点:声明什么类型的数组就只能存放什么数据类型的数据;
              数组是定长(数组长度不可变);
(2).当声明的数组没有给定值,会有默认的值  
		String(引用数据类型)-->null  char-->' '  int-->0  double-->0.0
(3).int[] arr3=new int[5]; -->初始化时必须指定数组的长度
(4).数组的声明和创建,
     ①int[] arr={1,2};//创建并且初始化
     ②int arr[]={1,2};
     ③int[] arr=new int[5];//创建数组且长度为5;默认值为0;
     ④int[] arr=new in[]{1,2,3}
(5).声明一个int类型的数组  那么只能存放int类型数据
	int[] arr1= {'a',2,3,4,5,5,5,123}; //发生char->int
(6).若给数组单独赋值,则不能超过数组函数长度,超过则出现数组越界异常
(7).通过Arrays类中的toString方法打印输出数组
    	System.out.println(Arrays.toString(arr));

2、数组内存分析

  数组中的数据存放在堆内存,其引用在栈内存
  int[] arr2= {}; //这样声明数组,数组没有长度,没有在对内存中创建数组对象

3、数组的访问

  使用for循环访问数组  数组索引的最大值是数组长度-1(arr.length-1)

4、加强for循环 foreach 一般用于数组对象或者集合的遍历

String[] student={"a","b","c"};
    for(String s:student) {
        System.out.println(s);
    }

5、二维数组

  (1).创建方式①int a[行][列];int[][] b;int[] c [];
  (2).快速访问
  for(int row=0;row<f.length;row++) {
               for(int colum=0;colum<f[row].length;colum++) {
                   System.out.print(f[row][colum]);
               }
               System.out.println();
          }

第四章-数组

1、数组的操作

  • *什么是时间复杂度?
    平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
    线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;
    O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序
    线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

  • 关于稳定性
    稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
    不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

  • 名词解释:
    n:数据规模
    k:"桶"的个数
    In-place:占用常数内存,不占用额外内存
    Out-place:占用额外内存

    稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
    在这里插入图片描述

图片出处:https://www.runoob.com/w3cnote/sort-algorithm-summary.html

(1). 数组拷贝

System.arraycopy(src, srcPos, dest, destPos, length);
src源数组,srcPos开始复 制索引,dest目标数组,destPos开始粘贴索引,长度
System类中的数组拷贝方法对数组进行更新
在这里插入图片描述

(2). 数组的排序

在这里插入图片描述

2、其他算法

(1). 冒泡排序
//arr.length-1和arr.length-j-1的区别,
//arr.length-1就是每一个都要排序,无论是排过序的还要重新对比
//arr.length-j-1,不用重新排已经排好序的数据,提高了排序的效率

在这里插入图片描述

  • 练习:
    在这里插入图片描述
(2). 选择排序
在长度为N的无序数组中,第一次遍历n-1个数,找到最小的数值与第一个元素交换;
第二次遍历n-2个数,找到最小的数值与第二个元素交换;
。。。
第n-1次遍历,找到最小的数值与第n-1个元素交换,排序完成。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e9f0e8c47fe04b8484ed641dd9d31bf7.png)
(3). 插入排序
基本思想: 在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。
如此反复循环,直到全部排好顺序。

在这里插入图片描述

(4). 快速排序
基本思想:(分治)
先从数列中取出一个数作为key值;
将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
对左右两个小数列重复第二步,直至各区间只有1个数。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/05b1038e96ed433ca495013623a2de74.png)
(5). 归并排序

基本思想:
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。
首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。 先递归的分解数列,再合并数列
在这里插入图片描述
过程:
在这里插入图片描述

第五章-方法的定义和调用

1、方法的定义和调用

方法的声明:
[访问控制符] [修饰符] 返回值类型 方法名(参数类型 形式参数,参数类型 形式参数,…){
方法体
}

一个方法的结束的标志: 遇到return语句或者方法的结束 一个有返回值的方法只会执行一次return语句

成员变量(属性): [访问修饰符]数据类型 + 名(int age);

无参无返回值: void代表该方法没有返回值

public void getName() {
      System.out.println("getName()");
 }

有参无返回值: int age 是形式参数 --为了告诉开发者需要传入什么类型的参数

public void setName(int age) {
      System.out.println("setName()");
}

无参有返回值: return 有返回值的方法必须有return语句 返回一个和声明方法时定义的返回值类型相同数据类型值

public int getAge() {
//业务逻辑处理后的一个结果返回给调用此方法的方法
              int age=10;
              return age;
       }

注意:方法里面也可以调用其他的方法

public String getAge1() {
       String studentName=null;
        int age=getAge();
        System.out.println(age);
        if(age>5) {
            studentName="wangwu";
        }
       return studentName;
       //return 123; 必须和声明的数据类型一致
   }

有参有返回值:

public boolean setAge(int sex) {        
        boolean s =true;
       System.out.println("我的返回值是boolean");
        return true;
   }

注意:
1)调用有参的方法时,一定要给方法传入实参 --具体的参数 方法需要多少个参数,调用时就需要传多少个参数
2)有返回值的方法被调用后,获得的结果可以用对应的数据类型变量来接收 ,拿到结果后可以对结果进行进一步操作

2、方法的重载

方法的重载:
前提:在同一个类中
一同一不同:方法名相同参数列表不同(参数个数不同或者参数的类型不同);返回值可同可不同;
在这里插入图片描述

3、引用传递和值传递

引用传递:传递的是地址
值传递:传递的是值
在这里插入图片描述

内存图:
在这里插入图片描述

第六章-面向对象基础

1、类的对象

面向对象与面向过程的区别:
面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了
面向对象:是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为

类的定义、属性、方法
类的定义:把相似的对象划归成一个类。
成员变量(属性):对象共同的特征的描述

成员变量和局部变量的对比
成员变量(属性):
1)声明在类中,是对象的成员;
2)存放于堆区,在对象被创建的时候就已经初始化有了默认值

作用域:1)声明过后,成员方法都可以使用

局部变量:1)声明在方法或者语句块中;2)存在在栈区

成员方法:  1)只有实例化后才可以使用(就是创建对象 new  xxx)

注意:  
1)成员属性和成员方法只有在实例化之后才能调用
2)在创建对象(实例化)时,就已经将所有的成员变量初始化,赋予了初始值

创建对象:
1)对象就是类的实体,是具体的实例
2)类就是用来产生对象的模板
3)只要看到有new才创建对象

在堆内存中创建了一个实例(对象),引用s1在栈内存中的存放该对象的内存地址

2、方法的递归

方法的递归:自己调用自己
在这里插入图片描述

3、垃圾回收机制

垃圾回收机制:
1)当没有引用指向的时候,该对象就会被当成垃圾被回收;
2)finalize()方法,垃圾回收线程;
3)Java虚拟机的回收机制是达到某一个空间容量就会执行;

4、构造方法

构造方法(构造器):
1)方法名和类名相同
       public class xxx(){} 则其方法名为public xxx(){无参构造},public xxx(int a){有参构造}
2)没有返回值也没有void    
3)一般用public来修饰
4)在Java中,每个类都至少要有一个构造器
5)当你新建的一个类中没有写构造方法时,其实编译器默认给你添加了无参的构造方法
6)当你的类中,有有参构造时,无参构造则消失

作用:
1)初始化对象,当构造方法执行完成时就完成了对象的创建
2)当你使用new创建一个对象时,调用对应的构造方法先完成对象的初始化工作,再将对象那个创建出来
3)一般用于给成员变量初始化

构造方法可以重载:
1)当你实例化一个对象时会默认调用无参构造方法
2)当你的类中有了一个有参的构造方法时,无参的构造方法就会被删除
3)一般我们在提供有参的构造方法时也会提供一个无参的构造方法


this关键字:
1)this()括号中的参数表示你调用了哪一个构造器 ,只能在代码的第一行
2)只能在本类的构造方法中
3)调用其它重载的构造方法
4)谁创建的对象this就是谁的
在这里插入图片描述

5、类图(课外知识)

在这里插入图片描述

注意: + 代表public - 代表 private # 代表 protected Animal代表类名

public class Animal {
   public String name;
   private int age;
   protected String trueName;
   String sex;
   public String getName() {
         return name;
   }
   private void setName(String name) {
         this.name = name;
   }
}

6、包名命名规范

包名命名规范:
1)在同一个包中,类不能同名
2)包允许将类组合成较小的单元
       有助于避免命名冲突
       包允许在更广的范围内保护类、数据和方法

3)若要使用到不在本包的类时,则需要导包import 包名.类名
4)将类放入包中,语法为:package 包名 ;
5)在java中位于包中的类,在文件系统中的存放位置,
        必须有与包名层次相对应的目录结构
       每个源文件只能声明一个包
       java中的层次结构对应操作系统中的文件系统
6)Java类库中常用的包:
       java.lang
           Java默认包,任何程序中,该包都被自动导入。
       java.io
           输入/输出操作有用的类的组成。
7)[面试题]package语句作为java源文件的第一条语句  如果没有package语句,则默认为无名包java.lang不需要手动导入,任何时候jdk面向独享编程自动导入该包

7、构造代码块

8、静态代码块

public class Static {
	      int age;
	      static int price;
	      public Static() {
	             this(12);// 调用有参构造,无参构造器失效
	            {
	                   System.out.println("构造代码块");
	            }
	            System.out.println("无参构造");
	      }
	      public Static(int age) {
	            this.age = age;
	            System.out.println("有参构造");
	      }
	      static {
	            price = 50;
	            System.out.println("静态代码块");
	      }// 静态代码块在类加载的时候就已经加载到内存了
	      public static void Static() {
	            System.out.println("静态方法");
	      }
	      public static void main(String[] args) {
	            Static s = new Static();
	            s.Static();//调用静态方法,又加载一遍
	      }
	}
结果:
静态代码块
有参构造
构造代码块
无参构造
静态方法
构造代码块:永远放在对象实例化对应的构造器的第一行,会默认放上构造器中
成员代码块 :初始化成员
对象实例化的过程:静态代码块-->构造代码块-->构造方法
实例化多少个对象就调用多少次构造代码块

9、访问修饰符

访问修饰符可用于属性和方法

10、static关键字

static关键字 :静态在类中声明,属于类级别,静态资源在类加载的时候就被初始化了

static修饰变量:类变量(静态变量)
static修饰方法:类方法(静态方法)
调用方式:通过类名直接调用,也可以通过实例化过后的对象引用进行调用
注意:
1)静态方法不能修饰构造器   为什么?
  由于static修饰的方法在类加载的时候就完成初始化工作,构造器时在创建对象的时候才调用
2)静态方法中不能使用this关键字
3)静态方法里只能直接访问静态属性和方法,而不能直接访问类中的非静态属性和方法
4)静态方法不能调用成员属性和成员方法 :因为成员方法在实例化之后才能被调用,而静态方法类加载时就完成初始化

static代码块:
1)静态代码块 --完成静态属性的初始化工作
2)类加载在实例化之前就已经完成了,并且类只加载一次,但是可以实例化多次
3)当jvm虚拟机运行时会找到该类对应的.class文件加载到内存中,会初始化静态资源,自动调
4)用静态代码块完成静态变量的初始化工作
5)静态代码块:在类加载的时候自动执行

成员变量:对象级别,只能实例化过后才能调用,在对象被创建时才初始化不属于某个对象,而是所有对象共享

成员方法:对象级别,只能实例化过后才能调用,在对象被创建时才初始化

静态方法:王室   王室不访问平民

成员方法:平民   平民访问王室

11、super关键字

super关键字:
1)super会默认添加在构造器的第一行,只能出现在子类的构造器中,且必须是第一行
2)super():父类的空的构造方法
3)super()和this()不能同时出现在一个构造器中

注意:
1)实例化子类时,会默认先调用父类的空的构造方法,如果父类中没有空的构造方法时就会编译出错
2)super()中的参数,决定了调用父类哪个构造器
3)构造器只有初始化对象的作用,没有创建对象的作用,只有看见new才会创对象,构造器执行完成时就是对象创建完成的标志

super和this关键字:

super. 指向父类的引用
1)通过关键字super我们可以指定子类在构造时调用父类的哪个构造器,达到先初始化父类然后实例化子类的目的。

2)子类的构造器默认的调用父类无参构造器,即子类构造器中没有用super指明调用父类哪个构造器的话,实际上编译器会自动的在子类构造器第一行加入代码super( );

this.指向本类的引用

1)我们知道子类在实例化时必须调用父类的构造器,实际上有的子类构造器也可以先调用本类的其他构造器,然后再通过那个构造器调用父类的构造器

2)无论是调用父类的构造器还是子类的构造器,最终都是找到最顶级的父类自上而下将父类初始化再实例化子类。只要中间环节有一个构造器没找到,这个子类就无法完成实例化。

12、单例模式

单例模式:
保证一个类在内存中只存在一个实例
1)提供一个私有构造器
2)提供一个私有的静态的自身属性的变量
3)提供一个静态的返回自身类型的方法--全局访问点 public
public class TestInstance {
	    private TestInstance() {
	          System.out.println("构造器");
	    }
	    //懒汉式
	    /*private static TestInstance testInstance;
	   
	    public static TestInstance getInstance() {
	          if(testInstance==null) {
	               testInstance=new TestInstance();
	          }
	          return testInstance;
	         
	    } */
	    //饿汉式
	    private static TestInstance testInstance=new TestInstance();
	   
	    public static TestInstance getInstance() {
	          System.out.println(testInstance);
	          return testInstance;
	    }
	   public static void main(String[] args) {
	       //懒汉式
	         /* TestInstance t1=new TestInstance();
	         TestInstance t2=new TestInstance();
	         System.out.println(t1==t2);//false*/
	       //饿汉式
	          TestInstance t1=TestInstance.getInstance();
	          TestInstance t2=TestInstance.getInstance();
	          System.out.println(t1==t2);//true
	    }
	}

13、类的实例化过程

  • 子类实例化过程:类加载时第一步执行的
  • 父类Person的静态代码块->子类SonOne的静态代码块->父类Person的成员代码块->父类Person的无参构造方法->子类SonOne的成员代码块->子类SonOne的无参构造方法
  • 在子类实例化之前一定是先初始化父类

第七章-面向对象高级特性

1、继承

继承的含义:
1)继承也称为泛化
2)被继承的类称为父类(超类,基类),新的类称为子类(派生类)
3)public class 子类 extends 父类

作用:
1)子类继承类父类,子类就继承父类的所有非private修饰的方法和属性
2)提高了代码的复用率
3)扩展了父类的功能

注意:
1)一个类可以被多个类继承,但是一个类只能有一个父类,java是单继承
2)若一个类继承一个拥有子类的父类时,它将拥有父类及其子类的所有非private的方法和属性(扩展了父类的功能)

2、重写方法的覆盖

重写:
规则:
1)相同的方法名
2)相同的参数列表(参数数量、参数类型、参数顺序都要相同)
3)相同的返回值类型
@Override 表明该方法是重写了父类的方法

注意:
1)一定要有继承关系
2)返回值类型一定相同
3)方法名和参数列表必须相同
4)子类中重写的方法的访问权限不小于父类被重写的方法
5)父类中private,final,static修饰的方法不能被重写
6)父类不能重写子类的方法
7)当子类和父类有相同的方法时,编译器会识别成同一个方法,如果不重写那么就编译出错
8)如果子类和父类中不存在方法名相同的方法,那么就不存在重写

作用:
1)重写了父类的方法后,可对该方法实现自己想实现的功能
2)提高代码的复用率
3)一个方法有不同的功能
4)扩展方法功能

父类
public class Person {
	    public void getName() {
	          System.out.println("父类的getName方法");
	    }
	    public void setCar() {
	          System.out.println("父类的setCar方法");
	    }
	}
	子类
	public class Student extends Person{
	    //Student继承Person
	    @Override
	    public void getName() {
	          System.out.println("父类的getName方法");
	    }
	    public static void main(String[] args) {
	          //向上塑型
	          Person p=new Student();
	          p.getName();
	    }
	}

3、final关键字

final:
1)修饰的局部变量不能重新赋值
  修饰的方法不能被重写
  修饰的类不能被继承
2)一般不用fian来修饰成员变量,将此变量声明称常量即可static final int AGE=20;

3)常量必须给初始值



4、引用数据类型转换
子类重写类父类的方法,实例化子类后(子类 子类引用=new 子类()),通过子类引用.(点)重写父类的方法,调用的是子类重写父类的方法
塑型(造型):
1)向上造型:父类引用指向子类实例  自动转换
2)向上造型后,父类引用.重写的方法   会调用子类重写父类的方法
3)向上造型后,子类会丢失本身自有的方法(自有方法失效)
4)向下塑型  必须先经过向上塑型才能向下塑型
5)向下塑型后,自有方法生效

父类:
public class Animal {
    public void eat() {
          System.out.println("动物吃东西");
    }
    public static void main(String[] args) {
          //向上塑型
          Animal a=new Cat();
          a.eat();
          //向下塑型:必须将子类向上塑型后,才可以向下塑型
          //向下塑型后,可以调用子类的特有方法
          Cat c=(Cat)a;
          c.catchMouse();
    }
子类:
public class Cat extends Animal{
    @Override
    public void eat() {
          System.out.println("猫吃东西");
    }
    public void catchMouse() {
          System.out.println("抓老鼠");
    }
}

5、抽象方法和抽象类(abstract)

抽象方法:
1)没有方法体,没有具体的实现
2)当你不知道这个方法具体的实现(还不清楚具体的作用)
3)抽象方法不能使用final和private来修饰,因为抽方法就是用来被实现(重写后实现)的

抽象类:
1)抽象类一般是作为父类用被子类继承
2)当子类继承了一个抽象父类,如果没有实现完(重写)父类中抽象方法时,那么该子类就会变成一个抽象类

抽象类不能被实例化:但是抽象类中有构造方法

什么情况下一个类会变成抽象:
1)当类是一个抽象类的子类,并且没有实现父类的所有抽象方法,即只实现部分,这个子类就应该声明成抽象类
2)当一个类的一个或多个方法是抽象方法时;
3)当一个类实现一个接口,并且不能为全部抽象方法都提供实现时;

注意:
1)不能用fianl关键修饰抽象类
2)抽象类中可以有抽象方法,成员属性,非抽象方法,静态变量
3)抽象类中可以全部是抽象方法
4)抽象类中可以全部为非抽象方法
public abstract class CarAbstract {
	    //构造方法
	    public CarAbstract() {
	          super();
	          System.out.println("我是你爸爸的构造器");
	    }
	   
	    int age;
	    static String name;
	    //成员方法
	    public void getCar() {
	          System.out.println("Car车车");
	    }
	    public static void setCat(){
	          System.out.println("静态Car车车");
	    }
	    //抽象方法
	    /*public final abstract void driverCar() ;//not ok*/  
	    public abstract void driverCar();
	    public abstract String driverCar1() ;
	   
	    public static void main(String[] args) {
	          BigCarImpl bc=new BigCarImpl();
	          bc.age=10;
	          bc.name=" ";
	          bc.setCat();
	         
	          CarAbstract bc1=new BigCarImpl();
	          bc1.setCat();
	          bc1.getCar();
	   
	    }
	}

6、多态

多态:
行为上的多态:同一个方法,实现的功能不同
    方法的重载
    方法的重写
形体上的多态:对象的多态,类的多态
规则:
1)必须有继承或者实现接口
2)必须有重写
3)必须有向上塑型:父类引用指向子类实例
多态的优点:
1)简化代码
2)改善代码的组织性和可读性
3)易于扩展
父类:
public class Aminal {
    public void eat(){
    }
}
子类:
public class Dog extends Aminal {
	    public void eat() {
	          System.out.println("狗吃东西");
	    }
	    public void lookDoor() {
	          System.out.println("看门");
	    }
	}
测试类:
public class TestAminal {
   
    public static void main(String[] args) {
          //向上塑型
          Aminal a2=new Dog();
          a2.eat();
          //向下塑型
          Dog d1=(Dog)a2;
          d1.lookDoor();
          Dog d=new Dog();
    }
}

7、接口

接口:interface   接口的方法都是抽象方法

什么时候用到接口:
一般接口就是用来被实现的,所以它里面都是一套行为规范

注意:
1)一个类实现接口必然实现接口中的所有抽象方法,不然该类就会变成一个抽象类
2)接口不能被实例化,接口中没有构造器
3)一个类可以同时继承一个类实现多个接口,利于我们开发时的功能扩展
4)一个类只能继承一个类但是可以实现多个接口
5)接口可以多继承,当一个接口继承了多个接口那么就继承这些接口中所以抽象方法

接口中的成员:
1)抽象方法
2)常量:在接口中声明的常量一定是默认使用 public、static、final修饰,改不了
3)静态方法(jdk1.8之后有静态方法)
4)默认方法(jdk1.8之后)

作用:
1)简化代码,提高代码复用率
2)解耦合

接口:
public interface UserIfac {
	    //常量
	    public static final int AGE=10;
	    int SEX=20;  //public、static、final
	    static String name="zhsngsna"; //public、static、final
	    final int SAL=2000;//public、static、final
	    public int COMM=200;//public、static、final
	    public static void getName() {
	          System.out.println("接口中的静态方法");
	    }
	   
	    public void queryUserNameById(int userId);
	    public void queryUserId(int userId);
	    public void queryUser();
	    public void updateUserById(int userId);
	     
	    default public void deleteUserById() {
	    //主要用于实现类重写,有些类不需要全部重写接口中的所有方法,某个方法需要被重写的次数不多时可以声明称默认的方法
	    }
	    public static void main(String[] args) {
	          UserIfac.getName();
	          UserIfac u=new StudentImpl();
	          u.deleteUserById();
	    }
	}
接口的使用://实现的接口必须实现所有抽象方法,不然该类变抽象类
public class UserImpl implements UserIfac{
	    public UserImpl() {
	          super();
	    }
	    @Override
	    public void queryUserNameById(int userId) {
	          System.out.println("通过id求用户名");
	    }
	    @Override
	    public void queryUserId(int userId) {
	    }
	    @Override
	    public void queryUser() {
	    }
	    @Override
	    public void updateUserById(int userId) {
	    }
	    public static void main(String[] args) {
	    /*   UserIfac user=new UserIfac();*/
	          UserIfac user=new UserImpl();
	          user.queryUserNameById(1);
	          int a=UserIfac.AGE;
	    }
	    @Override
	    public void getAge() {
	    }
	}

8、抽象类和接口的区别

抽象类:
1)抽象类中都可以全是抽象方法也可以全是非抽象方法
2)抽象方法中可以有成员变量也可以有非抽象方法
3)类是单继承
4)抽象类中有构造方法

接口:
1)接口中只能有常量和抽象方法(jdk1.8之后能有默认的方法和静态方法)
2)接口可以是多实现
3)接口中没有构造方法
4)接口和接口之间可以多继承

什么时候声明成抽象类:
1)当一个类想拥用自己的方法和属性但是又有抽象方法时可以声明称抽象类
2)当类是一个抽象类的子类,并且没有实现父类的所有抽象方法,即只实现部分,这个子类就应该声明成抽象类
3)当一个类的一个或多个方法是抽象方法时;
4)当一个类实现一个接口,并且不能为全部抽象方法都提供实现时;

什么时候声明成接口(*****):
当你只想定义一套行为规范时可以定义成接口

9、内部类

内部类:
1)一类中的数据结构,也是用存放数据的地方
2)一个类中的成员
3)不能定义static变量
public class TestInnerClass {
   //成员变量
   int age;
   
   int phone;
  
   //静态变量
   static int sex;
  
   //成员方法
   public void getAge() {
         System.out.println("getAge");
   }
   //静态方法
   public static void getSex() {
         System.out.println("getSex");
   }
   //成员内部类  四种访问修饰符都能用  对象级别
   public class InnerClass{
         /*
      如果内部类中的成员变量和外部类的成员变量名字相同时
      调用内部类时访问该变量,内部类中的成员变量权限比较大,
      如果你想调换用外部类中的变量的话可以用TestInnerClass.this.属性来表明
          */
         int phone=10;
         public void show() {
              this.phone=10;
              /*getSex();
              age=10;
              getAge();//能访问
              System.out.println(TestInnerClass.this.phone);
         }
        
   }
   //静态内部类 类级别
    static class StaticInnerClass{
         public void show() {
              /*getAge(); not ok */
              System.out.println(sex);
         }
   }
}
测试类
public class TestClass {
    public static void main(String[] args) {
        //调换用成员内部类 先将外部类实例化 再实例化内部类
          TestInnerClass t1=new TestInnerClass();
         
          TestInnerClass.InnerClass t2=t1.new InnerClass();
          t2.show();
    /*   new TestInnerClass().new InnerClass().show();*/
          /*//调用静态内部类
          TestInnerClass.StaticInnerClass t3=new TestInnerClass.StaticInnerClass();
          t3.show();
           */
    }
}

第八章-异常处理

1、异常

异常:
异常的处理好坏,决定着你是一个什么水平的程序员
异常的类型:
check:检查性异常,在程序启动之前jvm就已经预料到有可能会发生某种异常,那么我们做预先处理,IOException

非check:(RunTimeException),这种异常不需要我们check,程序没有启动的时候我们检查不到会不会发生异常
,只有程序启动时我们出异常才知道,所以我们要很好的把控这些异常,避免这样的异常发生,NullPointerException
NumberFormatException

Throwable :异常的顶级父类
两个儿子:Error(错误,程序员搞不定)  ,Exception(我们可以控制)
1、需要check的异常:这种异常我们程序员无法控制,你只管管cherck它就可以了
一种是抛出去,交给调用该方法的人去处理,往往当你解决不了的时候抛出去,或者你不知带如何解决时,或者不由你来解决的时候,你就抛出去
一种try-catch,自己处理

2、RunTimeException:非检查性异常,不需要我们处理,但是为了程序的健壮,我们要预测,尽量避免他们发生,
所以以后我们写代码时例如非空,数字格式判断是司空见惯的事情

2、finally语句块

finally语句块:
一般和try—catch一起使用;finally一定会执行,而且是最后才执行
注意:
1)如果有finally语句块先将要返回的值存放在该return语句中,并没有马上放回,再去执行finally语句块
2)如果try或者catch中有return值 finally也有return值 那么会执行finally中的return
3)没有异常的时候执行try,错误执行catch
public class TestFinally {
	   
	    public static int getMessage() {
	          int i=0;
	          try {
	               File f=new File("D:/a.txt");
	               FileInputStream re=new FileInputStream(f);
	               i++;
	               System.out.println("try:"+i);
	               /*return i++;*/ //如果有finally语句块先将要返回的值存放在该return语句中,并没有马上放回,再去执行finally语句块
	          }catch(Exception e){
	               i++;
	               System.out.println("catch:"+i);
	               e.printStackTrace();
	          }finally {
	               //如果try或者catch中有return值 finally也有return值 那么会执行finally中的return
	               i++;
	               System.out.println("我是finally语句块");
	               return i++;
	          }
	    }
	 public static void main(String[] args) {
	      System.out.println("输出结果"+getMessage());
	}
	}

3、object

Object是所有类的顶级父类,在Object类中定义的方法,在所有类中都可以使用。

1)==符号用于数值型比交的时候比较的是值是否相同,用于引用类型比较时比较的是物理地址

2)equals在Object类中的作用是比较两个对象物理地址是否相同

3)equals在String类中作用是先比较两个对象的地址,如果相同那么放回true,如果不同再去比较两个字符串的内容,如果内容相同返回true
在这里插入图片描述

4)所以要清楚的知道,equals方法在不同类中的作用有可能不同
在这里插入图片描述

4、哈希值

hash: 是一种函数,它就是散列算法
hash表:用来存放通过hash算法计算过后得到的值
hashCode:它其实就是经过hash算法之后得到的值,总的来说:hashCode就是存放在hash表中
hashCode相同的两个对象,不一定equals(物理地址相同),但是物理地址相同的两个对象一定equals

*hashCode不同的两个对象,他们一定不equals
hashCode的作用:用来比较两个对象是否相同,我们以后学到的HashMap包括字符串常量池中的字符串的比较等 hashMap中不能有重复对象和值

快速判断两个对象:
假如我有1000个对象需要存放到hashMap中,当你每一次存放都要与前面已经存放的对象进行比较吗?如果用equals方法就是需要全部比较
存到998个对象时,第999个对象那个就要和前面的998个对象进行比较
但是你有了hash表以及hashCode,假如你的hash表空间只有10;当你第501个对象需要存到hashMap中,首先获取该对象的hashCode值与
hash表中的hashCode比较,找到相同的hashCode,然后与hash表中相同的hashCod的少数对象进行equals(物理地址比较)比较
          //HashCode()通过hash算法将对象的物理地址转为16进制哈希表地址
          Object o1=new Object();
          Object o2=new Object();
          String s="zhangsan";
          String a="zhangsan";
          System.out.println(o1);
          //java.lang.Object@7852e922物理地址  永远唯一
          System.out.println(o1.hashCode());
          //2018699554 通过这个方法我可以获取到该对象在hash表中的地址
          System.out.println(o2.hashCode());//1311053135

第九章-工具类

1、包装类

(1).包装类:

Java编程语言不把基本数据类型看作对象。Java 编程语言提供包装类来将基本数据类型看作对象。
在功能上包装类能够完成数据类型之间(除boolean)的相互转换,尤其是基本数据类型和String类型的转换。
包装类中包含了对应基本数据类型的值,封装了String和基本数据类型之间相互转换的方法,还有一些处理这些基本数据类型时非常有用的属性和方法。
数据类型    包装类
boolean    Boolean
byte       Byte
char       Character
double     Double
float      Float
int        Integer
long       Long
short      Short

(2). 包装类常用方法和属性

(3).字符串与基本数据类型、包装类型转换图

(4).转换成包装类

int-->String

(5). 包装类转换为其他类型

(6).自动装箱和自动拆箱

//jdk1.5之后基本数据类型和封装类之间的转换变的简单了许多
           /*自动装箱*/
           int a6=10;
           Integer i6=9;
           Integer i7=a6;
           /*自动拆箱*/
           int a7=i7;

(7).面试题

//包装类中的数据存在方法区中的常量池中
//Integer k3 = 200;    
自动装箱如果数据的范围在-128-127之间不会创建对象,还是实现的数值,否则创建对象
          Integer k6 = -2;
          Integer k7 = -2;
          System.out.println(k6==k7);//true  调用了intValue方法
         
          Integer k3 = 200;
          Integer k4 = 200;
          System.out.println(k3==k4);//false
          System.out.println(k5.hashCode());// 100不是hash码值,是对应数值

(8).字符串常量池

public class Student {
    int age;
    String name;
    public static void main(String[] args) {
          Student s1=new Student();
          s1.name="zhangsan";
          s1.age=20;
         
          String ename="zhangsan";
          System.out.println(s1.name==ename);//true
          Student s2=new Student();
          s2.name="lisi";
          s2.age=30;
         
          Student s3=new Student();
          s3.name="lisi";
          s3.age=30;
         
          System.out.println(s2.name==s3.name);
          //true 一个类只有一个字符串常量池,所有对象共享
         
    }
}

在这里插入图片描述

2、Character

Character 类用于对单个字符进行操作;
Character 类在对象中包装一个基本类型 char 的值;

(1).character的方法:

public class TestCharacter {
    public static void main(String[] args) {
          //Character
          Character ct=new Character('A');
          System.out.println(ct);//A
          System.out.println(ct.isDigit('A'));
          //false  判断一个字符是否为数字
          System.out.println(Character.isDigit('8'));
          //true    判断是否为数字
          System.out.println(Character.isLetter('A'));
          //true    判断是否为字母
          System.out.println(Character.isLowerCase('A'));
          //false    判断是否为小写字母
    }
}

(2).转义序列

前面有反斜杠(\)的字符代表转义字符,它对编译器来说是有特殊含义的。

3、字符串类

(1).字符串类:

字符串是我们在编程中最常使用的一种数据类型,它的表现形式可以分为两种:
String
StringBuffer。
字符串不属于8种基本的数据类型,而是一种引用类型。
String对象代表一组不可改变的Unicode字符序列,对它的任何修改实际上又产生一个新的字符串,String类对象的内容一旦被初始化就不能再改变。
StringBuffer对象代表一组可改变的Unicode字符序列。
String类是final类型的类。

(2).String类

1)String的创建:String的创建:
有两种方式:
①静态方式(常用):像是给变量直接赋值一样来使用。如:String s1 = “abc”; String s2 = “abc”;

在这里插入图片描述

②动态方式:动态的内存分配,使用new运算符。如:String s3= new String(“abc”); String s4= new String(“abc”);

两种方式创建的字符串区别:

使用静态方式创建的字符串,在方法区的常量池中只会产生唯一一个字符串对象,使用该方式产生同样一个字符串时,内存中不再开辟另外一块空间,而是两个引用变量指向同一个字符串对象。
使用动态方式创建的字符串,在堆内存中会产生出不同的对象。

(3).String类构造方法

(4).String的对象创建
在这里插入图片描述

4、String的特点

String类的特点:
任何一个String对象在创建之后都不能对它的内容作出任何改变(immutable)
连接、获得子串和改变大小写等操作,如果返回值同原字符串不同,实际上是产生了一个新的String对象
在程序的任何地方,相同的字符串字面常量都是同一个对象
String类重置了equals方法,用于比较两个字符串的内容

5、String类中常用的方法

在这里插入图片描述

//equals 先比较两个字符串的地址是否相同,如果相同那么true,地址不同那么比较内容
	          String s1="zhangsan";
	          String s2="Zhangsan";
	          System.out.println("s1.equals(s2):"+s1.equals(s2));//false
	          // equalsIgnoreCase 忽略大小写比较两个字符串的内容
	          System.out.println("s1.equals(s2):"+s1.equalsIgnoreCase(s2));//true
	
	//将字符串转大写
	          String s3=s2.toUpperCase();
	          System.out.println(s3);//ZHANGSAN
	
	//charAt();返回指定索引处的 char值  字符串索引从0开始
	          char c1=s1.charAt(7);
	          System.out.println("c1:"+c1);
	
	//substring(index), 字符串截取 ,包括开始索引
	          String s4="www.baidu.com";
	          String s5=s4.substring(2);
	          System.out.println(s5);//www.baidu.com
	          /*System.out.println(s4.substring(-1));索引越界*/
	
	//substring(begin,end), 字符串截取 ,包括开始索引,不包括结束索引
	          String s6=s4.substring(2,6);
	          System.out.println(s6);
	
	//indexOf(char) 返回字符串中某字符第一次出现的索引位置  如果找不到返回-1
	          int indexOne=s4.indexOf('.');
	          int indexThree=s4.indexOf(']');
	          System.out.println("indexOne:"+indexOne);//11
	          System.out.println("indexThree:"+indexThree);//-1
	
	//lastIndexOf(char)返回字符串中某字符最后一次出现的索引位置 如果找不到返回-1
	          int indexTwo=s4.lastIndexOf('.');
	
	//indexOf(char,int) 从索引处(包括索引在内)返回字符串中某字符第一次出现的索引位置  如果找不到返回-1
	          int index1=s4.indexOf('.',11);
	
	//indexOf(String) 返回字符串中某子串第一次出现的索引位置  如果找不到返回-1
	          System.out.println(s4.indexOf("com"));
	
	//lastIndexOf(String) 返回字符串中某子串最后一次出现的索引位置  如果找不到返回-1
	          System.out.println(s4.lastIndexOf("o"));
	
	//indexOf(String,int) 从索引处(包括索引在内)返回字符串中某子串第一次出现的索引位置  如果找不到返回-1   
	www.baidu.com 从左往右
	          System.out.println(s4.indexOf("com",12));//-1
	          System.out.println(s4.indexOf("com",11));//-1
	          System.out.println(s4.indexOf("com",10));
	
	//lastIndexOf(char,int) 从索引处(包括索引在内)返回字符串中某子串从索引位置开始往左找第一个出现的字符的索引位置  如果找不到返回-1  
	从右往左 当int小于com中的c索引是返回-1
	          System.out.println(s4.lastIndexOf("com",12));
	
	//trim() 返回新的字符串,忽略前导空白和尾部空白
	          System.out.println("      baidu     ".trim());// concat将指定字符串连接到此字符串的结尾
	          String s9="hell";
	          String s10=s9.concat("o world");
	          System.out.println(s9);//hell
	          System.out.println(s10);//hello world
	
	//split 劈开根据给定正则表达式的匹配拆分此字符串。  注意 "."不成立
	          String s11="com/neusoft/util";
	          String[] ss1=s11.split("/");
	          System.out.println(Arrays.toString(ss1));//[com, neusoft, util]
	//replace返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的
	          String s13="wuyuehong";
	          String s14=s13.replace('y','k');//wukuehong
	         
	          String s15=s13.replaceAll("wu", "wuwu");
	          System.out.println(s15);//wuwuyuehong
	
	//endsWith 测试此字符串是否以指定的后缀结束
	          String s16="123.jpg";
	          if(s16.endsWith(".jpg")) {
	               System.out.println("是图片");
	          };
	
	//startsWith 测试此字符串是否以指定的前缀
	          boolean temp=s16.startsWith("123");
	          System.out.println(temp);//true
	
	//编码 字符串--byte数组
	          String s6="我爱你中国";
	          byte[] b1=s6.getBytes();
	          //utf-8的编码下,一个中文3个字节
	          //[-26, -120, -111, -25, -120, -79, -28, -67, -96, -28, -72, -83, -27, -101, -67]
	          System.out.println(Arrays.toString(b1));
	          //解码 byte数组--字符串
	          String s7=new String(b1);
	          System.out.println(s7);
	
	//String 变量名 = new String(byte[] bytes, int offset, int length, Charset charset);
	          /**bytes  要解码 的字节数组
	           * offset  开始索引 索引从0开始
	           * length  长度
	           * charset 解码格式
	           */
	          String s10=null;
	          try {
	               s10=new String(b3,2,b3.length-2,"GBK");
	          } catch (UnsupportedEncodingException e) {
	               // TODO Auto-generated catch block
	               e.printStackTrace();
	          }
	          System.out.println(s10);

6、StringBuffer类

(1).Stringbuffer类:

可以使用StringBuffer来对字符串的内容进行动态操作,不会产生额外的对象;
字符串超过指定容量时会自动扩充;

缓冲区的作用:
在于节省内存;
多线程并发操作时,提高操作字符串的速度;

(2).StringBuffer类常用方法

	//创建一个默认容量为16的字符串缓冲区
	          StringBuffer sb1=new StringBuffer();
	
	//创建一个容量为16+指定字符串长度大小的字符串缓冲区,append()也可以将一个对象的内容添加到另一对象上
	          StringBuffer sb2=new StringBuffer("你好");
	          sb2.append(",中国");//添加
	          System.out.println(sb2);//你好中国
	          System.out.println(sb2.capacity());//当前容量
	
	//创建一个指定容量的字符串缓冲区
	//如果超过指定容量之后 当超出指定容量时候(指定容量+1)*2 ,
	//但是小于(指定容量+1)*2容量还是(指定容量+1)*2,
	//超过(指定容量+1)*2 ,容量等于字符串的长度
	          StringBuffer sb3=new StringBuffer(5);
	          sb3.append("www.baidu.com");
	          System.out.println(sb3.capacity());
	
	//面试题  字符串作为常量相加的时不会再内存中创建对象
	          String s2="c"+"b"+"a";//创建一个对象


​	
​	
	//setCharAt
	StringBuffer sb7=new StringBuffer("hahaha");
	          sb7.setCharAt(1, 'u');
	
	StringBuffer sb8=new StringBuffer("asddasfasd");
	          //包括开始索引,不包括结束索引
	          sb8.delete(1, 3);//删除索引1-3(不等于三)之间的字符
	          System.out.println(sb8);
	
	//将字符串插入此字符序列中
	          String k="nihao";
	          sb9.insert(2, k);
	          System.out.println(sb9);//abnihaoc123abc
	          //将字符插入此字符序列中
	          sb9.insert(3, 'y');
	          System.out.println(sb9);//abnyihaoc123abc

7、StringBuilder类

1)StringBuilder类

2)StringBuilder常用方法

8、String类,StringBuffer与StringBuilder的区别

(1).String 类与StringBuilder的比较:

Java中定义了String与StringBuffer两个类来封装对
字符串的各种操作
String类与StringBuffer类都被放到了java.lang包中
两者的主要区别在于
String类对象中的内容初始化不可以改变
StringBuffer类对象中的内容可以改变

(2).StringBuffer与StringBuilder的区别

StringBuffer和StringBuilder都是长度可变的字符串。
两者的操作基本相同。
两者的主要区别在于
StringBuffer类是线程安全的;
StringBuilder类是线程不安全的。
StringBuffer在JDK1.0中就有,而StringBuilder是在JDK5.0后才出现的。
StringBuilder的一些方法实现要比StringBuffer快些。

9、Math类

(1).Math类常用方法

Math类中都是一些对数值进行操作的静态方法

**

方法含义
static int abs(int)返回参数的绝对值,返回值类型与参数类型相同
static double abs(double)返回参数的绝对值
static double ceil(double)返回大于所给参数的最小的整数值
static double floor(double)返回不大于所给参数的最大的整数值
static int max(int a,int b)返回两个int值中较大的一个
static double max(double,double)返回两个double值中较大的一个
static int min(int a,int b)返回两个int值中较小的一个
static double min(double,double)返回两个double值中较小的一个
static double random( )返回在0.0~1.0之间的随机的double值
static int round(double)返回同所给值最接近的整数,采用4舍5入法
static double sin/cos/tan(double)返回给定的弧度值对应的三角函数值
static double sqrt(double)返回所给值的平方根,若所给值为负数则返回NaN
public class TestMath {
	    public static void main(String[] args) {
	          //abs 返回参数的绝对值,返回值类型与参数类型相同
	          int a=Math.abs(-154);
	          System.out.println(a);//154
	         
	          //ceil返回大于所给参数的最小的整数值
	          double a1=Math.ceil(98.2);
	          System.out.println(a1);//99.0  向上取整
	         
	          //floor返回不大于所给参数的最大的整数值
	          double a2=Math.floor(98.98);
	          System.out.println(a2);//98.0   向下取整
	         
	          System.out.println(Math.max(12, 15));//15
	          //random返回在0.0~1.0之间的随机的double值
	          double d=Math.random();
	          System.out.println(d);
	         
	          //四舍五入 返回同所给值最接近的整数,采用4舍5入法
	          double d1=98.98;
	          long d2=Math.round(d1);
	          System.out.println(d2);//99
	         
	          //南粤风采30选7  
	          for(int i=0;i<7;i++) {
	               long l=Math.round(Math.random()*30);
	               System.out.println(l);
	          }
	                         
	    }
	}

10、日期类

1.Date 类

java.util.Date。
Date 类表示指定的时间信息,可以精确到毫秒。
不支持国际化。
该类的许多方法已过时。
获取当前系统时间:new Date();

2.Date常用的方法

方法含义
boolean after(Date when)测试此日期是否在指定日期之后
boolean before(Date when)测试此日期是否在指定日期之前
int compareTo(Date anotherDate)比较两个日期的顺序。如果参数 Date 等于此 Date,则返回值 0;如果此 Date 在 Date 参数之前,则返回小于 0 的值;如果此 Date 在 Date 参数之后,则返回大于 0 的值。
boolean equals(Object obj)比较两个日期的相等性。
方法的使用例子,比较两个日期谁最大:
public class TestDateMethod {
	    //传入两个字符串将其转为日期类型,并返回两个日期之间的最大的日期
	    public static Date getMaxDate(final String s1,final String s2) throws ParseException {
	          if("".equals(s1)||"".equals(s2)) {
	               return null;
	          }
	          SimpleDateFormat sf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
	          Date d1=sf.parse(s1);
	          Date d2=sf.parse(s2);
	          //测试此日期是否在指定日期之后
	          /*boolean temp=d1.after(d2);// d2时指定日期
	          if(temp) {
	               return d1;
	          }
	          return d2;*/
	          /*boolean temp=d1.before(d2);
	          if(temp) {
	               return d2;
	          }
	          return d1;*/
	          /*比较两个日期的顺序。
	           * 如果参数 Date 等于此 Date,则返回值 0;
	           * 如果此 Date 在 Date 参数之前,则返回小于 0 的值;
	           * 如果此 Date 在 Date 参数之后,则返回大于 0 的值。
	           */
	          int i=d1.compareTo(d2);
	          if(i==0) {
	               return d1;
	          }else if(i<0){
	               return d2;
	          }else {
	               return d1;
	          }
	    }
	    public static void main(String[] args) throws ParseException {
	          //声明两个String类型变量
	          String s1="2020-3-23 10:23:56";
	          String s2="2019-2-23 15:23:56";
	          //编写转换格式的方法
	         
	          //调用转换格式的方法,获取最大日期
	          Date maxDate=getMaxDate(s1, s2);
	          System.out.println(new java.sql.Date(maxDate.getTime()));
	    }
	}

2.1.Calendar类

2.2.1.Calendar类

java.util.Calendar。
java.util.GregorianCalendar
常用的日期处理的对象。可以设置自己的时区和国际化格式。
是一个抽象类。
Calendar 抽象类定义了足够的方法,让我们能够表述日历的规则 。
获取Calendar对象的实例:
Calendar c = Calendar.getInstance();
设置Calendar实例所表示的时间:
c.set(2011,6,16);
获取指定的时间属性    
c.get(Calendar.YEAR);

2.2.2.Calendar的常用属性

属性                 含义
static int HOUR----小时时间
static int MINUTE----分时间
static int SECOND----秒时间
static int DATE----日期的Date部分
static int MONTH----日期的Month部分
static int YEAR----日期的年部分


3.1. SimpleDateFormat类

3.3.1.SimpleDateFormat类 :java.text.SimpleDateFormat

一个以与语言环境相关的方式来格式化和分析日期的具体类。是抽象类java.text.DateFormat类的子类。
SimpleDateFormat使得可以选择任何用户定义的日期-时间格式的模式。

3.3.2.SimpleDateFormat类的使用

获取SimpleDateFormat的实例
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
将日期格式成指定的字符串
sdf.format(new Date());
将格式化的字符串转换成日期对象
sdf.parse(“2011-07-16”);

3.2.SimpleDateFormat格式说明

5、Date,.Calendar,SimpleDateFormat的使用

Date
format将Date--String

parse将String--Date
注意:String的格式是什么样的那么SimpleDateFormat具体的格式也必须什么样

1.Date
//Tue Mar 03 09:53:22 CST 2020
          Date d1=new Date();//创建一个当时时间的时间对象
          System.out.println(d1);
          System.out.println(d1.getTime()); //将当前日期转为毫秒值表示  距离1970年

2.Calendar
          Calendar c1=Calendar.getInstance();
          c1.set(2008, 8, 1);
          System.out.println(c1);
          System.out.println(c1.get(Calendar.YEAR)+"年"+c1.get(Calendar.MONTH)+"月"+c1.get(Calendar.DATE)+"日");
          System.out.println(Calendar.YEAR);//1

3.SimpleDateFormat
          String param="2012-12-12";
          SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd");
          Date d2=null;
          try {
               d2=sf.parse(param);
          } catch (ParseException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
          }
          //Sun Jan 01 00:00:00 CST 2012
          System.out.println(d2);
          
4.format
          Date d3=new Date();
          System.out.println(d3);//Tue Mar 03 10:13:41 CST 2020
          String date1=sf.format(d3);//将Date转String
          System.out.println(date1);//2020-03-03

5.java.util.Date与java.sql.Date之间的转换
          /*java.util.Date--java.sql.Date*/
          Long d5=d3.getTime();//java.util.Date转换成毫秒值
          java.sql.Date d6=new java.sql.Date(d5);
          System.out.println(d6);//2020-03-03

          /*java.sql.Date--java.util.Date*/
          Date d7=new Date(d6.getTime());
          System.out.println(d7);//Tue Mar 03 10:28:11 CST 2020

第十章-集合

1、Java中的集合类

集合概述
	Java中集合类是用来存放对象的
	集合相当于一个容器,里面包容着一组对象 —— 容器类
	其中的每个对象作为集合的一个元素出现
	Java API提供的集合类位于java.util包内
Java中数组与集合的比较
	数组也是容器,它是定长的,访问较快,但是数组不会自动扩充
	数组可以包含基本数据类型或引用类型的对象,而集合中只能包含引用类型的对象
Java中集合框架层次结构

2、Collection接口

Collection接口:是所有集合的父接口

collection的方法
          //声明一个set集合
          Set set1=new HashSet<>();
          set1.add(null);
          set1.add(123);

          //以后我们这样声明集合 规定一个集合中只能存放什么样类型的数据
          Set<Integer> set2=new HashSet<>();

          //addAll集合中加入另外一个集合对象
          Set<Integer> set3=new HashSet<>();
          set3.addAll(set2);
          System.out.println(set3.size());//6
         
          //isEmpty()判断集合是否为空
          System.out.println(set3.isEmpty());//false
         
          //判断集合中是否有该对象
          System.out.println(set3.contains(1238));//true
          //toArray()返回一个包含所有元素的对象数组
          Object[] is=set3.toArray();
          System.out.println(Arrays.toString(is));
         
          //remove 从集合中删除对象 返回boolean值
          /*boolean temp=set3.remove(1230);
          System.out.println(set3);*/
         
          //removeAll清空指定集合
          /*boolean temp1=set3.removeAll(set2);
          System.out.println(temp1+"--"+set3);*/
         
          //retainAll 取set3中有的,set2中也有的
          /**
           * 仅保留此 collection 中那些也包含在指定 collection 的元素
           */
          boolean temp2=set3.retainAll(set2);
          System.out.println(temp2+"--"+set3);
`          
          交集
          Set<Integer> set4=new HashSet<>();
          set4.add(123);
          set4.add(456);
          set4.add(900);
          Set<Integer> set5=new HashSet<>();
          set5.add(123);
          set5.add(456);
          set5.add(789);
          boolean temp3=set4.retainAll(set5);
          System.out.println(temp3+"--"+set4);
         
          //清空集合
          set4.clear();
          System.out.println(set4);

		1.Set接口
		2.Collection的子接口
		3.用来包含一组 无序无重复 的对象
		无序 — 是指元素加入集合中的顺序和集合内存储的顺序不同;
		无重复 — 两个对象e1和e2,如果e1.equals(e2)返回true,则认为e1和e2重复,在set中只保留一个。
		
		Set接口的实现类
		
		HashSet(值可为null)
		数据结构:散列集合=数组+链表
		Set集合的特点:
		三无产品:无重复值,无索引,无序
		相对于treeSet,HashSet效率更高  ,有且只有一个null
		为什么重复值:
		先获取每个对象的hashCode值,比较hashCode值,若果相同才使用equals方法比较两个对象是否相同
		TreeSet(值不能为空),按照升序排列遍历集合中的元素
		List集合
		      list集合:有重复值,有序,有索引,有null  线性表
		ArrayList: 基于数组,顺序存储:逻辑地址有序,对应的物理地址也有序
		查询数据的时候速度快
		但是做插入删除速度慢
		LinkedList:基于双链表,链式存储:逻辑地址有序,对应的物理地址不一定有序
		可操作性强,做元素插入,删除,修改速度快
		但是查询速度慢
		Collection的子接口
		用来包含一组 有序有重复 的对象
		List中的元素都对应一个整数型的序号,记载其在容器中的位置,可以根据序号存取容器中的元素
		List有两种主要的集合实现类:

		ArrayList,以数组的形式保存集合中的元素,如下图
		LinkedList,以链表结构(是一种数据结构)保存集合中的元素,如下图
		ArrayList元素之间的内存地址是连续的
		LinkList元素之间内存地址不连续
		List接口常用方法
		ArrayList:
		          List<Integer> list1=new ArrayList<>();
		          list1.add(4);
		          list1.add(null);
         
          //指定索引位置调加元素
          list1.add(3, 456);
         
          //在list中查询第一个元素的索引值,如不存在,返回-1
          int i=list1.indexOf(12);
          System.out.println(i);//5
         
          //lastIndexOf(o)是返回最后一个匹配元素的index,如不存在,返回-1
          int i1=list1.lastIndexOf(12);
          System.out.println(i1);//6
         
          //remove(index)移除列表中指定位置的元素  索引最大值是集合长度
          /*list1.remove(0);
          System.out.println(list1);*/
         
          //用指定元素替换列表中指定位置的元素  返回被替换的元素
          Integer i3=list1.set(0, 90);
          System.out.println(list1+"--"+i3);

也可以将一个对象添加到ArrayList中
          Product p1=new Product();
          p1.proName="澳大利亚进口红酒 杰卡斯(Jacob’s Creek)经典系列西拉干红葡萄酒 750ml";
          p1.proNum=1000;
          p1.proPic=79.0;
          p1.proWeight=7.9;
         
          Product p2=new Product();
          p2.proName="奔富 Penfolds 洛神山庄Rawson's Retreat红葡萄酒设拉子赤霞珠750ml*6";
          p2.proNum=2000;
          p2.proPic=34.0;
          p2.proWeight=8.8;
         
          List<Product> list2=new ArrayList<>();
          list2.add(p1);
          list2.add(p2);

          //访问list集合   List集合有索引,索引从o开始
          System.out.println(list2.get(0));
          System.out.println(list2.get(1));*/
         
          //快速访问   用for循环访问ArrayList速度比用迭代器快,因为他有索引
          /*for(int i=0;i<list1.size();i++) {
               System.out.println(list1.get(i));
          }*/

          增强for循环
          /*for(int i:list1) {
               System.out.println(i);
          }*/

          迭代器遍历ArrayList
          /*Iterator it=list1.iterator();
          while(it.hasNext()) {
               System.out.println(it.next());
          }*/

实现类的初始化
ArrayList的构造方法
List 变量名 = new ArrayList() ;
List 变量名 = new ArrayList(int capacity) ;
List 变量名 = new ArrayList(Collection c) ;
LinkedList类的构造方法
List 变量名 = new LinkedList() ;
List 变量名 = new LinkedList(Collection c) ;
ArrayList和LinkedList


LinkedList增加方法
LinkedList:List l1=new LinkedList<>();
l1.add(4);
l1.add(5);
l1.add(1);
l1.add(16);
l1.add(12);
l1.add(12);
l1.add(null);
l1.add(null);
l1.add(3, 6);
System.out.println(l1.get(6));
System.out.println(l1);

          //LinkedList自有的方法
          LinkedList<Integer> l2=new LinkedList<>();
          l2.add(4);
          l2.add(5);
          l2.add(1);
          l2.addFirst(45);
          System.out.println(l2);
ArrayList与LinkedList的比较
存储结
ArrayList是线性顺序存储
LinkedList对象间彼此串连起来的一个链表
操作性能
ArrayList适合随机查询的场合
LinkedList元素的插入和删除操作性高
从功能上,LinkedList要多一些
集合的转换
          //将HashSet集合转为ArrayList集合
          Set s1=new HashSet<>();
          s1.add(123);
          s1.add("张三");
         
           List l1=new ArrayList<>(s1);
          l1.add(2, 0);
          l1.add(2, 0);
          l1.add(2, 0);
          l1.add(2, 0);
          System.out.println(l1);
         
          //将ArrayList转为HashSet集合
          Set s2=new HashSet<>(l1);
          System.out.println(s2);
         
          //ArrayList转为LinkedList
          LinkedList l2=new LinkedList<>(l1);
          System.out.println(l2);
集合与数组

Iterator接口(迭代器)
Iterator接口
Iterator对象称作迭代器,用于对集合内的元素进行遍历操作
所有实现了Collection接口的集合类都有一个iterator( )方法,返回一个实现了Iterator接口的对象
Iterator的功能上比较简单,
使用中,只能单向移动

Iterator接口方法

3、Map

Map接口
Map内存储的是键/值对这样以成对的对象组(可以把一组对象当成一个元素),通过“键”对象来查询“值”对象
Map是不同于Collection的另外一种集合接口
Map中,key值是唯一的(不能重复),而key对象是与value对象关联在一起的

Map接口有两个实现类:
HashMap
HashMap 是双列集合  key-value形式
key可以为null,value值可以重复
如果存放数据是出现重复的key值,那么后一个key对应的value会替换前一个key的value值
TreeMap
HashMap:
基于hash表,key能为null,线程不安全,多线程并发操作时速度快
TreeMap:
基于树,key不能为null,通过key进行升序排序,线程安全
          Map<Integer,String> map1=new TreeMap<>();
          map1.put(1002, "zhangsan");
          map1.put(1001, "zhangsan1");
          map1.put(1003, "zhangsan3");
          map1.put(10, "zhangsan3");
          /*map1.put(null, "zhangsan3");*/
          System.out.println(map1);
Map的常用方法

HashMap
   //指定key和value是什么类型的数据就只能放什么类型的数据
          Map<Integer,String> map1 =new HashMap<>();
          map1.put(1001, "zhangsan");
          map1.put(1002, "zhangsan1");
        /* map1.put(1003, "zhangsan3");*/
          map1.put(1003, "zhangsan2");
          /*map1.put(null, "zhangsan2");*/
          map1.put(null, "zhangsan100");
         
          /*Map<Integer,String> map3 =new HashMap<>();
          map1.put(111, "lisi");
          map1.put(112, "lisi1");
          map1.put(1113, "lisi2");
          map1.put(1003, "lisi2");
          //将另一个map调加到指定map集合中,注意数据类型要一致
          map1.putAll(map3);*/
         
          //zhangsan2   通过key移除对应的key-value值,返回移除的value值
          System.out.println(map1.remove(1003));
         
          //若此映射包含指定键的映射关系,返回 true
          System.out.println(map1.containsKey(1002));//true
         
          //若此映射为指定值映射一个或多个键,返回 true
          System.out.println(map1.containsValue("zhangsan"));//true
         
          //返回键值对数量
          System.out.println(map1.size());//3
         
          //判断map集合是否为空
          System.out.println(map1.isEmpty());//false
         
          //get(key) 通过键获取value值,value 什么类型数据get方法就返回什么类型
          System.out.println(map1);
         
          //values() 将map中的value存储为单列集合
          Collection c=map1.values();
          System.out.println(c);
          Student s1=new Student();
          s1.age=10;
          s1.name="zhangsan";
          s1.sex=1;
         
          Student s2=new Student();
          s2.age=12;
          s2.name="zhangsan2";
          s2.sex=2;
         
          Student s3=new Student();
          s3.age=13;
          s3.name="zhangsan3";
          s3.sex=3;
         
          Map<String,Student> map2 =new HashMap<>();
          map2.put("zhangsan", s1);
          map2.put("zhangsan1", s2);
          map2.put("zhangsan2", s3);
          /*System.out.println(map2.get("zhangsan1").toString());*/
         
HashMap类
HashMap的构造方法
构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap
Map 变量名 = new HashMap();
构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap
Map 变量名 = new HashMap(int capacity) ;
构造一个带指定初始容量和加载因子的空 HashMap
Map 变量名 = new HashMap(int capacity,float lodeFactor) ;
构造一个映射关系与指定 Map 相同的新 HashMap
Map 变量名 = new HashMap(Map m) ;
//构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap

/**
 * 加载因子:可以帮助优化HaspMap存储容量,当集合中存放的数据超过16时,会根据加载因子自动扩容,16*0.75
 */
Map<Integer,String> map1=new HashMap<>();
map1.put(1001, "zhangsan");
map1.put(1002, "zhangsan1");
map1.put(1003, "zhangsan3");
   
System.out.println(map1);
Map<Integer,String> map2=new HashMap<>(map1);
System.out.println(map2);

Queue 队列
/**
* 使用LinkedList实现队列
* @author Administrator
*
*/
public class TestQueue {
    private LinkedList date;
    private static int index=0;
    public TestQueue(LinkedList date) {
          this.date=date;
    }
    //往队列中添加数据
    public void add(int a) {
          date.add(a);
    }
    //从队列中取数据
    public int get() {
          int result=(int) date.get(index);
          index++;
          return result;
         
    }
    public static void main(String[] args) {
          LinkedList<Integer> l1=new LinkedList<>();
          TestQueue t=new TestQueue(l1);
          t.add(789);
          t.add(90);
          t.add(45);
          System.out.println(t.get());
          System.out.println(t.get());
          System.out.println(t.get());
    }
}

4、泛型应用

集合在使用上的问题
集合中的add( )方法接受的是一个Object对象的参数 ,在获取集合中的对象时,必须进行造型(强制类型转换)操作。
造型操作可能出现问题,一般在程序运行时才能发生,发现问题比较困难。
集合中泛型的使用
在集合类型的变量声明时,就限制了集合中的元素类型
在获取集合中的对象时,不用进行造型的操作
当有不同类型的对象添加到集合中的时候,编译时就能检查出错误
集合中泛型的使用
泛型经常被称为参数化类型,它能够像方法一样接受不同类型的参数。
定义方式:   ArrayList<E> 变量名;
E是变量类型
List<String>   arr;
arr = new ArrayList<String>();
Map<Integer,String> hm = new HashMap<Integer,String>();
泛型:参数化类型,不明确规定类型,只有在创建对象时才知道准确的类型
public class Tool<T> {
    private T t;
    public T getT() {
          return t;
    }
    public void setT(T t) {
          this.t=t;
    }
    public static void main(String[] args) {
          Tool t=new Tool();
          t.setT("你好啊");
          /*t.setT(123);*/
          System.out.println(t.getT());
         
          Tool<String> t1=new Tool<>();
          t1.setT("你好啊");
          /*t1.setT(123);*/
          System.out.println(t1.getT());
    }
}
钻石操作符
JDK 1.7引入一个新的操作符<>,也被称作钻石操作符,它使得构造方法也可以进行类型推导 。在这之前,你得在对象创建表达式的左右两边同时指定类型,现在你只需要在左边指定就可以了。
JDK 7之前:
Map<String, List<String>> employeeRecords = new HashMap<String, List<String>>();
List<Integer> primes = new ArrayList<Integer>();
JdK 7之后:
Map<String, List<String>> employeeRecords = new HashMap<>();
List<Integer> primes = new ArrayList<>();

第十二章-文件与流

1、文件管理

File类:
File(File parent, String child)
创建从一个家长的抽象路径名和一个孩子的路径字符串的新 File实例。
File(String pathname)
通过将给定的路径名的字符串转换成一个抽象路径名创建一个新的 File实例。
File(String parent, String child)
创建从父路径名的字符串和一个孩子的一个新的 File实例文件。
File的方法

      /*File(String pathname)
      通过将给定的路径名的字符串转换成一个抽象路径名创建一个新的 File实例。  */
      File f1=new File("D:/a.txt");
      if(!f1.exists()) { //如果文件不存在
           try {
                 f1.createNewFile(); //创建空白文件实例
           } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
           }
           
      }
      File parent=new File("D:");
      //parent 是一个目录,在指定目录下创建文件
      /*File(File parent, String child)
      创建从一个家长的抽象路径名和一个孩子的路径字符串的新 File实例。  */
      File f2=new File(parent,"b.txt");
      if(!f2.exists()) {
           try {
                 f2.createNewFile();
           } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
           }
      }
      /*File(String parent, String child)
      创建从父路径名的字符串和一个孩子的一个新的 File实例文件。  */
      File f3=new File("D:","pic.txt");
      if(!f3.exists()) {
           try {
                 f3.createNewFile();
           } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
           }
      }
      //这样会在当前项目根目录中创建一个目录
      File f4=new File("file","pic");
      if(!f4.exists()) {
           f4.mkdirs();//创建多级目录,包括父目录一起创建出来
      }
      File f5=new File("D:/pic","C/F");
      if(!f5.exists()) {
           f5.mkdirs();//创建多级目录
      }
      //创建一级目录
      File f6=new File("D:/pic","C");
      if(!f6.exists()) {
           f6.mkdirs();//创建一级目录
      }
      /*File f8=new File("D:/pic","C");
      if(!f8.exists()) {
           f8.mkdir();//创建一级目录
      }*/


File f7=new File(f4,“a.txt”);
if(!f7.exists()) {
try {
f7.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

      //文件类常用方法
      //a.txt
      System.out.println(f7.getName());
      //E:\workspace-java15\java15-0304\file\pic\a.txt 获取文件绝对路径
      System.out.println(f7.getAbsolutePath());
      //删除文件
      /*System.out.println(f7.delete());*/
      //获取文件内容长度 以字节为单位
      System.out.println(f7.length());//ABC中国   9 UTF-8 一个中文三个字节
     
      //判断该File对象是否是一个目录
      System.out.println(f7.isDirectory());//false
     
      //判断该File对象是否是一个文件
      System.out.println(f7.isFile());//true
      //列出目录下的所有文件,返回一个数组
      System.out.println(Arrays.toString(parent.list()));

2、Io流

流:流(Stream )的概念代表的是程序中数据的流通
数据流:是一串连续不断的数据的集合
流的分类:
按照流向来分:输入流(xxxInputStream,xxxReader)  输出流(xxxOutputSteam,xxxWriter)
按处理数据的单位不同:字节流(xxxInputStream,xxxOutputSteam) 字符流(xxxReader,xxxWriter)
按照功能来分: 节点流,处理流
什么时候使用字节流,什么时候用字符流? 字节流的读写速度没有字符流快
当你需要读写图片、音频、视频等二进制文件数据时可以使用字节流,
当你需要读写大量文档或者文件时你可以使用字符流

3、序列化与反序列化

对象(Object)序列化是指将对象转换为字节序列的过程
反序列化则是根据字节序列恢复对象的过程
序列化一般用于以下场景:
永久性保存对象,保存对象的字节序列到本地文件中
通过序列化对象在网络中传递对象
通过序列化在进程间传递对象
概念:
   java对象序列化的意思就是将对象的状态转化成字节流,以后可以通过这些值再生成相同状态的对象。
对象序列化是对象持久化的一种实现方法,它是将对象的属性和方法转化为一种序列化的形式用于存储和传输。
反序列化就是根据这些保存的信息重建对象的过程。

序列化:将java对象转化为字节序列的过程。
反序列化:将字节序列转化为java对象的过程。
       
为什么要序列化和反序列化
   我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,
包括文本、图片、音频、视频等,而这些数据都会以二进制序列的形式在网络上传送,程序在交流,能否实现进程间的对象传送呢?
答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。
换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;
另一方面,接收方需要从字节序列中恢复出Java对象。当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。
其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),
二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
     
序列化ID的作用:
   其实,这个序列化ID起着关键的作用,它决定着是否能够成功反序列化!
简单来说,java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。
在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,
如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。等会我们可以通过代码验证一下。

序列化ID如何产生:
   当我们一个实体类中没有显示的定义一个名为“serialVersionUID”、类型为long的变量时,
Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,
这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。
譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,
这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。
那么如何解决呢?便是在本地类中添加一个“serialVersionUID”变量,值保持不变,便可以进行序列化和反序列化。

第十二章-File类,读写,copy,转换

1、Copy文件

方法一
public static void testCopy1() {
          //第一步创建文件对象
          File source=new File("D:/a.txt");
          File dest=new File("E:/b.txt");
          //第二部搭建输入输出管道
          FileInputStream fi=null;
          FileOutputStream fo=null;
          try {
               fi=new FileInputStream(source);
               fo=new FileOutputStream(dest); //输出到该文件时,会自动创建文件对象
               /*int a=fi.read();
               fo.write(a);
               int b=fi.read();
               fo.write(b);
               int c=fi.read();
               fo.write(c);
               int d=fi.read();
               fo.write(d);*/
               //先读后写
               int result=0;
               while((result=fi.read())!=-1) {
                     fo.write(result);
               }
               
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          }finally {
               try {
                     fi.close();
                     fo.close();
               } catch (IOException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
               }
          }   
    }
方法二:
    //开辟缓冲区读写 提高读写速度
    public static void testCopy2() {
          //第一步创建文件对象
          File source=new File("D:/a.txt");
          File dest=new File("D:/b.txt");
          //第二部搭建输入输出管道
          FileInputStream fi=null;
          FileOutputStream fo=null;
          try {
               fi=new FileInputStream(source);
               fo=new FileOutputStream(dest);
               //先将内容读到该缓冲区中
               byte[] b=new byte[6];//开辟一个大小为6个字节的缓冲区
               //工业环境下:byte[] b=new byte[1024];
          /*   int a=fi.read(b);
               fo.write(b);
               System.out.println(a);
               int c=fi.read(b);
               fo.write(b);
               System.out.println(c);
               int d=fi.read(b);//返回的是读到的字节数
               System.out.println(d);
               fo.write(b);*///a.txt中一共自有14个字节,读的时候读到了14个,输出时却输出18个,就会出错
               int a=0; //每次读到的字节数
               //读到多少字节就写入多少
               while((a=fi.read(b))!=-1) {
                     /**
                      * b 从哪里输出
                      * off 从字节数组的那个位置开始
                      * len 写入的长度
                      */
                     fo.write(b, 0, a);
               }
               fo.flush();//如果开辟缓冲区读写,那么flush很有必要,但你内存紧张的时候flush,如果你想马上看到结果那么flush
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          }finally {
               try {
                     fi.close();
                     fo.close();//执行close方法会默认先执行flush方法
               } catch (IOException e) {

                     e.printStackTrace();
               }
          }
         
    }
方法三:字符流复制文件内容
    static File source=new File("D:/a.txt");
    static File dest=new File("D:/b.txt");
    //使用字符流读取文件a.txt的内容
    public static void readFile() {
          FileReader fr=null;
          try {
               fr=new FileReader(source);
               char c=' ';
               /*int a=fr.read(); 读第一个字符
               c=(char)a;
               int b=fr.read();读第二个字符*/
               int b=0;
               while((b=fr.read())!=-1) {
                     c=(char)b;
                     System.out.println(b);
               }
               System.out.println(c);
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          }finally {
               try {
                     fr.close();
               } catch (IOException e) {
                     e.printStackTrace();
               }
          }
   }

2、字节流读取文件的内容

/**
           * 读取文件a.txt中的内容
           * ascii编码下一个中文2个字节,一一个英文1个字节,一个数字也是1个字节
           */
          File f1=new File("D:\\a.txt");
          FileInputStream read=null;
          try {
               //将输入管道搭建好了
               read=new FileInputStream(f1);
               int a=0;
               while((a=read.read())!=-1) {
                     System.out.println(a);
               }
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          }
    }

字节流写内容到文件

File f1=new File("D:/b.txt");
          //搭建输出管道
          FileOutputStream writer=null;
          try {
               writer=new FileOutputStream(f1);
               writer.write(49);//写入  一个一个字节写
               writer.write(50);
               writer.write(51);
               writer.write(214);
          /*   writer.write(208);*/
          } catch (FileNotFoundException e) {              
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          }
    }

3、字符流

static File source=new File("D:/a.s");
static File dest=new File("D:/b.txt");
    //开辟缓冲区读写
    public static void fileCopy2() {
          FileWriter fw=null;
          FileReader fr=null;
          try {
               fr=new FileReader(source);
               fw=new FileWriter(dest);
               char[] c=new char[10];
               int a=0;//读到的字符数,可以用于判断是否读完
               while((a=fr.read(c))!=-1) {
                     fw.write(c, 0, a);
                     fw.flush();
               }
               
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          }finally {
               try {
                     fw.close();
                     fr.close();
               } catch (IOException e) {
                     e.printStackTrace();
               }
          }
    }
    //使用字符流进行文件拷贝
    public static void fileCopy1() {
          FileWriter fw=null;
          FileReader fr=null;
          try {
               fr=new FileReader(source);
               fw=new FileWriter(dest);
               char c=' '; //临时存放字符的地方
               int a=0;
               
               while((a=fr.read())!=-1) {//读到没有内容返回-1
                     fw.write(c);
                     c=(char)a;
                     fw.flush();//当你不想内存紧张就flush,如果你想马上看到结果那么flush
          }
          } catch (IOException e) {
               e.printStackTrace();
          }finally {
               try {
                     fw.close();
                     fr.close();
               } catch (IOException e) {
                     e.printStackTrace();
               }
          }
    }
    //使用字符流写操作
    public static void writerFile() {
          FileWriter fw=null;
          char[] c= {'a','b','c'};
          try {
               fw=new FileWriter(dest);
               fw.write(c);
               fw.flush();
          } catch (IOException e) {
               e.printStackTrace();
          }finally{
               try {
                     fw.close();
               } catch (IOException e) {
                     e.printStackTrace();
               }
          }   
    }
    //使用字符流读取文件a.txt的内容
    public static void readFile() {
          FileReader fr=null;
          try {
               fr=new FileReader(source);
               char c=' ';
               /*int a=fr.read(); 读第一个字符
               c=(char)a;
               int b=fr.read();读第二个字符*/
               int b=0;
               while((b=fr.read())!=-1) {
                     c=(char)b;
                     System.out.println(c);
               }
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          }finally {
               try {
                     fr.close();
               } catch (IOException e) {
                     e.printStackTrace();
               }
          }
    }

4、转换

将字节流转为字符流
使用缓冲流读写

static File source=new File("D:/a.txt");
    static File dest=new File("D:/b.txt");
    //将字节流转为字符流
    public static void fileCopy1() {
          /*FileInputStream fi=null;
          FileOutputStream fo=null;*/
          //处理流将字节流转为字符流
          InputStreamReader ir=null;
          OutputStreamWriter or=null;
          try {
               /*fi=new FileInputStream(source);
               fo=new FileOutputStream(dest);*/
               ir=new InputStreamReader(new FileInputStream(source));
               or=new OutputStreamWriter(new FileOutputStream(dest));
               char[] c=new char[10];
               int a=0;
               while((a=ir.read(c))!=-1) {
                     or.write(c, 0, a);
                     or.flush();
               }
               
          } catch (FileNotFoundException e) {
               
               e.printStackTrace();
          } catch (IOException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
          }finally {
               try {
                     ir.close();
                     or.close();
               } catch (IOException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
               }
          }
    }
    //使用缓冲流进行读写  一行一行的进行读写操作
    public static void fileCopy2() {
          BufferedReader br=null;
          BufferedWriter bw=null;
          try {
               br=new BufferedReader(new FileReader(source));
               bw=new BufferedWriter(new FileWriter(dest));
               String result=null;//一行行的读取内容  回车才算一行结束
               while((result=br.readLine())!=null) { //当没有内容了返回空
                     bw.write(result);
                     bw.newLine();//相当于敲了回车
                     bw.flush();
               }


} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
bw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

第十二章-序列化与反序列化(代码)
序列化与反序列化

public class Student implements Serializable{
    private String name;
    private int age;
    private int stuId;
}    
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.imageio.stream.FileImageInputStream;
/**
* 序列化和反序列化代码实现
* 要将一个类序列化,这个类必须实现Serializable接口
* 没有经过序列化的对象不能反序列化
* 静态变量不能序列化,因为序列化针对的是对象
*/
public class TestStudentObject {
    public static void main(String[] args) {
          //将一个Studnet对象序列化后,存放在H:/student.txt
          File f1=new File("H:/student.txt");
         
          Student s=new Student();
          s.setAge(10);
          s.setName("zhangsan");
          s.setStuId(123);
         
          ObjectOutputStream os=null;
          ObjectInputStream is=null;
          try {
               os=new ObjectOutputStream(new FileOutputStream(f1));
               os.writeObject(s);
               
               is=new ObjectInputStream(new FileInputStream(f1));
               Student student=(Student) is.readObject();
               System.out.println(student);
          } catch (Exception e) {
               e.printStackTrace();
          }finally {
               try {
                     os.close();
                     is.close();
               } catch (IOException e) {
                     e.printStackTrace();
               }
          }
    }
}

第十三章-多线程编程

1、线程与进程

什么是进程,什么是线程?

进程就是计算机在执行的一个任务
进程 :计算机在执行的程序的实体
e.g:一个.class文件
一个.exe文件 
          
线程:线程是进程的一条执行线索  
为什么使用多线程,多线程的好处
提高用户体验
线程的创建

第一种方式:
1、继承Thread类
2、重写run方法
3、实例化线程对象,线程对象调用start方法

继承Thread线程
public class TestThread extends Thread{
//当线程跑起来的时候,真正执行的是run方法
    @Override
    public void run() {
          int i=0;
          while(i<50) {
//Thread.currentThread()获取正在执行的线程对象
               System.out.println(Thread.currentThread().getName()+":"+i);
               i++;
          }
    }
}
开启线程
public class EmpThread {
    public static void main(String[] args) {
   //线程的创建
           Thread t1=new Thread();
          //让线程进入可执行状态(就绪状态),当t1线程抢到cpu时间片,那么该线程就跑起来
          t1.start();
          //线程的创建
           Thread t2=new Thread();
          //让线程进入可执行状态(就绪状态),当t1线程抢到cpu时间片,那么该线程就跑起来
          t2.start();          
    }
}

第二种方式:
1、实现Runnable接口,并重写run方法
2、实例化线程类,将该线程类对象作为参数传给Thread对象,并将Thread对象创建出来
3、Thread对象调用start()方法

实现Runnable接口启动线程
public class TestRunnable implements Runnable{
    @Override
    public void run() {
          int i=0;
          while(i<50) {
               System.out.println(Thread.currentThread().getName()+":"+i);
               i++;
          }
    }
}
开启线程
public class EmpRunnable {
    public static void main(String[] args) {
          TestRunnable tt=new TestRunnable();//创建状态
          Thread tr=new Thread(tt);//向上转型
          tr.start();//进入就绪状态
    }
}

线程生命周期
创建状态
就绪状态
运行状态
阻塞状态
死亡状态

执行状态下的线程

线程结论
main方法时主线程,优先级很高
垃圾回收机制有个垃圾回收线程一直在监听,只是它的线程优先级比较低
多线程的特点:交替执行
线程的方法

线程的优先级
线程的优先级:getPriority()  没有设置线程优先级时默认都是5
设置线程优先级的两种方式:1、setPriority设置线程优先级,但是这个方法这会让线程获得时间片的概率改变了而已,抢不抢得到cpu时间片我们不知道
设置了线程优先级1-10,优先级越大,线程抢到cpu时间片的几率增大
线程休眠
sleep方法会让线程进入阻塞状态,让线程休眠指定毫秒值后进入就绪状态
join方法:会让主线程进入阻塞状态,等副线程执行完后,主线程才能可继续执行
jvm是从上往下执行代码的,所以没有join方法时,下面三个线程都会抢cpu时间片,但抢不过main方法
当把main方法,放在join方法后,则会先运行副线程,但又两个线程抢时间片
在没有join方法或join方法在main方法的后面时,任何的线程的优先级都没有比main方法的线程优先级高,所以在有多个线程时,main方法抢到时间片的概率比较高
yield方法 : 当前线程进入就绪的队列等待调度

线程中断
自动终止 — 一个线程完成执行后,不能再次运行

手动终止
stop( ) —— 已过时,基本不用
interrupt( ) —— 粗暴的终止方式
可通过使用一个标志指示 run 方法退出,从而终止线程

线程的高级操作
Object类中线程的相关方法:
void wait()
导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll() 方法。
void notify()
唤醒在此对象监视器上等待的单个线程。
void notifyAll()
唤醒在此对象监视器上等待的所有线程。

线程同步的三个写法
	//给方法上锁
    public synchronized void saleTicket() {
          while(0<id) {
               System.out.println(Thread.currentThread().getName()+":卖了"+id);
               id--;
          }

    //给对象上锁
    public void saleTicket() {
          synchronized (this) {
               while(0<id) {
               System.out.println(Thread.currentThread().getName()+":卖了"+id);
               id--;
               }
          }
    }
    
	//JDk提供的真正的锁
	private Lock lock=new ReentrantLock();
	//   jdk中真正的锁
	    public synchronized void saleTicket() {
	          lock.lock();//上锁
	          while(0<id) {
	               System.out.println(Thread.currentThread().getName()+":卖了"+id);
	               id--;
	          }
	          lock.unlock();//解锁
	    }

注意:
上锁了必须要解锁,不然上锁的代码则无法执行

锁
为什么使用同步锁?
当两个或者多个线程访问同一资源时,为了避免数据不一致的情况出现,我们需要给该资源上同步锁
为了确保在任何时间点一个共享的资源只被一个线程使用,使用了同步

优点:
可以显示的知道哪些方法是
被synchronized关键字保护的
缺点:
方法中有些内容是不需要同步的,如果该方法执行会花很长时间,那么其他人就要花较多时间等待锁被归还;
只能取得自己对象的锁,有时候程序设计的需求,可能会需要取得其他对象的锁;

优点:
可以针对某段程序代码同步,不需要浪费时间在别的程序代码上;
可以取得不同对象的锁;
缺点:
无法显示的得知哪些方法
是被synchronized关键字保护的;

死锁:
一、定义
	线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。
当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

当然死锁的产生是必须要满足一些特定条件的:
互斥条件:线程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该线程释放
请求和保持条件:一个线程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
不剥夺条件:任何一个资源在没被该线程释放之前,任何其他线程都无法对他剥夺占用
循环等待条件:当发生死锁时,所等待的线程必定会形成一个环路(类似于死循环),造成永久阻塞。

预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。
因而,系统在进行资源分配之前预先计算资源分配的安全性。 (动态分配锁资源,预先估计死锁发生的可能性)若此次分配不会导致系统进入不安全的状态,则将资源分配给线程;
否则,线程等待。其中最具有代表性的避免死锁算法是银行家算法。银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。
因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。
安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。
如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

第十四章-网络编程

.网络基础知识**

  1. 网络应用程序

    • 两个计算机在相同程序上是可以进行数据的交换

      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  2. 网络编程

    • 通过使用套接字来达到进程间通信目的编程就是网络编程
  3. IP地址和端口号

    • IP

      • TCP/IP协议

        • 协议 —网络中计算机之间通信的规则
        • 大多数的网络协议都以TCP/IP协议为基础
      • IP地址

      • IPV4/IPV6

        • IPV4 — 4个字节,即32位的二进制数表示
        • IPV6 — 16个字节的格式来表示IP地址
    • 端口号

      • 端口号是一个整数,用于表示该数据帧交给哪个应用程序来处理
      • 同一台计算机上不能有两个使用同一个端口的程序运行
      • 端口数范围为0~65535
      • 0~1023用于一些知名的网络服务和应用,用户的普通网络程序应使用1024以上的端口号
      • netstat -a -n可以查看程序的端口号
    • TCP与UDP

      • TCP

        • 面向连接的通信协议
        • 提供两台计算机间的可靠的、端到端的字节流通信的协议
      • UDP

        • 无连接的通信协议
        • UDP不保证可靠数据的传输
  4. Socket原理

    • Socket—套接字

    • 是网络应用程序编程的接口和一种机制

    • 用套接字中的相关函数来建立连接和完成通信
      Socket可以看成在两个程序进行通讯连接中的一个端点

    • 将区分不同应用程序进程的三个参数

      • 通信的目的的IP地址
      • 使用的传输层协议
      • 使用的端口号与一个socket绑定
    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  5. 基于TCPSocket编程

    • 利用TCP协议进行通信

    • 交互过程

      • 两个应用程序,有主从之分一个称为服务器程(Server), 另外一个称为客户机程(Client)服务器程序创建一个ServerSocket,然后调用accept方法等待客户来连接
      • 客户端程序创建一个socket并请求与服务器建立连接
      • 刚才建立了连接的两个socket在一个单独的线程上对话服务器开始等待新的连接请求
    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • ServerSocket----java.net.ServerSocket

      • 实现服务器套接字

      • 构造函数

        • ServerSocket(int port)
        • ServerSocket(int port, int maxqu)
      • accept( ) 方法用于等待客户端触发通信,返回值类型为Socket

    • Socket —— java.net.Socket

      • 实现客户端套接字

      • 构造函数

        • Socket(String hostName, int port)
        • Socket(InetAddress a, int port)
      • 可用方法

        • InetAddress getInetAddress( )
        • int getPort( )
        • int getLocalPort( )
        • InputStream getInputStream()
        • OutputStream getOutputStream()
        • void close()
  6. 使用Socket实现的群聊功能

    1. 服务器线程

      1. /**
      2. * 开辟一个线程专门负责和这个客户聊天,不停的获取客户端发送的信息,转发给群里的其他用户
    • @author Administrator

    */
    public class MyServer_Thread implements Runnable {
    7.
    private Socket socket;
    public MyServer_Thread(Socket socket) {
    this.socket=socket;
    }
    @Override
    public void run() {
    ry(BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()))){
    ring messgae=null;
    ​ //如果读到的消息不为空转发给群里的其他人
    17. ​ while((messgae=br.readLine())!=null) {
    18. ​ for(Socket s:MyServer.socketList) {
    19. //如果当前套接字和该套接字相同,表示就是这个客户端发送的消息,那么就不要转发给他了
    20. ​ if(s==socket) {
    2​ continue;
    22. ​ }
    PrintWriter pw=new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);
    ​ pw.println(messgae);
    ​ }

                   }
                    br.readLine();
    
    1. ​ }catch(Exception e) {
      e.printStackTrace();

      3} }
      客户程 ./*
    • 客户端:开辟一个线程,负责不停的接收消息
    • @author Administrator

    */
    public class MyClient_Thread implements Runnable {
    //必须依赖于socket
    8. private Socket socket;
    public MyClient_Thread(Socket socket) {
    10. ​ this.socket=socket;
    Ove rride
    . public void run() {
    try(BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()))){
    String message=null;
    //如果读到的信息不为空,输出到控制台
    while((message=br.readLine())!=null) {
    System.out.println(message);
    }

    }catch(Exception e) {
    e.printStackTrace();
    }
    }
    }
    服务 ./**

    • 群聊服务器的实现
    • @author Administrator

    */
    public class MyServer {
    7. //定义静态集合属性保存所有的套接字
    8. //为了避免多线程访问集合出并发问题,所以要给集合上锁
    9. public static List socketList=Collections.synchronizedList(new ArrayList<>());
    public static void main(String[] args) {
    1 ServerSocket server=null;

    1. ​ try {
      server=new ServerSocket(23456);
      //main:不断地接收客户端套接字,并将其放入集合中
      while(true) {

    2. ​ Socket client=server.accept();

    3. ​ //把这个套接字放入集合中
      socketList.add(client);

    4. ​ //技术实现:开辟一个线程专门负责和这个客户聊天,不停的获取客户端发送的信息,转发给群里的其他用户
      2 new Thread(new MyServer_Thread(client)).start();;
      }

    5. ​ } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }
      }

    6. }

    7. 客户端

      1. /**
      2. * 群聊客户端的实现
      3. * @author Administrator
      4. */
      5. public class MyClient {
      6. public static void main(String[] args) {
      7. ​ Socket client=null;
      8. ​ //开启聊天之旅,一边读取消息一遍发送消息互不影响
      9. ​ try {
        client=new Socket(“127.0.0.1”,23456);
        ​ //开辟一个线程,负责不停的接收消息
        new Thread(new MyClient_Thread(client)).start();

​ //技术实现:让main方法所在的线程,负责不停的发送消息
16. ​ try(PrintWriter pw=new PrintWriter(new OutputStreamWriter(client.getOutputStream()), true)){
17. ​ //接收控制台输入
18. ​ BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
19. ​ while(true) {
20. ​ String count=br.readLine();
21. ​ pw.println(“clinet:”+count);
22. ​ }
23. ​ }catch(Exception e) {
24. ​ e.printStackTrace();
25. ​ }
26. ​ } catch (UnknownHostException e) {
27. ​ // TODO Auto-generated catch block
28. ​ e.printStackTrace();
29. ​ } catch (IOException e) {
30. ​ // TODO Auto-generated catch block
31. ​ e.printStackTrace();
32. ​ }
33. }
34. }


2、JDBC连接&介绍&增删改查

简介:
JDBC的简介:
对于我们项目的开发,我们都是围绕着数据库展开的,很少见到单独存在而没有数据库的项目;那么,只要是一门编程语言都会为我们的数据库的开发提供一个技术支持,同样,java在起初也为数据库的开发提供一个技术支持的标准--JDBC(java数据库连接),它是由java编写的一组类和接口组成,jdbc不算一门技术,它是一种服务,服务的特征:按照一定的套路(流程)去执行,那么咱们java就享受JDBC提供的服务。
java--jdbc--数据库
作用:提供和不同数据库连接的接口,缓解了java应用程序和数据库之间的耦合,提高了java程序对数据库的移植性
JDBC:网络协议连接(程序--jdbc--数据库)
ODBC连接:(程序--jdbc--odbc--数据库)
JAVA为JDBC提供了一个模块(java.sql)--统一接口,它里边有一个包(java.sql)
JDBC关键类和接口
关键类:DriverManager 管理数据库驱动,建立数据库连接的
关键的接口:
Connection:数据库的连接
Statement:这个接口中的方法都是对数据库进行操作的方法
ResultSet:执行数据库查询操作后得到的结果
怎么连接?
conn=DriverManager.getConnection("jdbc:oracle:thin:localhost:1521:orcl", "scott", "123456");
详细用法
public static String getEnameByEmpno(int empno) {
       //第一步:获取数据库连接
       Connection conn=null;
       tatement stat=null;
       ResultSet rs=null;
       String ename=null;
       try {
             conn=DriverManager.getConnection("jdbc:oracle:thin:localhost:1521:orcl", "scott", "123456");
             //第二步:编写SQL语句
             String queryEnameByEmpno="select ename from emp where empno="+empno;
             //第三步:通过连接对象(Connection)获取Statement对象,并且执行sql语句
            tat=conn.createStatement();
            rs=stat.executeQuery(queryEnameByEmpno);
             //第四步:返回ResultSet(结果集)对象,从结果集中获取数据
                               
             if(rs.next()) {
                 //如果结果集中有数据,那么获取它
                 //结果集中columnIndex返回什么类型的数据那么对应的get方法就是什么类型
                 ename=rs.getString(1);
                 // getString(columnIndex) ,columnIndex表示在结果集中的第几列数据
              }
        } catch (SQLException e) {
                  e.printStackTrace();
                  }
                   return ename;
}   

3、连接:

第一种连接方式
conn=DriverManager.getConnection("jdbc:oracle:thin:localhost:1521:orcl", "scott", "123456");
第二种连接方式
使用配置文件db.properties方便日后修改用户名和密码
db.properties配置文件:
jdbc.driver=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:localhost:1521:orcl
jdbc.username=scott
jdbc.password=123456
连接类
/**
* 数据库工具类
*
*
*/
public class DBUtil {
      private static String JDBC_URL="";
      private static String JDBC_USERNAME="";
      private static String JDBC_PASSWORD="";
      private static String JDBC_DRIVER="";
      //提供一个全局获取数据库连接的方法
      static{
            Properties prop=new Properties();
            //加载驱动,放在static代码块中,保证驱动只加载一次
            try {
                   prop.load(new FileInputStream(new File("config/db.properties")));
                    /*或者可以使用这个方式//类路径下的加载db.properties(和包同一目录下)
                   prop.load(DBUtil2.class.getClassLoader().getResourceAsStream("db.properties"));*/
                   JDBC_URL=prop.getProperty("jdbc.url");
                   JDBC_USERNAME=prop.getProperty("jdbc.username");
                   JDBC_PASSWORD=prop.getProperty("jdbc.password");
                   JDBC_DRIVER=prop.getProperty("jdbc.driver");
                   Class.forName(JDBC_DRIVER);//加载驱动
            } catch (Exception e) {
                   e.printStackTrace();
            }
      }
     
      public static Connection getConnection() {
            Connection conn=null;
           
            try {
                   //提出疑问:项目开发中-->甲方要求更改数据库的账号密码
                   
                   //数据库连接
                   conn=DriverManager.getConnection(JDBC_URL,JDBC_USERNAME,JDBC_PASSWORD);  
            } catch (SQLException e) {
                   e.printStackTrace();
            }
            return conn;
     
      }
      //提供一个全局关闭资源方法
      public static void closeResouce(Connection conn,Statement stat,ResultSet rs) {
                   if(rs!=null) {
                          try {
                                rs.close();
                          } catch (SQLException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                          }
                   }
                   if(stat!=null) {
                          try {
                                stat.close();
                          } catch (SQLException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                          }
                   }
                   if(conn!=null) {
                          try {
                                conn.close();
                          } catch (SQLException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                          }
                   }
            }
      public static void main(String[] args) {
            if(getConnection()!=null) {
                   System.out.println("登陆成功");
            }
           
      }
}
预防SQL注入
public class TestPreparedStatement {
      /*
       * 预防sql注入
       * */
      public static void main(String[] args) {
            Emp emp=empLogin("SCOTT",7788);
            if(emp!=null) {
                   System.out.println("登录成功");
                   System.out.println(emp.getComm());//0.0 因为在给Emp初始化时没给comm注入值
                   System.out.println(emp.getEname());
            }else{
                   System.out.println("请重新登陆");
            }
      }
      static Connection conn=null;
      static PreparedStatement stat=null;//预防sql注入
      static ResultSet rs=null;
      private final static String QUERY_EMP_BYENAME_ANDEMPNO="select *from emp where ename=? and empno=?";//可以让sql多次执行   ?代表的是占位符
     
      public static Emp empLogin(String ename,int password) {
            conn=DBUtil.getConnection();//数据库连接
            Emp emp=null;//创建Emp的实体类对象
           
            try {
                   stat=conn.prepareStatement(QUERY_EMP_BYENAME_ANDEMPNO);//将sql预编译
                   stat.setString(1, ename);//设置parameterIndex占位符索引位置 从1开始 x你要赋的值
                   stat.setInt(2, password);
                   rs=stat.executeQuery();//获取sql语句
                   while(rs.next()) {
                          emp=new Emp();
                          ename=rs.getString("ename");
                          double sal=rs.getDouble("sal");
                          emp.setEname(ename);
                          emp.setSal(sal);
                   }
            } catch (Exception e) {
                   e.printStackTrace();
            }finally {
                   DBUtil.closeResouce(conn, stat, rs);//关闭资源
            }
           
            return emp;
           
      }
}
ResultSet
表示数据库结果集的数据表,它通常是通过执行查询数据库的语句生成的
可以通过next()方法,与循环语句结合,遍历出数据查询的得到的结果集
ResultSet中有多行处理

4、JDBC连接:

1.jdbc:java数据库连接  API
2、怎么用?访问数据库的步骤
①导入jar包     ojdbc6.jar
②加载驱动 相当于import 某个类
③获取连接对象 Connection
④定义sql语句
⑤获取操作对象preparedstatement --预编译(java提前将sql编译好)  执行速度快  预防sql注入    一个sql多次调用
⑥如果sql语句中有?,那么给占位符赋值
⑦执行sql语句并返回结果,如果查询返回结果集,如果DML返回影响的行数
⑧如果是查询那么从结果集中获取数据,可能是一行数据(if),也可能是多行数据(while)
⑨关闭资源
3.sql注入,提示我们Statement有风险,所以用preparedstatement
4.日期类型处理
5.数据库的数据类型和java程序中的数据类型如何对应
数值类型对应:number(5)-->int/Integer  number(7,2)-->float/double
对应的封装类字符串的对应  : varchar2()/char()-->String
日期类型的对应:Date/DateTime(java.sql.Date)-->java.util.Date
6、封装了一个工具类DBUtil
实体类
            //员工编号
            private int empno;
            //工作
            private String job;
            //工资
            private double sal;
            //员工姓名
            private String ename;
            //奖金
            private double comm;
            //入职日期
            private Date hiredate;
            //经理
            private int mgr;
            //部门名称
            private int deptno;

//模糊查询
/**
* 模糊查询
* @author Administrator
*
*/
public class EmpDao {
      /**
       * 模糊查询 :查询名字包含S的员工信息
       * @return
       */
      private static final String QUERY="select empno from emp where ename like ?";
      public List<Integer> queryEmpByLike(String ename) {
            Connection conn=null;
            PreparedStatement stat=null;
            ResultSet rs=null;
            List<Integer> empnos=new ArrayList<>();
            try {
                   conn=DBUtil.getConnection();
                   stat=conn.prepareStatement(QUERY);
                   stat.setString(1, "%"+ename+"%");
                   rs=stat.executeQuery();
                   while(rs.next()) {
                          Integer empno=rs.getInt("empno");
                          empnos.add(empno);
                   }
            } catch (Exception e) {
                   e.printStackTrace();
            }
            return empnos;
           
      }
      public static void main(String[] args) {
            System.out.println(new EmpDao().queryEmpByLike("S"));
      }
     
}

//组合查询
public class EmpDao {
      private String job;
      private Integer deptno;
      private Integer year;
      /*
       * 组合查询:根据用户输入的信息查询如果希望查询
       * */
      public List<Emp> getEmpByCondition(String job,Integer deptno,Integer year){
            List<Emp> emps=new ArrayList<>();
           
            Connection conn=null;
            PreparedStatement stat=null;
            ResultSet rs=null;
            try {
                   conn=DBUtil.getConnection();
                   /**
                    * sql语句的定义是一门技术:当查询条件不为空时拼接sql,为空那么不拼接,查询条件都为空查询全部
                    */
                   
                   StringBuffer query=new StringBuffer("select *from emp where 1=1");
                   if(job!=null && !"".equals(job)) {
                          query.append(" and job='"+job+"'");
                   }
                   if(deptno!=null && 0<deptno) {
                          query.append(" and deptno="+deptno);
                   }
                   if(year!=null && 0<year) {
                          query.append(" and extract(year from hiredate)="+year);
                   }
                   /*System.out.println(query);*/
                   stat=conn.prepareStatement(query.toString());
                   rs=stat.executeQuery();
                   while(rs.next()) {
                          Emp emp=new Emp();
                          emp.setEname(rs.getString("ename"));
                          emps.add(emp);
                   }
            } catch (Exception e) {
                   e.printStackTrace();
            }finally {
                   DBUtil.closeResouce(conn, stat, rs);
            }
           
            return emps;
           
      }
      public static void main(String[] args) {
            System.out.println(new EmpDao().getEmpByCondition(null, 10,null));
      }
}

5、批量删除

//第一种删除的方法:
public class EmpDao {

      //就是无论删除的数据在数据库有没有,都会执行删除有数据
      public boolean deleteEmpBatch1(int[] empnos) {
            Connection conn=null;
            PreparedStatement stat=null;
            boolean temp=false;
            //如果传过来的数组长度为0,就删除失败
            if(empnos.length==0) {
                   return temp;
            }
            try {
                   //连接数据库
                   conn=DBUtil.getConnection();
                   StringBuffer sb=new StringBuffer("delete emp where empno in(");
                   for(int i=0;i<empnos.length;i++) {
                          //拼接字符串
                          sb.append(empnos[i]+",");
                   }
                   //delete emp where empno in(8017,8018,1323, 去除字符串末尾的都逗号
                   sb=new StringBuffer(sb.substring(0, sb.length()-1)); //返回一个新的字符串
                   sb.append(")");//添加)括号
                   
                   stat=conn.prepareStatement(sb.toString());
                   int rows=stat.executeUpdate();
                   //有些情况:如果批量删除有些数据不存在我们也需要删除选中的其他数据
                   if(0<rows) {
                          temp=true;
                   }
                   
            } catch (Exception e) {
                   e.printStackTrace();
            }finally {
                   DBUtil.closeResouce(conn, stat, null);
            }
            return temp;
      }
	//第二种方法:有约束删除,一定是删除数据库有的数据,不然不能执行
      private static final String REMOVE_EMP="delete emp where empno=?";
      public boolean deleteEmpBatch2(int[] empnos) {
            //三大金刚
            Connection conn=null;
            PreparedStatement stat=null;
            boolean temp=false;
            //如果传过来的数组长度为0,就删除失败
            if(empnos.length==0) {
                          return temp;
            }
            int num=0;//用于返回行数与数组长度进行比较
            try {
                   conn=DBUtil.getConnection();
                   conn.setAutoCommit(false);
                   stat=conn.prepareStatement(REMOVE_EMP);
                   for(int i=0;i<empnos.length;i++) {
                          //每次删除一条数据
                          //delete emp where empno=8020
                          stat.setInt(1, empnos[i]);
                          //执行了sql返回每次执行影响行数
                          int row=stat.executeUpdate();
                          if(0<row) {
                                num++;
                          }
                   }
     //有些情况:如果批量删除有些数据不存在我们就删除失败,一定要全部删除才是完成删除
                   if(num==empnos.length) {
                          temp=true;
                          conn.commit();
                   }else {
                          conn.rollback();
                   }
                   
            } catch (Exception e) {
                   try {
                          //如果删除多行数据期间出现异常,那么回滚
                          conn.rollback();
                   } catch (Exception e1) {
                          e1.printStackTrace();
                   }
                   e.printStackTrace();
            }finally {
                   DBUtil.closeResouce(conn, stat, null);
            }
            return temp;
      }


	//第三种方法:是一条一条删除的DML语句,存储到内存中,然后最后一次性执行全部的DML语句
	      public void deleteEmpBatch3(int[] empnos) {
	            //三大金刚
	            Connection conn=null;
	            PreparedStatement stat=null;
	            //如果传过来的数组长度为0,就删除失败
	            if(empnos.length==0) {
	                          return;
	            }
	            try {
	                   conn=DBUtil.getConnection();
	                   stat=conn.prepareStatement(REMOVE_EMP);
	                   for(int i=0;i<empnos.length;i++) {
	                          //delete emp where empno=8020
	                          stat.setInt(1, empnos[i]);
	                          //delete emp where empno=8021
	                          stat.addBatch();//把这个已经传入值得sql语句放入批处理中,开了一个缓存
	                   }
	                   //最后面批量执行  批处理不做事务控制
	                   stat.executeBatch();
	            } catch (Exception e) {
	                   e.printStackTrace();
	            }finally {
	                   DBUtil.closeResouce(conn, stat, null);
	            }
	      }
	      public static void main(String[] args) {
	            if(new EmpDao().deleteEmpBatch1(new int[]{123,123,123,123})) {
	                   System.out.println("删除成功");
	            }
	      }
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值