admin管理员组文章数量:1794759
Java基础学习
详细笔记
- 第一章:Java语言概述
- 一、Java编译运行过程与跨平台
- 二、JDK JRE JVM解释
- 第二章:基本语法
- 标识符
- ASCII编码表
- 关键字
- 注释
- 变量
- 数据类型
- 1、基本类型介绍(八种)
- 2、 引用类型
- 3、基本类型的字面值
- 4 、 基本类型的类型转换
- 5、 运算规则
- 运算符
- 1.1 概述
- 1.2 运算符速查表
- 分支结构
- switch结构
- 1、概述:
- 2、switch结构的注意事项
- 循环结构
- for
- 嵌套for循环
- 概述:
- 增强for
- break与continue
- while
- do-while
- 三种循环的区别:
- 变量
- 局部变量
- 成员变量
- 方法
- 方法调用顺序图:
- 方法的重载
- 第三章:数组
- 概念:
- 创建数组
- 创建数组过程分析
- 向数组中存入数据hello
- 数组的长度
- 数组的遍历
- 冒泡排序
- 概念:
- 形式:
- 实现冒泡排序
- 冒泡排序优化
- 数组工具类Arrays
- 二维数组
- 创建二维数组
- 遍历二维数组
- 第四章:面向对象
- 概念:
- 面向对象的三大特征
- 封装
- 继承
- super
- 方法重写Override
- 重载Overload 与重写Override的区别
- 继承的用法
- 成员变量的使用
- 成员方法的使用
- 构造方法的使用
- 多态
- 特点:
- 多态的使用
- 向上转型和向下转型
- 类和对象
- 类的创建使用
- 对象在内存中的存储
- 创建对象的流程
- 创建多个对象
- 匿名对象
- 访问控制符
- 构造方法
- 概念:
- 形式:
- 构造代码块与局部代码块
- 代码块的加载顺序
- this
- this与super的区别
- static
- 特点:
- static静态调用关系
- 静态代码块、构造代码块、局部代码块
- 静态变量和实例变量的区别
- final
- 抽象类
- 概念:
- 抽象方法的格式:
- 特点:
- abstract注意事项
- 拓展:
- 接口
- 概念:
- 接口格式:
- 接口的特点:
- 接口的用法
- 接口的多继承多实现
- 总结
- 拓展
- 软件设计的开闭原则OCP:
- 第五章:异常
- 异常处理
- 第六章 :常用API
- 概念:
- Object
- 概念:
- 常用方法介绍:
- toString()
- hashCode()
- equals()
- String
- 特点:
- 创建String对象的方式
- 常见方法
- StringBuilder/StringBuffer
- 特点:
- StringBuilder和StringBuffer的区别
- 正则表达式Regex
- 概述:
- String提供了支持正则表达式的方法
- BigDecimal/BigInteger
- 概述:
- 创建对象:
- 常用方法
- 包装类
- Integer
- Double
- 自动装箱和自动拆箱
- 概述:
- 拓展:
- ==和equals的区别
- 第七章:IO
- 继承结构
- Stream流的概念
- File文件流
- 概述:
- 创建对象:
- 常用方法:
- 递归求目录总大小
- 字节流读取
- InputStream抽象类
- FileInputStream子类
- BufferedInputStream子类
- 字符流读取
- Reader抽象类
- FileReader子类
- BufferedReader子类
- 字节流写出
- OutputStream抽象类
- FileOutputStream 子类
- BufferedOutputstream 子类
- 字符流写出
- Writer 抽象类
- FileWriter 子类
- BufferedWriter子类
- 文件复制测试
- 文件批量读写
- IO的继承结构
- BIO、NIO、AIO的区别
- 序列化/反序列化
- 特点/应用场景
- ObjectOutputStream
- ObjectInputStream
- 编码转换流
- 第八章:泛型
- 概念:
- 作用:
- 泛型声明:
- 常用名称:
- 第九章:Collection接口
- 概述:
- 集合的继承结构:
- 常用方法
- List接口
- 概述:
- 特点:
- 常用方法:
- ArrayList
- 概述:
- 4种迭代方式
- ArrayList扩容
- LinkedList
- 概述:
- 常用方法
- set接口
- 概述:
- Set集合的特点:
- 常用方法:
- HashSet
- 概述:
- 去重:
- Map接口
- 概述:
- 特点:
- 继承结构
- 常用方法:
- Map集合迭代:
- HashMap
- 前言
- 概述:
- 拓展
- 第十章:进程与线程
- 进程
- 概念:
- 特点:
- 并行和并发
- 线程
- 概念:
- 进程与线程的关系
- 多线程的特性
- CPU分时调度
- 线程的状态
- 线程生命周期,主要有五种状态:
- 多线程代码创建方式
- 继承Thread
- 实现Runnable接口
- Callable接口
- 线程创建的其他方式
- 票超卖
- 同步锁
- 前言
- 同步与异步
- synchronized同步关键字
- 拓展:线程锁
- 悲观锁和乐观锁
- 两种常见的锁
- 两种方式的区别
- 第十一章:注解与枚举类
- 枚举类
- 枚举类的实现
- 枚举类的属性
- 自定义枚举类
- Enum类的主要方法
- 实现接口的枚举类
- 注解(Annotation)
- 概述:
- 示例:
- 示例一:生成文档相关的注解
- 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
- 示例三:跟踪代码依赖性,实现替代配置文件功能
- 自定义 Annotation
- JDK 中的元注解
- JDK8中注解的新特性
- 第十二章: 反射
- Java反射机制概述
- 动态语言 vs 静态语言
- Java反射机制提供的功能
- 理解Class类并获取Class实例
- Class 类
- Class类的常用方法
- 应用举例
- 获取Class类的实例(四种方法)
- 哪些类型可以有Class对象?
- 类的加载与ClassLoader的理解
- 类的加载过程
- 什么时候会发生类初始化?
- 了解:ClassLoader
- 创建运行时类的对象
- 有了Class对象,能做什么?
- 获取运行时类的完整结构
- 调用运行时类的指定结构
- 调用指定方法
- 调用指定属性
- 关于setAccessible方法的使用
- 反射的应用:动态代理
- Proxy :
- 动态代理步骤
- 动态代理与AOP(Aspect Orient Programming)
- 第十三章: 网络编程
- 网络编程概述:
- 网络基础
- 网络通信要素概述
- 如何实现网络中的主机互相通信
- 网络通信协议
- 通信要素
- IP和端口号
- InetAddress类
- 网络协议
- TCP/IP协议簇
- TCP 和 UDP介绍
- TCP协议:
- UDP协议:
- Socket
- Socket类的常用构造器:
- Socket类的常用方法:
- TCP网络编程
- 基于Socket的TCP编程
- 客户端Socket的工作过程包含以下四个基本的步骤:
- 客户端创建Socket对象
- 服务器程序的工作过程包含以下四个基本的步骤:
- 服务器建立 ServerSocket 对象
- UDP网络编程
- DatagramSocket 类的常用方法
- DatagramPacket类的常用方法
- UDP网络通信流程:
- 发送端与接收端是两个独立的运行程序
- URL编程
- URL类
- URL类构造器
- URL类常用方法
- 针对HTTP协议的URLConnection类
- URI、URL和URN的区别
- 小 结
JDK(Java Development Kit) — Java开发工具包 — JRE+开发工具
开发java程序最小的环境为JDK,所以JDK是JAVA语言的核心
JRE(Java Runtime Environment) — Java运行时环境 — JVM+运行java程序所必需的环境
运行java程序最小的环境为JRE
JVM(Java Virtual Machine)—负责加载.class并运行.class文件
JVM(JAVA虚拟机)将JAVA代码转换为对应的操作系统可以理解的指令,不同的操作系统有不同虚拟机与之对应,同一段代码交给虚拟机之后,虚拟机再转化给操作系统
什么是将java代码翻译成当前操作系统所理解的指令?
这指的就是编译的过程,将.java文件编译成.class字节码文件.编译完成会产生一个.class文件,这个文件称为字节码文件,操作系统看的指令文件就是字节码文件.
第二章:基本语法 标识符标识符可以简单的理解成一个名字。在Java中,我们需要标识代码的很多元素,包括类名、方法、字段、变量、包名等等。我们选择的那个名称就称为标识符,一个正确的标识符需要遵循以下规则:
标识符可以由字母、数字、下划线(_)、美元符($)组成,但不能包含 @、%、空格等其它特殊字符
不能以数字开头。如:123name不合法
标识符严格区分大小写。如:tmooc 和 Tmooc是两个不同的标识符
标识符的命名最好能反映出其作用,做到见名知意。
标识符不能是Java的关键字
ASCII(American Standard Code for Information Interchange)编码表,美国标准信交换代码。
在计算机中,所有的数据在存储和运算时都要使用二进制数表示。
a、b、c、d这样的52个字母(包括大写)、以及0、1等数字还有一些常用的符号, 在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则。
于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示。
中国 gb2312【字少】 àgbk【识别4万+汉字】à万国码表unicode【ISO制定】
所有码表都兼容ASCALL
附:ASCII码表
关键字在Java中,有一些单词被赋予了特定的意义,一共有53个关键字。这53个单词都是全小写,其中有两个保留字:const和goto。
注释几乎所有编程语言都允许程序员在代码中输入注释,因为编译器会忽略注释,所以注释并不会影响程序的运行结果。
注释的真正作用是: 它可以向任何阅读代码的人描述或者解释程序的实现思路,如何使用以及其它任何相关信,提高代码的可读性,方便后期的维护与复用。Java的注释有3种:
格式: 每行都以”//”开头.
快捷方式:Ctrl+/添加注释,同样的快捷键,再按一次,取消注释
格式: 以/* 开头,以*/ 结束.
快捷方式:Ctrl+Shift+/添加注释,Ctrl+Shift+\\取消注释,也可以输入”/*”之后按回车添加注释
格式: 以 /** 开头。 以 */ 结尾.
快捷方式:输入 /** 之后按回车添加注释
变量在JAVA中,我们需要记录一些数据,但这些数据的值是不固定的,总在变,我们可以把这些数据理解为变量。
我们通过三个元素来描述变量:变量类型 变量名以及变量值。
int age = 18; //声明int类型的变量并赋值
String tel ; //声明String类型的变量
数据类型 1、基本类型介绍(八种)变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。
java是一个强类型的语言,会把日常见到的数据,分成好多类型.
内存管理系统根据变量类型来分配存储空间,分配的空间只能用来储存该类型数据。
位 bit,来自英文bit,音译为”比特”,表示二进制位
1Byte = 8 bits (1字节 = 8 位)
1KB = 1024Bytes 1MB = 1024KB 1GB=1024M 变量交换
int t ; //t = 1;//把数字1交给变量t保存 t = a;//把变量a中保存的值交给变量t来保存 a = b;//把变量b中保存的值交给变量a来保存 b = t;//把变量t中保存的值交给变量b来保存 2、 引用类型引用类型是一个对象类型,值是什么呢?它的值是指向内存空间的引用,就是地址,所指向的内存中保存着变量所表示的一个值或一组值。如:类,接口,数组
3、基本类型的字面值3.1整数字面值是int类型(byte1 short2 int4 long8 float4 double8)
int x = 99999;//对,右面数据的字面值是int类型 int x = 99999999999;//错,右面数据的字面值是int类型,但是已经超出int的取值范围。3.2 byte,short,char三种比int小的整数可以用范围内的值直接赋值
byte b1=127;//对,可以用范围内的值直接赋值 byte b2=128;//错,超出byte范围3.3浮点数的字面值是double类型
double r =3.14;//对,小数字面值类型就是double float r =3.14;//错,右面的数据字面值是double,float是4字节存不下double类型的数据3.4字面值后缀L D F
long x =99999999999L;//字面值是int类型,需转成long类型的数据,加字面值后缀L即可 float b = 3.0F;//3.0字面值是double类型,加后缀F会变成float类型 double d = 3D;//3字面值是int类型,加后缀D,会变成double类型3.5进制前缀 0b - 标识这是2进制 ,如:0b0101
0 - 标识这是8进制, 8进制是三位,如: 023
0x - 标识这是16进制,如: 0x0001
\\u -标识这是char类型,属于16进制
4 、 基本类型的类型转换箭头开始的地方是小类型,箭头指向的地方是大类型 4.1 小到大(隐式转换)
byte m = 120; int n = m;//小转大,右面的m是小类型,给左面的n大类型赋值,可以直接使用 float f = 3.2f; double d = f; -->可以执行4.2 大到小(显示转换)
int x = 999; byte y =(byte)x;//大转小,右面x给左面的y小类型赋值,不可以,需要强制类型转换注意: 容量大的类型转换为容量小的类型时必须使用强制类型转换。
例如:int i =128; byte b = (byte)i; //打印的结果是-128
因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。
例如:float f = 32.7f; int a2 =(int) f; //打印的结果是32
4.3 口诀: 小到大,直接转 大到小,强制转 浮变整,小数没
低 ------------------------------------> 高 byte,short,char—> int —> long—> float —> double 5、 运算规则5.1 计算结果的数据类型,与最大数据类型一致
System.out.println(3/2);//1,int/int,得到的结果类型还是int System.out.println(3/2d);//1.5,int/double。得到的结果是double5.2 byte,short,char三种比int小的整数,运算时会先自动转换成int
byte a = 1; byte b = 2; byte c = (byte)(a+b); //a+b会自动提升成int类型,右面得运算结果就是int大类型 //给左面的byte小类型赋值,不可以,需要强转。5.3 整数运算溢出 整数运算,类似于一个钟表,转到最大时,再转会回到最小。
计算:光速运行一年的长度是多少米?3亿m/s
//溢出的现象: //因为做了大的整型数据的运算,会超出int取值范围。解决方法:把整数提升成long类型。 System.out.println(300000000L*60*60*24*365);5.4 浮点数运算不精确 使用Java 的 BigDecimal
public static void main(String[] args){ BigDecimal a = new BigDecimal("4.5"); BigDecimal b = new BigDecimal("1.5"); System.out.println("a + b =" + a.add(b)); System.out.println("a - b =" + a.subtract(b)); System.out.println("a * b =" + a.multiply(b)); System.out.println("a / b =" + a.divide(b)); }5.5浮点数的特殊值
System.out.println(3.14/0);//Infinity--无穷大 System.out.println(0/0.0);//NaN-Not a Number System.out.println(0.0/0);//NaN-Not a Number 运算符 1.1 概述运算符 用于连接 表达式 的 操作数,并对操作数执行运算。
例如,表达式num1+num2,其操作数是num1和num2,运算符是”+”。
在java语言中,运算符可分为5种类型:
算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符。
根据操作数的不同,运算符又分为单目运算符、双目运算符和三目运算符。
单目运算符只有一个操作数,双目运算符有两个操作数,三目运算符则有三个操作数。
位运算符涉及到二进制位的运算,在java 程序中运用不是很多。
1.2 运算符速查表a是操作数,++是自增运算符,–是自减运算符,自增和自减运算符即可以放在变量的前面,也可以放在变量的后面,例如:a++、++a、a–、–a等。 自增(++):将变量的值加1 分前缀式(如++a)和后缀式(如a++)。前缀式是先加1再使用;后缀式是先使用再加1。 自减(–):将变量的值减1 分前缀式(如–a)和后缀式(如a–)。前缀式是先减1再使用;后缀式是先使用再减1。
逻辑运算符连接两个关系表达式或布尔变量,用于解决多个关系表达式的组合判断问题 注意逻辑运算符返回的运算结果为布尔类型 通常,我们用0表示false,用1表示true 与:表示并且的关系 &单与: 1 & 2 ,结果想要是true,要求1和2都必须是true &&双与(短路与):1 && 2 ,当1是false时,2会被短路,提高程序的效率 或:表示或者的关系 |单或: 1 | 2,结果想要是true,要求1和2只要有一个为true就可以 ||双或(短路或):1 || 2,当1是true时,2会被短路,提高程序效率 TIPS:当一个表达式包含多个运算符时,就需要考虑运算符的优先级,优先级高的运算符先参与运算,优先级低的运算符后参与运算。在实际的开发中,不需要特别去记忆运算符的优先级别,也不要刻意的使用运算符的优先级别,对于不清楚优先级的地方使用小括号辅助进行优先级管理。
//定义变量max来保存比较的最大值 int max = a > b ? a : b; 分支结构 //判断用户的打折段位并打折 if(price >= 5000) {//满5000 count = price *0.5;//打5折 }else if(price >= 2000) {//满2000 count = price * 0.8;//打折8折 }else if(price >= 1000) {//满1000 count = price *0.9;//打9折 } switch结构 1、概述:switch case 语句用来判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。
当一个case成立,从这个case向后穿透所有case,包括default,直到程序结束或者遇到break程序才结束
int a = 5; /**总结1:a 可以支持5种数据类型:byte short char int * jdk1.5以后支持String*/ switch(a) { case 1 : System.out.println(1);break; case 2 : System.out.println(2);break; /**总结2:break表示结束当前的case,如果不加break, * 会向后穿透所有的case,包括default*/ case 3 : System.out.println(3);break; case 4 : System.out.println(4);break; /**总结3:default是保底选项,可加可不加 * default是如果没有case被匹配到时执行的默认选项 * 如果在default之前遇到了break,default才不会执行*/ default : System.out.println(0); } 2、switch结构的注意事项存在至少2层for循环,根据外层的条件,判断里层能否执行 如果能执行,就把里层代码都循环完毕后,再继续判断是否执行外层循环的下一轮循环
for(开始条件;循环条件;更改条件){ //外层循环 for(开始条件;循环条件;更改条件){ //内层循环 循环体; } } //需求:打印矩形(3*5) //***** //***** //***** for(int i = 1; i<=3 ; i++) {//外循环--行数 for(int j = 1; j<=5;j++) {//内循环--列数 System.out.print("*"); } System.out.println();//空白行用来换行 } 增强for public static void print(Integer[] a) { //使用普通循环遍历数组比较复杂,引入高效for循环 //普通循环的好处是可以控制循环的步长(怎么变化) for (int i = 0; i < a.length; i=i+2) { System.out.println(a[i]); } /** * 高效for/foreach循环--如果只是单纯的从头到尾的遍历,使用增强for循环 * 好处:比普通的for循环写法简便,而且效率高 * 坏处:没有办法按照下标来操作值,只能从头到尾依次遍历 * 语法:for(1 2 : 3){代码块} 3是要遍历的数据 1是遍历后得到的数据的类型 2是遍历起的数据名 */ for(Integer i : a) { System.out.print(i); } } break与continuebreak: 直接结束当前循环,跳出循环体,简单粗暴 TIPS: break以后的循环体中的语句不会继续执行,循环体外的会执行 continue: 跳出本轮循环,继续下一轮循环 TIPS:continue后本轮循环体中的语句不会继续执行,但是会继续执行下轮循环,循环体外的也会执行
for(){ 代码1 if(条件){ 代码3… break;//如果成立,直接跳出这个for循环 } 代码2… } continue:跳出本次循环,进入下一轮 for(){ 代码1 if(条件){ 代码3… continue;//如果成立,跳出本次for循环,进入下一轮 } 代码2… } while形式(先判断,再执行)
while(执行条件){ 循环体; } do-while形式(先执行,再判断,循环体代码保证最少执行一次)
do{ 循环体; } while (执行条件); 三种循环的区别:三种循环都可以互相代替 1、 for:知道循环次数 2、 while/do while:当循环次数不确定时 3、 while:先判断,不符合规则,不执行代码 4、 do while:代码最少被执行一次,再去判断,符合规则,再次执行代码
变量概念: 可以改变的数,称为变量。在Java语言中,所有的变量在使用前必须声明。 一般通过“变量类型 变量名 = 变量值 ;”这三部分来描述一个变量。如:int a = 3 ; 变量的使用原则:就近原则,即尽量控制变量的使用范围到最小
局部变量 位置:定义在方法里或者局部代码块中注意:必须手动初始化来分配内存.如:int i = 5;或者int i; i = 5; 作用域:也就是方法里或者局部代码块中,方法运行完内存就释放了
成员变量位置:定义在类里方法外 注意:不用初始化,也会自动被初始化成默认值 作用域:整个类中,类消失了,变量才会释放
方法概述: 被命名的代码块,方法可以含参数可以不含参数,可以提高代码的复用性。 形式:
方法的修饰符 方法的返回值 方法名([参数列表…]){方法体;} public static void main(String[] args){ …. } 方法调用顺序图:顺序执行代码,调用指定方法,执行完毕,返回调用位置
方法的重载方法的重载是指在一个类中定义多个同名的方法,但是每个方法的参数列表不同(也就是指参数的个数和类型不同),程序在调用方法时,可以通过传递给他们的不同个数和类型的参数来决定具体调用哪个方法.
public static void main(String[] args) { //重载:在一个类中有多个同名的方法&方法的参数列表不同(个数不同/类型不同) //参数列表指的是参数的类型顺序和个数,而不是参数名 //(int a,int b)和(int b,int a)—不属于重载 //(int a,String b)和(String b,int a)—属于重载 //方法的调用是根据方法名+参数列表来调用的 method(); method(20); method("海绵宝宝",3); method(100,100); } //创建method(int i,int j) public static void method(int i, int j) { System.out.println(i+j); } //创建method(String s,int i) public static void method(String s, int i) { System.out.println(s+"今年"+i+"岁啦"); } //创建method() public static void method() { System.out.println("哈哈哈哈我没有参数"); } //创建method(int num) public static void method(int num) { System.out.println(num*num); } 第三章:数组 概念:数组Array,标志是[ ] ,用于储存多个相同类型数据的集合 想要获取数组中的元素值,可以通过脚标(下标)来获取 数组下标是从0开始的,下标的最大值是数组的长度减1
创建数组数组的创建方式一般分为动态初始化和静态初始化
//1. 动态初始化 int[] a = new int[5]; //2. 静态初始化 int[] b = new int[]{1,2,3,4,5}; int[] c = {1,2,3,4,5}; 创建数组过程分析程序创建数组 int[] a = new int[5]; 时发生了什么?
TIPS: 数组名是个引用类型的变量,它保存着的是数组的地址,不是数组中的数据
向数组中存入数据hello 数组的长度数组的长度用 length属性来表示,数组一旦创建,长度不可改变,数组的长度允许为0
数组的遍历遍历:从头到尾,依次访问数组每一个位置,获取每一个位置的元素.形式如下:
我们通过数组的下标操作数组,所以for循环变量操作的也是数组下标 开始:开始下标0 结束:结束下标length-1 如何变化:++ for(从下标为0的位置开始;下标的取值<=数组的长度-1;下标++){ 循环体; } 冒泡排序 概念:冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。 这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
形式:相邻比较,从小到大排序,如果小的就往前换一 代表了从头到尾遍历循环数据
实现冒泡排序 //1.外层循环,控制比较的轮数,假设有n个数,最多比较n-1次 //开始值:1 结束值:<= a.length-1 变化:++ //控制的是循环执行的次数,比如5个数,最多比较4轮,<= a.length-1,最多取到4,也就是[1,4]4次 for(int i = 1 ; i <= a.length-1 ; i++) { System.out.println("第"+i+"轮:"); //2.内层循环:相邻比较+互换位置 for(int j=0; j < a.length-1 ; j++) { //相邻比较,a[j]代表的就是前一个元素,a[j+1]代表的就是后一个元素 if(a[j] > a[j+1]) { //交换数据 int t = a[j]; a[j] = a[j+1]; a[j+1] = t; System.out.println("第"+(j+1)+"次比较交换后:"+Arrays.toString(a)); } } System.out.println("第"+i+"轮的结果:"+Arrays.toString(a)); } 冒泡排序优化 //1.外层循环,控制比较的轮数,假设有n个数,最多比较n-1次 //开始值:1 结束值:<= a.length-1 变化:++ //控制的是循环执行的次数,比如5个数,最多比较4轮,<= a.length-1,最多取到4,也就是[1,4]4次 for(int i = 1 ; i <= a.length-1 ; i++) { boolean flag = false;//优化2 System.out.println("第"+i+"轮:"); //2.内层循环:相邻比较+互换位置 //结束值:a.length-i -i是因为之前轮中找到的最大值无序参与比较,i轮会产生i个最大值,所以需要 for(int j=0; j < a.length-i ; j++) {//优化1 //相邻比较,a[j]代表的就是前一个元素,a[j+1]代表的就是后一个元素 if(a[j] > a[j+1]) { //交换数据 int t = a[j]; a[j] = a[j+1]; a[j+1] = t; flag = true; //System.out.println("第"+(j+1)+"次比较交换后:"+Arrays.toString(a)); } } if(flag == false) { return a; } System.out.println("第"+i+"轮的结果:"+Arrays.toString(a)); }优化的点有2个 : 第1个就是前面几轮排序产生的最大值不需要参与后面轮的比较,执行过几轮就会产生几个值不参与比较,i轮i个,所以需要-i 第2个优化的点就是我们要设置一个量,这个量用来检测在当前这一轮的相互比较中究竟有没有发生元素的互换位置,如果发生了互换,说明顺序还没排好,flag就改成true,进行下一轮比较,但是如果在当前轮,所有元素都进行了相互比较,并没有互换位置,这就说明顺序已经排好序了,无需下一轮比较,直接return结束方法即可
数组工具类ArraysArrays.toString(数组) 把数组里的数据,用逗号连接成一个字符串[值1,值2]
Arrays.sort(数组) 对数组进行排序,对于基本类型的数组使用的是优化后的快速排序算法,效率高 对引用类型数组,使用的是优化后的合并排序算法
Arrays.copyOf(数组,新的长度) 把数组赋值成一个指定长度的新数组 新数组的长度 大于 原数组, 相当于复制,并增加位置 新数组的长度 小于 原数组, 相当于截取一部分数据
二维数组概念:存放数组的数组,也就是说数组里存的还是数组的数据形式。
创建二维数组 int[][] a = {{3,5},{7,9},{1,2}}; --创建外部数组,长度是3 --给每个外部数组的位置创建内部数组,每个内部数组的长度是2 --给每个内部数组进行数据初始化 --二维数组生成唯一的地址值 --把地址值交给引用类型变量a来保存 遍历二维数组 for (int i = 0; i < a.length; i++) {//遍历外部数组 for (int j = 0; j < a[i].length; j++) {//遍历内部数组 System.out.println(a[i][j]);//依次打印二维数组中每个元素的值 } } 第四章:面向对象 概念:面向对象其实是一种编程思想,通过它可以把生活中复杂的事情变得简单化,从原来的执行者变成了指挥者。 面向对象是基于面向过程而言的。
- 面向过程强调的是过程,比如: 1. 打开冰箱门 2. 把大象放进去 3. 关上冰箱门
- 面向对象强调的是结果,比如: 什么样的冰箱?什么样的大象?谁负责把大象装进去? 而不是关注那个负责的人怎么把大象装冰箱里. 衣服脏了,直接让女盆友去处理,等着穿干净的就可以了。你不关注中间的过程,只要找好对象就可以了~ 再比如.我们想吃一道菜,无需考虑是怎么传菜,怎么做菜的,只需点菜即可.传菜和做菜都有具体的对象来帮我们完成具体的功能.我们不需要关注实现的过程,只需要关注结果就好 这就是我们所说的面向对象的编程实现(OOP,Object Oriented Programming)
概述: 封装是隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式,比如类和方法 好处:
private关键字: 是一个权限修饰符 ,可以用来修饰成员变量和成员方法.被私有化的成员只能在本类中访问
/**本类用于测试封装*/ public class Test4_Private 函数main public static void main(String[] args) { //5.创建学生类对象--通过new关键字创建学生类对象 Student s = new Student(); //6.初步测试Student类对象s System.out.println(s.name);//可以通过"."来调用s对象的name属性,查看它的值 s.study();//可以通过"."来调用s对象的study() //7.给s对象的属性赋值 s.name = "程序猿"; s.sno = 666; //s.subject = "Java培优"; //8.查看赋值后的属性值 System.out.println(s.name); System.out.println(s.sno); //System.out.println(s.subject); //10.通过Student类中提供 的公共的subject属性的设置与访问方法来给subject属性赋值并查看 s.setSubject("JavaCGB"); System.out.println(s.getSubject()); //eat(); s.study(); } } //1.通过class关键字创建学生类--用来描述学生这一类型--属性+行为 /*** * 封装:通过private关键字(权限修饰符)来修饰成员变量/成员方法 * 被修饰的成员就实现了私有化,访问权限只能在本类中访问 */ class Student{ //2.定义属性--成员变量 String name;//姓名 int sno;//学号 //9.对成员变量进行封装 private String subject;//科目 /**对外提供公共的属性值设置方式*/ public void setSubject(String s) { subject = s; } /**对外提供公共的属性值查看方式*/ public String getSubject() { return subject; } //3.定义行为--方法 public void study() { System.out.println("正在学习JAVA"); /**我们可以在公共的方法里调用私有方法*/ eat(); } //11.封装方法 private void eat() { System.out.println("干饭人 干饭魂"); } }TIPS:如何封装?封装后的资源如何访问? 我们可以使用private关键字来封装成员变量与方法 如何访问私有资源? 关于成员变量:
概念: 继承是面向对象最显著的一个特征 继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并扩展新的能力. Java继承是会用已存在的类的定义作为基础建立新类的技术 新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,但不能选择性的继承父类(超类/基类) 这种继承使得复用以前的代码非常容易,能够大大的缩短开发的周期,降低开发费用.
特点:
可以通过这个关键字使用父类的内容,Super代表的是父类的一个引用对象 注意:在构造方法里,出现的调用位置必须是第一行
方法重写Override概念: 多态指同一个实体同时具有多种形式 它是面向对象程序设计(OOP)的一个重要特征。 主要是指同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。 好处是:可以把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。 水果有两种形态:水果和苹果,不关心买回来的是苹果还是西瓜,只要是水果就行
class Animal{//1.定义父类Animal ....eat(){syso("吃啥都行")} } class Cat extends Animal{//2.1定义子类Cat ....eat(){syso("吃小鱼干")} } class Dog extends Animal{//2.2定义子类Dog ....eat(){syso("吃肉骨头")} } class Pig extends Animal{//2.3定义子类Pig ....eat(){syso("吃菜叶子")} } main(){ //3.创建子类对象 Cat c = new Cat();//小猫是小猫 Dog d = new Dog();//小狗是小狗 Pig p = new Pig();//小猪是小猪 //4.创建多态对象(父类引用指向子类对象/编译看左边,运行看右边) Animal a1 = new Cat();//小猫是小动物 Animal a2 = new Dog();//小狗是小动物 Animal a3 = new Pig();//小猪是小动物 } 特点:特点: 1) 成员变量: 使用的是父类的 2) 成员方法: 由于存在重写现象,所以使用的是子类的(成员方法使用的是父类的声明,子类的实现) 3) 静态成员: 随着类的加载而加载,谁调用就返回谁的 4)如果父子类都有静态方法,使用的是父类的
/**本类用于多态中的元素测试*/ public class DuoTai { public static void main(String[] args) { //7.创建子类对象进行测试 Dog2 d = new Dog2(); System.out.println(d.sum);//20 d.eat();//小狗要吃肉骨头 d.play();//小狗爱打滚儿~~~ //10.创建多态对象进行测试 /**口诀1:父类引用指向子类对象*/ /**口诀2:编译(保存)看左边,运行(测试)看右边*/ Animal2 a = new Dog2(); /**2.多态中,成员变量使用的都是父类的*/ System.out.println(a.sum);//10 /**3.多态中,成员方法使用的是父类的声明,子类的实现*/ a.eat(); /**4.多态中,如果父子类都有静态方法,使用的是父类的*/ a.play();//玩啥都行 } } //1.创建父类 class Animal2{ //2.创建成员变量 int sum = 10; //3.创建成员方法 public void eat() { System.out.println("吃啥都行"); } //8.父类中定义静态方法play() public static void play() { System.out.println("玩啥都行"); } } /**1.多态的前提:继承+重写*/ //4.定义子类Dog2 class Dog2 extends Animal2{ //5.定义子类的成员变量 int sum = 20; //6.重写父类的eat() /**@Override 这个注解加在方法上,表示这是一个重写的方法*/ @Override //注解--标签 public void eat() { System.out.println("小狗要吃肉骨头"); } //9.定义子类的静态方法play() //@Override --不是重写,不能加这个注解 public static void play() { System.out.println("小狗爱打滚儿~~~"); } }注意!!!静态资源属于类,不存在重写现象,只是两个类中有同样声明的方法而已,不属于重写
向上转型和向下转型在JAVA中,继承是一个重要的特征,通过extends关键字,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展。 在应用中就存在着两种转型方式,分别是:向上转型和向下转型。 比如:父类Parent,子类Child 向上转型:父类的引用指向子类对象Parent p=new Child(); 说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类的方法就根据这个引用指向调用子类重写方法。 向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。 Parent p = new Child();//向上转型,此时,p是Parent类型 Child c = (Child)p;//此时,把Parent类型的p转成小类型Child //其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的 说明:向下转型时,是为了方便使用子类的特殊方法,也就是说当子类方法做了功能拓展,就可以直接使用子类功能。
类和对象分析手机事物: 属性:颜色,尺寸,品牌,价格 功能:打电话,发短信,听音乐 类:手机类,抽取相同的属性和行为 对象:可以按照模板生产很多个手机,比如1号手机对象,包含特有的成员变量和方法 通过class关键字创建类,通过new关键字创建对象。
//在一个java文件中可以写多个class,但是被public修饰的只能有一个,而且这个类的名字就是文件名 public class Test3_ClassExec { public static void main(String[] args) { //2.在main()中通过new关键字来创建对应类的对象 Phone p = new Phone(); //3.通过.来完成对象功能的调用 p.call(); p.message(); p.learn(); //4.通过.来查看对象的属性值 System.out.println(p.brand); System.out.println(p.price); System.out.println(p.size); System.out.println(p.color); } } //1.通过class关键字创建手机类--用来描述手机这一类事物--特征+行为 //类是一类事物的抽象,只抽象的规定这一类事物的特征和行为 class Phone{ //特征(属性)--类的成员变量来描述--位置:类里方法外 String brand;//品牌 double price;//价格 double size;//尺寸 String color;//颜色 //行为(功能)--类的方法来描述--修饰符 返回值类型 方法名(参数列表){方法体} public void call() { System.out.println("正在打电话"); } public void message() { System.out.println("正在发短信"); } public void learn() { System.out.println("正在看直播"); } } 对象在内存中的存储 创建对象的流程Person p = new Person();//短短这行代码发生了很多事情
Java把内存分成5大区域,我们重点关注栈和堆。
创建多个对象
p2.brand = “HUAWEI”; 就是先到栈内存中找到p2中保存的唯一的地址值 然后根据地址值找到Phone对象,并对其对应的属性值进行修改
p2.learn(); 也是先到栈内存中找到p2中保存的唯一的地址值 然后根据地址值找到Phone对象,执行Phone对象的lean()方法
匿名对象没有名字的对象,是对象的简化表示形式。 使用场景: 当被调用的对象只调用一次时(多次会创建多个对象浪费内存)
Demo d = new Demo(); d.sleep(); d.game(); //这个d就是对象的名字。 //也可以写成: new Demo().show();//创建了一个对象调方法 new Demo().game();//又创建了一个对象调方法 访问控制符用来控制一个类,或者类中的成员的访问范围。 TIPS:default是表示不写修饰符,默认,如果写default单词来修饰会报错
构造方法 概念:构造方法是一种特殊的方法,它是一个与类同名且没有返回值类型的方法 对象创建就是通过构造方法完成的,主要功能是完成对象的创建或者对象的初始化 当类创建对象(实例化)时,会自动调用构造方法 构造方法与普通方法一样也可以重载.
形式: 与类同名,且没有返回值类型,可以含参也可以不含参修饰符 方法名([参数列表]){ 注意:方法名与类名一样 代码…… }
TIPS:关于构造函数怎么记忆 特点:方法名与类名相同,且没有返回值类型 执行时机:创建对象时立即执行 默认会创建无参构造,但是,如果自定了含参构造,默认的无参构造会被覆盖,注意要手动添加哦
构造代码块与局部代码块构造代码块的特点
局部代码块
概念: this 代表本类对象的一个引用对象 形式: this.name = name ;
/** * this可以用来进行构造方法之间的调用\\ * 但是注意!!!!!调用是单向的,不是双向来回调用,会死循环 * */ //1.创建类Dog class Dog{ //2.定义属性name String name; //3.提供无参构造 public Dog() { /**在无参构造中调用含参构造的功能*/ /**规定:this关键字必须在构造方法中的第一行*/ this("拉拉"); System.out.println("无参构造"); } //4.提供含参构造 public Dog(String s) { /**在含参构造中调用无参构造的功能*/ /**规定:this关键字必须在构造方法中的第一行*/ //this(); System.out.println("含参构造"+s); } } this与super的区别概念: 是java中的一个关键字,用于修饰成员(成员变量和成员方法)
特点:1.静态资源只能调用静态资源 2.非静态资源既可以调用静态资源,也可以调用非静态资源
静态代码块、构造代码块、局部代码块静态代码块格式: static {} 静态资源随着类的加载而加载,并且只被加载一次,一般用于项目的初始化 特点: 被static修饰,位置在类里方法外
三种代码块的比较
几种代码块的关系: 1.代码之间的执行顺序: 静态代码块–>构造代码块–>构造方法–>局部代码块 2.为什么是这样的顺序呢? 静态代码块要优先于对象进行加载 是随着类的加载而加载到内存中的 只加载一次,并且一直存在,直到类消失,它才会消失 3.每个元素的作用: 1)静态代码块:专门用来完成一些需要第一时间加载并且只加载一次的资源 2)构造代码块:创建对象时才会触发,用来提取构造方法中的共性内容 3)构造方法:创建对象时调用,用来创建对象,在构造代码块执行后执行 4)局部代码块:调用所在的方法时才会调用,用来控制变量的作用范围
静态变量和实例变量的区别在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。 在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
final概念:
特点:
Java中可以定义被abstract关键字修饰的方法,这种方法只有声明,没有方法体,叫做抽象方法. Java中可以定义被abstract关键字修饰的类,被abstract关键字修饰的类叫做抽象类 如果一个类含有抽象方法,那么它一定是抽象类 抽象类中的方法实现交给子类来完成
抽象方法的格式:权限修饰符 abstract 返回值类型 方法名(参数列表);
特点:抽象类中的构造函数通常在子类对象实例化时使用
/**本类用于测试抽象类中的成员*/ public class Test4_Abstract3 { public static void main(String[] args) { //7.创建多态对象进行测试 Fruit f = new Apple(); System.out.println(f.sum);//10 //f.name = "lemon";//常量的值不可以被修改The final field Fruit.name can not be assigned System.out.println(f.name); f.eat();//父类的功能 f.eat2();//子类实现的功能 f.clean();//子类实现的功能 } } //1.创建抽象父类--水果类 abstract class Fruit{ /**1.抽象类中可以有成员变量吗?--可以!!!*/ //3.1创建抽象父类的成员变量 int sum = 10; /**2.抽象类中可以有成员常量吗?--可以!!!*/ //3.2创建抽象父类的成员常量,注意初始化 final String name ="banana"; /**3.抽象类中可以有普通方法吗?--可以!!! * 抽象类中可以都是普通方法吗?--可以!!! * */ /**4.如果一个类中都是普通方法,为啥还要被声明成抽象类呢? * 原因:抽象类不可以创建对象 * 如果不想让外界创建本类对象,可以把普通类声明成抽象类 * */ //4.创建抽象父类的普通方法 public void eat() { System.out.println("吃啥水果都行"); } //5.创建抽象父类中的抽象方法 /**5.抽象类中可以有抽象方法 * 一旦类中有抽象方法,这个类必须被声明成一个抽象类*/ public abstract void eat2() ; public abstract void clean(); } /** * 6.当一个类继承了父类,并且父类是抽象父类时 * 子类需要重写(实现)父类中的所有抽象方法或者把自己变成抽象子类 * */ //2.创建子类--苹果类 //6.1解决方案一:把自己变成一个抽象子类 //abstract class Apple extends Fruit{ //6.2解决方案二:重写抽象父类中的所有抽象方法 class Apple extends Fruit{ @Override public void eat2() { System.out.println("水果中最爱吃苹果"); } @Override //注解,相当于标记,标记了这个方法是一个重写的方法 public void clean() { System.out.println("苹果还是要好好洗洗再吃的"); } } abstract注意事项抽象方法要求子类继承后必须重写。 那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中。用是可以用的,只是没有意义了。
递归案例:
//需求:求用户输入数字的阶乘结果 //f(int n)--用来求阶乘 //规律: //f(n)= n*f(n-1) //f(5)= 5*4*3*2*1 = 5*f(4) //f(4)= 4*3*2*1 = 4*f(3) //f(3)= 3*2*1 = 3*f(2) //f(2)= 2*1 = 2*f(1) //f(1)= 1 // //5!=5*4*3*2*1=120 //4!=4*3*2*1 //3!=3*2*1 //2!=2*1 //1!=1 接口 概念:Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现,Java接口和Java抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现 OOP面向对象编程,如果要提高程序的复用率,增加程序的可维护性,可扩展性,就必须是面向接口编程,面向抽象的变成,正确的使用接口/抽象类这些抽象类型作为java结构层次上的顶层.
接口格式: interface 接口名{ 代码… } 接口的特点:总结: 接口里是没有构造方法的。在创建实现类的对象时默认的super(),是调用的默认Object的无参构造。 接口里没有成员变量,都是常量。所以,你定义一个变量没有写修饰符时,默认会加上:public static final 接口里的方法,默认就都是抽象的,如果你不写明是abstract的,那会自动补齐。例如:public abstract void save
接口的多继承多实现 public class Test4 { //5.创建入口函数 public static void main(String[] args) { //6.创建实现类对象进行测试 Inter3Impl i3 = new Inter3Impl(); i3.update(); //7.创建多态对象进行测试 Inter3 i4 = new Inter3Impl(); i4.find(); } } //1.创建接口1 interface Inter1{ void save();//保存功能 void delete();//删除功能 } //2.创建接口2 interface Inter2{ void update();//更新功能 void find();//查询功能 } //3.创建接口3用来测试接口与接口的继承关系 /**1.接口之间可以建立继承关系,而且还可以多继承 * 接口与接口之间用逗号隔开 * */ interface Inter3 extends Inter1,Inter2{ } //4.创建Inter3接口的实现类并添加未实现的方法 /**2.接口和实现类之间可以建立实现关系,通过implments关键字来完成 * 注意,java类是单继承,而接口不限,写接口时,我们一般先继承再实现 * */ class Inter3Impl implements Inter3{ @Override public void save() { System.out.println("稍等...正在保存中..."); } @Override public void delete() { System.out.println("稍等...正在删除中...."); } @Override public void update() { System.out.println("客官,马上就更新好啦~~"); } @Override public void find() { System.out.println("小二正在马不停蹄的查询~~~"); } } 总结1.类与类的关系
–继承关系,只支持单继承 –比如,A是子类 B是父类,A具备B所有的功能(除了父类的私有资源和构造方法) –子类如果要修改原有功能,需要重写(方法签名与父类一致 + 权限修饰符>=父类修饰符)
2.类和接口的关系 –实现关系.可以单实现,也可以多实现 –class A implements B,C{} –其中A是实现类,B和C是接口,A拥有BC接口的所有功能,只是需要进行方法的重写,否则A就是抽象类
3.接口与接口的关系 –是继承关系,可以单继承,也可以多继承 –interface A extends B,C{} –其中ABC都是接口,A是子接口,具有BC接口的所有功能(抽象方法) –class X implements A{} –X实现类需要重写ABC接口的所有方法,否则就是抽象类 –class A extends B implements C,D{} –其中A是实现类,也是B的子类,同时拥有CD接口的所有功能 –这时A需要重写CD接口里的所有抽象方法
4.抽象类与接口的区别
类: 对事物/算法/逻辑/概念等等的抽象,可以把它理解成”模板/图纸” 封装:相关的数据/运算代码封装成一个”类”组件
对象: 从”类”出发创建具体的”实例” 每个对象,占用独立的内存空间,保存各自的属性数据 可以独立控制一个对象,来执行指定的方法代码
软件设计的开闭原则OCP:开放功能扩展,关闭源码修改。等 开闭原则的英文全称是Open Close Principle,缩写是OCP,它是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。 开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。 开闭原则,是一种设计模式,随着面向对象程序设计的思想,应运而生。 开,指的是可以在源代码的基础上进行扩展,比如继承,接口,抽象类等。在JAVA中,之所以用继承,是在可以直接调用类库的前提下,对其功能进行扩展。不需要应用者去了解封装类的内部逻辑就可以做开发。 闭:指不允许对原有的代码进行修改。以免影响其他现有功能,造成功能瘫痪。
第五章:异常概述: 用来封装错误信的对象
异常的继承结构:
Throwable : 顶级父类 --Error : 系统错误,无法修复 --Exception : 可以修复的错误 -- RunTimeException -- ClassCastException -- ClassNotFoundException -- ... 异常处理当程序中遇到了异常,通常有两种处理方式:捕获或者向上抛出 当一个方法抛出异常,调用位置可以不做处理继续向上抛出,也可以捕获处理异常
API(Application Programming Interface,应用程序接口)是一些预先定义的函数。目的是提供应用程序与开发人员基于某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节. API是一种通用功能集,有时公司会将API作为其公共开放系统,也就是公司制定自己的系统接口标准,当需要进行系统整合,自定义和程序应用等操作时,公司所有成员都可以通过该接口标准调用源代码.
Java.util包是java中的工具包,包含各种实用工具类/集合类/日期时间工具等各种常用工具包 import java.util.Scanner; import java.util.Arrays; java.lang包是java的核心,包含了java基础类,包括基本Object类/Class类/String类/基本数学类等最基本的类 这个包无需导入,默认会自动导入 import java.lang.Object; import java.lang.String; import java.lang.StringBuilder/StringBuffer; 正则表达式 包装类等等 Object 概念:Object类是所有Java类的祖先,也就是说我们所说的”顶级父类” 存在于java.lang.Object,这个包不需要我们手动导包 每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法.在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类.
常用方法介绍: toString()本方法用于返回对应对象的字符串表示.
hashCode()本方法用于返回对应对象的哈希码值 TIPS:哈希码是一种算法,使不同的对象有不同的哈希码值,但是也有相同的情况,我们称之为”哈希碰撞”
equals()本方法用于指示其他某个对象是否与当前对象”相等”
String 特点:String是一个封装char[]数组的对象,字符串不可变 通过下图中的底层实现可以看出:被final修饰,是常量 String str = “abc”; 等效于:char data[] = {‘a’, ‘b’, ‘c’};
创建String对象的方式方式一: String(char[] value) 分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。 方式二: String str = “abc”;
1.在线程安全上 : –StringBuffer是旧版本就提供的,线程安全的。@since JDK1.0 –StringBuilder是jdk1.5后产生,线程不安全的。@since 1.5 2. 在执行效率上,StringBuilder > StringBuffer > String 3.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题。 abstract class AbstractStringBuilder implements Appendable, CharSequence {
正则表达式Regex 概述:正确的字符串格式规则。 常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。
String提供了支持正则表达式的方法 Matches(正则) : 当前字符串能否匹配正则表达式 replaceAll(正则,子串) : 替换子串 split(正则) : 拆分字符串 /**本类用于测试正则表达式 * 需求:测试输入身份证号,测试用户输入是否正确 * */ public class Test4_Regex { public static void main(String[] args) { //1.提示并接收用户输入的身份证号: System.out.println("请您输入您的身份证号:"); String input = new Scanner(System.in).nextLine(); //2.编辑正则表达式 //身份证号的规律:一般都是18位,前17位都是数字,最后一位可能是数字,也有可能是xX String regex = "[0-9]{17}[0-9xX]"; //3.判断,是否符合正则表达式的规则(也就是输入的是正确的身份证号吗?) if( input.matches(regex) ) {//matches()是String类提供的功能,可以用来判断字符串是否符合正则表达式的要求 System.out.println("输入正确!"); }else { System.out.println("输入不正确,请重新输入!"); } } } BigDecimal/BigInteger 概述:BigDecimal:常用来解决精确的浮点数运算 BigInteger: 常用来解决超大的整数运算
创建对象:BigDecimal(double val) 将double转换为BigDecimal,后者是double的二进制浮点值十进制表示形式,有坑! BigDecimal(String val) 将String类型字符串的形式转换为BigDecimal
常用方法Add(BigDecimal bd) : 做加法运算 Subtract(BigDecimal bd) : 做减法运算 Multiply(BigDecimal bd) : 做乘法运算 Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常 Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用 setScale(保留位数,舍入方式) : 同上 pow(int n) : 求数据的几次幂
public static void main(String[] args) { method2();//使用BigDecimal来解决浮点数运算不精确的现象 } public static void method2() { //1.提示并接收用户输入的两个小数 System.out.println("请输入您要计算的两个小数:"); double a = new Scanner(System.in).nextDouble(); double b = new Scanner(System.in).nextDouble(); //2.创建工具类对象,把基本类型的a和b交给工具类对象BigDecimal来保存 /**1.最好不要使用double作为构造函数的参数,不然还会产生不精确的现象,有坑!!!!*/ /**2.最好使用重载的,参数类型是String的构造函数,double转String,直接拼个空串就可以*/ BigDecimal bd1 = new BigDecimal(a+""); BigDecimal bd2 = new BigDecimal(b+""); //3.通过BigDecimal上的方法,做精确运算 //3.1定义对象来保存结果 BigDecimal bd3; //3.2 add(BigDecimal bd) : 做加法运算 bd3 = bd1.add(bd2); System.out.println(bd3); //3.3 subtract(BigDecimal bd) : 做减法运算 bd3 = bd1.subtract(bd2); System.out.println(bd3); //3.4 multiply(BigDecimal bd) : 做乘法运算 bd3 = bd1.multiply(bd2); System.out.println(bd3); //3.5 add(BigDecimal bd) : 做除法运算 /**java.lang.ArithmeticException,除法运算,除不尽时会抛异常*/ //bd3 = bd1.divide(bd2);--方案一 /**divide(m,n,o) --m是要除以哪个对象保存的值,n要保留几位,o是摄入方式,最常使用的是四舍五入*/ bd3 = bd1.divide(bd2, 3, BigDecimal.ROUND_HALF_UP);//方案二: System.out.println(bd3); } 包装类把基本类型进行包装,提供更加完善的功能。 基本类型是没有任何功能的,只是一个变量,记录值,而包装类可以有更加丰富的功能
Integer创建对象
1. new Integer(5); 2. Integer.valueOf(5); 在Integer类中,包含256个Integer缓存对象,范围是 -128到127。 使用valueOf()时,如果指定范围内的值,访问缓存对象,而不新建;如果指定范围外的值,直接新建对象。 public static void main(String[] args) { //1.创建int基本类型的包装类Integer的对象方式1 /**1.Integer包装类的默认值是null*/ Integer i0; Integer i1 = new Integer(5); //2.创建int基本类型的包装类Integer的对象方式2 /**2.Integer有一个高效的效果(-128~127) * 因为静态的valueOf(),相同的数据只会存一次,后续再存都会使用已经存过的数据 * */ Integer i2 = Integer.valueOf(127); Integer i3 = Integer.valueOf(127); Integer i4 = Integer.valueOf(300); Integer i5 = Integer.valueOf(300); System.out.println(i1==i2);//false,==比较的是地址值 System.out.println(i2==i3);//true,是Integer,并且是在(-128~127)范围内,相同数据只存一次 System.out.println(i4==i5);//false,是Integer,但是不在(-128~127)范围内,所以存了两次,两个对象 } Double创建对象
1. new Double(3.14) 2. Double.valueOf(3.14)//和 new 没有区别 //3.创建Double对象 Double d1 = new Double(3.4); Double d2 = Double.valueOf(3.4); Double d3 = Double.valueOf(3.4); System.out.println(d2==d3);//false,只有Integer包装类有高效的效果 //4.测试常用方法 //原因:parseInt()已经把字符串8000转换成了int类型的数字8000,可以参与运算 System.out.println(i1.parseInt("8000")+10);//8010,执行了加法运算 System.out.println(d1.parseDouble("2.2")+1);//3.2,执行了加法运算 自动装箱和自动拆箱 概述:自动装箱:把 基本类型 包装成对应的 包装类型 的过程 Integer a = 5;//a是引用类型,引用了包装对象的地址。 编译器会完成对象的自动装箱:Integer a = Integer.valueOf(5);
自动拆箱:从包装类型的值,自动变成 基本类型的值 int i = a;//a现在是包装类型,没法给变量赋值,需要把5取出来。 编译器会完成自动拆箱:int i = a.intValue();
public static void main(String[] args) { //1.定义包装类型的数据 //之前的方式:创建包装类型的两种方式: Integer i11 = new Integer(127); Integer i22 = Integer.valueOf(127); //现在的方式: /**1.自动装箱:编译器会自动把基本类型int数据5,包装成包装类型Integer,然后交给i1保存 * 自动装箱底层发生的代码:Integer.valueOf(5); * valueOf()的方向:int --> Interger * */ Integer i1 = 5;//不会报错,这个现象就是自动装箱 /**2.自动拆箱:编译器会自动把包装类型的5,拆掉箱子,变回到基本类型数据5 * 自动拆箱底层发生的代码:i1.intValue() * intValue()的方向:Integer --> int * */ int i2 = i1;//不会报错,这个现象就是自动拆箱 } 拓展: ==和equals的区别1.当使用 = = 比较时,如果相比较的两个变量是引用类型,那么比较的是两者的物理地值(内存地址),如果相比较的两个变量都是数值类型,那么比较的是具体数值是否相等。 2.当使用equals()方法进行比较时,比较的结果实际上取决于equals()方法的具体实现 众所周知,任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String、integer等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,而在Object类的默认实现中,equals()方法的底层是通过 = =来实现的。 System.out.println(p1.name = = p2.name);//true,name是String类型,保存在常量池中,值是同一个
/**本类用于测试==与equlas的区别*/ public class Test7 { public static void main(String[] args) { Person p1 = new Person("凹凸曼",16); Person p2 = new Person("凹凸曼",16); System.out.println(p1 == p2);//false,new了两个对象,p1和p2保存的地址值不同 System.out.println(p1.name == p2.name);//true,name是String类型,保存在常量池中,值是同一个 System.out.println(p1.age == p2.age);//true,age是int基本类型,保存的值都是18 //第一次测试:结果false,使用的是Object默认的逻辑,也是用==来比较的 //第二次测试:结果true,重写equals()后,就会使用子类重写后的功能,也就是说此时比较的不再是地址,而是类型&属性值 System.out.println(p1.equals(p2)); } } //1.创建Person类 class Person { String name; int age; public Person() { System.out.println("我是手动添加的无参构造"); } public Person(String name, int age) { super(); this.name = name; this.age = age; } //需求:比较两个对象的属性值,如果属性值都一样,就是"同一个对象",要求equals返回true @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } 第七章:IO 继承结构In/out 相对于程序而言的输入(读取)/输出(写出)的过程. 在java中,根据处理的数据单位不同,分为字节流和字符流 字节流 : 针对二进制文件 字符流 : 针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8
Java.io包下: File 字节流:针对二进制文件 InputStream --FileInputStream --BufferedInputStream --ObjectInputStream OutputStream --FileOutputStream --BufferedOutputStream --ObjectOutputStream 字符流:针对文本文件 Reader --FileReader --BufferedReader --InputStreamReader Writer --FileWriter --BufferedWriter --OutputStreamWriter --PrintWriter一行行写出 Stream流的概念数据的读写可以抽象成数据在管道中流动
- 流只能单方向流动
- 输入流用来读取à in
- 输出流用来写出 àout
- 数据只能从头到尾顺序的读写一次
封装一个磁盘路径字符串,对这个路径可以执行一次操作 可以封装文件路径、文件夹路径、不存在的路径
创建对象: File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例 new File(“d:/abc/a.txt”); new File(“d:/abc”,”a.txt”); 常用方法: //1.创建File对象 //参数是具体的路径,可以是文件的路径,也可以是文件夹的路径 //注意:需要手动在windows中创建D:\\ready\\1.txt并添加内容 /**1.\\在代码中具有特殊的意义,转义的作用, * 所以表示这个是一个真正的斜杠,需要转义一下*/ File file = new File("D:\\\\ready\\\\1.txt");//创建的是java对象 //2.测试常用方法 //2.1文件与文件夹属性 System.out.println(file.length());//15,获取指定文件的字节量 System.out.println(file.exists());//true,判断指定文件是否存在 System.out.println(file.isFile());//true,判断指定内容是否是文件 System.out.println(file.isDirectory());//false,判断指定内容是否是文件夹 System.out.println(file.getName());//1.txt,获取文件名 System.out.println(file.getParent());//D:\\ready,获取父级目录 System.out.println(file.getAbsolutePath());//D:\\ready\\1.txt,获取绝对路径 //2.2创建与删除 file = new File("D:\\\\\\\\ready\\\\\\\\2.txt"); /**如果指定创建文件的文件夹不存在 * 会抛出异常:java.io.IOException:系统找不到指定的路径 * 所以需要处理,目前的方式是抛出异常*/ System.out.println(file.createNewFile());//在win中创建不存在的文件2.txt file = new File("D:\\\\ready\\\\m"); System.out.println(file.mkdir());//在win中创建不存在的单级目录 file = new File("D:\\\\ready\\\\a\\\\b\\\\c"); System.out.println(file.mkdirs());//在win中创建不存在的多级目录 System.out.println(file.delete());//删除文件或者空文件夹,c文件夹被删除 file = new File("D:\\\\ready\\\\a"); System.out.println(file.delete());//由于a目录里还有b目录,所以删除不了 //2.3文件列表 file = new File("D:\\\\ready"); //查看文件夹下所有文件的名称,返回值类型是String[] String[] list = file.list(); System.out.println(Arrays.toString(list)); /**列出文件夹中所有的文件夹和文件对象,返回值是File[] * 数组的每个元素都是File对象,可进一步操作 * */ File[] listFiles = file.listFiles(); System.out.println(Arrays.toString(listFiles)); System.out.println(listFiles[0].length()); 递归求目录总大小 需求:递归求目录的总大小 D:\\\\ready,步骤分析如下: 1.列出文件夹中的所有资源--listFiles()-->File[] 2.判断,当前资源是文件还是文件夹--文件夹大小为0,文件大小需要累加 --是文件,求文件的字节量大小length(),累加就行 --是文件夹,继续列出文件夹下的所有资源--listFiles()-->File[] --判断,是文件,求文件的字节量大小length(),累加就行 --判断,是文件夹,再一次列出文件夹下的所有资源 --......重复操作 也就是说,规律就是:只要是文件夹,就需要重复步骤1 2 private static long size(File file) { //1.列出文件夹中的所有资源--listFiles()-->File[] File[] fs = file.listFiles(); //2.遍历数组,获取每file对象 //2.1定义变量,记录总和 long sum = 0; for(int i = 0;i < fs.length ; i++) { //2.2通过下标操作当前遍历到的资源 File f = fs[i]; //2.3判断,当前资源是文件还是文件夹--文件夹大小为0,文件大小需要累加 if(f.isFile()) { //--是文件,求文件的字节量大小length(),累加就行 sum += f.length();//相当于:sum = sum + f.length(); }else if(f.isDirectory()) { //--是文件夹,继续列出文件夹下的所有资源,1 2步骤--listFiles()-->File[] /**方法的递归,递归现象,就是在方法的内部调用方法自身*/ sum += size(f); } } return sum;//把sum记录的值返回调用位置 } 字节流读取字节流是由字节组成的,字符流是由字符组成的. Java里字符由两个字节组成.字节流是基本,主要用在处理二进制数据。 流式传输主要指将整个音频和视频及三维媒体等多媒体文件经过特定的压缩方式解析成一个个压缩包,由视频服务器向用户计算机顺序或实时传送。在采用流式传输方式的系统中,用户不必像采用下载方式那样等到整个文件全部下载完毕,而是只需经过几秒或几十秒的启动延时即可在用户的计算机上利用解压设备对压缩的A/V、3D等多媒体文件解压后进行播放和观看。此时多媒体文件的剩余部分将在后台的服务器内继续下载。
InputStream抽象类此抽象类是表示字节输入流的所有类的超类/抽象类,不可创建对象 常用方法:
abstract int read() 从输入流中读取数据的下一个字节 int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中 int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组,off表示存时的偏移量 void close() 关闭此输入流并释放与该流关联的所有系统资源 FileInputStream子类直接插在文件上,直接读取文件数据 创建对象 FileInputStream(File file)—直接传文件对象 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定FileInputStream(String pathname)—传路径 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定
BufferedInputStream子类BufferedInputStream 为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组(默认8k大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。 创建对象 BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
public static void main(String[] args) { method();//字节流的读取 method2();//高效字节流的读取 } public static void method() { InputStream in = null; try { //1.创建字节流读取对象 //new InputStream();//报错:抽象父类不可以创建对象 //FIS传入的参数是一个File对象 //InputStream in2 = new FileInputStream(new File("D:\\\\ready\\\\4.txt")); //FIS传入的参数是一个路径,保证此路径下的文件是存在且有内容的 in = new FileInputStream("D:\\\\ready\\\\4.txt"); //2.开始读取,read()每次读取1个字节,如果读到了数据的末尾,返回-1 // System.out.println(in.read());//97 // System.out.println(in.read());//98 // System.out.println(in.read());//99 // System.out.println(in.read());//-1 //3.1定义变量,记录读到的数据 int b; while((b = in.read()) != -1) {//3.2返回值为-1的时候表示没有数据了,循环结束 //3.3打印每次读取到的内容 System.out.println(b); } } catch (IOException e) { e.printStackTrace();//打印错误信到控制台 } finally {//try-catch-finally,finally中的代码一定会被执行,常用于关流 try { //4.释放资源 in.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void method2() { BufferedInputStream in = null; try { //1.创建高效字节流读取对象 //new InputStream();//报错:抽象父类不可以创建对象 //BIS是高效的读取流 // in2 = new BufferedInputStream( // new FileInputStream(new File("D:\\\\ready\\\\4.txt"))); in = new BufferedInputStream( new FileInputStream("D:\\\\ready\\\\4.txt")); //2.开始读取,read()每次读取1个字节,如果读到了数据的末尾,返回-1 // System.out.println(in.read());//97 // System.out.println(in.read());//98 // System.out.println(in.read());//99 // System.out.println(in.read());//-1 //3.1定义变量,记录读到的数据 int b; while((b = in.read()) != -1) {//3.2返回值为-1的时候表示没有数据了,循环结束 //3.3打印每次读取到的内容 System.out.println(b); } } catch (IOException e) { e.printStackTrace();//打印错误信到控制台 } finally {//try-catch-finally,finally中的代码一定会被执行,常用于关流 try { //4.释放资源 in.close(); } catch (IOException e) { e.printStackTrace(); } } } 字符流读取常用于处理纯文本数据
Reader抽象类用于读取字符流的抽象类。 常用方法:
int read() 读取单个字符 int read(char[] cbuf) 将字符读入数组 abstract int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分 int read(CharBuffer target) 试图将字符读入指定的字符缓冲区 abstract void close() 关闭该流并释放与之关联的所有资源 FileReader子类用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
创建对象 FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader
BufferedReader子类从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
创建对象 BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流
public static void main(String[] args) { //method();//普通字符流读取 method2();//高效字符流读取 } public static void method() { try { //1.创建字符流的读取对象 //Reader in = new Reader();//Reader是字符流读取的父类,而且是一个抽象类,不能new //Reader in = new FileReader(new File("D:\\\\ready\\\\1.txt"));//传入的是file对象 Reader in = new FileReader("D:\\\\ready\\\\1.txt");//传入的是路径 //2.开始读取,read()每次读取一个字符,如果读取到了数据的末尾,返回-1 // System.out.println(in.read());//97 // System.out.println(in.read());//98 // System.out.println(in.read());//99 // System.out.println(in.read());//100 // System.out.println(in.read());//-1 //3.需求:重复的读取文件中的所有内容 //3.1定义变量,记录读取到的数据 int b; //3.2循环读取文件中所有内容,只要不是-1,就表示还有数据,继续循环 while( (b = in.read()) != -1) { System.out.println(b); } //4.释放资源 in.close(); } catch (IOException e) { e.printStackTrace(); } } public static void method2() { try { //1.创建高效的字符流的读取对象 //Reader in = new Reader();//Reader是字符流读取的父类,而且是一个抽象类,不能new //BufferedReader是高效的字符读取流,原因是底层维护了一个char[],默认的容量也是8*1024字节8k Reader in = new BufferedReader(new FileReader("D:\\\\ready\\\\1.txt"));//传入的是路径 //2.开始读取,read()每次读取一个字符,如果读取到了数据的末尾,返回-1 // System.out.println(in.read());//97 // System.out.println(in.read());//98 // System.out.println(in.read());//99 // System.out.println(in.read());//100 // System.out.println(in.read());//-1 //3.需求:重复的读取文件中的所有内容 //3.1定义变量,记录读取到的数据 int b; //3.2循环读取文件中所有内容,只要不是-1,就表示还有数据,继续循环 while( (b = in.read()) != -1) { System.out.println(b); } //4.释放资源 in.close(); } catch (IOException e) { e.printStackTrace(); } } 字节流写出 OutputStream抽象类此抽象类是表示输出字节流的所有类的超类.输出流接受输出字节并将这些字节发送到某个接收器.
常用方法: Void close() 关闭此输出流并释放与此流相关的所有系统资源 Void flush() 刷新此输出流并强制写出所有缓冲的输出字节 Void write(byte[ ] b) 将b.length个字节从指定的byte数组写入此输出流 Void write(byte[ ] b,int off ,int len) 将指定byte数组中从偏移量off开始的len个字节写入输出流 Abstract void write(int b) 将指定的字节写入此输出流
FileOutputStream 子类直接插在文件上,直接写出文件数据 构造方法(创建对象): FileOutputStream(String name) 创建一个向具有指定名称的文件中写入数据的文件输出流 FileOutStream(File file) 创建一个向指定File对象表示的文件中写入数据的文件输出流 FileOutStream(File file,boolean append)—如果第二个参数为true,表示追加,不覆盖 创建一个向指定File对象表示的文件中写入数据的文件输出流,后面的参数是指是否覆盖原文件内容
BufferedOutputstream 子类该类实现缓冲的输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每次针对字节写出调用底层系统 构造方法(创建对象): BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,用以将数据写入指定的底层输出流
public static void main(String[] args) { //method();//1.创建使用普通字节输出流对象输出数据 method2();//2.创建使用高效字节输出流对象输出数据 } public static void method2() { //5.声明在此方法内部都生效的局部变量,并且局部变量需要初始化,对象的默认值是null OutputStream out = null; try { //0.用来测试的路径必须是文件路径,不是文件夹路径,而且文件得存在 //1.创建高效字节输出流对象 //OutputStream out =new BufferedOutputStream(new FileOutputStream(new File("D:\\\\ready\\\\out.txt"))); out =new BufferedOutputStream(new FileOutputStream("D:\\\\ready\\\\out.txt")); //2.开始写出数据 out.write(100);//ascii码表存在对应关系:100-d out.write(100);//ascii码表存在对应关系:100-d out.write(100);//ascii码表存在对应关系:100-d out.write(100);//ascii码表存在对应关系:100-d } catch (IOException e) { e.printStackTrace(); } finally {/**3.要保证代码一定会被执行,就通通放在finally代码块中*/ //4.释放资源 try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void method() { //4.声明在此方法内部都生效的局部变量,并且局部变量必须初始化,对象的默认值是null OutputStream out=null; try { //0.需要在windows环境下创建一个文件,路径:D:\\ready\\out.txt用来查看输出的数据 /**注意:指定的路径是文件路径,不是文件夹路径,而且文件得存在*/ //1.创建字节输出流对象 //new OutputStream();//报错:原因:OutputStream是字节输出流的抽象父类,不能实例化 //OutputStream out = new FileOutputStream(new File("D:\\\\ready\\\\out.txt")); out = new FileOutputStream("D:\\\\ready\\\\out.txt"); //2.开始写出数据 out.write(97);//ascii码表对应的关系:97--a out.write(98);//ascii码表对应的关系:98--b out.write(99);//ascii码表对应的关系:99--c } catch (IOException e) { e.printStackTrace(); } finally {/**如果要保证代码一定会执行,就通通放在finally代码块中*/ //3.释放资源 try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } 字符流写出 Writer 抽象类写入字符流的抽象类 常用方法:
Abstract void close() 关闭此流,但要先刷新它 Void write(char[ ] cbuf) 写入字符数组 Void write(int c) 写入单个字符 Void write(String str) 写入字符串 Void write(String str,int off,int len) 写入字符串的某一部分 Abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分
FileWriter 子类用来写入字符文件的便捷类,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的.如果需要自己自定义这些值,可以先在FileOutputStream上构造一个OutputStreamWriter.
构造方法(创建对象): FileWriter(String filename) 根据给定的文件名构造一个FileWriter对象 FileWriter(String filename,boolean append) 根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter
BufferedWriter子类将文本写入字符输出流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入.可以指定缓冲区的大小,或者接受默认的大小,在大多数情况下,默认值就足够大了
构造方法(创建对象): BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
public static void main(String[] args) { method1();//普通字符输出流 method2();//高效字符输出流 } /**高效字符输出流*/ public static void method2() { Writer out = null; try { //1.创建高效字符输出流对象,并且数据输出的方式是追加 //默认状态是不追加,也就是覆盖原有数据 //out2 = new BufferedWriter(new FileWriter(new File("D:\\\\ready\\\\out.txt"))); out = new BufferedWriter(new FileWriter("D:\\\\ready\\\\out.txt",true)); //2.输出数据 out.write(97); out.write("hello"); out.write("io"); } catch (IOException e) { e.printStackTrace(); }finally { //3.释放资源 try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } /**普通字符输出流*/ public static void method1() { Writer out = null; try { //0.在路径D:\\\\ready\\\\out.txt下创建对应的文件.查看输出的数据 //1.创建流对象 //new Writer();//报错:Writer是抽象父类,不可以创建对象 //Writer out2 = new FileWriter(new File("D:\\\\ready\\\\out.txt")); //out = new FileWriter("D:\\\\ready\\\\out.txt");//默认覆盖 /**需求:保持原有的数据 * 在不改变原数据的基础上,在它的末尾添加新数据*/ /** * 此构造函数的第二个参数表示,是否覆盖写出文件中的原有数据 * 默认覆盖,如果不想覆盖,就把这个参数的值设置为true * */ out = new FileWriter("D:\\\\ready\\\\out.txt",true); //2.输出数据 out.write(97); out.write(98); out.write(99); out.write(100); } catch (IOException e) { e.printStackTrace(); }finally { //3.释放资源 try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } 文件复制测试 public static void main(String[] args) { //1.提示并接收用户输入要复制的原文件的路径--复制啥 System.out.println("请输入原文件路径:"); String f = new Scanner(System.in).nextLine(); //2.提示并接收用户输入的目标文件所在的路径--复制到哪 System.out.println("请输入目标文件路径:"); String t = new Scanner(System.in).nextLine(); //3.根据原文件路径封装File对象from File from = new File(f); //4.根据目标文件路径封装File对象to File to = new File(t); //5.根据用户提供的路径完成文件的复制操作 //5.1自定义字符流文件复制方法 //ZFCopy(from,to);//字符流--只能操作字符相关文件 ZJCopy(from,to);//字节流--什么文件都能操作 } /**自定义字节复制文件的方法*/ public static void ZJCopy(File from, File to) { //1.定义整个方法都生效的局部变量,需要手动初始化,默认值null InputStream in = null;//字节输入流--抽象父类 OutputStream out = null;//字节输出流--抽象父类 try { //2.1创建字节输入流对象 in = new BufferedInputStream(new FileInputStream(from)); //2.2创建字节输出流对象 out = new BufferedOutputStream(new FileOutputStream(to)); //3.完成复制操作--边读边写 int b ; while((b=in.read()) != -1) { out.write(b); } System.out.println("恭喜您!复制成功!"); } catch (IOException e) { System.out.println("很抱歉!复制失败!"); e.printStackTrace(); }finally { //4.释放资源 /**1.流资源必须释放,释放的是之前使用过程中的所有流对象 * 2.关流是有顺序的,注意,后面出现的流先释放,为了不影响代码* */ try { out.close();//用来关闭字符输出流 } catch (IOException e) { e.printStackTrace(); } try { in.close();//用来关闭字符输入流 } catch (IOException e) { e.printStackTrace(); } } } /**自定义字符复制文件的方法*/ public static void ZFCopy(File from, File to) { //1.定义整个方法都生效的局部变量,需要手动初始化,默认值null Reader in = null;//字符输入流--抽象父类 Writer out = null;//字符输出流--抽象父类 try { //2.读取原文件from--获取字符输入流对象 in = new BufferedReader(new FileReader(from)); //3.写出到目标文件to--获取字符输出流对象 out = new BufferedWriter(new FileWriter(to)); //4.开始复制操作--边读边写 //4.1 定义变量用来保存读到的数据 int b; while( (b = in.read()) != -1 ) {//4.2while循环边读边写 out.write(b);//4.3把当前循环读到的数据写到目标文件中 } System.out.println("恭喜您!文件复制完成!"); } catch (IOException e) { System.out.println("很抱歉!文件复制失败!"); e.printStackTrace();//打印错误信 }finally {//finally代码块是try-catch最后且一定会指定的部分 //5.释放资源 /**1.流资源必须释放,释放的是之前使用过程中的所有流对象 * 2.关流是有顺序的,注意,后面出现的流先释放,为了不影响代码* */ try { out.close();//用来关闭字符输出流 } catch (IOException e) { e.printStackTrace(); } try { in.close();//用来关闭字符输入流 } catch (IOException e) { e.printStackTrace(); } } } 文件批量读写 public static void ZJcopy(File from, File to) { InputStream in = null;//定义在整个方法中都生效的字节输入流对象,注意是局部变量,需要初始化,对象的默认值是null OutputStream out = null;//定义在整个方法中都生效的字节输出流对象,注意是局部变量,需要初始化,对象的默认值是null try { //1.读取from文件--操作文件的是字节输入流 in = new BufferedInputStream(new FileInputStream(from)); //2.写出到to文件--操作文件的是字节输出流 out = new BufferedOutputStream(new FileOutputStream(to)); //3.边读边写 int b = 0;//定义变量b,记录读取到的数据 /**需求:想要实现批量读取,使用的是read(byte[] b)重载的形式,可以按照数组的方式来读 */ /**可以自定义数组,长度建议与源码保持一致,8*1024 = 8192*/ byte[] bs = new byte[8*1024]; while( (b=in.read(bs)) != -1 ) {//只有没有数据时,才返回-1,跳出循环,读写结束 out.write(bs);//将读到的数据写出到文件 } System.out.println("恭喜您!文件复制成功!"); } catch (IOException e) { System.out.println("很抱歉!文件复制失败!"); e.printStackTrace();//打印错误信 }finally {与上面相同略…} } } IO的继承结构 1. 主流分类 1) 按照方向进行分类:输入流 输出流(相对于程序而言,从程序写数据到文件中是输出) 2) 按照传输类型进行分类:字节流 字符流 3) 组合: 字节输入流 字节输出流 字符输入流 字符输出流 2. 学习方法:在抽象父类中学习通用的方法,在子类中学习如何创建对象 3.字节输入流: --InputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法 --FileInputStream 子类,操作文件的字节输入流,普通类 --BufferedInputStream 子类,缓冲字节输入流,普通类 4.字符输入流 --Reader 抽象类,不能new,可以作为超类,学习其所提供的共性方法 --FileReader,子类,操作文件的字符输入流,普通类 --BufferedReader,子类,缓冲字符输入流,普通类 5.字节输出流: --OutputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法 --FileOutputStream 子类,操作文件的字节输出流,普通类 --BufferedOutputStream 子类,缓冲字节输出流,普通类 6.字符输出流 --Writer 抽象类,不能new,可以作为超类,学习其所提供的共性方法 --FileWriter,子类,操作文件的字符输出流,普通类 --BufferedWriter,子类,缓冲字符输出流,普通类 BIO、NIO、AIO的区别阻塞IO,BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
非阻塞IO,NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
异步IO,AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。但目前还不够成熟,应用不多。
序列化/反序列化概述: 序列化(Serialization)是将对象的状态信转换为可以存储或传输形式的过程. 在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象. 序列化:利用ObjectOutputStream,把对象的信,按照固定的格式转成一串字节值输出并持久保存到磁盘 反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象 TIPS: 序列化: 对象 à 字节值 反序列化:字节值(之前序列化生成的) à 对象
特点/应用场景ObjectOutputStream将Java对象的基本数据类型和同行写入OutputStream.可以使用ObjectInputStream读取(重构)对象.通过在流中使用文件可以实现对象的持久存储. 如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象.
构造方法: ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream 方法: writeObject(Object obj) 将指定的对象写入 ObjectOutputStream ObjectInputStreamObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化
构造方法: ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream 方法: readObject() 从 ObjectInputStream 读取对象 /**本类用来封装学生类*/ /** * 如果本类想要完成序列化,必须实现可序列化接口,否则会报错: * 报错信:java.io.NotSerializableException: cn.tedu.serializable.Student * Serializable接口是一个空接口,里面一个方法都没有,作用是用来当做标志,标志这个类可以序列化/反序列化 * */ public class Student implements Serializable{ /**需要给每个进行序列化的文件分配唯一的UID值*/ //The serializable class Student does not declare a static final serialVersionUID field of type long //private static final long serialVersionUID = 1L; private static final long serialVersionUID = -3193364654654535741L; //1.定义学生的相关属性 + private封装 private String name;//姓名 private int age;//年龄 private String addr;//地址 private char gender;//性别 } public static void main(String[] args) { //method1();//测试序列化 method2();//测试反序列化 } /**创建用来测试反序列化的方法*/ public static void method2() { ObjectInputStream in = null; try { //1.创建ObjectInputStream来完成反序列化 in = new ObjectInputStream(new FileInputStream("D://ready//1.txt")); //2.完成反序列化操作 Object o = in.readObject(); System.out.println(o); System.out.println("恭喜您!反序列化成功!"); } catch (Exception e) { System.out.println("很抱歉!反序列化失败!"); e.printStackTrace(); }finally { //3.释放资源 try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } /**创建用来测试序列化的方法*/ public static void method1() { ObjectOutputStream out = null; try { //1.创建ObjectOutputStream流对象来完成序列化 out = new ObjectOutputStream( new FileOutputStream("D://ready//1.txt")); //2.完成序列化操作 //2.1创建学生对象 Student s = new Student("陈子枢",3,"男生","BeiJing"); //2.2通过OOS完成对象的序列化输出操作 out.writeObject(s); System.out.println("恭喜您!序列化成功"); } catch (IOException e) { System.out.println("很抱歉!序列化失败!"); e.printStackTrace(); } finally { //3.释放资源 try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } 编码转换流字节流:针对二进制文件 字符流:针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8
概述: 编码转换流(InputStreamReader/OutputStreamWriter)主要进行编码的转换,用来解决字符流读写乱码的问题 api:
OutputStreamWriter : OutputStreamWriter(OutputStream out)把传入的字节流转成字符流 OutputStreamWriter(OutputStream out ,String charsetName)把Unicode转成其他编码输出 InputStreamReader : InputStreamReader(InputStream in) 把传入的字节流转成字符流 InputStreamReader(InputStream in,String charsetName)读取其他编码转成Unicode 第八章:泛型 概念:public class LinkedList extends AbstractSequentialList implements List public interface Deque extends Queue {} public interface Queue extends Collection {} public interface Collection extends Iterable {}
我们上面的代码中出现的<?>是什么东西呢?它叫泛型,常用来和集合对象一同使用,所以在开始学习集合之前,必须先了解下什么是泛型。而且泛型概念非常重要,它是程序的增强器,它是目前主流的开发方式。
泛型是(Generics)JDK1.5 的一个新特性,其实就是一个『语法糖』,本质上就是编译器为了提供更好的可读性而提供的一种小手段,小技巧,虚拟机层面是不存在所谓『泛型』的概念的。是不有点神奇,不知所云,别着急等我讲完你就清楚了。
作用:通过泛型的语法定义<>,约束集合元素的类型,编译器可以在编译期提供一定的类型安全检查 这样可以避免程序运行时才暴露BUG,代码的通用性也会更强 泛型可以提升程序代码的可读性,但是它只是一个==『语法糖』==(编译后这样的部分会被删除,不出现在最终的源码中),所以不会影响JVM后续运行时的性能.
泛型声明:泛型可以在接口 类 方法上使用
public interface Collection<E>{} public class TestStudy<Student>{} public <E> void print(E e){}在方法的返回值前声明了一个<E>,表示后面出现的E是泛型,而不是普通的java变量
常用名称:- E - Element ( 在集合中使用,因为集合中存放的是元素 )
- T - Type ( Java类 )
- K - Key ( 键 )
- V - Value ( 值 )
- N - Number ( 数值类型 )
- ? - 表示不确定的java类型
前言: Java语言的java.util包中提供了一些集合类,这些集合类又称之为容器 提到容器不难想到数组,集合类与数组最主要的不同之处是,数组的长度是固定的,集合的长度是可变的 数组的访问方式比较单一,插入/删除等操作比较繁琐,而集合的访问方式比较灵活 常用的集合类有List集合,Set集合,Map集合,其中List集合与Set集合继承了Collection接口,各个接口还提供了不同的实现类.
概述:集合的英文名称是Collection,是用来存放对象的数据结构,而且长度可变,可以存放不同类型的对象,并且还提供了一组操作成批对象的方法.Collection接口层次结构 中的根接口,接口不能直接使用,但是该接口提供了添加元素/删除元素/管理元素的父接口公共方法. 由于List接口与Set接口都继承了Collection接口,因此这些方法对于List集合和Set集合是通用的.
集合的继承结构:Collection接口 –List 接口 : 数据是有下标的,所以数据是有序的,可以存重复值 –ArrayList子类 –LinkedList子类 –Set 接口 : 数据是没有下标的,所以数据是无序的,不可以存重复的值 –HashSet子类 –Map 接口 : 键值对的方式存数据 –HashMap子类
常用方法 public static void main(String[] args) { //1.创建Collection接口相关对象 //Collection c = new Collection();//报错,因为Collection是接口不能实例化new /**1.<Integer>是泛型,用来约束集合中的元素的类型,只能写引用类型,不能是基本类型*/ Collection<Integer> c = new ArrayList<Integer>(); //2.1测试常用方法--对于单个集合的方法 c.add(100);//向集合中添加元素 c.add(200);//向集合中添加元素 c.add(300);//向集合中添加元素 c.add(400);//向集合中添加元素 c.add(500);//向集合中添加元素 System.out.println(c);//直接打印查看集合中的元素 //c.clear();//清空集合中的元素 //System.out.println(c); System.out.println( c.contains(300) );//true,判断集合中是否包含元素300 System.out.println( c.hashCode() );//127240651,返回集合对应的哈希码值 System.out.println( c.isEmpty() );//false,判断集合是否为空 System.out.println( c.remove(100) );//true,移出集合中的元素100,移出成功返回true System.out.println( c );//[200, 300, 400, 500],100被成功移除 System.out.println( c.size() );//4,获取集合的元素个数/类似数组长度 System.out.println( c.equals(200) );//false,判断是否与100相等 Object[] array = c.toArray();//把集合中的元素放入数组 System.out.println(Arrays.toString(array));//使用数组的工具类查看数组中的元素内容 //2.2测试常用方法--集合间的操作 Collection<Integer> c2 = new ArrayList<Integer>(); c2.add(2);//给c2集合添加元素 c2.add(4);//给c2集合添加元素 c2.add(6);//给c2集合添加元素 System.out.println(c2);//[2, 4, 6],直接打印查看c2集合的内容 c.addAll(c2);//把c2集合添加到c集合中 System.out.println(c);//[200, 300, 400, 500, 2, 4, 6],追加操作 System.out.println(c.contains(c2));//false System.out.println(c.containsAll(c2));//true,查看c集合是否包含c2集合中的所有元素 System.out.println(c.removeAll(c2));//true,删除c集合中属于c2集合的所有元素 System.out.println(c);//[200, 300, 400, 500],查看c集合删除c2集合后的结果,正确删除 //System.out.println(c.retainAll(c2));//true,删除c集合 //System.out.println(c);//[] //2.3 用来遍历/迭代集合中的元素 Iterator<E> iterator() /** * 1.如何获取迭代器 c.iterator() * 2.判断集合是否有下个元素 it.hasNext() * 3.获取当前迭代到的元素 it.next() */ Iterator<Integer> it = c.iterator(); //通过iterator迭代器,循环获取集合中的元素 while(it.hasNext()) { //hasNext()用来判断集合中是否有下个元素,有就返回true,继续循环取值 Integer num = it.next();//next()用来获取迭代到的元素 System.out.println(num); } } List接口 概述:有序的colletion(也称为序列).此接口的用户可以对列表中的每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)来访问元素,并搜索列表中的元素.
特点:创建对象: ArrayList() 构造一个初始容量为10的空序列 源码摘抄:int newCapacity = oldCapacity + (oldCapacity >> 1); 解释:数组的新容量 = 旧容量/2的一次方 --相当于原来的1.5倍扩容
//1.创建对象,使用的是无参构造 //底层会自动帮我们创建数组存放对象,并且数据的初始容量是10 ArrayList<Integer> list = new ArrayList();//简写方式 //2.放入数据进行测试常用方法 list.add(100);//向集合中添加元素 list.add(200);//向集合中添加元素 list.add(300);//向集合中添加元素 list.add(400);//向集合中添加元素 list.add(300);//向集合中添加元素 list.add(200);//向集合中添加元素 list.add(0,777);//在指定下标处新增元素 System.out.println(list); //list.clear();//清空集合 //System.out.println(list); System.out.println(list.contains(300));//true,判断集合是否包含元素300 System.out.println(list.get(0));//777,获取集合中指定下标位置上的元素 System.out.println(list.indexOf(200));//2,判断集合中指定元素第一次出现的下标 System.out.println(list.lastIndexOf(200));//6,判断集合中指定元素最后一次出现的下标 System.out.println(list.isEmpty());//false,判断集合是否为空 System.out.println(list.remove(1));//100,移除集合中指定下标对应着的元素,移除成功,返回被移除的元素 /** *Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 300, Size: 6 *这是根据下标来删除元素的,而此集合没有下标300,最大下标为6,所以会数组下标越界 *System.out.println(list.remove(300));--错误 *如果想根据具体的元素内容移除元素,需要先把int类型的数据转成Integer数据类型 * * */ System.out.println(list.remove(Integer.valueOf(300)));//true System.out.println(list); System.out.println(list.set(2, 77));//更改集合中对应下标上元素的值 System.out.println(list); System.out.println(list.size()); System.out.println(Arrays.toString(list.toArray()));//将集合元素存入数组,打印数组的具体值 4种迭代方式 //方式1:for循环 System.out.println("方式1"); //开始:0 结束:最大下标(集合长度-1) 变化:++ for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i));//根据下标获取对应下标位置上的元素 } //方式2:增强for循环 System.out.println("方式2"); for (Integer i : list) {//遍历list集合,每次循环得到的元素是Integer类型的i System.out.print(i);//打印每次循环到的集合元素 } //方式3:Iterator /**1.获取迭代器 2.判断是否还有元素(一般用来做循环条件) 3.获取当前遍历到的元素*/ System.out.println("方式3"); Iterator<Integer> it = list.iterator();//获取集合用来迭代的迭代器,此迭代器是继承自Collection接口中的 while(it.hasNext()) {//通过迭代器来判断集合中是否还有元素,如果有,继续迭代,如果没有,结束循环 Integer num = it.next();//获取当前遍历到的集合元素 System.out.print(num);//打印当前遍历到的集合元素 } //方式4:ListIterator System.out.println("方式4"); ListIterator<Integer> it2 = list.listIterator();//获取集合用来迭代的迭代器,此迭代器是List接口中的迭代器 while(it2.hasNext()) {//通过迭代器来判断集合中是否还有元素,如果有继续迭代,如果没有,结束循环 Integer s2 = it2.next();//获取当前遍历到的集合元素 System.out.print(s2);//打印当前遍历到的集合元素 } ArrayList扩容ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10;之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15;当添加第16个数据时,继续扩容变为15 * 1.5 =22个
ArrayList没有对外暴露其容量个数,查看源码我们可以知道,实际其值存放在elementData对象数组中,那我们只需拿到这个数组的长度,观察其值变化了几次就知道其扩容了多少次。怎么获取呢?只能用反射技术了。
LinkedList 概述:链表,两端效率高,底层就是链表实现的
总结: ArrayList底层是数组结构,查询快,增删慢,适合查询较多的场景 LinkedList底层是链表结构,查询慢,增删快,适合增删操作较多的场景 注意:LinkedList查询慢是指数据量大时,查询中间要慢,首位操作还是比较快的
创建对象: LinkedList() 构造一个空列表
常用方法 void addFirst(E e) 将指定元素插入此列表的开头 void addLast(E e) 将指定元素添加到此列表的结尾 E getFirst() 返回此列表的第一个元素 E getLast() 返回此列表的最后一个元素 E removeFirst()移除并返回此列表的第一个元素 E removeLast() 移除并返回此列表的最后一个元素 E element() 获取但不移除此列表的头(第一个元素) boolean offer(E e) 将指定元素添加到此列表的末尾(最后一个元素) boolean offerFirst(E e) 在此列表的开头插入指定的元素 boolean offerLast(E e) 在此列表末尾插入指定的元素 E peek() 获取但不移除此列表的头(第一个元素) E peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null E peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null E poll()获取并移除此列表的头(第一个元素) E pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null E pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null //1.创建对象 LinkedList<String> list = new LinkedList(); //2.添加数据 list.add("孙悟空"); list.add("猪八戒"); list.add("唐三藏"); list.add("沙师弟"); list.add("白龙马"); System.out.println(list); //3.1自行测试从collection继承过来的共性方法测试 //3.2 LinkedList特有方法测试 list.addFirst("蜘蛛精");//添加首元素 list.addLast("玉兔精");//添加尾元素 System.out.println(list); System.out.println(list.getFirst());//获取首元素 System.out.println(list.getLast());//获取尾元素 System.out.println(list.removeFirst());//移除首元素,成功移除会返回移除的数据 System.out.println(list); System.out.println(list.removeLast());//移除尾元素,成功移除会返回移除的数据 System.out.println(list); //4.1创建对象 LinkedList<String> list2 = new LinkedList(); //4.2添加数据 list2.add("水浒传"); list2.add("三国演义"); list2.add("西游记"); list2.add("红楼梦"); System.out.println(list2); System.out.println(list2.element());//获取但不移除此列表的首元素(第一个元素) /**别名:查询系列*/ System.out.println(list2.peek());//获取但不移除此列表的首元素(第一个元素) System.out.println(list2.peekFirst());//获取但不移除此列表的首元素(第一个元素) System.out.println(list2.peekLast());//获取但不移除此列表的尾元素(最后一个元素) /**别名:新增系列*/ System.out.println(list2.offer("遮天"));//将指定元素添加到列表末尾 System.out.println(list2.offerFirst("斗罗大陆"));//将指定元素插入列表开头 System.out.println(list2.offerLast("斗破苍穹"));//将指定元素插入列表末尾 System.out.println(list2); /**别名:移除系列*/ System.out.println(list2.poll());//获取并且移除此列表的首元素(第一个元素),成功移除,返回移除元素 System.out.println(list2.pollFirst());//获取并且移除此列表的首元素(第一个元素),成功移除,返回移除元素,如果此列表为空,则返回null System.out.println(list2.pollLast());//获取并且移除此列表的尾元素(最后一个元素),成功移除,返回移除元素,如果此列表为空,则返回null System.out.println(list2); set接口 概述:学习Collection接口中的方法即可
//1.创建对象 //Set s = new Set();//报错,Set是接口,接口不可以实例化,也就是创建对象 Set<String> set = new HashSet<String>(); //2.set集合数据存放测试 set.add("牛气冲天");//向set集合添加数据 set.add("牛气冲天");//向set集合添加重复的数据 set.add("牛气冲天");//向set集合添加重复的数据 set.add("虎虎生威");//向set集合添加数据 //set.add("null");//向set集合添加字符串类型的null数据 set.add(null);//向set集合添加数据null /**总结1:set集合中的元素都是无序的*/ /**总结2:set集合中的元素不能重复*/ /**总结3:set集合中可以存放null元素,也只允许存放0-1个*/ System.out.println(set);//查看set集合中的元素 //3.set集合常用方法测试 //set.clear();//清空Set集合 System.out.println(set.contains("小兔纸"));//false,判断set集合中是否包含指定元素"小兔纸" System.out.println(set.equals("牛气冲天"));//false,判断set集合对象与指定元素是否相等 System.out.println(set.hashCode());//1961052313,获取当前set集合对象的哈希码 System.out.println(set.isEmpty());//false,判断当前集合是否为空 System.out.println(set.remove("null"));//false,移除指定元素,没有"null"元素,所以返回false System.out.println(set.remove(null));//true,成功移除指定元素null,所以返回true System.out.println(set); System.out.println(set.size());//2,获取当前set集合的元素个数,类似数组长度 Object[] array = set.toArray();//把集合中的元素放入数组中 System.out.println(Arrays.toString(array));//使用数组工具类查看数组中的元素 //4.集合间的操作 Set<String> set2 = new HashSet(); set2.add("小老鼠");//给set2集合添加指定元素 set2.add("小牛犊");//给set2集合添加指定元素 set2.add("小脑斧");//给set2集合添加指定元素 set2.add("小兔纸");//给set2集合添加指定元素 System.out.println(set2);//查看set2集合中的元素 System.out.println(set.addAll(set2));//true,把集合set2中的元素添加到set集合中,成功返回true System.out.println(set.containsAll(set2));//true,判断set集合中是否包含set2集合中的所有元素,如果包含返回true System.out.println(set.removeAll(set2));//ture,移除set集合中属于set2集合的所有元素 System.out.println(set.containsAll(set2));//false,判断set集合中是否包含set2集合中的所有元素,不包含返回false System.out.println(set.retainAll(set2)); /**retainAll()方法是取两个集合直接的公共部分,谁调用,影响谁*/ // set.add("小海滕"); // set2.add("小海滕"); // System.out.println(set.retainAll(set));//set没变 // System.out.println(set.retainAll(set2));//set剩set与set2的交集 // System.out.println(set2.retainAll(set));//set2剩set2与set的交集 // System.out.println(set); // System.out.println(set2); //5.集合的迭代 Iterator<String> it = set2.iterator();//5.1获取集合的迭代器 while(it.hasNext()) {//5.2判断集合是否有下个元素 String s = it.next();//5.3如果有,进循环获取当前遍历到的元素 System.out.println(s); } } HashSet 概述:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K存入内部的HashMap中,其中K不允许重复,允许使用null.
//1.创建HashSet对象 HashSet<Integer> set = new HashSet(); //2.向HashSet集合添加元素 set.add(100); set.add(200); set.add(300); set.add(200); set.add(200); /**总结1:HashSet中的元素没有顺序,且不允许重复*/ System.out.println(set); //3.测试常用方法 //set.clear();//清空set集合 System.out.println(set.contains(200));//true,判断集合是否包含指定元素 System.out.println(set.isEmpty());//false,判断集合是否为空 System.out.println(set.remove(100));//true,移除集合中的指定元素 System.out.println(set.size()); //4.迭代set集合 Iterator<Integer> it = set.iterator();//获取集合的迭代器用来遍历 while(it.hasNext()) {//判断集合中是否有下一个元素,没有则跳出循环 Integer num = it.next();//获取当前遍历到的元素 System.out.println(num);//打印当前遍历到的元素 } 去重://总结:
//1.如果想用set集合给自定义的对象去重,那么需要在自己的类中同时提供重写的hashCode()与equals() //底层源码: if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //重写hashCode():我们并不想使用自动计算出的哈希值,而是要根据对象的属性值进行计算,如果两个对象的属性值都相同,想生成同一个哈希码 //重写equals():我们想比较的不是对象的地址值(==),而是如果两个对象的属性值都一样,则返回true
//1.创建set集合对象 Set<Student> set = new HashSet<Student>(); //新版JDK中后面的泛型类型与尖括号都可以不写,三种方式皆可,想用哪个用哪个 // Set<Student> set = new HashSet<>(); // Set<Student> set = new HashSet(); //2.1创建自定义对象 Student s1 = new Student("tony",38,"BeiJing"); Student s2 = new Student("susan",20,"ShangHai"); Student s3 = new Student("Jack",3,"ShenZhen"); //创建对象,与之前对象的属性值完全一致 Student s4 = new Student("susan",20,"ShangHai"); Student s5 = new Student("Jack",3,"ShenZhen"); //3.查看两个对象的哈希码 System.out.println("s2对象的哈希码:"+s2.hashCode()); System.out.println("s4对象的哈希码:"+s4.hashCode()); //2.2把自定义的student对象添加到set集合中 set.add(s1); set.add(s2); set.add(s3); set.add(s4); set.add(s5); System.out.println(set);总结: 重复的给set集合添加了属性相同的对象,为什么没有像之前那样去重呢? 翻阅源码,得知:需要保证两个条件: 1.保证对象拥有相同的哈希码值 底层默认使用的是Object提供的hashCode()来计算哈希码值,每次new对象,默认的哈希码值是不同的 解决方案:如果想根据两个对象的属性值来计算哈希值,就需要重写hashCode() 2.保证两个对象的equals()返回true –底层默认使用的是Object提供的逻辑,==比较,也就是说当地址值相同时,才返回true 解决方案:重写equals()
Map接口 概述:Java.util接口Map<K,V> 类型参数 : K - 表示此映射所维护的键 V – 表示此映射所维护的对应的值 也叫做哈希表、散列表. 常用于键值对结构的数据.其中键不能重复,值可以重复
特点:TIPS:源码摘抄:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 初始容量1<<4,相当于1*(2^4),也就是16 static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认的加载因子是0.75f,也就是存到75%开始扩容,按照2的次幂进行扩容 继承结构 常用方法:学习Collection接口中的方法即可 void clear() 从此映射中移除所有映射关系(可选操作)。 boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。 boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true。 Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图。 boolean equals(Object o) 比较指定的对象与此映射是否相等。 V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 int hashCode() 返回此映射的哈希码值。 boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true。 Set<K> keySet() 返回此映射中包含的键的 Set 视图。 V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)。 void putAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)。 V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。 int size() 返回此映射中的键-值映射关系数。 Collection values() 返回此映射中包含的值的 Collection 视图。
//1.创建Map对象 //map中的数据要符合映射规则,一定注意要同时指定K和V的数据类型 //至于K和V需要指定成什么类型的数据,取决于你的具体需求 Map<Integer,String> map = new HashMap();//注意导包:java.util //2.常用方法测试 //添加数据,需要同时指定K和V map.put(9527, "白骨精");//向map集合添加数据 map.put(9528, "黑熊精");//向map集合添加数据 map.put(9528, "唐三藏"); map.put(9529, "者行孙"); /** * 总结1:Map存放的都是无序数据 * 总结2:Map中的key不可以重复,如果重复,此Key对应的值会被覆盖 *map打印结果: {9527=白骨精, 9528=唐三藏, 9529=者行孙} */ //查看map集合中的元素 System.out.println(map); //map.clear();//清空map集合 System.out.println(map.containsKey(9527));//true,判断当前map集合是否包含指定的key System.out.println(map.containsValue("土地老儿"));//false,判断当前map集合是否包含指定的value System.out.println(map.equals("者行孙"));//false,判断"者行孙"与map是否相等 System.out.println(map.get(9529));//者行孙,根据对应的key来获取对应的value System.out.println(map.hashCode());//84598429,获取当前map集合的哈希码 System.out.println(map.isEmpty());//false,判断当前map集合是否为空 System.out.println(map.remove(9529));//者行孙,删除map中key对应的value,正确删除后返回被删除元素 System.out.println(map.get(9529));//null,没有拿到任何元素,根据指定的key获取对应value System.out.println(map.size());//2,获取集合中元素的个数 Collection<String> values = map.values();//把map中的所有value收集起来放到collection中 System.out.println(values);//[白骨精, 唐三藏] Map集合迭代: //对map集合进行迭代 /**方式一 * 遍历map中的数据,需要把map集合转换成set集合 * Set<K> keySet() : 把map集合中的所有的key存到set集合中 * */ Set<Integer> keySet = map.keySet(); //想要遍历set集合,需要先拿到集合的迭代器对象 Iterator<Integer> it = keySet.iterator(); while(it.hasNext()) {//判断集合中是否有下个元素,如果有,继续迭代,如果没有,跳出循环 Integer key = it.next();//依次获取/set集合中的每一个key String value = map.get(key);//通过key获取对应的value System.out.println("{" + key + "," + value + "}"); } /**方式二 * 遍历map中的数据,需要把map集合转换成set集合 * Set<Entry<Integer,String>> entrySet() : 把map集合中的一组key&value数据整体放入set中 * 一对儿 K,V 是一个Entry */ Set<Entry<Integer, String>> entrySet = map.entrySet(); //遍历set集合,得到每个Entry对象 //获取此set集合对应的迭代器对象it2 Iterator<Entry<Integer, String>> it2 = entrySet.iterator(); while(it2.hasNext()) { Entry<Integer, String> entry = it2.next(); Integer key = entry.getKey(); String value = entry.getValue(); System.out.println("[" + key + ":" + value + "]"); } HashMap 前言HashMap的键要同时重写hashCode()和equlas() hashCode()用来判定二者的hash值是否相同,重写后根据属性生成 equlas()用来判断属性的值是否相同,重写后,根据属性判断 –equlas()判断数据如果相等,hashCode()必须相同 –equlas()判断数据如果不等,hashCode()尽量不同
概述:HashMap底层是一个Entry[ ]数组,当存放数据时,会根据hash算法来计算数据的存放位置 算法:hash(key)%n , n就是数组的长度,其实也就是集合的容量 当计算的位置没有数据的时候,会直接存放数据 当计算的位置,有数据时,会发生hash冲突/hash碰撞,解决的办法就是采用链表的结构,在对应的数据位置存放链表的头节点,对于这个链表来说,每次新加的节点会从头部位置开始加入,也就是说,数组中的永远是新节点.
HashMap<Integer,String> map = new HashMap(); /** * 源码摘抄: * static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 * 初始容量为1<<4,相当于1*(2^4)=16 * static final float DEFAULT_LOAD_FACTOR = 0.75f; * 默认的加载因子是0.75,也就是说存到75%开始扩容,按照2的次幂进行扩容 */ /* * 达到容量的加载因子后,就会重新开辟空间,重新计算所有对象的存储位置,也叫做rehash * 设置初始容量与加载因子要讲求相对平衡,如果加载因子过低,则rehash过于频繁,影响性能 * 如果初始容量设置太高或者加载因子设置太高,影响查询效率 */ 拓展加载因子: static final float DEFAULT_LOAD_FACTOR = 0.75f; 前面的讲述已经发现,当你空间只有仅仅为10的时候是很容易造成2个对象的hashcode 所对应的地址是一个位置的情况。这样就造成 2个 对象会形成散列桶(链表)。这时就有一个加载因子的参数,值默认为0.75 ,如果你hashmap的 空间有 100那么当你插入了75个元素的时候 hashmap就需要扩容了,不然的话会形成很长的散列桶结构,对于查询和插入都会增加时间,因为它要一个一个的equals比较。但又不能让加载因子很小,如0.01,这样显然是不合适的,频繁扩容会大大消耗你的内存。这时就存在着一个平衡,jdk中默认是0.75,当然负载因子可以根据自己的实际情况进行调整。
第十章:进程与线程 进程 概念:进程就是正在运行的程序,它代表了程序所占用的内存区域
特点:== 独立性== 进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间. == 动态性== 进程与程序的区别在于,程序只是一个静态的指令集合,而进程一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的. 并发性 多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
并行和并发TIPS: HA–高可用 在高并发的环境下,系统如何完成正常功能供给
线程 概念:线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位. 一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程 我们看到的进程的切换,切换的也是不同进程的主线程 多线程扩展了多进程的概念,使的同一个进程可以同时并发处理多个任务 简而言之,一个程序运行后至少一个进程,一个进程里包含一个线程(单线程)或者多个线程(多线程)
进程与线程的关系一个操作系统中可以有多个进程,一个进程中可以有多个线程 每个线程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存(这个非常重要!!!) 所以想使用线程技术,得先有进程,进程的创建是OS操作系统创建的.我们不能实现,一般都是C或者C++完成
多线程的特性线程的随机性指的是同一时刻,只有一个程序在执行 我们宏观上觉得这些程序像是同时运行,但是实际上微观时间是因为CPU在高效的切换着,这使得各个程序从表面上看是同时进行的,也就是说,宏观层面上,所有的进程/线程看似同时运行,但是微观层面上,同一时刻,一个CPU只能处理一件事. 时间单位:1/ms甚至更快,切换的速度是纳秒级别的,非常快
CPU分时调度时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。 注意:我们无法控制OS如何选择以及选择哪些线程来执行,OS底层有自己规则: FCFS(First Come First Service 先来先服务算法) SJS(Short Job Service短服务算法)
线程的状态由于线程状态比较复杂,所以我们由易到难,先学习基础模型,线程的三种状态及其转换,简称”三态模型” l 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行 l 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态 l 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的暂停状态,即线程执行阻塞。 Ø 就绪–>执行:为就绪线程分配CPU即可变为执行状态 Ø 执行–>就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态 Ø 执行–>阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞 (例如线程正在访问临界资源,而资源正在被其他线程访问) 反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
TIPS:PCB(Process Control Block):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程
线程生命周期,主要有五种状态:概述: Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例 启动线程的唯一方法就是通过Thread类的start()实例方法 start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run() 这种方式实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run()方法 模拟开启多个线程,每个线程调用run()方法. 常用方法: 构造方法:
Thread() 分配新的Thread对象 Thread(String name) 分配新的Thread对象 Thread(Runnable target) 分配新的Thread对象 Thread(Runnable target,String name) 分配新的Thread对象
普通方法:static Thread currentThread( ) 返回对当前正在执行的线程对象的引用 long getId() 返回该线程的标识 String getName() 返回该线程的名称 void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法 static void sleep(long millions) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) void start() 使该线程开始执行:Java虚拟机调用该线程的run()
public static void main(String[] args) { //4.创建线程对象进行测试 /*4.new对应的是线程的新建状态 * 5.要想模拟多线程,至少得启动2个线程,如果只启动1个,是单线程程序*/ MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); MyThread t4 = new MyThread(); /*6.这个run()如果直接这样调用,是没有多线程抢占执行的效果的 * 只是把这两句话看作普通方法的调用,谁先写,就先执行谁*/ //t1.run(); //t2.run(); /*7.start()对应的状态就是就绪状态,会把刚刚新建好的线程加入到就绪队列之中 * 至于什么时候执行,就是多线程执行的效果,需要等待OS选中分配CPU * 8.执行的时候start()底层会自动调用我们重写的run()种的业务 * 9.线程的执行具有随机性,也就是说t1-t4具体怎么执行 * 取决于CPU的调度时间片的分配,我们是决定不了的*/ t1.start();//以多线程的方式启动线程1,将当前线程变为就绪状态 t2.start();//以多线程的方式启动线程2,将当前线程变为就绪状态 t3.start();//以多线程的方式启动线程3,将当前线程变为就绪状态 t4.start();//以多线程的方式启动线程4,将当前线程变为就绪状态 } //1.自定义一个多线程类,然后让这个类继承Thread class MyThread extends Thread{ /*1.多线程编程实现的方案1:通过继承Thread类并重写run()来完成的 */ //2.重写run(),run()里是我们自己的业务 @Override public void run() { /*2.super.run()表示的是调用父类的业务,我们现在要用自己的业务,所以注释掉*/ //super.run(); //3.完成业务:打印10次当前正在执行的线程的名称 for (int i = 0; i < 10; i++) { /*3.getName()表示可以获取当前正在执行的线程名称 * 由于本类继承了Thread类,所以可以直接使用这个方法*/ System.out.println(i+"="+getName()); } } } 实现Runnable接口概述: 如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口
常用方法: void run()使用实现接口Runnable的对象创建线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法
public static void main(String[] args) { //5.创建自定义类的对象--目标业务类对象 MyRunnable target = new MyRunnable(); //6.如何启动线程?自己没有,需要与Thread建立关系 Thread t1 = new Thread(target); Thread t2 = new Thread(target); Thread t3 = new Thread(target); Thread t4 = new Thread(target); t1.start(); t2.start(); t3.start(); t4.start(); } //1.自定义多线程类 class MyRunnable implements Runnable{ //2.添加父接口中的抽象方法run(),里面是自己的业务 @Override public void run() { //3.写业务,打印10次当前正在执行的线程名称 for (int i = 0; i < 10; i++) { /*问题:自定义类与父接口Runnable中都没有获取名字的方法 * 所以还需要从Thread中找: * currentThread():静态方法,获取当前正在执行的线程对象 * getName():获取当前线程的名称*/ System.out.println(i+"="+Thread.currentThread().getName()); } } } Callable接口概述: Callable接口是一种能够返回计算结果并且可以抛出异常的任务。 Callable接口的实现类需要定义一个无参数的方法:call()。 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); //1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask<Integer> result = new FutureTask<>(td); new Thread(result).start(); //2.接收线程运算后的结果 try { Integer sum = result.get(); //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的 System.out.println(sum); System.out.println("------------------------------------"); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } class ThreadDemo implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 100000; i++) { sum += i; } return sum; } } 线程创建的其他方式ExecutorService/Executors ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理
execute(Runnable任务对象) 把任务丢到线程池
Executors 辅助创建线程池的工具类
newFixedThreadPool(int nThreads) 最多n个线程的线程池 newCachedThreadPool() 足够多的线程,使任务不必等待 newSingleThreadExecutor() 只有一个线程的线程池
public static void main(String[] args) { //5.创建接口实现类TicketR3类的对象作为目标业务对象 TicketR3 target = new TicketR3(); /*Executors是用来辅助创建线程池的工具类对象 * 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象 * 创建出来的线程池对象是ExecutorService:用来存储线程的池子,负责:新建/启动/关闭线程*/ //6.使用Executors工具创建一个最多有5个线程的线程池对象ExecutorService池对象 ExecutorService pool = Executors.newFixedThreadPool(5); for (int i = 0; i < 5; i++) { /*execute()让线程池中的线程来执行业务,每次调用都会将一个线程加入到就绪队列*/ pool.execute(target);/*本方法的参数就是你要执行的业务,也就是目标业务类对象*/ } } //同步锁问题解决方案笔记:1.4.1从26行复制到58行,TicketR2改成TicketR3 //1.创建自定义多线程类 class TicketR3 implements Runnable { //3.定义成员变量,保存票数 int tickets = 100; //创建锁对象 Object o = new Object(); //2.实现接口中未实现的方法,run()中放着的是我们的业务 @Override public void run() { //4.通过循环结构完成业务 while (true) { /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码} * 同步代码块在同一时刻,同一资源只会被一个线程独享*/ /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/ //synchronized (new Object()){ //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一 synchronized (o) {//同步代码块解决的是重卖的问题 //如果票数>0就卖票 if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //4.1打印当前正在售票的线程名以及票数-1 System.out.println(Thread.currentThread().getName() + "=" + tickets--); } //4.2退出死循环--没票的时候就结束 if (tickets <= 0) break; } } } } 票超卖每次创建线程对象,都会生成一个tickets变量值是100,创建4次对象就生成了400张票了。不符合需求,怎么解决呢?能不能把tickets变量在每个对象间共享,就保证多少个对象都是卖这100张票。 解决方案: 用静态修饰 产生超卖,0 张 、-1张、-2张。 产生重卖,同一张票卖给多人。 多线程安全问题是如何出现的?常见情况是由于线程的随机性+访问延迟。 以后如何判断程序有没有线程安全问题?
同步锁 前言经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象 在多线程程序中 + 有共享数据 + 多条语句操作共享数据
多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务) 所以思路可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题 那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行
同步与异步那怎么"把有可能出现问题的代码都包裹起来"呢?我们可以使用synchronized关键字来实现同步效果 也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的
接下来介绍下同步与异步的概念: 同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。 坏处就是效率会降低,不过保证了安全。 异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。 坏处就是有安全隐患,效率要高一些。
synchronized同步关键字1.写法:
synchronized (锁对象){ 需要同步的代码(也就是可能出现问题的操作共享数据的多条语句); }
2.同步效果的使用有两个前提:
前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题) 前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
3.特点 1)synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一! 2)synchronized同步关键字可以用来修饰方法,称为同步方法 3)同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的 4)但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢? 因为同步代码块可以保证同一个时刻只有一个线程进入 但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步
拓展:线程锁 悲观锁和乐观锁悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态. 悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态. 乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
两种常见的锁synchronized 互斥锁(悲观锁,有罪假设) 采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。 每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。
ReentrantLock 排他锁(悲观锁,有罪假设) ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。
ReentrantReadWriteLock 读写锁(乐观锁,无罪假设) 因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。 读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。 读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。
class SaleTicketsV3 implements Runnable{ static int tickets = 100; //1.定义可重入读写锁对象,静态保证全局唯一 static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); @Override public void run() { while(true) { //2.在操作共享资源前上锁 lock.writeLock().lock(); try { if(tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "=" + tickets--); } if(tickets <= 0) break; } catch (Exception e) { e.printStackTrace(); }finally { //3.finally{}中释放锁,注意一定要手动释放,防止死锁,否则就独占报错了 lock.writeLock().unlock(); } } } } 两种方式的区别需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内! 与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。
第十一章:注解与枚举类 枚举类 枚举类的实现1. values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的 枚举值。 2. valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符 串必须是枚举类对象的“名字”。如不是,会有运行时异常: IllegalArgumentException。 3. toString():返回当前枚举类对象常量的名称
实现接口的枚举类从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解)
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加 载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员 可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信。代 码分析工具、开发工具和部署工具可以通过这些补充信进行验证 或者进行部署。
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方 法, 成员变量, 参数, 局部变量的声明, 这些信被保存在 Annotation 的 “name=value” 对中。
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能, 忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如 用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 代码和XML配置等。
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以 上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的 Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上 可以说:框架 = 注解 + 反射 + 设计模式。
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
示例一:生成文档相关的注解@author 标明开发该类模块的作者,多个作者之间使用,分割 @version 标明该类模块的版本 @see 参考转向,也就是相关主题 @since 从哪个版本开始增加的 @param 对方法中某参数的说明,如果没有参数就不能写 @return 对方法返回值的说明,如果方法的返回值类型是void就不能写 @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 其中 @param @return 和 @exception 这三个标记都是只用于方法的。 @param的格式要求:@param 形参名 形参类型 形参说明 @return 的格式要求:@return 返回值类型 返回值说明 @exception的格式要求:@exception 异常类型 异常说明 @param和@exception可以并列多个
/** * @author shkstart * @version 1.0 * @see Math.java */ public class JavadocTest { /** * 程序的主方法,程序的入口 * @param args String[] 命令行参数 */ public static void main(String[] args) { } /** * 求圆面积的方法 * @param radius double 半径值 * @return double 圆的面积 */ public static double getArea(double radius){ return Math.PI * radius * radius; } } 示例二:在编译时进行格式检查(JDK内置的三个基本注解)1.@Override: 限定重写父类方法, 该注解只能用于方法 2.@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为 所修饰的结构危险或存在更好的选择 3.@SuppressWarnings: 抑制编译器警告
public class AnnotationTest{ public static void main(String[] args) { @SuppressWarnings("unused") int a = 10; } @Deprecated public void print(){ System.out.println("过时的方法"); } @Override public String toString() { return "重写的toString方法()"; } } 示例三:跟踪代码依赖性,实现替代配置文件功能Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署
@WebServlet("/login") public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>spring框架中关于“事务”的管理
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3) public void buyBook(String username, String isbn) { //1.查询书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2. 更新库存 bookShopDao.updateBookStock(isbn); //3. 更新用户的余额 bookShopDao.updateUserAccount(username, price); } <!-- 配置事务属性 --> <tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice"> <tx:attributes> <!-- 配置每个方法使用的事务属性 --> <tx:method name="buyBook" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" read-only="false" timeout="3" /> </tx:attributes> </tx:advice> 自定义 Annotation●定义新的 Annotation 类型使用 @interface 关键字 ●自定义注解自动继承了java.lang.annotation.Annotation接口 ●Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其 方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能 是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。 ●可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始 值可使用 default 关键字 ●如果只有一个参数成员,建议使用参数名为value ●如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value, 可以省略“value=” ●没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数 据 Annotation 注意:自定义注解必须配上注解的信处理流程才有意义。
@MyAnnotation(value="企鹅企鹅") public class MyAnnotationTest { public static void main(String[] args) { Class clazz = MyAnnotationTest.class; Annotation a = clazz.getAnnotation(MyAnnotation.class); MyAnnotation m = (MyAnnotation) a; String info = m.value(); System.out.println(info); } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface MyAnnotation{ String value() default "啦啦啦"; } JDK 中的元注解●JDK 的元 Annotation 用于修饰其他 Annotation 定义 ●JDK5.0提供了4个标准的meta-annotation类型,分别是: ●Retention ●Target ●Documented ●Inherited 元数据的理解: String name = “啦啦啦”;
● @Retention : 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命 周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值: ●RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的 注释 ●RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值 ●RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会 保留注释。程序可以通过反射获取该注释。 ●==@Target==: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于 修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。 ●@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。 ●定义为Documented的注解必须设置Retention值为RUNTIME。 ●@Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解。 ●比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以 继承父类类级别的注解 ●实际应用中,使用较少
JDK8中注解的新特性Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。此外, 反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法 参数上的注解。 ●JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个: TYPE_PARAMETER,TYPE_USE。 ●在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用 在任何地方。 ●ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语 句中(如:泛型声明)。 ●ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
public class TestTypeDefine<@TypeDefine() U> { private U u; public <@TypeDefine() T> void test(T t){ } } @Target({ElementType.TYPE_PARAMETER}) @interface TypeDefine{ } 第十二章: 反射 Java反射机制概述●Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信,并能直接操作任意对象的内 部属性及方法。 ●加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。
动态语言 vs 静态语言1、动态语言 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以 被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运 行时代码可以根据某些条件改变自身结构。 主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。 2、静态语言 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、 C++。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动 态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更加灵活!
Java反射机制提供的功能●在运行时判断任意一个对象所属的类 ●在运行时构造任意一个类的对象 ●在运行时判断任意一个类所具有的成员变量和方法 ●在运行时获取泛型信 ●在运行时调用任意一个对象的成员变量和方法 ●在运行时处理注解 ●生成动态代理
理解Class类并获取Class实例在Object类中定义了以下的方法,此方法 将被所有子类继承: ● public final Class getClass() 以上的方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即: 可以通过对象反射求出类的名称
Class 类对象照镜子后可以得到的信:某个类的属性、方法和构造器、某个类到底实现了哪些接 口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含 了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信。 ● Class本身也是一个类 ● Class 对象只能由系统建立对象 ● 一个加载的类在 JVM 中只会有一个Class实例 ● 一个Class对象对应的是一个加载到JVM中的一个.class文件 ● 每个类的实例都会记得自己是由哪个 Class 实例所生成 ● 通过Class可以完整地得到一个类中的所有被加载的结构 ● Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象
Class类的常用方法static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod(String name,Class … paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
• String str = “test4.Person”; • Class clazz = Class.forName(str); • Object obj = clazz.newInstance(); • Field field = clazz.getField(“name”); • field.set(obj, “Peter”); • Object name = field.get(obj); • System.out.println(name);
获取Class类的实例(四种方法)1)前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高 实例:Class clazz = String.class; 2)前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象 实例:Class clazz = “www.xx”.getClass(); 3)前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方 法forName()获取,可能抛出ClassNotFoundException 实例:Class clazz = Class.forName(“java.lang.String”); 4)其他方式(不做要求) ClassLoader cl = this.getClass().getClassLoader(); Class clazz4 = cl.loadClass(“类的全类名”);
哪些类型可以有Class对象?(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类 (2)interface:接口 (3)[ ]:数组 (4)enum:枚举 (5)annotation:注解@interface (6)primitive type:基本数据类型 (7)void
Class c1 = Object.class; Class c2 = Comparable.class; Class c3 = String[].class; Class c4 = int[][].class; Class c5 = ElementType.class; Class c6 = Override.class; Class c7 = int.class; Class c8 = void.class; Class c9 = Class.class; int[] a = new int[10]; int[] b = new int[100]; Class c10 = a.getClass(); Class c11 = b.getClass(); // 只要元素类型与维度一样,就是同一个Class System.out.println(c10 == c11); 类的加载与ClassLoader的理解 类的加载过程当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化 ●加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的 过程需要类加载器参与。 ●链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。 ●验证:确保加载的类信符合JVM规范,例如:以cafe开头,没有安全方面的问题 ●准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。 ●解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。 ●初始化: ●执行类构造器<clinit>()方法的过程。类构造器()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 的,不是构造该类对象的构造器)。 ●当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。 ●虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
public class ClassLoadingTest { public static void main(String[] args) { System.out.println(A.m); } } class A { static { m = 300; } static int m = 100; } //第二步:链接结束后m=0 //第三步:初始化后,m的值由<clinit>()方法执行决定 // 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并 产生,类似于 // <clinit>(){ // m = 300; // m = 100; // } 什么时候会发生类初始化?● 类的主动引用(一定会发生类的初始化) ●当虚拟机启动,先初始化main方法所在的类 new一个类的对象 ●调用类的静态成员(除了final常量)和静态方法 ●使用java.lang.reflect包的方法对类进行反射调用 ●当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类 ●类的被动引用(不会发生类的初始化) ●当访问一个静态域时,只有真正声明这个域的类才会被初始化 ●当通过子类引用父类的静态变量,不会导致子类初始化 ●通过数组定义类引用,不会触发此类的初始化 ●引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常 量池中了)
class Father { static int b = 2; static { System.out.println("父类被加载"); } } class A extends Father { static { System.out.println("子类被加载"); m = 300; } static int m = 100; static final int M = 1; } public class ClassLoadingTest { public static void main(String[] args) { // 主动引用:一定会导致A和Father的初始化 // A a = new A(); // System.out.println(A.m); // Class.forName("com.atguigu.java2.A"); // 被动引用 A[] array = new A[5];//不会导致A和Father的 初始化 // System.out.println(A.b);//只会初始化 Father // System.out.println(A.M);//不会导致A和 Father的初始化 } static { System.out.println("main所在的类"); } }类加载器的作用: ● 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方 法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为 方法区中类数据的访问入口。 ● 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器 中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
了解:ClassLoader类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
• //1.获取一个系统类加载器 • ClassLoader classloader = ClassLoader.getSystemClassLoader(); • System.out.println(classloader); • //2.获取系统类加载器的父类加载器,即扩展类加载器 • classloader = classloader.getParent(); • System.out.println(classloader); • //3.获取扩展类加载器的父类加载器,即引导类加载器 • classloader = classloader.getParent(); • System.out.println(classloader); • //4.测试当前类由哪个类加载器进行加载 • classloader = Class.forName(“exer2.ClassloaderDemo”).getClassLoader(); • System.out.println(classloader); • //5.测试JDK提供的Object类由哪个类加载器加载 • classloader = • Class.forName(“java.lang.Object”).getClassLoader(); • System.out.println(classloader); • //*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路 径下的指定文件的输入流 • InputStream in = null; • in = this.getClass().getClassLoader().getResourceAsStream(“exer2\\test.properties”); • System.out.println(in);
创建运行时类的对象 有了Class对象,能做什么?创建类的对象:调用Class对象的newInstance()方法 要 求: 1)类必须有一个无参数的构造器。 2)类的构造器的访问权限需要足够。 难道没有无参的构造器就不能创建对象了吗? 不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。 步骤如下: 1)通过Class类的==getDeclaredConstructor(Class … parameterTypes)==取得本类的指定形参类 型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过Constructor实例化对象。
//1.根据全类名获取对应的Class对象 String name = “atguigu.java.Person"; Class clazz = null; clazz = Class.forName(name); //2.调用指定参数结构的构造器,生成Constructor的实例 Constructor con = clazz.getConstructor(String.class,Integer.class); //3.通过Constructor的实例创建对应类的对象,并初始化类属性 Person p2 = (Person) con.newInstance("Peter",20); System.out.println(p2); 获取运行时类的完整结构使用反射可以取得:
通过反射,调用类中的方法,通过Method类完成。步骤: 1.通过Class类的 getMethod(String name,Class…parameterTypes) 方法取得 一个Method对象,并设置此方法操作时所需要的参数类型。 2.之后使用 Object invoke(Object obj, Object[] args) 进行调用,并向方法中 传递要设置的obj对象的参数信。 Object invoke(Object obj, Object … args) 说明: 1.Object 对应原方法的返回值,若原方法无返回值,此时返回null 2.若原方法若为静态方法,此时形参Object obj可为null 3.若原方法形参列表为空,则Object[] args为null 4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用 方法对象的setAccessible(true)方法,将可访问private的方法。
调用指定属性在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。 ● public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。 ● public Field getDeclaredField(String name) 返回此Class对象表示的类或接口的指定的Field ●在Field中: ●public Object get(Object obj) 取得指定对象obj上此Field的属性内容 ●public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
关于setAccessible方法的使用●Method和Field、Constructor对象都有setAccessible()方法。 ● setAccessible启动和禁用访问安全检查的开关。 ● 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。 ● 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。 ● 使得原本无法访问的私有成员也可以访问 ● 参数值为false则指示反射的对象应该实施Java语言访问检查。
反射的应用:动态代理 Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一 个或多个接口动态地生成实现类。 ●提供用于创建动态代理类和动态代理对象的静态方法
动态代理步骤1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作。 2.创建被代理的类以及接口 3.通过Proxy的静态方法 newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个Subject接口代理 4.通过 Subject代理调用RealSubject实现类的方法
动态代理与AOP(Aspect Orient Programming)前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制 ● 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有 太大的意义。通常都是为指定的目标对象生成动态代理 ● 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理 包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异: AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理
第十三章: 网络编程 网络编程概述:●Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。 ●Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。 并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
网络基础●计算机网络: 把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规 模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信、 共享硬件、软件、数据信等资源。 ●网络编程的目的: 直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。 ●网络编程中有两个主要的问题: ●如何准确地定位网络上一台或多台主机;定位主机上的特定的应用 ●找到主机后如何可靠高效地进行数据传输
网络通信要素概述 如何实现网络中的主机互相通信●通信双方地址 ●IP ●端口号 ●一定的规则(即:网络通信协议。有两套参考模型) ●OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广 ●TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
网络通信协议通信要素 IP和端口号
IP 地址:InetAddress 1)唯一的标识 Internet 上的计算机(通信实体) 2)本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost 3)IP地址分类方式1:IPV4 和 IPV6 IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已 经用尽。以点分十进制表示,如192.168.0.1 IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984 4) IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168. 开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机 构内部使用 5)特点:不易记忆
端口号标识正在计算机上运行的进程(程序) 1) 不同的进程有不同的端口号 2)被规定为一个 16 位的整数 0~65535。 3)端口分类: ① 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23) ② 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占 用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。 ③ 动态/私有端口:49152~65535。
端口号与IP地址的组合得出一个网络套接字:Socket
1.使用TCP协议前,须先建立TCP连接,形成传输数据通道 2. 传输前,采用“三次握手”方式,点对点通信,是可靠的 3. TCP协议进行通信的两个应用进程:客户端、服务端。 4. 在连接中可进行大数据量的传输 5. 传输完毕,需释放已建立的连接,效率低
UDP协议:Java语言的基于套接字编程分为服务端编程和客户端编程,其通信模 型如图所示:
基于Socket的TCP编程 客户端Socket的工作过程包含以下四个基本的步骤:发送端:
DatagramSocket ds = null; try { ds = new DatagramSocket(); byte[] by = "hello,baidu".getBytes(); DatagramPacket dp = new DatagramPacket(by, 0, by.length, InetAddress.getByName("127.0.0.1"), 10000); ds.send(dp); } catch (Exception e) { e.printStackTrace(); } finally { if (ds != null) ds.close(); }接收端 在接收端,要指定监听的端口。
DatagramSocket ds = null; try { ds = new DatagramSocket(10000); byte[] by = new byte[1024]; DatagramPacket dp = new DatagramPacket(by, by.length); ds.receive(dp); String str = new String(dp.getData(), 0, dp.getLength()); System.out.println(str + "--" + dp.getAddress()); } catch (Exception e) { e.printStackTrace(); } finally { if (ds != null) ds.close(); } URL编程 URL类一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的 方法来获取这些属性:
URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个 资源。 而==URL是uniform resource locator,统一资源定位符,==它是一种具体 的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。 而URN,uniform resource name,统一资源命名,是通过名字来标识资源, 比如mailto:java-net@java.sun。也就是说,URI是以一种抽象的,高层 次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL 和URN都是一种URI。
在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符 合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信,因此它不能是相对的
小 结版权声明:本文标题:Java基础学习 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1687086237a131688.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论