Java学习笔记 - 高级部分 - 后半
Java笔记系列:
Java学习笔记–基础内容
Java学习笔记-类的基本概念
Java学习笔记 - 类的特征
Java学习笔记 - 高级部分-前半
九、常用类
1.包装类
-
概念:将八种基本数据类型封装,表格前六种的父类是Number
-
基本数据类型 包装类 byte Byte short Short int Integer long Long float Float double Double boolean Boolean char Character -
装箱:把基本数据类型转为包装类对象。转为对象,方便使用API和对象特性。
- 基本数值---->包装对象
-
拆箱:把包装类对象拆为基本数据类型。转为数值方便运算。
- 包装对象---->基本数值
-
包装类与基本类型的转换代码结构是类似的,每种包装类由一个静态方法
valueOf()
接受基本类型,返回引用类型,也都有一个实例方法xxxValue()
返回对应的基本类型。 -
自动装箱与拆箱:JDK5.0后基本类型和包装类的拆箱装箱动作可以自动完成。
-
通过构造方法:也可通过包装类的构造方法将基本类型转化为包装类型。但不建议,因为会创造新的对象浪费空间。
Boolean类型:
b1 = false;
Boolean bObj = Boolean.valueOf(b1);//装箱
boolean b2 = bObj.booleanValue();//拆箱
Integer类型:
int i1 = 12345;
Integer iObj = Integer.valueOf(i1);
int i2 = iObj.intValue();
---------------自动装箱-------------------
Integer a = 4; // Integer a = Integer.valueOf(4)
int b = a; // int b = a.intValue();
---------构造方法--------------
Integer a = new Integer(4);
Boolean b = new Boolean(true);
-
基本数据类型和包装类字符串之间的转化:
-
-
基本数据类型–>字符串
-
字符串重载的
valueOf()
方法int a = 10; String str = String.valueOf(a);
-
更直接的方式
int a = 10; String str = a + "";
-
-
字符串–>基本数据类型
-
除了
Character
类外,可以使用parseXxx
静态方法String str = "10"; int a = Integer.parseInt(str);
-
字符串转为包装类,然后自动拆箱
String str = "10"; int a = Integer.valueOf(str);
-
-
-
-
包装类常用方法:
------数据类型的最大最小值--------- Integer.MAX_VALUE 和 Integer.MIN_VALUE Long.MAX_VALUE 和 Long.MIN_VALUE Double.MAX_VALUE 和 Double.MIN_VALUE -------字符转大小写----------- Character.toUpperCase('x'); Character.toLowerCase('X'); --------整数转进制------------- Integer.toBinaryString(int i) Integer.toHexString(int i) Integer.toOctalString(int i) ---------比较的方法------------ Double.compare(double d1, double d2) Integer.compare(int x, int y)
2.String类
2.1 string类概念
-
概念:String对象用于保存字符串,字符串常量是用双引号括起的字符序列,使用Unicode字符编码,一个字符占两个字节。
-
创建方式:
-
-
直接赋值:
String s = "aaa";
方式:从常量池查看是否有“aaa”数据空间,如果有直接指向,没有则重新创建然后指向,s指向的是常量池的空间地址。
-
调用构造器:
String s = new String("aaa");
方法:先在堆中创建空间,里面维护了value属性,指向常量池的”aaa“空间,如果常量池中没有”aaa“,要求重新创建,如果有,则直接通过value指向。最终指向的是堆中的空间地址。
-
-
-
特性:
-
- String类是一个final类,代表不可变的字符序列,不可被继承。
- 字符串是不可变的,一个字符串对象一旦被分配,其内容不可变。字符串的修改代表产生新的对象。
-
2.2 字符串常用API
- (1)boolean isEmpty():字符串是否为空
- (2)int length():返回字符串的长度
- (3)String concat(xx):拼接
- (4)boolean equals(Object obj):比较字符串是否相等,区分大小写
- (5)boolean equalsIgnoreCase(Object obj):比较字符串是否相等,不区分大小写
- (6)int compareTo(String other):比较字符串大小,区分大小写,按照 Unicode 编码值比较大小
- (7)int compareToIgnoreCase(String other):比较字符串大小,不区分大小写
- (8)String toLowerCase():将字符串中大写字母转为小写
- (9)String toUpperCase():将字符串中小写字母转为大写
- (10)String trim():去掉字符串前后空白符
- (11)public String intern():结果在常量池中共享
- -----------查找-----------
- (12)boolean contains(xx):是否包含 xx
- (13)int indexOf(xx):从前往后找 当前字符串中 xx,即如果有返回第一次出现的下标,要是没有返回-1
- (14)int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
- (15)int lastIndexOf(xx):从后往前找当前字 符串中 xx,即如果有返回最后一次出现的下标,要是没有返回-1
- (16)int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
- -----------截取------------
- (17)String substring(int beginIndex) :返回一个新的字符串,它是此字符串的从 beginIndex 开始截取到最后的一个子字符串。
- (18)String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从 beginIndex 开始截取到 endIndex(不包含)的一个子字符串。
- ---------和字符字符数字有关---------
- (19)char charAt(index):返回[index]位置的字符
- (20)char[] toCharArray(): 将此字符串转换为一个新的字符数组返回
- (21)static String valueOf(char[] data) :返回指定数组中表示该字符序列的 String
- (22)static String valueOf(char[] data, int offset, int count) : 返回指定数组中表示该字符序列的 String
- (23)static String copyValueOf(char[] data): 返回指定数组中 表示该字符序列的 String
- (24)static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的 String
- ----------开头结尾---------
- (25)boolean startsWith(xx):测试此字符串是否以指定的前缀开始
- (26) boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
- (27)boolean endsWith(xx):测试此字符串是否 以指定的后缀结束
- -----------替换----------
- (28)String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 不支持正则。
- (29)String replace(CharSequence target, CharSequence replacement): 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
- (30)String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
- (31)String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
- (32)String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
- (33)String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串。
2.3 format格式化处理
- String.format()字符串常规类型格式化的两种重载方式
- format(String format, Object… args):新字符串使用本地语言环境,制定字符串格式和参数生成格式化的新字符串。
- format(Locale locale, String format, Object… args) 使用指定的语言环境,制定字符串格式和参数生成格式化的字符串。
- 占位符完整格式为: %[index$][标识]*[最小宽度][.精度]转换符
- % ,占位符的其实字符,若要在占位符内部使用%,则需要写成 %% 。
- [index$] ,位置索引从1开始计算,用于指定对索引相应的实参进行格式化并替换掉该占位符。
- [标识] ,用于增强格式化能力,可同时使用多个 [标识] ,但某些标识是不能同时使用的。
- [最小宽度] ,用于设置格式化后的字符串最小长度,若使用 [最小宽度] 而无设置 [标识] ,那么当字符串长度小于最小宽度时,则以左边补空格的方式凑够最小宽度。
- [.精度] ,对于浮点数类型格式化使用,设置保留小数点后多少位。
- 转换符 ,用于指定格式化的样式,和限制对应入参的数据类型。
- 对字符、字符串进行格式化:%[index$][标识][最小宽度]转换符
- 可用标识:
- -,在最小宽度内左对齐,右边用空格补上。
- 可用转换符
- s,字符串类型。
- c,字符类型,实参必须为char或int、short等可转换为char类型的数据类型,否则抛IllegalFormatConversionException异常。
- b,布尔类型,只要实参为非false的布尔类型,均格式化为字符串true,否则为字符串false。
- n,平台独立的换行符(与通过 System.getProperty(“line.separator”) 是一样的)
- 可用标识:
将"hello"格式化为"hello "
String raw = "hello";
String str = String.format("%1$-7s", raw);
// 简化
//String str = String.format("%-7s", raw);
- 对整数进行格式化:%[index$][标识]*[最小宽度]转换符
- 可用标识:
- -,在最小宽度内左对齐,不可以与0标识一起使用。
- 0,若内容长度不足最小宽度,则在左边用0来填充。
- #,对8进制和16进制,8进制前添加一个0,16进制前添加0x。
- +,结果总包含一个+或-号。
- 空格,正数前加空格,负数前加-号。
- ,,只用与十进制,每3位数字间用,分隔。
- (,若结果为负数,则用括号括住,且不显示符号。
- 可用转换符:
- b,布尔类型,只要实参为非false的布尔类型,均格式化为字符串true,否则为字符串false。
- d,整数类型(十进制)。
- x,整数类型(十六进制)。
- o,整数类型(八进制)
- n,平台独立的换行符, 也可通过System.getProperty(“line.separator”)获取
- 可用标识:
将1显示为0001
int num = 1;
String str = String.format("%04d", num);
将-1000显示为(1,000)
int num = -1000;
String str = String.format("%(,d", num);
- 对浮点数进行格式化:%[index$][标识]*[最小宽度][.精度]转换符
- 可用标识:
- -,在最小宽度内左对齐,不可以与0标识一起使用。
- 0,若内容长度不足最小宽度,则在左边用0来填充。
- #,对8进制和16进制,8进制前添加一个0,16进制前添加0x。
- +,结果总包含一个+或-号。
- 空格,正数前加空格,负数前加-号。
- ,,只用与十进制,每3位数字间用,分隔。
- (,若结果为负数,则用括号括住,且不显示符号。
- 可用转换符:
- b,布尔类型,只要实参为非false的布尔类型,均格式化为字符串true,否则为字符串false。
- n,平台独立的换行符, 也可通过System.getProperty(“line.separator”)获取。
- f,浮点数型(十进制)。显示9位有效数字,且会进行四舍五入。如99.99。
- a,浮点数型(十六进制)。
- e,指数类型。如9.38e+5。
- g,浮点数型(比%f,%a长度短些,显示6位有效数字,且会进行四舍五入)
- 可用标识:
double num = 123.4567899;
System.out.print(String.format("%f %n", num)); // 123.456790
System.out.print(String.format("%a %n", num)); // 0x1.edd3c0bb46929p6
System.out.print(String.format("%g %n", num)); // 123.457
- 对日期时间进行格式化: %[index$]t转换符
- 可用转换符
- 日期的转换符
- c,星期六 十月 27 14:21:20 CST 2007
- F,2007-10-27
- D,10/27/07
- r,02:25:51 下午
- T,14:28:16
- R,14:28
- b, 月份简称
- B, 月份全称
- a, 星期简称
- A, 星期全称
- C, 年前两位(不足两位补零)
- y, 年后两位(不足两位补零)
- j, 当年的第几天
- m, 月份(不足两位补零)
- d, 日期(不足两位补零)
- e, 日期(不足两位不补零)
- 时间的转换符
- H, 24小时制的小时(不足两位补零)
- k, 24小时制的小时(不足两位不补零)
- I, 12小时制的小时(不足两位补零)
- i, 12小时制的小时(不足两位不补零)
- M, 分钟(不足两位补零)
- S, 秒(不足两位补零)
- L, 毫秒(不足三位补零)
- N, 毫秒(不足9位补零)p, 小写字母的上午或下午标记,如中文为“下午”,英文为pm
- z, 相对于GMT的时区偏移量,如+0800
- Z, 时区缩写,如CST
- s, 自1970-1-1 00:00:00起经过的秒数
- Q, 自1970-1-1 00:00:00起经过的豪秒
- 日期的转换符
- 可用转换符
3.StringBuffer和StringBuilder类
- 概念:StringBuffer和StringBuilder是可变的字符序列,可以对字符串内容进行增删,API一致。String对象是不可变对象,如果频繁操作字符串,效率变低,空间消耗变高。
- 三者区分:
- String:不可变的字符序列
- StringBuffer:可变的字符序列,线程安全,效率低
- StringBuilder:可变的字符序列,线程不安全,效率高
- StringBuffer和StringBuilder常用API
- (1)StringBuffer append(xx):提供了很多的 append()方法,用于进行字符串追加的方式拼接
- (2)StringBuffer delete(int start, int end):删除[start,end)之间字符
- (3)StringBuffer deleteCharAt(int index):删除[index]位置字符
- (4) StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列为 str
- (5)void setCharAt(int index, char c):替换[index]位置字符
- (6)char charAt(int index):查找指定 index 位置上的字符
- (7)StringBuffer insert(int index, xx):在[index]位置插入 xx
- (8)int length():返回存储的字符数据的长度
- (9)StringBuffer reverse():反转
- StringBuffer和StringBuilder其它API
- (1)int indexOf(String str):在当前字符序列中查询 str 的第一次出现下标
- (2)int indexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中 查询 str 的第一次出现下标
- (3)int lastIndexOf(String str):在当前字符序列中 查询 str 的最后一次出现下标
- (4)int lastIndexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中查询str 的最后一次出现下标
- (5)String substring(int start):截取当前字符序列[start,最后]
- (6)String substring(int start, int end):截取当前字符序列[start,end)
- (7)String toString():返回此序列中数据的字符串表示形式
- (8)void setLength(int newLength) :设置当前字符序列长度为 newLength
4.Math类
4.1 java.lang.Math
- 概念:java.lang.Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。类似这样的工具类,其所有方法均为静态方法,并且不会创建对象。
- 常用方法:
- public static double abs(double a) :返回 double 值的绝对值。
- public static double ceil(double a) :返回大于等于参数的最小的整数。
- public static double floor(double a) :返回小于等于参数最大的整数。
- public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)
- public static double pow(double a,double b):返回 a 的 b 幂次方法
- public static double sqrt(double a):返回 a 的平方根
- public static double random():返回[0,1)的随机值
- public static final double PI:返回圆周率
- public static double max(double x, double y):返回 x,y 中的最大值
- public static double min(double x, double y):返回 x,y 中的最小值
- 其它:acos,asin,atan,cos,sin,tan 三角函数
4.2 java.math
4.2.1 BigInteger
- 概念:java.math 包的 BigInteger 可以表示不可变的任意精度的整数。BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。另外, BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。
- 构造器
- – BigInteger(String val):根据字符串构建 BigInteger 对象
- 方法
- – public BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
- – BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
- – BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的BigInteger
- – BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的BigInteger
- – BigInteger divide(BigInteger val) :返回其值为 (this / val) 的BigInteger。整数相除只保留整数部分。
- – BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的BigInteger。
- – BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟 (this % val) 的两个BigInteger 的数组。
- – BigInteger pow(int exponent) :返回其值为 (this^exponent) 的BigInteger。
4.2.2 BigDecimal
- 概念:般的 Float 类和 Double 类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal 类。BigDecimal 类支持不可变的、任意精度的有符号十进制定点数。
- 构造器
- – public BigDecimal(double val)
- – public BigDecimal(String val) --> 推荐
- 常用方法
- – public BigDecimal add(BigDecimal augend)
- – public BigDecimal subtract(BigDecimal subtrahend)
- – public BigDecimal multiply(BigDecimal multiplicand)
- – public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode):divisor 是除数,scale 指明保留几位小数,roundingMode 指明舍入模式(ROUNDUP :向上加 1、ROUNDDOWN :直接舍去、 ROUNDHALFUP:四舍五入)
5.Arrays类
- 概念:java.util.Arrays 类即为操作数组的工具类,包含了用来操作数组(比如排序和搜 索)的各种方法。
- 常用方法:
- 数组元素拼接:
- – static String toString(int[] a) :字符串表示形式由数组的元素列表组成,括在方括号(“[]”)中。相邻元素用字符 ", "(逗号加空格)分隔。形式为:[元素 1,元素 2,元素 3。。。]
- – static String toString(Object[] a) :字符串表示形式由数组的元素列表组成,括在方括号(“[]”)中。相邻元素用字符 ", "(逗号加空格)分隔。元素将自动调用自己从 Object 继承的 toString 方法将对象转为字符串进行拼接,如果没有重写,则返回类型@hash 值,如果重写则按重写返回的字符串进行拼接。
- 数组排序:
- – static void sort(int[] a) :将 a 数组按照从小到大进行排序
- – static void sort(int[] a, int fromIndex, int toIndex) :将 a 数组的[fromIndex, toIndex)部分按照升序排列
- – static void sort(Object[] a) :根据元素的自然顺序对指定对象数组按升序进行排序。
- – static void sort(T[] a, Comparator<? super T> c) :根据指定比较器产生的顺序对指定对象数组进行排序。
- 数组元素的二分查找:
- – static int binarySearch(int[] a, int key)
- static int binarySearch(Object[] a, Object key) :要求数组有序,在数组中查找 key 是否存在,如果存在返回第一次找到的下标,不存在返回负数。
- 数组的复制:
- – static int[] copyOf(int[] original, int newLength) :根据 original 原数组复制一个长度为 newLength 的新数组,并返回新数组
- – static T[] copyOf(T[] original,int newLength):根据 original 原数组复制一个长度为 newLength 的新数组,并返回新数组
- – static int[] copyOfRange(int[] original, int from, int to) :复制 original 原数组的[from,to)构成新数组,并返回新数组
- – static T[] copyOfRange(T[] original,int from,int to):复制 original 原数组的[from,to)构成新数组,并返回新数组
- 比较两个数组是否相等:
- – static boolean equals(int[] a, int[] a2) :比较两个数组的长度、元素是否完全相同
- – static boolean equals(Object[] a,Object[] a2):比较两个数组的长度、元素是否完全相同
- 填充数组:
- – static void fill(int[] a, int val) :用 val 值填充整个 a 数组
- – static void fill(Object[] a,Object val):用 val 对象填充整个 a 数组
- – static void fill(int[] a, int fromIndex, int toIndex, int val):将 a 数组[fromIndex,toIndex)部分填充为 val 值
- – static void fill(Object[] a, int fromIndex, int toIndex, Object val) :将 a 数组[fromIndex,toIndex)部分填充为 val 对象
- 数组元素拼接:
6.日期类
6.1 第一代日期类
- Date:精确到毫秒,代表特定瞬间
- SimpleDateFormat:格式化和解析日期的类
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Date01 {
public static void main(String[] args) throws ParseException {
//1. 获取当前系统时间
//2. 这里的 Date 类是在 java.util 包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1);
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
System.out.println("d2=" + d2); //获取某个时间对应的毫秒数
//1. 创建 SimpleDateFormat 对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);
//1. 可以把一个格式化的 String 转成对应的 Date
//2. 得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常
String s = "1996 年 01 月 01 日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse))
-
日期和时间的格式化编码
时间模式字符串用来指定时间格式。在此模式中,所有的 ASCII 字母被保留为模式字母,定义如下:
字母 描述 示例 G 纪元标记 AD y 四位年份 2001 M 月份 July or 07 d 一个月的日期 10 h A.M./P.M. (1~12)格式小时 12 H 一天中的小时 (0~23) 22 m 分钟数 30 s 秒数 55 S 毫秒数 234 E 星期几 Tuesday D 一年中的日子 360 F 一个月中第几周的周几 2 (second Wed. in July) w 一年中第几周 40 W 一个月中第几周 1 a A.M./P.M. 标记 PM k 一天中的小时(1~24) 24 K A.M./P.M. (0~11)格式小时 10 z 时区 Eastern Standard Time ’ 文字定界符 Delimiter " 单引号 `
6.2 第二代日期类
- 指java.util.Calendar(日历)类。
- Date 类的 API 大部分被废弃了,替换为 Calendar。
- Calendar 类是一个抽象类,主用用于完成日期字段之间相互操作的功能。
import java.util.Calendar;
public class Calendar_ {
public static void main(String[] args) {
//1. Calendar 是一个抽象类, 并且构造器是 private
//2. 可以通过 getInstance() 来获取实例
//3. 提供大量的方法和字段提供给程序员
//4. Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
//5. 如果我们需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为 Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
System.out.println(c.get(Calendar.YEAR) + "-"
+ (c.get(Calendar.MONTH) + 1) + "-"
+ c.get(Calendar.DAY_OF_MONTH) + " "
+ c.get(Calendar.HOUR_OF_DAY) + ":"
+ c.get(Calendar.MINUTE) + ":"
+ c.get(Calendar.SECOND) );
}
-
常用字段:
常量 描述 Calendar.YEAR 年份 Calendar.MONTH 月份 Calendar.DATE 日期 Calendar.DAY_OF_MONTH 日期,和上面的字段意义完全相同 Calendar.HOUR 12小时制的小时 Calendar.HOUR_OF_DAY 24小时制的小时 Calendar.MINUTE 分钟 Calendar.SECOND 秒 Calendar.DAY_OF_WEEK 星期几
- 一个 Calendar 的实例是系统时间的抽象表示,可以修改或获取 YEAR、MONTH、 DAYOFWEEK、HOUROFDAY 、MINUTE、SECOND 等 日历字段对应的时间值。
- – public int get(int field):返回给定日历字段的值
- – public void set(int field,int value) :将给定的日历字段设置为指定的值
- – public void add(int field,int amount):根据日历的规则,为给定的日历字段 添加或者减去指定的时间量
- – public final Date getTime():将 Calendar 转成 Date 对象
- – public final void setTime(Date date):使用指定的 Date 对象重置 Calendar 的时间
import java.util.Calendar;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner console = new Scanner(System.in);
int year = console.nextInt();
//获取Calendar的实例
Calendar calendar=Calendar.getInstance();
//循环遍历所有的月份
for(int month=1;month<=12;month++){
//设置年、月、日
calendar.set(year,month,0);
//输出对应年份各个月的天数
System.out.println(year+"年"+month+"月:"+calendar.getActualMaximum(Calendar.DATE)+"天");
}
}
}
6.3 第三代日期类
-
Calendar存在的问题:
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:Date 中的年份是从 1900 开始的,而月份都从 0 开始。
- 格式化:格式化只对 Date 有用,Calendar 则不行。
- 此外,它们也不是线程安全的;不能处理闰秒等。
-
本地日期时间:
- LocalDate:只包含日期,可以获取日期字段
- LocalTime:只包含时间,可以获取时间字段
- LocalDateTime:包含日期+时间,可以获取日期和时间字段
-
方法:
- now()/ now(ZoneId zone) 静态方法,根据当前时间创建对象/指定 时区的对象
- of(xx,xx,xx,xx,xx,xxx) 静态方法,根据指定日期/时间创建对象
- getDayOfMonth()/getDayOfYear() 获得月份天数(1-31) /获得年份天数(1- 366)
- getDayOfWeek() 获得星期几(返回一个 DayOfWeek 枚举 值)
- getMonth() 获得月份, 返回一个 Month 枚举值
- getMonthValue() / getYear() 获得月份(1-12) /获得年份
- getHours()/getMinute()/getSecond() 获得当前对象对应的小时、分钟、秒
- withDayOfMonth()/withDayOfYear()/ withMonth()/withYear() 将月份天数、年份天数、月份、年份修 改为指定的值并返回新的对象
- with(TemporalAdjuster t) 将当前日期时间设置为校对器指定的日 期时间
- plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours() 向当前对象添加几天、几周、几个月、 几年、几小时
- minusMonths() / minusWeeks()/minusDays()/minusYe ars()/minusHours() 从当前对象减去几月、几周、几天、几 年、几小时
- plus(TemporalAmount t)/minus(TemporalAmount t) 添加或减少一个
- Duration 或 Period isBefore()/isAfter() 比较两个 LocalDate
- isLeapYear() 判断是否是闰年(在 LocalDate 类中声 明)
- format(DateTimeFormatter t) 格式化本地日期、时间,返回一个字符串
- parse(Charsequence text) 将指定格式的字符串解析为日期、时间
-
DateTimeFormatter格式日期类形式
- 方法:
- ofPattern(String pattern) 静态方法,返回一个指定字符串格式的 DateTimeFormatter
- format(TemporalAccessor t) 格式化一个日期、时间,返回字符串
- parse(CharSequence text) 将指定格式的字符序列解析为一个日期、时间
DateTimeFormat dtf = DateTimeFormatter.ofPattern(格式);//yyyy年MM月dd日 HH小时mm分钟ss秒 String str = dtf.format(日期对象); -------------- public void test(){ 自定义的方式(关注、重点) DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //格式化 String strDateTime = dateTimeFormatter.format(LocalDateTime.now()); System.out.println(strDateTime); //2022/12/04 21:05:42 //解析 TemporalAccessor accessor = dateTimeFormatter.parse("2022/12/04 21:05:42"); LocalDateTime localDateTime = LocalDateTime.from(accessor); System.out.println(localDateTime); //2022-12-04T21:05:42 }
- 方法:
6.3 瞬时:Instant时间戳
- Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。
- 时间戳是指格林威治时间 1970 年 01 月 01 日 00 时 00 分 00 秒(北京时间 1970 年 01 月 01 日 08 时 00 分 00 秒)起至现在的总秒数。
- 方法及描述:
- now() 静态方法,返回默认 UTC 时区的 Instant 类的对象
- ofEpochMilli(long epochMilli) 静态方法,返回在 1970-01-01 00:00:00 基础上 加上指定毫秒数之后的 Instant 类的对象
- atOffset(ZoneOffset offset) 结合即时的偏移来创建一个 OffsetDateTime
- toEpochMilli() 返回 1970-01-01 00:00:00 到当前时间的毫秒数,即为时间戳
十、集合
1.基本概念
- 集合与数组的比较
- 数组:
- 特点:
- 数组初始化以后,长度就确定了。
- 数组中的添加的元素是依次紧密排列的,有序的,可以重复的。
- 数组声明的类型,就决定了进行元素初始化时的类型。不是此类型的变量,就不能添加。
- 可以存储基本数据类型值,也可以存储引用数据类型的变量
- 劣势:
- 数组初始化以后,长度就不可变了,不便于扩展
- 数组中提供的属性和方法少,不便于进行添加、删除、插入、获取元素个 数等操作,且效率不高。
- 数组存储数据的特点单一,只能存储有序的、可以重复的数据
- 特点:
- 集合:
- 集合可以动态保存任意多个对象,使用方便。
- 集合中提供了一系列方便的操作对象的方法
- 使用集合添加删除新元素的代码简洁明了
- 数组:
- 集合框架:Java 集合可分为
Collection
和Map
两大体系Collection
接口:用于存储一个一个的数据,也称单列数据集合。List
子接口:用来存储有序的、可以重复的数据(主要用来替换数组,"动 态"数组)- 实现类:
ArrayList
(主要实现类)、LinkedList
、Vector
- 实现类:
Set
子接口:用来存储无序的、不可重复的数据(类似于高中讲的"集合")- 实现类:
HashSet
(主要实现类)、LinkedHashSet
、TreeSet
- 实现类:
Map
接口:用于存储具有映射关系“key-value 对”的集合,即一对一对的数据,也称双列数据集合。(类似于高中的函数、映射。(x1,y1),(x2,y2) —> y = f(x) )HashMap
(主要实现类)、LinkedHashMap
、TreeMap
、Hashtable
、Properties
2.Collection接口
- 特点:
-
Collection
实现子类可以存放多个元素,每个元素可以是Object
- 有些
Collection
的实现类,可以存放重复的元素,有些不可以 - 有些
Collection
的实现类是有序的(List),有些是无序的(Set) Collection
接口没有直接的实现子类,通过子接口List
和Set
来实现
-
- 方法:
- 添加:
- (1)add(E obj):添加元素对象到当前集合中
- (2)addAll(Collection other): 添加 other 集合中的所有元素对象到当前集合中,即 this = this ∪ other
- 判断:
- (3)int size():获取当前集合中实际存储的元素个数
- (4)boolean isEmpty():判断当前集合是否为空集合
- (5)boolean contains(Object obj):判断当前集合中是否存在一个与 obj 对象 equals 返回 true 的元素
- (6)boolean containsAll(Collection coll):判断 coll 集合中的元素是否在当前集合中都存在。 即 coll 集合是否是当前集合的“子集”
- (7)boolean equals(Object obj):判断当前集合与 obj 是否相等
- 删除:
- (8)void clear():清空集合元素
- (9) boolean remove(Object obj) :从当前 集合中删除第一个找到的与 obj 对象 equals 返回 true 的元素。
- (10)boolean removeAll(Collection coll):从当前集合中删除所有与 coll 集合中相同的元素。 即 this = this - this ∩ coll
- (11)boolean retainAll(Collection coll):从当前集合 中删除两个集合中不同的元素,使得当前集合仅保留与 coll 集合中的元素相同 的元素,即当前集合中仅保留两个集合的交集,即 this = this ∩ coll;
- 其他:
- (12)Object[] toArray():返回包含当前集合中所有元素的数组
- (13)hashCode():获取集合对象的哈希值
- (14)iterator():返回迭代器对象,用于集合遍历
- 添加:
import java.util.ArrayList;
import java.util.List;
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);
// remove:删除指定元素
//list.remove(0);//删除第一个元素
list.remove(true);//指定删除某个元素
System.out.println("list=" + list);
// contains:查找元素是否存在
System.out.println(list.contains("jack"));//T
// size:获取元素个数
System.out.println(list.size());//2
// isEmpty:判断是否为空
System.out.println(list.isEmpty());//F
// clear:清空
list.clear();
System.out.println("list=" + list);
// addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//T
// removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);//[聊斋]
// 说明:以 ArrayList 实现类来演示. }
}
3.Iterator接口(迭代器)
-
概念:针对需要遍历集合中的所有元素的需求,JDK 提供了一个接口 java.util.Iterator。
-
Iterator:被称为迭代器接口,本身并不提供存储对象的能力,主要用于遍历 Collection 中的元素。
-
Iterator 接口的常用方法如下:
- public E next():返回迭代的下一个元素。(要求先调用hasNext()检测,否则如果下一条记录无效会抛出异常
- public boolean hasNext():如果仍有元素可以迭代,则返回 true。
- void remove() :删除元素,通过迭代器对象的 remove 方法,不是 集合对象的 remove 方法。
-
实现原理:在遍历集合时,内部采用指针的方式来跟踪集合中的元素
-
foreach循环:foreach 循环(也称增强 for 循环)是 JDK5.0 中定义的一个高级 for 循环,专门用来 遍历数组和集合的。
-
语法格式:
for(元素的数据类型 局部变量 : Collection 集合或数组){ //操作局部变量的输出操作 }
-
while(iterator.hasNext()){ //指针下移-->将下移以后集合位置上元素返回 next(); } ---------------------------- import java.util.ArrayList;//daobao import java.util.Collection; import java.util.Iterator; List list = new ArrayList(); list.add(new Dog("小黑", 3)); list.add(new Dog("大黄", 100)); list.add(new Dog("大壮", 8)); //先使用 for 增强 for (Object dog : list) { System.out.println("dog=" + dog); } //使用迭代器 System.out.println("===使用迭代器来遍历==="); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Object dog = iterator.next(); System.out.println("dog=" + dog); } //当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素 // iterator.next();//NoSuchElementException //如果希望再次遍历,需要重置我们的迭代器 iterator = col.iterator(); System.out.println("===第二次遍历==="); while (iterator.hasNext()) { Object odog = iterator.next(); System.out.println("dog=" + dog); }
4.List接口
4.1 List接口
- 特点:有序、可重复。集合中每个元素都有其对应的顺序索引。
- 实现类:ArrayList、LinkedList、Vector
- 接口方法:
- 插入元素
- – void add(int index, Object ele):在 index 位置插入 ele 元素
- – boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有 元素添加进来
- 获取元素
- – Object get(int index):获取指定 index 位置的元素
- – List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
- 获取元素索引
- – int indexOf(Object obj):返回 obj 在集合中首次出现的位置
- – int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
- 删除和替换元素
- – Object remove(int index):移除指定 index 位置的元素,并返回此元素
- – Object set(int index, Object ele):设置指定 index 位置的元素为 ele
- 插入元素
4.2 ArrayList
- ArrayList是List接口的主要实现类,本质上ArrayList是对象引用的一个“变长”数组,可以重复添加任意元素,包括null。
- ArrayList基本等同于Vector,但ArrayList线程不安全(执行效率高)多线程情况下,不建议使用
- 底层源码分析:
- ArrayList中维护的是一个Object类型的数组elementData
- 创建ArrayList对象时,如果使用无参构造器,则构造器初始elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容容量为1.5倍elementData
- 如果使用了指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
4.3 Vector
- Vector底层也是一个对象数组
- Vector是线程同步的,即线程安全(效率低于ArrayList),操作方法带有
synchronized
- 创建Vector对象时,如果是无参构造器,默认是10,如果需要扩容按2倍扩容
- 如果使用的指定大小构造器,需要扩容时按2倍扩容
4.4 LinkedList
-
LinkedList底层实现了双向链表和双向队列,可以重复添加任意元素,包括null。
-
线程不安全,没有实现同步
-
底层源码分析:
- 底层维护了一个双向链表
- 维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,prev指向前一个,通过next指向后一个节点,实现双向链表
- LinkedList通过链表完成元素的添加和删除,相对而言效率高于数组
-
特有方法:
- – void addFirst(Object obj) 向首结点添加
- – void addLast(Object obj)
- – Object getFirst()
- – Object getLast()
- – Object removeFirst()
- – Object removeLast()
-
LinkedList linkedList = new LinkedList(); linkedList.add(1); linkedList.add(2); linkedList.add(3); System.out.println("linkedList=" + linkedList); //演示一个删除结点的 linkedList.remove(); // 这里默认删除的是第一个结点 //linkedList.remove(2); System.out.println("linkedList=" + linkedList); //修改某个结点对象 linkedList.set(1, 999); System.out.println("linkedList=" + linkedList); //得到某个结点对象 //get(1) 是得到双向链表的第二个对象 Object o = linkedList.get(1); System.out.println(o);//999 //因为 LinkedList 是 实现了 List 接口, 遍历方式 System.out.println("===LinkeList 遍历迭代器===="); Iterator iterator = linkedList.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println("next=" + next); } System.out.println("===LinkeList 遍历增强 for===="); for (Object o1 : linkedList) { System.out.println("o1=" + o1); } System.out.println("===LinkeList 遍历普通 for===="); for (int i = 0; i < linkedList.size(); i++) { System.out.println(linkedList.get(i)); }
5.Set接口
5.1 Set接口
- 特点:无序(添加和取出的顺序不一致,但顺序固定),没有索引,不允许重复元素,最多只能包含一个null
- 遍历方式:
- 迭代器
- foreach(不能使用索引来便利
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetMethod {
public static void main(String[] args) {
//1. 以 Set 接口的实现类 HashSet 来讲解 Set 接口的方法
//2. set 接口的实现类的对象(Set 接口对象), 不能存放重复的元素, 可以添加一个 null
//3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
//4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定. Set set = new HashSet();
Set set = new HashSet();
set.add("john");
set.add("lucy");
set.add("john");//重复添加不会报错,但是无效
set.add("jack");
set.add("hsp");
set.add("mary");
set.add(null);//null
set.add(null);//再次添加 null
System.out.println("set=" + set);
//set=[null, hsp, mary, john, lucy, jack]
//遍历
//方式 1: 使用迭代器
System.out.println("=====使用迭代器====");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
set.remove(null);
//方式 2: 增强 for
System.out.println("=====增强 for====");
for (Object o : set) {
System.out.println("o=" + o);
}
//set 接口对象,不能通过索引来获取
}
}
5.2 HashSet
- 特点:
- HashSet实现了Set接口,HashSet实际上是HashMap
- 可以存放null值但是只能有一个null
- 不保证存放元素的顺序和取出元素的顺序一致
- 不能有重复的元素对象
- 判断元素相等:
- 标准:两个对象通过 hashCode() 方法得到的 哈希值相等,并且两个对象的 equals()方法返回值为 true。
- 对于存放在 Set 容器中的对象,对应的类一定要重写 hashCode()和 equals(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
- 无序性:无序性不等于随机性,我们在添加每一个元素到数组中时,具体的存储位置是由元素的 hashCode()调用后返回的 hash 值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。
- 添加元素过程:
- 第 1 步:当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法得到该对象的 hashCode 值,然后根据 hashCode 值,通过某个散列函数决定该对象在 HashSet 底层数组中的存储位置。
- 第 2 步:如果要在数组中存储的位置上没有元素,则直接添加成功。 元素保存在底层数组中
- 第 3 步:如果要在数组中存储的位置上有元素,则继续比较:
- – 如果两个元素的 hashCode 值不相等,则添加成功;通过链表的方式继续链接存储
- – 如果两个元素的 hashCode()值相等,则会继续调用 equals()方法:
- 如果 equals()方法结果为 false,则添加成功。通过链表的方式继续链接存储,链表上元素数量太多会树化(红黑树
- 如果 equals()方法结果为 true,则添加失败。
- HashSet的扩容和红黑树
-
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12
- 如果table数组到了临界值12,就会扩容到 16*2=32 ,新的临界值就是 32*0.75 = 24,以此类推
- Java8中,如果一条链表个数达到 TREEIFY_THRSHOLD(默认为8)并且 table大小>= MIN_TREEIFY_CAPACITY(默认64),此时就会进行树化(红黑树)否则仍采用数组扩容机制
-
5.3 LinkedHashSet
- 特点:
-
- LinkedHashSet 是 HashSet 的子类
- LinkedHashSet 底层是一个 LinkedHashMap ,底层维护了一个数组+双向链表
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,使用双向链表维护元素的次数,使得元素看起来是以插入的顺序保存的
- 不允许添加重复元素
-
- 底层结构:
- 维护了一个 hash 表和双向链表(head 和 tail)
- 每个节点有 before 和 after 属性
- 在添加元素时,先求 hash 值,再求索引。确定元素在 table 中的位置,然后将添加的元素加入双向链表
5.4 TreeSet
- 特点:
- TreeSet 是 SortedSet 接口的实现类,TreeSet 可以按照添加的元素的指定的属性的 大小顺序进行遍历。
- TreeSet 底层使用红黑树结构存储数据
- 不允许重复、实现排序(自然排序或定制排序)
- 去重机制:
- 如果传入一个 Comparator 匿名对象,使用实现的 compare 去重,如果方法返回0,就认为是相同的元素/数据,就不添加
- 如果没有传入一个 Comparator 匿名对象,则使用添加的对象实现的 Compareable 接口的 CompareTo 去重。
- TreeSet 两种排序方法:
- 自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比 较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
- 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
- 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。
- 定制排序:如果元素所属的类没有实现 Comparable 接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过 Comparator 接口来实现。需要重写 compare(T o1,T o2)方法。
- 利用 int compare(T o1,T o2)方法,比较 o1 和 o2 的大小:如果方法返回正整数,则表示 o1 大于 o2;如果返回 0,表示相等;返回负整数,表示 o1 小于 o2。
- 要实现定制排序,需要将实现 Comparator 接口的实例作为形参传递给 TreeSet 的构造器。
- 自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比 较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
- 判断相等:两个对象通过 compareTo(Object obj) 或 compare(Object o1,Object o2)方法比较返回值。返回值为 0,则认为两个对象相等。
6.Map接口
6.1 Map
-
特点:
- Map 与 Collection 并列存在。用于保存具有映射关系的数据:key-value – Collection 集合称为单列集合,元素是孤立存在的(理解为单身)。
- Map 集合称为双列集合,元素是成对存在的(理解为夫妻)。
- Map 中的 key 和 value 都可以是任何引用类型的数据。但常用 String 类作为 Map 的“键”。
- key 的值不允许重复,但 value 值可以重复
- key 和 value 都可以为 null,但key 为 null 只能有一个
-
Map 接口的常用实现类:HashMap、LinkedHashMap、TreeMap 和 `Properties。其中,HashMap 是 Map 接口使用频率最高的实现类。
-
key 和 value 特点:
-
-
Map 中的 key 用 Set 来存放,不允许重复,即同一个 Map 对象所对应的类,须重写 hashCode()和 equals()方法
-
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value,不同 key 对应的 value 可以重复。value 所在的类要重写 equals()方法。
-
key 和 value 构成一个 entry。所有的 entry 彼此之间是无序的、不可重复的。
-
一对 k-v 就是一个Entry
-
-
-
常用方法:
- 添加、修改操作:
- Object put(Object key,Object value):将指定 key-value 添加到(或修改)当前 map 对象中
- void putAll(Map m):将 m 中的所有 key-value 对存放到当前 map 中
- 删除操作:
- Object remove(Object key):移除指定 key 的 key-value 对,并返回 value
- void clear():清空当前 map 中的所有数据
- 元素查询的操作:
- Object get(Object key):获取指定 key 对应的 value
- boolean containsKey(Object key):是否包含指定的 key
- boolean containsValue(Object value):是否包含指定的 value
- int size():返回 map 中 key-value 对的个数
- boolean isEmpty():判断当前 map 是否为空
- boolean equals(Object obj):判断当前 map 和参数对象 obj 是否相等
- 元视图操作的方法:
- Set keySet():返回所有 key 构成的 Set 集合
- Collection values():返回所有 value 构成的 Collection 集合
- Set entrySet():返回所有 key-value 对构成的 Set 集合
- 添加、修改操作:
-
遍历方式:
-
- containsKey:查找键是否存在
- keySet:获取所有键值
- enterySet:获取所有关系 k-v
- values:获取所有值
-
import java.util.*;
public class MapMethod {
public static void main(String[] args) {
//演示 map 接口常用方法
Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");//替换
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put("鹿晗", "关晓彤");//OK
map.put("hsp", "hsp 的老婆");
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val=" + val);
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty());//F
// clear:清除 k-v
//map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("hsp"));//T
//遍历:
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();
//(1) 增强 for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
6.2 HashMap
- 特点:
-
- HashMap 是线程不安全的。允许添加 null 键和 null 值。
- 存储数据采用的哈希表结构,底层使用一维数组+单向链表+红黑树进行 key-value 数据的存储。与 HashSet 一样,元素的存取顺序不能保证一致。
- HashMap 判断两个 key 相等的标准是:两个 key 的 hashCode 值相等,通过 equals() 方法返回 true。
- HashMap 判断两个 value 相等的标准是:两个 value 通过 equals() 方法返回 true。
-
- 底层源码扩容机制:
-
- HashMap底层维护了Node类型的数组table,默认为null
- 创建对象时,将加载因子(loadfactor)初始化为0.75
- 添加 key-val
- 通过 key 的哈希值得到在 table 的索引,判断该索引处是否有元素
- 没有元素可直接添加,有元素需要继判断该元素的 key 和准备加入的 key 是否相等
- 如果相等则直接替换val,不相等要求继续判断是树结构还是链表结构作出处理,添加时如果容量不够需要继续扩容
- 第一次添加,扩容 table=16 ,临界值 threshold = 16*0.75 =12
- 下次扩容则 table = 2*16 = 32 ,临界值为原来的2倍, threshold = 2*12 = 24,依此类推
- Java8中,如果一条链表的元素个数超过 TREEIFY_THRESHOLD(默认为8),并且 table >= MIN_TREEIFY_CAPACITY(默认为64),则进行树化(红黑树
-
6.3 LinkedHashMap
- 特点:
-
- LinkedHashMap 是 HashMap 的子类
- 存储数据采用的哈希表结构+链表结构,在 HashMap 存储结构的基础上,使用了一对 双向链表来记录添加元素的先后顺序,可以保证遍历元素时,与添加的顺序一致。
- 通过哈希表结构可以保证键的唯一、不重复,需要键所在类重写 hashCode()方法、 equals()方法。
-
6.4 TreeMap
-
特点:
-
- TreeMap 存储 key-value 对时,需要根据 key-value 对进行排序。TreeMap 可以保 证所有的 key-value 对处于有序状态
- TreeSet 底层使用红黑树结构存储数据
-
-
TreeMap 的 Key 的排序:
- 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,构造器传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
-
TreeMap 判断两个 key 相等的标准:两个 key 通过 compareTo()方法或者 compare() 方法返回 0。
6.5 Hashtable
- 特点:
-
- 存放的元素是键值对 k-v
- Hashtable 的键和值都不能为null ,否则会抛出 NullPointerException
- Hashtable 的使用方法基本上和 HashMap一样
- Hashtable 是线程安全的(synchronized),HashMap 是线程不安全的,效率相对较低
-
6.6 Properties
- 特点:
-
- Properties 类是 Hashtable 的子类,该对象用于处理属性文件
- 由于属性文件里的 key、value 都是字符串类型,所以 Properties 中要求 key 和 value 都是字符串类型
- 存取数据时,建议使用 setProperty(String key,String value)方法和 getProperty(String key)方法
- 通常作为配置文件
-
- 常见用法:
- load:加载配置文件的键对值到Properties对象
- list:将数据显示到指定设备
- getProperty(key):根据键获取值
- setProperty(key,value):设置键对值
- store:将Properties中的键值对存储到配置文件,如果有中文会存储为unicode码
7.集合实现类总结
- 判断存储类型
- 一组对象[单列]:collection接口
- 允许重复:List
- 增删多:LinkedList[双向链表]
- 改查多:ArrayList[可变数组]
- 不允许重复:Set
- 无序:HashSet[底层HashMap,哈希表:数组+链表+红黑树]
- 排序:TreeSet
- 插入和取出顺序一致:LinkedHashSet[数组+双向链表]
- 允许重复:List
- 一组键对值[双列]:Map接口
- 无序:HashMap[哈希表:数组+链表+红黑树]
- 排序:TreeMap
- 插入取出一致:LinkedHashMap
- 读取文件:Properties
8.Collections工具类
- 概念:Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法(均为 static 方法)
- 方法:
- 排序操作:
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
- 查找:
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素
- Object min(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合 中的最小元素
- int binarySearch(List list,T key)在 List 集合中查找某个元素的下标,但是 List 的元素必须是 T 或 T 的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也先必须是有序的,否则结果不确定。
- int binarySearch(List list,T key,Comparator c)在 List 集合中查找某个元素的下标,但是 List 的元素必须是 T 或 T 的子类对象,而且集合也事先必须是按照 c 比较器规则进行排序过的,否则结果不确定。
- int frequency(Collection c,Object o):返回指定集合中指定元素的出现次数
- 复制、替换:
- void copy(List dest,List src):将 src 中的内容复制到 dest 中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象 的所有旧值
- 提供了多个 unmodifiableXxx()方法,该方法返回指定 Xxx 的不可修改的视图。
- 添加:
- boolean addAll(Collection c,T… elements)将所有指定元素添加到指定 collection 中。
- 同步:
- Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成 线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
- 排序操作:
import java.util.*;
@SuppressWarnings({"all"})
public class Collections_ {
public static void main(String[] args) {
//创建ArrayList 集合,用于测试.
ArrayList list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
// reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println("list=" + list);
// shuffle(List):对 List 集合元素进行随机排序
// for (int i = 0; i < 5; i++) {
// Collections.shuffle(list);
// System.out.println("list=" + list);
// }
// sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println("自然排序后");
System.out.println("list=" + list);
// sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
//我们希望按照 字符串的长度大小排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//可以加入校验代码.
return ((String) o2).length() - ((String) o1).length();
}
});
System.out.println("字符串长度大小排序=" + list);
// swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
//比如
Collections.swap(list, 0, 1);
System.out.println("交换后的情况");
System.out.println("list=" + list);
//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println("自然顺序最大元素=" + Collections.max(list));
//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
//比如,我们要返回长度最大的元素
Object maxObject = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("长度最大的元素=" + maxObject);
//Object min(Collection)
//Object min(Collection,Comparator)
//上面的两个方法,参考max即可
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));
//void copy(List dest,List src):将src中的内容复制到dest中
ArrayList dest = new ArrayList();
//为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
for(int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest, list);
System.out.println("dest=" + dest);
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
//如果list中,有tom 就替换成 汤姆
Collections.replaceAll(list, "tom", "汤姆");
System.out.println("list替换后=" + list);
}
}
十一、泛型
1.泛型概述
- 概念:泛型(广泛类型)即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的某种通用类型。是JDK5.0出现的新特性,解决数据类型的安全问题,在类声明或实例化时只要制定好需要的具体类型。
- 作用:在类声明时,通过一个标识表示类中某个属性的类型,或者某个方法的返回值类型,或者参数类型。
- 理解:相当于给瓶子贴标签后,瓶子就只能装标签上所表示的东西。给了公式,然后实际计算时就套公式。
- 集合中没有使用泛型:引入泛型前,JDK5.0前如果不能确定类型,只能使用Object类型表示,使用麻烦又不安全。
- 集合中使用泛型:把集合中的内容限制为一个特定的数据类型。
- 语法声明:interface 接口{} 和 class 类<K,V>{}
- 说明:
-
- T,K,V不代表值,表示类型,只能是引用类型。
- 任意字母都可以,常用T表示,是Type的缩写
-
- 举例:
public class GenericDetail {
public static void main(String[] args) {
//1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>(); //OK
//List<int> list2 = new ArrayList<int>();//错误
//2. 说明
//因为 E 指定了 A 类型, 构造器传入了 new A()
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f();
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.f();
//3. 泛型的使用形式
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
//在实际开发中,我们往往简写
//编译器会进行类型推断, 老师推荐使用下面写法
ArrayList<Integer> list3 = new ArrayList<>();
List<Integer> list4 = new ArrayList<>();
ArrayList<Pig> pigs = new ArrayList<>();
//4. 如果是这样写 泛型默认是 Object
ArrayList arrayList = new ArrayList();
//等价 ArrayList<Object> arrayList = new ArrayList<Object>();
}
}
class Tiger<E> {//类
E e;
public Tiger() {}
public Tiger(E e) {
this.e = e;
}
}
class A {}
class B extends A {}
class Pig<E> {//
E e;
public Pig(E e) {
this.e = e;
}
public void f() {
System.out.println(e.getClass()); //运行类型
}
}
2.自定义泛型
2.1 自定义泛型类
- 基本语法:class 类名<T,R,M…>{ 成员 }
- 说明:
-
- 普通成员可以使用泛型(属性,方法)
- 使用泛型的数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型类的类型,是在创建对象时确定的,如果没有指定类型,默认为Object
-
class Person<T> {
// 使用 T 类型定义变量
private T info;
// 使用 T 类型定义一般方法
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
// 使用 T 类型定义构造器
public Person() {
}
public Person(T info) {
this.info = info;
}
//静态方法static中不能声明泛型
//try-catch中不能使用泛型定义
}
2.2 自定义泛型接口
- 基本语法: interface 接口名<T,R…>{ }
- 说明:
-
- 接口中,静态成员不能使用泛型
- 泛型接口的类型,在继承接口或者实现接口时确定,没有指定类型,默认为Object
-
2.3 自定义泛型方法
- 基本语法: 修饰符<T,R…> 返回类型 方法名(参数列表){ }
- 说明:
-
- 泛型方法可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
- public void eat(E e){ },该方法不是泛型方法,而是使用了泛型
-
public static <T> void fromArrayToCollection(T[] a, Collection<T> c)
{
for (T o : a) {
c.add(o);
}
}
public static void main(String[] args) {
Object[] ao = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(ao, co);
String[] sa = new String[20];
Collection<String> cs = new ArrayList<>();
fromArrayToCollection(sa, cs);
Collection<Double> cd = new ArrayList<>();
// 下面代码中 T 是 Double 类,但 sa 是 String 类型,编译错误。
// fromArrayToCollection(sa, cd);
// 下面代码中 T 是 Object 类型,sa 是 String 类型,可以赋值成功。
fromArrayToCollection(sa, co);
}
2.4 泛型的继承和通配符
- 泛型不具备继承性
- <?>: 表示支持任意泛型类型
- <? extends A>: 表示支持A类以及A类的子类,规定了泛型的上限
- <? super A>: 支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
public static void main(String[] args) {
List<?> list = null;
list = new ArrayList<String>();
list = new ArrayList<Double>();
// list.add(3);//编译不通过
list.add(null);
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
l1.add("尚硅谷");
l2.add(15);
read(l1);
read(l2);
}
public static void read(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
十二、多线程
1.概念
- 程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process):程序的一次执行过程,或是正在内存中运行的应用程序,是动态过程,有自身的产生、存在和消亡的过程。如:运行中的 QQ,运行中的网易音乐播放器。
- 进程为操作系统调度和分配资源的最小单位(亦是系统运行程序的基本单位),系统在运行时会为每个进程分配不同的内存区域。
- 不同的进程之间是不共享内存的。
- 线程(thread):由进程创建,是进程的一个实体,是程序内部的一条执行路径。一个进程中至少有一个线程。
- 一个进程同一时间若并行执行多个线程,就是支持多线程的。
- 线程为 CPU 调度和执行的最小单位。
- 一个进程中的多个线程共享相同的内存单元,它们从同一个堆中分配对象,可以访问相同的变量和对象。但可能带来安全隐患。
- 线程调度:
-
- 分时调度:所有线程轮流使用 CPU 的使用权,并且平均分配每个线程占用 CPU 的时间。
- 抢占式调度:让优先级高的线程以较大的概率优先使用 CPU。如果线程的优先级相同,随机选择一个(线程随机性),Java 使用的为抢占式调度。
-
- 并行(parallel):指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个 CPU 上同时执行。比如:多个人同时做不同的事。物理上的同时发生。
- 并发(concurrency):指两个或多个事件在同一个时间段内发生。即在一段时间内,有多条指令在单个 CPU 上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。逻辑上的同时发生,貌似同时。
2.线程的基本使用
-
Java 语言的 JVM 允许程序运行多个线程,使用 java.lang.Thread 类代表线程,所有的线程对象都必须是 Thread 类或其子类的实例。
-
Thread类的特性:
-
- 每个线程都是通过某个特定 Thread 对象的 run()方法来完成操作的,因此把 run()方法体称为线程执行体
- 通过该 Thread 对象的 start()方法来启动这个线程,而非直接调用 run()
- 要想实现多线程,必须在主线程中创建新的线程对象。
-
-
线程创建的两种方式
-
- 继承 Thread 类
- 定义 Thread 类的子类,并重写该类的 run()方法,该 run()方法的方法体就代表了线程需要完成的任务
- 创建 Thread 子类的实例,即创建了线程对象
- 调用线程对象的 start()方法来启动该线程
- 一个线程对象只能调用一次 start()方法启动,如果重复调用了,则将抛出 以上的异常“IllegalThreadStateException”。
- 实现 Runnable 接口
- 定义 Runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样是该线程的线程执行体。
- 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 参数来创建Thread 对象,该 Thread 对象才是真正的线程对象。
- 调用线程对象的 start()方法,启动线程。调用 Runnable 接口实现类的 run 方法。
- 继承 Thread 类
-
-
线程终止:当线程完成任务后,会自动退出,可以通过使用变量来控制 run 方法退出的方式来停止线程。
-
线程两种创建方式的区别联系:
- 联系:
- Thread 类实际上也是实现了 Runnable 接口的类。
- 区别:
- 继承 Thread:线程代码存放 Thread 子类 run 方法中。
- 实现 Runnable:线程代码存在接口的子类的 run 方法。
- 联系:
-
例:
------------------Thread类------------------
public class TestMyThread {
public static void main(String[] args) {
//创建自定义线程对象 1
MyThread mt1 = new MyThread("子线程 1");
//开启子线程 1
mt1.start();
//创建自定义线程对象 2
MyThread mt2 = new MyThread("子线程 2");
//开启子线程 2
mt2.start();
//在主方法中执行 for 循环
for (int i = 0; i < 10; i++) {
System.out.println("main 线程!"+i);
}
}
}
class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的 String 参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写 run 方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
-----------------Runnable接口--------------------
public class TestMyRunnable {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t = new Thread(mr, "线程");
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("main " + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " "
+ i);
}
}
}
-------------匿名内部类继承Thread类和Runnable接口-------------
new Thread("新的线程!"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":" +
i);
}
}
}).start();
3.线程常用方法
- 构造器:
-
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :指定创建线程的目标对象,它实现了 Runnable 接口 中的 run 方法
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指 定名字。
-
- 方法:
-
- public void run() :此线程要执行的任务在此处定义代码。
- public void start() :导致此线程开始执行; Java 虚拟机调用此线程的 run 方法。
- public String getName() :获取当前线程名称
- public void setName(String name):设置该线程名称。
- public final void setPriority(int newPriority):更改线程优先级[1.10]之间
- public final int getPriority() :获取线程优先级
- interrupt:中断线程,但没有结束线程,一般用于中断正在休眠的线程
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在 Thread 子类中就是 this,通常用于主线程和 Runnable 实现类
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static void yield():[线程礼让]yield 只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但无法保证。存在情况如当某个线程调用了 yield 方法暂停之后,线程调度器又将其调度出来重新执行。
- public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
- void join() :等待该线程终止。[线程插队]
- void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果 millis 时间到,将不再等待。
- void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
- public final void stop():已过时,不建议使用。强行结束一个线程的执行,直接进入 死亡状态。run()即刻停止,可能会导致一些清理性的工作得不到完成,如文件,数据 库等的关闭。同时,会立即释放该线程所持有的所有的锁,导致数据得不到同步的处 理,出现数据不一致的问题
- void suspend() / void resume() : 这两个操作就好比播放器的暂停和恢复。二者必须成 对出现,否则非常容易发生死锁。suspend()调用会导致线程暂停,但不会释放任何锁 资源,导致其它线程都无法访问被它占用的锁,直到调用 resume()。已过时,不建议 使用。
-
- 用户线程和守护线程:
- 用户线程:工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般为工作线程服务,所有的用户线程结束后,守护线程自动结束[垃圾回收机制]
- 调用 setDaemon(true)方法可将指定线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException 异常。
- 调用 isDaemon()可以判断线程是否是守护线程。
4.线程的生命周期
-
JDK1.5之前:
-
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行 (Running)、阻塞(Blocked)、死亡(Dead)。CPU 需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。
-
-
-
新建
当一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它和其他 Java 对象一样,仅仅由 JVM 为其分配了内存,并初始化了实例变量的值。
-
就绪
当线程对象调用了 start()方法之后,线程就从新建状态转为就绪状态。JVM 创建方法调用栈和程序计数器,这个状态中的线程并没有开始运行,只表示已具备了运行的条件,随时可以被调度。
-
运行
如果处于就绪状态的线程获得了 CPU 资源时,开始执行 run()方法的线程体代码,则该线程处于运行状态。如果计算机只有一个 CPU 核心,在任何时刻只有一个线程处于运行状态,如果计算机有多个核心,将会有多个线程并行 (Parallel)执行。
-
阻塞
当在运行过程中的线程遇到如下情况时,会让出 CPU 并临时中止自己的执行,进入阻塞状态:
- 线程调用了 sleep()方法,主动放弃所占用的 CPU 资源;
- 线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;
- 线程执行过程中,同步监视器调用了 wait(),让它等待某个通知(notify);
- 线程执行过程中,同步监视器调用了 wait(time)
- 线程执行过程中,遇到了其他线程对象的加塞(join);
- 线程被调用 suspend 方法被挂起(已过时,因为容易发生死锁);
-
死亡
线程会以以下三种方式之一结束,结束后的线程就处于死亡状态:
- run()方法执行完成,线程正常结束
- 线程执行过程中抛出了一个未捕获的异常(Exception)或错误(Error)
- 直接调用该线程的 stop()来结束该线程(已过时)
-
-
-
-
JDK1.5及以后:六种状态
- NEW(新建):线程刚被创建,但是并未启动。还没调用 start 方法。
- RUNNABLE(可运行):这里没有区分就绪和运行状态。因为对于 Java 对象来说,只能标记为可运行,至于什么时候运行,不是 JVM 来控制的了,是 OS 来进行调度的, 而且时间非常短暂,因此对于 Java 对象的状态来说,无法区分。
- Teminated(被终止):表明此线程已经结束生命周期,终止运行。
- 阻塞状态分为三种:BLOCKED、WAITING、 TIMED_WAITING。
- BLOCKED(锁阻塞):一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。
- 比如,线程 A 与线程 B 代码中使用同一锁,如果线程 A 获取到 锁,线程 A 进入到 Runnable 状态,那么线程 B 就进入到 Blocked 锁阻塞状态。
- TIMED_WAITING(计时等待):一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。
- 当前线程执行过程中遇到 Thread 类的 sleep 或 join,Object 类 的 wait,LockSupport 类的 park 方法,并且在调用这些方法时, 设置了时间,那么当前线程会进入 TIMED_WAITING,直到时间到,或被中断。
- WAITING(无限等待):一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
- 当前线程执行过程中遇到遇到 Object 类的 wait,Thread 类的 join,LockSupport 类的 park 方法,并且在调用这些方法时,没有指定时间,那么当前线程会进入 WAITING 状态,直到被唤醒。
- 通过 Object 类的 wait 进入 WAITING 状态的要有 Object 的 notify/notifyAll 唤醒;
- 通过 Condition 的 await 进入 WAITING 状态的要有 Condition 的 signal 方法唤醒;
- 通过 LockSupport 类的 park 方法进入 WAITING 状态的要有 LockSupport 类的 unpark 方法唤醒
- 通过 Thread 类的 join 进入 WAITING 状态,只有调用 join 方法的线程对象结束才能让当前线程恢复;
- 当前线程执行过程中遇到遇到 Object 类的 wait,Thread 类的 join,LockSupport 类的 park 方法,并且在调用这些方法时,没有指定时间,那么当前线程会进入 WAITING 状态,直到被唤醒。
- BLOCKED(锁阻塞):一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。
5.同步机制(synchronized
- 线程同步:线程同步即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址操作。
- 同步代码块:synchronized(对象){ 需要同步的代码 }
- 同步方法:public synchronized void method(){ 可能产生线程安全问题的代码 }
- 互斥锁:
- Java引入对象互斥锁的概念,用于保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字 synchronized 用于与对象的互斥锁联系,当对象使用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
- 非静态同步方法的锁:this
- 静态同步方法的锁:当前类的Class对象
- 死锁:
- 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。
- 释放锁:
-
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
-
- 不会释放锁:
-
- 调用 Thread.sleep()\Thread.yield()方法会暂停执行但不会释放
- 调用 suspend() 方法会挂起线程,不会释放
-
public class Homework02 {
public static void main(String[] args) {
Acount one = new Acount();
// Acount two = new Acount();
Thread one1 = new Thread(one,"one");
Thread one2 = new Thread(one,"two");
one1.start();
one2.start();
}
}
class Acount implements Runnable{
private int account = 10000;
@Override
public void run() {
while(true){
synchronized (this){
if(account<=0){
System.out.println("没米了");
break;
}
account -= 1000;
System.out.println(Thread.currentThread().getName()+"取1000"+"余额"+account );
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
十三、File类与IO流
1.文件
- 文件:文件是保存数据的地方。
- 文件流:文件在程序中以流的形式来操作。
- 流:数据在数据源(文件)和程序(内存)之间经历的路径。
- 输入流:数据从数据源(文件)到程序(内存)的路径 [文件–>内存]
- 输出流:数据从程序(内存)到数据源(文件)的路径 [内存–>文件]
- 构造器:
- public File(String pathname) :以 pathname 为路径创建 File 对象,可以是绝对路径或者相对路径,如果 pathname 是相对路径,则默认的当前路径在系统属性 user.dir 中存储。
- public File(String parent, String child) :以 parent 为父路径,child 为 子路径创建 File 对象。
- public File(File parent, String child) :根据一个父 File 对象和子文件 路径创建 File 对象
- 路径:
- 绝对路径:从盘符开始的路径,这是一个完整的路径。
- 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
- IDEA 中,main 中的文件的相对路径,是相对于"当前工程"
- IDEA 中,单元测试方法中的文件的相对路径,是相对于"当前 module"
- 常用方法:
-
- 获取文件和目录基本信息
- public String getName() :获取名称
- public String getPath() :获取路径
- public String getAbsolutePath():获取绝对路径
- public File getAbsoluteFile():获取绝对路径表示的文件
- public String getParent():获取上层文件目录路径。若无,返回 null
- public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
- public long lastModified() :获取最后一次的修改时间,毫秒值
- 列出目录的下一级
- public String[] list() :返回一个 String 数组,表示该 File 目录中的所有子文件或目录。
- public File[] listFiles() :返回一个 File 数组,表示该 File 目录中的所有的子文件或目录。
- 重命名
- public boolean renameTo(File dest):把文件重命名为指定的文件路径。
- 判断功能
- public boolean exists() :此 File 表示的文件或目录是否实际存在。
- public boolean isDirectory() :此 File 表示的是否为目录。
- public boolean isFile() :此 File 表示的是否为文件。
- public boolean canRead() :判断是否可读
- public boolean canWrite() :判断是否可写
- public boolean isHidden() :判断是否隐藏
- 创建删除
- public boolean createNewFile() :创建文件。若文件存在,则不创建,返回 false。
- public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
- public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建。
- public boolean delete() :删除文件或者文件夹 删除注意事项:
- ① Java 中的 删除不走回收站。
- ② 要删除一个文件目录,请注意该文件目录内不能包含文件或者 文件目录。
- 获取文件和目录基本信息
-
import java.io.File;
import java.io.IOException;
public class FileInfoMethod {
public static void main(String[] args) throws IOException {
File file = new File("aaa.txt");
file.createNewFile();
System.out.println("文件名字=" + file.getName());
System.out.println("文件绝对路径=" + file.getAbsolutePath());
System.out.println("文件父级目录=" + file.getParent());
System.out.println("文件大小(字节)=" + file.length());
System.out.println("文件是否存在=" + file.exists());//T
System.out.println("是不是一个文件=" + file.isFile());//T
System.out.println("是不是一个目录=" + file.isDirectory());//
System.out.println("文件是否删除=" + file.delete());//
System.out.println("文件是否存在=" + file.exists());//T
}
}
2.IO流
- 原理:
-
- I/O是Input/Output的缩写,用于处理数据传输,如读/写文件,网络通讯
- Java程序中,对数据的输入/输出操作以”流(stream)”的方式进行
- java.io包下提供了各种“流”类和接口用于获取不同种类的数据,并通过方法输入或输出数据
- 输入input:读取外部数据(硬盘、光盘等中的)到程序(内存)中
- 输出output:将程序(内存)数据输出到硬盘、光盘等存储设备中
-
- 分类:
- 按数据流向:
- 输入流 :把数据从其他设备上读取到内存中的流。
- 以 InputStream、Reader 结尾
- 输出流 :把数据从内存中写出到其他设备上的流。
- 以 OutputStream、Writer 结尾
- 输入流 :把数据从其他设备上读取到内存中的流。
- 按操作数据单位
- 字节流(8bit) :以字节为单位,读写数据的流。
- 以 InputStream、OutputStream 结尾
- 字符流(16bit) :以字符为单位,读写数据的流。
- 以 Reader、Writer 结尾
- 字节流(8bit) :以字节为单位,读写数据的流。
- 按IO流的角色
- 节点流:直接从数据源或目的地读写数据
- 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
- 按数据流向:
- Java 的 IO 流共涉及 40 多个类,实际上非常规则,都是从如下 4 个抽象基类派生的。
(抽象基类) | 输入流 | 输出流 |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
- 常用节点流:
- 文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
- 字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、 CharArrayReader、CharArrayWriter
- 常用处理流:
- 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、 BufferedWriter
- 作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率。
- 转换流:InputStreamReader、OutputStreamReader
- 作用:实现字节流和字符流之间的转换。
- 对象流:ObjectInputStream、ObjectOutputStream
- 作用:提供直接读写 Java 对象功能
- 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、 BufferedWriter
3.节点流:FileReader\FileWriter
3.1 Reader\Writer
- 文本文件:Java 提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。不能操作图片,视频等非文本文件。
- 常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py 等
- 字符输入流:Reader
- public int read(): 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为 int 类型。返回该字符的 Unicode 编码值。如果已经到达流末尾了,则返回1。
- public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到 字符数组 cbuf 中 。每次最多读取 cbuf.length 个字符。返回实际读取的字符个数。 如果已经到达流末尾,没有数据可读,则返回-1。
- public int read(char[] cbuf,int off,int len):从输入流中读取一些字 符,并将它们存储到字符数组 cbuf 中,从 cbuf[off]开始的位置存储。每次最多读取 len 个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。
- public void close() :关闭此流并释放与此流相关联的任何系统资源。 注意:当完成流的操作时,必须调用 close()方法,释放系统资源,否则会造成内存泄漏。
- 字符输出流:Writer
- public void write(int c) :写出单个字符。
- public void write(char[] cbuf):写出字符数组。
- public void write(char[] cbuf, int off, int len):写出字符数组的某一部分。off:数组的开始索引;len:写出的字符个数。
- public void write(String str):写出字符串。
- public void write(String str, int off, int len) :写出字符串的某一部分。off:字符串的开始索引;len:写出的字符个数。
- public void flush():刷新该流的缓冲。
- public void close() :关闭此流。
3.2 FileReader\FileWriter
- FileReader:java.io.FileReader 类用于读取字符文件,构造时使用系统默认的字符编码和默认字节缓冲区。
- FileReader(File file): 创建一个新的 FileReader ,给定要读取的 File 对象。
- FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。
- FileWriter:java.io.FileWriter 类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区。
- FileWriter(File file): 创建一个新的 FileWriter,给定要读取的 File 对象。
- FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件 的名称。
- FileWriter(File file,boolean append): 创建一个新的 FileWriter,指明是 否在现有文件末尾追加内容
3.3 flush
- 概念:如果 FileWriter 不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush() 方法了
- flush() :刷新缓冲区,流对象可以继续使用。
- close():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
4.节点流:FileInputStream\FileOutputStream
4.1 InputStream 和 OutputStream
- InputStream:java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
- public int read(): 从输入流读取一个字节。返回读取的字节值。虽然读取了 一个字节,但是会自动提升为 int 类型。如果已经到达流末尾,没有数据可读,则返 回-1。
- public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字 节数组 b 中 。每次最多读取 b.length 个字节。返回实际读取的字节个数。如果已经 到达流末尾,没有数据可读,则返回-1。
- public int read(byte[] b,int off,int len):从输入流中读取一些字节 数,并将它们存储到字节数组 b 中,从 b[off]开始存储,每次最多读取 len 个字节 。 返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
- public void close() :关闭此输入流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统 资源。
- OutputStream:java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
- public void write(int b) :将指定的字节输出流。虽然参数为 int 类型四个字 节,但是只会保留一个字节的信息写出。
- public void write(byte[] b):将 b.length 字节从指定的字节数组写入此输出 流。
- public void write(byte[] b, int off, int len) :从指定的字节数组写 入 len 字节,从偏移量 off 开始输出到此输出流。
- public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
- public void close() :关闭此输出流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统资源。
4.2 FileInputStream 与 FileOutputStream
- FileInputStream:java.io.FileInputStream 类是文件输入流,从文件中读取字节。
- FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File 对象 file 命名。
- FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name 命名。
- FileOutputStream:java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。
- public FileOutputStream(File file):创建文件输出流,写出由指定的 File 对象表示的文件。
- public FileOutputStream(String name): 创建文件输出流,指定的名称为写出文件。
- public FileOutputStream(File file, boolean append): 创建文件输出流,指明是否在现有文件末尾追加内容。
5.处理流:缓冲流
- 基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用 8192 个字节(8Kb)的缓冲区),通过缓冲区读写,减少系统 IO 次数,从而提高读写的效率。
- 根据操作单位分类:
- 字节缓冲流:BufferedInputStream,BufferedOutputStream
- 字符缓冲流:BufferedReader,BufferedWriter
- 构造器:
- public BufferedInputStream(InputStream in) :创建一个 新的字节型的缓冲输入流。
- public BufferedOutputStream(OutputStream out): 创建一个新的字节型的缓冲输出流。
- 字符缓冲流特有方法:
- BufferedReader:public String readLine(): 读一行文字。
- BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Homework02 {
public static void main(String[] args) {
String path = "d:\\aaa.txt";
BufferedReader br = null;
String line = "";
int linen = 0;
try {
br = new BufferedReader(new FileReader(path));
while((line = br.readLine())!=null){
System.out.println(++linen + line);
};
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.处理流:转换流\数据流\对象流
6.1转换流
- 转换流:字节和字符间的桥梁。转换处理乱码。
- InputStreamReader:转换流 java.io.InputStreamReader,是 Reader 的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
- 构造器:
- InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
- InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
- 构造器:
- OutputStreamWriter:转换流 java.io.OutputStreamWriter ,是 Writer 的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
- 构造器:
- OutputStreamWriter(OutputStream in): 创建一个使用默认 字符集的字符流。
- OutputStreamWriter(OutputStream in,String charsetName): 创建一个指定字符集的字符流。
- 构造器:
6.2 数据流
- 数据流:DataOutputStream、DataInputStream
- DataOutputStream:允许应用程序将基本数据类型、String 类型的变量写 入输出流中
- DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取 基本数据类型、String 类型的变量。
6.3 对象流
- 对象流:ObjectOutputStream、ObjectInputStream
- ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。 通过在流中使用文件可以实现 Java 各种基本数据类型的数据以及对象的持久存储。
- ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写出的基本数据类型的数据和对象进行读入操作,保存在内存中。
- ObjectOutputStream 中的构造器:
- public ObjectOutputStream(OutputStream out): 创建一个指定的 ObjectOutputStream。
- ObjectOutputStream 中的方法:
- public void writeBoolean(boolean val):写出一个 boolean 值。
- public void writeByte(int val):写出一个 8 位字节
- public void writeShort(int val):写出一个 16 位的 short 值
- public void writeChar(int val):写出一个 16 位的 char 值
- public void writeInt(int val):写出一个 32 位的 int 值public void writeLong(long val):写出一个 64 位的 long 值
- public void writeFloat(float val):写出一个 32 位的 float 值。
- public void writeDouble(double val):写出一个 64 位的 double 值
- public void writeUTF(String str):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。根据字符的值,将字符串 s 中每个字符转 换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入 流中与将它作为 Object 写入流中明显不同。 如果 s 为 null,则抛出 NullPointerException。
- public void writeObject(Object obj):写出一个 obj 对象 • public void close() :关闭此输出流并释放与此流相关联的任何系统资源
- ObjectInputStream 中的构造器:
- public ObjectInputStream(InputStream in): 创建一个指定的 ObjectInputStream。
- ObjectInputStream 中的方法:
- public boolean readBoolean():读取一个 boolean 值
- public byte readByte():读取一个 8 位的字节
- public short readShort():读取一个 16 位的 short 值
- public char readChar():读取一个 16 位的 char 值
- public int readInt():读取一个 32 位的 int 值
- public long readLong():读取一个 64 位的 long 值
- public float readFloat():读取一个 32 位的 float 值
- public double readDouble():读取一个 64 位的 double 值
- public String readUTF():读取 UTF-8 修改版格式的 String
- public void readObject(Object obj):读入一个 obj 对象
- public void close() :关闭此输入流并释放与此流相关联的任何系统资源
7.对象序列化
- 概念:对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允 许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另 一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象。
- 序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型和 对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一 个对象的信息。
- 反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列 化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。
- 重要性:
- 序列化是 RMI(Remote Method Invoke、远程方法调用)过程的参数和返回值 都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平 台的基础。
- 序列化的好处,在于可将任何实现了 Serializable 接口的对象转化为字节数据, 使其在保存和传输时可被还原。
- 实现原理:
- 序列化:用 ObjectOutputStream 类保存基本类型数据或对象的机制。方法为:
- public final void writeObject (Object obj) : 将指定的对象写出。
- 反序列化:用 ObjectInputStream 类读取基本类型数据或对象的机制。方法为:
- public final Object readObject () : 读取一个对象。
- 序列化:用 ObjectOutputStream 类保存基本类型数据或对象的机制。方法为:
- 实现序列化机制的方法:
- 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序 列化的,为了让某个类是可序列化的,该类必须实现 java.io.Serializable 接口。
- Serializable 接口:Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序 列化或反序列化,会抛出 NotSerializableException 。
- 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现 Serializable 接口
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必 须注明是瞬态的,使用 transient 关键字修饰。
- 静态(static)变量的值不会序列化。因为静态变量的值不属于某个对象。
- Properties
- 特点:
-
- Properties 类是 Hashtable 的子类,该对象用于处理属性文件
- 由于属性文件里的 key、value 都是字符串类型,所以 Properties 中要求 key 和 value 都是字符串类型
- 存取数据时,建议使用 setProperty(String key,String value)方法和 getProperty(String key)方法
- 通常作为配置文件
-
- 常见用法:
- load:加载配置文件的键对值到Properties对象
- list:将数据显示到指定设备
- getProperty(key):根据键获取值
- setProperty(key,value):设置键对值
- store:将Properties中的键值对存储到配置文件,如果有中文会存储为unicode码
- 特点:
import java.io.*;
import java.util.Properties;
public class Homework03 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String p = "D:\\dog.txt";
Properties properties = new Properties();
properties.load(new FileReader(p));
String name = properties.get("name") + "";
int age = Integer.parseInt(properties.get("age")+"");
String color = properties.get("color") + "";
Dog dog = new Dog(name,age,color);
System.out.println("=======dog对象信息========");
System.out.println(dog);
properties.list(System.out);
//序列化dog-->dog.bat
String path = "d:\\dog.bat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeObject(dog);
//关闭
oos.close();
System.out.println("序列化完成...");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
Dog dog1 = (Dog)ois.readObject();
System.out.println("=====反序列化=====");
System.out.println(dog1);
}
}
class Dog implements Serializable{
private String name;
private int age;
private String color;
public Dog(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
}
8.标准输入输出流\打印流
8.1 标准输入输出流
- System.in 和 System.out 分别代表了系统标准的输入和输出设备
- 默认输入设备是:键盘,输出设备是:显示器
- System.in 的类型是 InputStream
- System.out 的类型是 PrintStream,其是 OutputStream 的子类 FilterOutputStream 的 子类
- 重定向:通过 System 类的 setIn,setOut 方法对默认设备进行改变。
- public static void setIn(InputStream in)
- public static void setOut(PrintStream out)
8.2 打印流
- 实现将基本数据类型的数据格式转化为字符串输出。
- 打印流:PrintStream 和 PrintWriter
- 提供了一系列重载的 print()和 println()方法,用于多种数据类型的输出
- PrintStream 和 PrintWriter 的输出不会抛出 IOException 异常
- PrintStream 和 PrintWriter 有自动 flush 功能
- PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需 要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
- System.out 返回的是 PrintStream 的实例
- 构造器
- PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
- PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自 动行刷新的新打印流。
- PrintStream(OutputStream out) :创建新的打印流。
- PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。 autoFlush 如果为 true,则每当写入 byte 数组、调用其中一个 println 方 法或写入换行符或字节 (‘\n’) 时都会刷新输出缓冲区。
- PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建 新的打印流。
- PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的 新打印流。
- PrintStream(String fileName, String csn) :创建具有指定文件名称和字符集 且不带自动行刷新的新打印流。
8.3 Scanner类
- 构造方法
- Scanner(File source) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
- Scanner(File source, String charsetName) :构造一个新的 Scanner,它生成的值是从 指定文件扫描的。
- Scanner(InputStream source) :构造一个新的 Scanner,它生成的值是从指定的输入 流扫描的。
- Scanner(InputStream source, String charsetName) :构造一个新的 Scanner,它生成 的值是从指定的输入流扫描的。
- 常用方法:
- boolean hasNextXxx(): 如果通过使用 nextXxx()方法,此扫描器输入信息中的下一个 标记可以解释为默认基数中的一个 Xxx 值,则返回 true。
- Xxx nextXxx(): 将输入信息的下一个标记扫描为一个 Xxx
十四、Java绘图事件处理
1.Java绘图坐标体系
- 介绍:Java坐标原点唯一左上角,以像素为单位,水平方向是x轴,竖直方向是y轴。(x,y)
- 像素:计算机的屏幕显示由像素组成,分辨率=800*600表示,一行有800个点,一共600行,整个计算机屏幕480 000 个像素。像素是密度单位,厘米是长度单位,无法比较。
- 绘图原理:
-
- paint(Graphics g)绘制组件外观,调用原因:
- 组件第一次在屏幕显示,程序自动调用
- 窗口最小化,再最大化
- 窗口大小发生变化
- repaint方法被调用
- repaint()刷新组件外观
- paint(Graphics g)绘制组件外观,调用原因:
-
- Graphics类:理解为画笔
- 画直线 drawLine(int x1, int y1, int x2, int y2)
- 画矩形边框 drawRect(int x, int y, int width, int height)
- 画椭圆边框 drawOval(int x, int y, int width, int height)
- 填充矩形 fillRect(int x, int y, int width, int height)
- 填充椭圆 fillOval(int x, int y, int width, int heigth)
- 画图片 drawImage(Image img, int x, int y, …)
- 画字符串 drawString(String str, int x, int y)
- 设置画笔的字体 setFont(Font font)
- 设置画笔的颜色 setColor(Color c)
2.事件处理机制
-
说明:Java事件处理采取的是“委派事件模型”。当事件发生时,产生事件的对象,会把此”信息“传递给”事件的监听者“处理,”信息“是值 java.awt.event事件类库里某个类所创建的对象,把它称为”事件的对象“。
-
事件源—事件---->事件监听者[处理事件]
-
具体机制:
-
- 事件源:事件源是一个产生事件的对象,比如按钮,窗口等
- 事件:事件是承载事件源状态改变时的对象,比如键盘事件、鼠标事件等会生成事件对象,事件对象中保存着事件的很多信息。
- 事件类型
- ActionEvent 按下按钮或双击一个列表或选中某个菜单时发生
- AdjustmentEvent 当操作一个滚动条时发生
- ComponentEvent 当一个组件隐藏,移动,改变大小时发生
- ContainerEvent 当一个组件从容器中加入或者删除时发生
- FocusEvent 当一个组件获得或是失去焦点时发生
- ItemEvent 当一个复选框或者列表项被选中
- KeyEvent 当从键盘的按键被按下,松开时发生
- MouseEvent 当鼠标被拖动,移动,点击,按下
- TextEvent 当文本区和文本域的文本发生变化时发生
- WindowEvent 当一个窗口激活、关闭、失效、最小化…
- 事件监听接口
- 当个事件源产生一个事件,可以传递给事件监听者处理
- 事件监听者实际上就是一个类,该类实现了某个事件监听器接口
- 事件监听器接口有多种,不同接口监听不同事件,一个类可以实现多个监听接口
- 这些接口在 java.awt.event 和 javax.swing.event 中定义
-
十五、网络编程
1.基础内容
1.1 概念
- 网络编程:
- Java 是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序 员能够很容易开发常见的网络应用程序。
- Java 提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。
- 软件架构:
- C/S架构:Client/Server结构,是客户端和服务端结构,如QQ、美团app等
- B/S架构:Browser/Server结构,指浏览器和服务器结构,常见有IE、谷歌等
- 网络编程目的:直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
- 网络编程主要问题:
- 问题 1:如何准确地定位网络上一台或多台主机
- 问题 2:如何定位主机上的特定的应用
- 问题 3:找到主机后,如何可靠、高效地进行数据传输
1.2 网络通信要素
-
网络通信协议:不同硬件、操作系统之间通信的规则称为协议,即网络通信协议。
-
通信双方地址:IP、端口号
-
IP地址和域名
- IP地址:互联网协议地址,用于给网络中的一台计算机设备做唯一的编号。
- IP 地址 = 网络地址 + 主机地址
- IP地址分类:
- IPv4:是一个 32 位的二进制数,通常被分为 4 个字节,表示成 a.b.c.d 的形式, 以点分十进制表示,例如 192.168.65.100 。E类用于科研,2011年初已用尽。
- IPv6:为了扩大地址空间,拟通过 IPv6 重新定义地址空间,采用 128 位地址长度,共 16 个字节,写成 8 个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开。比如:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
- 公网地址(万维网使用)
- 私有地址(局域网使用):192.168.开头的就是私 有地址,范围即为 192.168.0.0–192.168.255.255,专门为组织机构内部使用。
- IPv4:是一个 32 位的二进制数,通常被分为 4 个字节,表示成 a.b.c.d 的形式, 以点分十进制表示,例如 192.168.65.100 。E类用于科研,2011年初已用尽。
- 特殊IP地址:
- 本机回环地址:127.0.0.1
- 主机名:localhost
- 常用命令:
- 查看本机IP地址:ipconfig
- 检查网络是否联通:ping + IP地址
- 域名:IP地址不方便记忆,所以出现了域名。域名服务器(DNS)负责将域名转化为IP地址与主机建立连接
-
- 输入域名,操作系统先检查本地 hosts 是否有网址映射关系,如果有则调用完成域名解析。
- hosts 中没有域名映射,查找本地 DNS 解析器缓存是否有网址映射关系,如果有直接返回,完成域名解析。
- hosts 与本地 DNS 解析器缓存都没有相应的网址映射关系,寻找 TCP/IP 参数中设置的首选 DNS 服务器(本地DNS服务器),如果域名包含在本地配置区域资源中,返回解析结果,完成域名解析,该解析具有权威性。
- 域名不由本地 DNS 服务区域解析,但该服务器缓存了此网址的映射关系,则调用IP地址映射,完成解析,该解析不具有权威性。
- 本地 DNS 服务器本地区域文件与缓存解析都失效,则根据本地 DNS 服务器设置进行查询。
- 未用转发模式,本地 DNS 把请求发至13台根DNS,根DNS服务器收到请求后判断域名的上级授权管理,返回一个负责该顶级域名服务器的一个IP。本地DNS接受到IP信息后,联系负责该域名的服务器解析,如果负责该域名的服务器无法解析,则该服务器寻找一个管理该域的下一级DNS服务器的地址给本地DNS服务器。当本地DNS服务器收到地址后,寻找地址,重复动作直到找到。[踢皮球]
- 转发模式,本地DNS服务器把请求发给上一级DNS,由上一级服务器进行解析,如果不能解析,再向上发送,以此循环,然后最终把结果返回给本地DNS服务器,再由本地DNS服务器返回给客户机。
-
- IP地址:互联网协议地址,用于给网络中的一台计算机设备做唯一的编号。
-
端口号:用两个字节表示的整数,它的取值范围是 0~65535。
- IP地址可以唯一标识网络中的设备,端口号可以唯一标识设备中的进程。端口号被另一个服务占用会导致当前程序启动失败。
- 分类:
- 公认端口:0~1023。被预先定义的服务通信占用,如:HTTP(80),FTP (21),Telnet(23)
- 注册端口:1024~49151。分配给用户进程或应用程序。如:Tomcat (8080),MySQL(3306),Oracle(1521)。
- 动态/ 私有端口:49152~65535。
-
网络通信协议:OSI参考模型和TCP/IP参考模式
-
OSI模型:模型过于理想化,未能广泛推广
-
TCP/IP模型:事实上的国际标准
- TCP/IP协议:传输控制协议/因特网互联协议
- 四层介绍:
- 应用层:决定了向用户提供应用服务时通信的活动,主要协议:HTTP协议、FTP协议、SNMP(简单网络管理协议)、SMTP(简单邮件传输协议)和POP3等。
- 传输层:使网络程序进行通信,TCP协议或UDP协议
- TCP协议:传输控制协议,面向连接的、可靠的、基于字节流的传输层通信协议
- UDP协议:无连接的传输层协议,提供面向事务的简单不可靠信息传送服务
- 网络层:支持网间互连的数据通信,主要用具将传输的数据进行风阻,将分组数据发送到目标计算机或网络。
- 物理+数据链路层:链路层定义物理传输通道,针对于网络连接设备的驱动协议,
-
TCP和UDP协议
-
TCP:传输控制协议
-
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用"三次握手"方式,是可靠的
- TCP协议进行通信的两个应用进程: 客户端、服务端
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
-
例:打电话
-
-
UDP:用户数据报协议
-
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内,不适合传输大量数据
- 因无需连接,故是不可靠的
- 发送数据结束时无需释放资源(因为不是面向连接的),速度快
- 例: 发短信、视频会议
-
-
三次握手:TCP协议中,客户端和服务器之间要进行三次交互保证连接的可靠。
-
- 第一次握手,客户端向服务器端发起 TCP 连接的请求
- 客户端会随机一个初始序列号 seq=x,设置 SYN=1 ,表示这是 SYN 握手报文。然后就可以把这个 SYN 报文发送给服务端了,表示 向服务端发起连接,之后客户端处于同步已发送状态。
- 第二次握手,服务器端发送针对客户端 TCP 连接请求的确认
- 服务端收到客户端的 SYN 报文后,也随机一个初始序列号 (seq=y),设置 ack=x+1,表示收到了客户端的 x 之前的数据,希望客 户端下次发送的数据从 x+1 开始。 设置 SYN=1 和 ACK=1。表示这是一个 SYN 握手和 ACK 确认应答报文。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于同步已接收状态。
- 第三次握手,客户端发送确认的确认
- 客户端收到服务端报文后,还要向服务端回应最后一个应答报文, 将 ACK 置为 1 ,表示这是一个应答报文 ack=y+1 ,表示收到了服务器的 y 之前的数据,希望服务器下次发送的数据从 y+1 开始。 最后把报文发送给服务端,这次报文可以携带数据,之后客户端处于连接已建立状态。服务器收到客户端的应答报文后,也进入连接已建立状态。
- 第一次握手,客户端向服务器端发起 TCP 连接的请求
-
四次挥手:TCP 协议中,在发送数据结束后,释放连接时需要经过四次挥手。
-
- 第一次挥手:客户端向服务器端提出结束连接,让服务器做最后的准备工作。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。
- 客户端打算断开连接,向服务器发送 FIN 报文(FIN 标记位被设置为 1,1 表示为 FIN,0 表示不是),FIN 报文中会指定一个序列号,之后客户端进入 FINWAIT1 状态。也就是客户端发出连接释放报文段(FIN 报文),指定序列号 seq = u,主动关闭 TCP 连接,等待服务器的确认。
- 第二次挥手:服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端。并告知上层的应用进程不再接收数据。
- 服务器收到连接释放报文段(FIN 报文)后,就向客户端发送 ACK 应 答报文,以客户端的 FIN 报文的序列号 seq+1 作为 ACK 应答报文段 的确认序列号 ack = seq+1 = u + 1。接着服务器进入 CLOSEWAIT(等 待关闭)状态,此时的 TCP 处于半关闭状态(下面会说什么是半关闭状 态),客户端到服务器的连接释放。客户端收到来自服务器的 ACK 应答 报文段后,进入 FINWAIT_2 状态。
- 第三次挥手:服务器发送完数据后,会给客户端发送一个释放连接的报文。那么客 户端接收后就知道可以正式释放连接了。
- 服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后 服务器进入 LASK_ACK(最后确认)状态,等待客户端的确认。服务器的 连接释放(FIN)报文段的 FIN=1,ACK=1,序列号 seq=m,确认序列号 ack=u+1。
- 第四次挥手:客户端接收到服务器最后的释放连接报文后,要回复一个彻底断开的 报文。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会 等待 2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会 再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发 送最后的报文,并重新计时。如果等待 2MSL 后,没有收到,那么彻底断开。
- 客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送 一个 ACK 应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为 ACK 应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号 seq+1 作为确认序号 ack。
- 之后客户端进入 TIMEWAIT(时间等待)状态,服务器收到 ACK 应答报文 段后,服务器就进入 CLOSE(关闭)状态,到此服务器的连接已经完成 关闭。客户端处于 TIMEWAIT 状态时,此时的 TCP 还未释放掉,需要 等待 2MSL 后,客户端才进入 CLOSE 状态。
- 第一次挥手:客户端向服务器端提出结束连接,让服务器做最后的准备工作。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。
-
-
-
2.网络编程API
2.1 InetAddress类
- InetAddress 类主要表示 IP 地址,两个子类:Inet4Address、Inet6Address。
- 获取 InetAddress 实例
- public static InetAddress getLocalHost() :获取本机InetAddress对象
- public static InetAddress getByName(String host):根据指定主机名/域名获取ip地址对象
- public static InetAddress getByAddress(byte[] addr):在给定原始 IP 地址的情况下,返回 InetAddress 对象
- InetAddress 提供了如下几个常用的方法
- public String getHostAddress() :返回 IP 地址字符串(以文本表现形式)
- public String getHostName() :获取此 IP 地址的主机名
- public boolean isReachable(int timeout):测试是否可以达到该地址
2.2 Socket
- Socket(套接字):
- 网络上具有唯一标识的 IP 地址和端口号组合在一起构成唯一能识别的标识符套接字 (Socket)。
- 利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。
- 网络通信其实就是 Socket 间的通信。
- 通信的两端都要有 Socket,是两台机器间通信的端点。
- Socket 允许程序把网络连接当成一个流,数据在两个 Socket 间通过 IO 传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
- 分类:
- 流套接字(stream socket):使用 TCP 提供可依赖的字节流服务
- ServerSocket:此类实现 TCP 服务器套接字。服务器套接字等待请 求通过网络传入。
- Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是 两台机器间通信的端点。
- 数据报套接字(datagram socket):使用 UDP 提供“尽力而为”的数据报服 务
- DatagramSocket:此类表示用来发送和接收 UDP 数据报包的套接 字。
- 流套接字(stream socket):使用 TCP 提供可依赖的字节流服务
- 相关API:
- ServerSocket类
- 构造方法:ServerSocket(int port) :创建绑定到特定端口的服务器套接字。
- 常用方法:Socket accept():侦听并接受到此套接字的连接。
- Socket类
- 构造方法:
- public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
- public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
- 常用方法:
- public InputStream getInputStream():返回此套接字的输入流,可以用于接收消息
- public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送消息
- public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是 未连接的,则返回 null。
- public InetAddress getLocalAddress():获取套接字绑定的本地地址。
- public int getPort():此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
- public int getLocalPort():返回此套接字绑定到的本地端口。如果尚未绑定套接字,则 返回 -1。
- public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使 用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会 关闭该套接字的 InputStream 和 OutputStream。
- public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入 流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收 任何数据。
- public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以 前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调 用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通 过此套接字的输出流发送任何数据。
- 构造方法:
- DatagramSocket类
- 常用方法:
- public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端 口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
- public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指 定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
- public void close()关闭此数据报套接字。
- public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含 的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
- public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时, DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和 发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
- public InetAddress getLocalAddress()获取套接字绑定的本地地址。
- public int getLocalPort()返回此套接字绑定的本地主机上的端口号。
- public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接, 则返回 null。
- public int getPort()返回此套接字的端口。如果套接字未连接,则返回 -1。
- 常用方法:
- DatagramPacket类
- 常用方法:
- public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
- public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数据报 包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。
- public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
- public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
- public byte[] getData()返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。
- public int getLength()返回将要发送或接收到的数据的长度。
- 常用方法:
- ServerSocket类
3.TCP网络编程
-
基于TCP的Socket通信
-
开发步骤:
- 客户端程序包含以下四个基本的步骤 :
- 创建 Socket :根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器 端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
- 打开连接到 Socket 的输入/ 出流: 使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流,进行数据传输
- 按照一定的协议对 Socket 进行读/ 写操作:通过输入流读取服务器放入线路的信息 (但不能读取自己放入线路的信息),通过输出流将信息写入线路。
- 关闭 Socket :断开客户端到服务器的连接,释放线路
- 服务器端程序包含以下四个基本的步骤:
- 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
- 调用 accept() :监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
- 调用 该 Socket 类对象的 getOutputStream() 和 getInputStream () :获取输出流和输入流,开始网络数据的发送和接收。
- 关闭 Socket 对象:客户端访问结束,关闭通信套接字。
- 客户端程序包含以下四个基本的步骤 :
-
多个客户端和服务器之间的多次通信:
- Java 程序通常会通过循环,服务端不断地调用 ServerSocket 的 accept()方法来不断接受客户端的请求
- 如果服务器端要“同时”处理多个客户端的请求,因此服务器端需要为每一个客户端单独分配一个线程来处理,否则无法实现“同时”。
-
客户端:自定义或浏览器
-
服务端:自定义或Tomcat服务器
package net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TestServer {
public static void main(String[] args) throws IOException {
//监听端口等待链接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端999监听,等待链接....");
Socket socket = serverSocket.accept();
System.out.println("服务端 socket ="+socket.getClass());
//读取客户端数据写入
InputStream inputStream = socket.getInputStream();
//IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen = inputStream.read(buf))!= -1){
System.out.println(new String(buf,0,readLen));
}
//输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,client".getBytes());
//结束标志
socket.shutdownOutput();
//关掉
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
}
}
package net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TestClient {
public static void main(String[] args) throws IOException {
//连接服务端
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端 socket = "+socket.getClass());
//生成Socket,写入数据到输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,server".getBytes());
//结束标志
socket.shutdownOutput();
//获得服务端输入流,读取显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen = inputStream.read(buf))!= -1){
System.out.println(new String(buf,0,readLen));
}
//关闭
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客户端退出...");
}
}
4.UDP网络编程
- UDP(User Datagram Protocal,用户数据报协议):面向非连接,不可靠但速度快,节省空间和流量。UDP 适用于一次只传送少量数据、对可靠性要求不高的应用环境,数据报 大小限制在 64K 以下。
- 通信模型:
- 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
- UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP 数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地 址和端口号以及接收端的 IP 地址和端口号。
- UDP 协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接 收方的连接。如同发快递包裹一样。
- 开发步骤:
- 发送端程序包含以下四个基本的步骤:
- 创建 DatagramSocket :默认使用系统随机分配端口号。
- 创建 DatagramPacket:将要发送的数据用字节数组表示,并指定要发送的数据长 度,接收方的 IP 地址和端口号。
- 调用 该 DatagramSocket 类对象的 send 方法 :发送数据报 DatagramPacket 对象。
- 关闭 DatagramSocket 对象:发送端程序结束,关闭通信套接字。
- 接收端程序包含以下四个基本的步骤 :
- 创建 DatagramSocket :指定监听的端口号。
- 创建 DatagramPacket:指定接收数据用的字节数组,起到临时数据缓冲区的效果, 并指定最大可以接收的数据长度。
- 调用 该 DatagramSocket 类对象的 receive 方法 :接收数据报 DatagramPacket 对 象。
- 关闭 DatagramSocket :接收端程序结束,关闭通信套接字。
- 发送端程序包含以下四个基本的步骤:
package net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPTestReceiverA {
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象
DatagramSocket datagramSocket = new DatagramSocket(9999);
//构建一个 DatagramPacket 对象,准备接收数据
byte[] buf = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
System.out.println("接收端A等待接收数据....");
datagramSocket.receive(datagramPacket);
//拆包数据显示
int length = datagramPacket.getLength();
byte[] data = datagramPacket.getData();
String s = new String(data,0,length);
System.out.println(s);
data = "好的,明天见".getBytes();
datagramPacket = new DatagramPacket(data,data.length, InetAddress.getByName("192.168.31.219"),9998);
datagramSocket.send(datagramPacket);
datagramSocket.close();
System.out.println("A退出....");
}
}
package net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPTestSenderB {
public static void main(String[] args) throws IOException {
//创建 DatagramSocket 对象,准备在 9998 端口 接收数据
DatagramSocket socket = new DatagramSocket(9998);
//将需要发送的数据,封装到 DatagramPacket 对象
byte[] data = "hello,明天吃火锅".getBytes();
DatagramPacket packet= new DatagramPacket(data, data.length,InetAddress.getByName("192.168.31.219"),9999);
socket.send(packet);
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
//把 packet 进行拆包,取出数据,并显示
int length = packet.getLength();
data = packet.getData();
String s = new String(data,0,length);
System.out.println(s);
socket.close();
System.out.println("B端退出");
}
}
5. URL编程
- URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
- 基本结构:
- <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
如:http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
– 片段名:即锚点,例如看小说,直接定位到章节
– 参数列表格式:参数名=参数值&参数名=参数值…
- 构造器初始化(要try-catch):
- public URL (String spec):通过一个表示 URL 地址的字符串可以构造一个
URL 对象。 - public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个
URL 对象。 - public URL(String protocol, String host, String file)
- public URL(String protocol, String host, int port, String file)
- public URL (String spec):通过一个表示 URL 地址的字符串可以构造一个
- 常用方法:
- public String getProtocol( ) 获取该 URL 的协议名
- public String getHost( ) 获取该 URL 的主机名
- public String getPort( ) 获取该 URL 的端口号
- public String getPath( ) 获取该 URL 的文件路径
- public String getFile( ) 获取该 URL 的文件名
- public String getQuery( ) 获取该 URL 的查询名
- 针对HTTP协议的URLConnection类
- URL 的方法 openStream():能从网络上读取数据
- 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一 些数据,则必须先与 URL 建立连接,然后才能对其进行读写,此时需要使用 URLConnection 。
- URLConnection:表示到 URL 所引用的远程对象的连接。当与一个 URL 建立连接时, 首先要在一个 URL 对象上通过方法 openConnection() 生成对应的 URLConnection 对象。如果连接过程失败,将产生 IOException.
- URL netchinaren = new URL (“http://www.atguigu.com/index.shtml”);
- URLConnectonn u = netchinaren.openConnection( );
- 通过 URLConnection 对象获取的输入流和输出流,即可以与现有的 CGI 程序进行交 互。
- public Object getContent( ) throws IOException
- public int getContentLength( )
- public String getContentType( )
- public long getDate( )
- public long getLastModified( )
- public InputStream getInputStream ( ) throws IOException
- public OutputSteram getOutputStream( )throws IOException
十六、反射
1.反射概念
-
反射:反射机制允许程序在运行期间借助 Reflection API 取得任何类的内部信息,并能操作对象的属性及方法。加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类 只有一个 Class 对象),这个对象就包含了完整的类的结构信息。这个Class对象就像一面镜子,可以通过这个对象看到类的结构。故称之为反射。
-
反射机制功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
-
主要类:
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造器,Constructor对象表示构造器
-
反射优缺点:
- 优点:
- 提高了 Java 程序的灵活性和扩展性,降低了耦合性,提高自适应能力
- 允许程序创建和控制任何类的对象,无需提前硬编码目标类
- 缺点:
- 反射的性能较低。反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
- 反射会模糊程序内部逻辑,可读性较差。
- 优点:
2. Class类
-
基本介绍:
- 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
-
获取Class实例:
- 方式 1:要求编译期间已知类型
- 前提:若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序 性能最高
- 实例: Class clazz = String.class;
- 应用:多用于参数传递,通过反射得到对应构造器对象
- 方式 2:获取对象的运行时类型
- 前提:已知某个类的实例,调用该实例的 getClass()方法获取 Class 对象
- 实例: Class clazz = a.getClass();
- 应用:通过创造好的对象,获取Class对象
- 方式 3:可以获取编译期间未知的类型
- 前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName()获取,可能抛出 ClassNotFoundException
- 实例: Class clazz = Class.forName(“java.lang.String”);
- 应用:多用于配置文件,读取类全路径,加载类
- 方式 4:其他方式(不做要求)
- 前提:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
- 实例: ClassLoader cl = this.getClass().getClassLoader(); Class clazz4 = cl.loadClass(“类的全类名”);
- 方式5:基本数据类型和对应的包装类型
- 基本数据类型:Class cls = 基本数据类型.class
- 对应的包装类:Class cls = 包装类.TYPE
- 方式 1:要求编译期间已知类型
-
所有Java类型都可以有Class对象:1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部 类 (2)interface:接口 (3)[]:数组 (4)enum:枚举 (5)annotation: 注解@interface (6)primitive type:基本数据类型 (7)void
3.类加载
- 类的生命周期:类在内存中完整的生命周期:加载–>使用–>卸载。其中加载过程又分为:装载、链接、初始化三个阶段。
- 类加载的时机:
- 创建对象时(new)–>静态加载
- 子类被加载时,父类也加载–>静态加载
- 调用类中的静态成员时–>静态加载
- 通过反射–>动态加载
- 类加载的过程:
- (1)装载(Loading)
- 将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类 加载器完成
- (2)链接(Linking)
- ①验证 Verify:确保加载的类信息符合 JVM 规范,例如:以 cafebabe 开头,没 有安全方面的问题。
- ②准备 Prepare:正式为类变量(static)分配内存并设置类变量默认初始值的阶 段,这些内存都将在方法区中进行分配。
- ③解析 Resolve:虚拟机常量池内的符号引用(常量名)替换为直接引用(地 址)的过程。
- (3)初始化(Initialization) •
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动 收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构 造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的 初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
- (1)装载(Loading)
4.反射与类
4.1通过反射获取类的结构信息
- java.lang.Class类
- 1.getName:获取全类名
- 2.getSimpleName:获取简单类名
- 3.getFields:获取所有public修饰的属性,包含本类以及父类的
- 4.getDeclaredFields:获取本类中所有属性
- 5.getMethods:获取所有public修饰的方法,包含本类以及父类的
- 6.getDeclaredMethods:获取本类中所有方法
- 7.getConstructors: 获取本类所有public修饰的构造器
- 8.getDeclaredConstructors:获取本类中所有构造器
- 9.getPackage:以Package形式返回包信息
- 10.getSuperClass:以Class形式返回父类信息
- 11.getInterfaces:以Class[]形式返回接口信息
- 12.getAnnotations:以Annotation[] 形式返回注解信息
- java.lang.reflect.Field类
- 1.getModifiers: 以int形式返修饰符
- [说明: 默认修饰符 是0,public 是1 ,private 是 2,protected 是4,static 是 8 ,final 是 16] public(1) + static (8) = 9
- 2.getType:以Class形式返回类型
- 3.getName:返回属性名
- 1.getModifiers: 以int形式返修饰符
- java.lang.reflect.Method类
- 1.getModifiers:以int形式返回修饰符
- [说明: 默认修饰符 是0,public 是1 ,private 是 2,protected 是4.static 是 8,final 是 16]
- 2.getReturnType:以Class形式获取 返回类型
- 3.getName:返回方法名
- 4.getParameterTypes:以Class[]返回参数类型数组
- 1.getModifiers:以int形式返回修饰符
- java.lang.reflect.Constructor类
- 1.getModifiers: 以int形式返回修饰符
- 2.getName:返回构造器名 (全类名)
- 3.getParameterTypes:以Class[返回参数类型数组
4.2 通过反射创建对象
- 1.方式一调用类中的public修饰的无参构造器
- 2.方式二:调用类中的指定构造器
- 3.Class类相关方法
- newlnstance :调用类中的无参构造器,获取对应类的对象
- getConstructor(Class…clazz):根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor(Class…clazz):根据参数列表,获取对应的所有构造器对象
- 4.Constructor类相关方法
- setAccessible:暴破
- newInstance(Object…obj):调用构造器
4.3 通过反射访问类中的成员
- 访问属性
- 1.根据属性名获取Field对象
- Field f = clazz对象.getDeclaredField(属性名);
- 2.暴破 :
- f.setAccessible(true); //f 是Field
- 3.访问
- f.set(o,值): // o 表示对象
- syso(f.get(o)): //o 表示对象
- 4.注意: 如果是静态属性,则set和get中的参数o,可以写成null
- 1.根据属性名获取Field对象
- 访问方法
- 1.根据方法名和参数列表获取Method方法对象 :
- Method m =clazz.getDeclaredMethod(方法名,XX.class);//得到本类的所有方法
- 2.获取对象:
- Object o=clazz.newlnstance();
- 3.暴破 :
- m.setAccessible(true);
- 4.访问 :
- Object returnValue = m.invoke(o,实参列表);//o 就是对象
- 5.注意: 如果是静态方法,则invoke的参数o,可以写成null!
- 1.根据方法名和参数列表获取Method方法对象 :