JAVA基础
根据各类网络教程总结的JAVA语言基础知识点,仍在持续更新中…
· 入门
java11之后不单独提供JRE
编译运行步骤 字节码 机器码
==不同操作系统的机器码不通用,所以中间借用字节码==
· 类设计
成员变量+成员方法+构造器+代码块+内部类
Java文件定义多个类中只能有一个由public修饰,且该类名必须是文件名
1 |
|
对象数组
数组中元素使用自己创造的对象,而不是常规的INT等变量
1
2
3
4
5
6
7
8
9
10
11class Point{...}
public class example{
public static void main(String args[])
{
Point p1 = new Point(10,11);
Point p2 = new Point(20,21);
Point p3 = new Point(30,31);
//初始化的多种方法
Point point1 = {p1,p2,p3,new Point(40,41)};
}
}域中定义变量 static int才能发挥全局作用
访问权限 (封装Encapsulation)
Java的封装可以使用保护修饰词,有private, protected, public
访问权限 本类 本包的类 子类 非子类的外包类 public 是 是 是 是 protected 是 是 是 否 default 是 是 否 否 private 是 否 否 否 类的继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class father
{
//无参构造器
father(){...}
//有参构造器
father(a,b,c){...}
}
class son extends father
{
son(){...}
son(a,b,c)
{
//先初始化父类构造器,不写也是隐式调用无参的,如要有参必须写
super();
//再构造自己
this.name = name;
}
//子类域
//子类方法
}
main:
son a = new son();
//先执行父类无参构造器,再执行子类相应(有参或无参构造器)!!子类可以继承父类的域和方法,就当作自身定义的一样使用
子类可能不适用父类的域或方法,重新定义父类的域称为==域的隐藏==,重新定义继承自父类的方法称为==方法的重写==
域的隐藏:在子类域中取域父类域相同名称的变量
方法的重写:在子类方法中写相同名称的函数
super关键字
若子类隐藏了父类的域,或重写了父类的方法,但仍想引用这些方法,可通过super关键字访问1
2
3
4super.域
super.方法([para])
//调用父类的构造方法
super([para])Object类(类的大爹)
我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为 Object 的子类。
Object类中有很多方法可以用以重写,如clone、equals、toString…
https://blog.csdn.net/weixin_43232955/article/details/89705350用final关键字标记的域或方法不能被重写
public static final double PI = 3.1415926
public final int getA()@Override注解
它标注的方法必须是对父类的重写,若父类没有则报错
组合类&派生类
类的多态
多态指的是同一类对象的不同行为,eg.飞机&汽车都属于交通工具类,但运行方式不同
多态更强调行为的多态,而不是变量的多态
赋值兼容规则
类Circle是类Point1的子类,则可以有
Point p = new Circle(15,25,10);
称p(父类对象)为子类对象的上转型对象==多态的优势==
Animal a = new Dog() Animal a = new Tiger() //多态使得右边对象可以解耦合,便于扩展和维护
因此,JAVA推荐在强转前用**instanceof**判断对象真实类型再进行强转 `if(t instanceof Tiger){//强转}`1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2. 定义方法的时候用父类型作为参数,所有子类型都可以传参
==多态的劣势==
1. 多态下不能访问子类的独有功能---》**sol.** **引用数据类型的类型转换**
==引用数据类型的类型转换==
1. 自动类型转换(子👉父)
2. 强制类型转换(父👉子)
```java
Animal a = new Tiger();
//此时不能调用Tiger类独有function
Tiger t = (Tiger)a;
//强制类型转换后可以调用独有功能
//!下面写法是运行时报错的
Dog d = (Dog)a;//上面new的是Tiger,不能转Dog,异常:ClassCastException
抽象类/方法&接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16abstract class Shape//抽象类
{
public abstract double area();//抽象方法
public abstract double perimeter();//抽象方法
}
//重写抽象类的方法以适应子类特征(必须重写抽象方法)
class Circle extends Shape{...}
class Rectangle extends Shape{...}
public class example{
public static void main(String args[]){
Shape aShape;声明抽象类的对象
//aShape = new Shape是不被允许的,即实例化抽象类是不允许的
aShape = new Circle(10);
aShape = new Rectangle(15.2,10.8);
}
}接口可看作特殊的抽象类,但interface与class不同,class是对一类事物的描述,而interface可以描述不同类型的事物
接口 弥补了单继承的不足
接口的域只能是不可重新赋值的常量;接口只能声明方法,不能定义方法
1
2
3
4
5
6
7
8
9
10
11interface Shape
{
public final static double PI = 3.1415926;
//interface中必须100%的抽象类
public abstract double area();
public abstract double perimeter();
}
class Circle implements Shape{
//构造函数
//接口方法实现的函数
}如何选择使用抽象类还是接口呢?
- 抽象类可以定义类的全部普通成员,接口只能定义常量和抽象方法;
- 抽象类只能单继承,接口可以多实现;
- 抽象类适合写模板,提高代码的复用性;接口更适合给功能做解耦。
static关键字的使用
==静态成员变量==
static int a = 0
可以用类名.a
的方式访问==实例成员变量==
private int b = 1
只能用实例化后的对象访问,即类名 c = new 类名();c.b = 1
==静态成员方法==
属于类,public static int get(){...}
,共享调用==实例方法==
属于实例,public void get(){...}
,实例调用
内部类(lambda表达式)
静态内部类、成员内部类、匿名内部类
1
2
3
4
5
6
7
8//静态内部类
public class Outer{
public static class inner{...}
}
//成员内部类
public class Outer{
public class inner{...}
}==匿名内部类==:方便创建子类对象,简化代码的书写 (可以将构造器作为参数直接传入方法)
1
2
3
4Employee a = new Employee(){
public void work(){...}
};
a.work();==Lambda表达式==:简化匿名内部类的代码写法!!
(只能简化==函数式接口==的匿名内部类的写法)
函数式接口:接口中只有一个抽象方法的形式 @FunctionalInterface1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//Lambda的格式
//(匿名内部类被重写方法的形参列表) -> {
// 重写的方法体;
//}
//简化Arrays.sort Comparator接口
public class Lambda {
public static void main(String[] args) {
Integer[] ages = {34,12,42,23};
Arrays.sort(ages, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;//升序
}
});
}
}
简化为👇
public class Lambda {
public static void main(String[] args) {
Integer[] ages = {34,12,42,23};
Arrays.sort(ages, (o1, o2) -> {
return o1-o2;//升序
});
}
}
可变参数
1
2
3
4
5
6
7public static void main(String args[]){
sum(10);
sum(10,20)
...//都是合理的
}
//传入就是一个数组
public static void sum(int ... sum){...}
· 核心类库(API)
String类
使用构造器创建
new String()
👉 堆内存 👉 不同地址使用
“str”
创建 👉 常量池 👉 相同地址==String类API==
和长度有关的方法
返回类型 方法名 作用
int length() 得到一个字符串的字符个数(一个中文是一个字符,一个英文是一个字符,一个转义字符是一个字符)和数组有关的方法
返回类型 方法名 作用
byte[] getBytes() 将一个字符串转换成字节数组
char[] toCharArray() 将一个字符串转换成字符数组
String[] split(String) 将一个字符串按照指定内容劈开和判断有关的方法
返回类型 方法名 作用
boolean equals(String) 判断两个字符串的内容是否一模一样
boolean equalsIgnoreCase(String) 忽略大小写的比较两个字符串的内容是否一模一样
boolean contains(String) 判断一个字符串里面是否包含指定的内容
boolean startsWith(String) 判断一个字符串是否以指定的内容开头
boolean endsWith(String) 判断一个字符串是否以指定的内容结尾和改变内容有关的方法
和改变内容有关的方法,都不会直接操作原本的字符串
而是将符合条件的字符串返回给我们,所以注意接收
返回类型 方法名 作用
String toUpperCase() 将一个字符串全部转换成大写
String toLowerCase() 将一个字符串全部转换成小写
String replace(String,String) 将某个内容全部替换成指定内容
String replaceAll(String,String) 将某个内容全部替换成指定内容,支持正则
String repalceFirst(String,String) 将第一次出现的某个内容替换成指定的内容
String substring(int) 从指定下标开始一直截取到字符串的最后
String substring(int,int) 从下标x截取到下标y-1对应的元素
String trim() 去除一个字符串的前后空格和位置有关的方法
返回类型 方法名 作用
char charAt(int) 得到指定下标位置对应的字符
int indexOf(String) 得到指定内容第一次出现的下标
int lastIndexOf(String) 得到指定内容最后一次出现的下标Object类
toString ()
返回当前对象的地址
需要自己重写方法来实现具体功能equals()
判断对象是否相等(基本类型&String-compare value;引用类型-compare LOC),instanceof/强转等
重写后一般连着
hashcode()
一块重写,因为equals不再具有’==’的从地址判断的功能,而两个对象hashcode相同
对象并不一定相同(也就是hashcode相同,equals的结果可能仍不同)
Objects类
继承于Object类,
Objects.equals(a,b)
写法更安全(null不报错)StringBuilder&&StringBuffer类
可变string类,可看作对象容器,好处就是不用创建新的string对象
StringBuilder a = new StringBuilder()
,append()、reverse()、length()Math类
都是静态方法,可以通过类名直接调用
System类
BigDecimal类(大数据类型)
解决浮点型运算精度失真
调用 .valueof() 方法获取BD对象
日期类
Date类
时间毫秒值可以用来方便计算日期对象
System.currentTimeMills()
获取系统时间毫秒值
Date d = new Date(时间毫秒值对象)
ord.setTime(时间毫秒值对象)
可以将时间毫秒值转为日期对象SimpleDateFormat类
Date对象或时间毫秒值格式化为
年月日时间
的时间形式
字符串时间形式解析成日期对象
public Date parse(String source)
https://blog.csdn.net/qq_26817225/article/details/93593989 对于年代标识符的使用
Calendar类(抽象类,不能直接创建对象)
JDK8的新增日期API
==LocalDate、LocalTime、LocalDateTime==:
of()
设定时间,now()
获取当前时间…==Instant==: 时间戳,类似
System.currentTimeMillis()
.now()
方法默认获取世界标准时间,不是东八区时间
Date与Instant对象互转,Date.from(Instant)
,Date.toInstant()
==DateTimeFormatter==:格式化format:
解析parse:
==Duration || Period==:计算时间跨度
Period对应日期操作,Duration对应时间操作
1
2
3
4
5Period p = Period.between(a,b)//a,b都是LocalDate对象,默认用b-a
//Duration用于更精细的操作
LocalDateTime today = LocalDateTime.now();
LocalDateTime birth = LocalDateTime.of(2001,11,25,0,5,0);
Duration duration = Duration.between(birth,today);==可以用ChronoUnit简化==
1
2
3LocalDateTime today = LocalDateTime.now();
LocalDateTime birth = LocalDateTime.of(2001,11,25,0,5,0);
ChronoUnit.YEARS.between(birth,today).sout;
包装类
==Parsexxx可以用valueOf替代==
Arrays类
对所有数组类型可以操作
toString、sort、binarySearch等
Comparater接口用来==制定比较器规则==(当sort对象为自定义类对象时非常好用)
1
2
3
4
5
6
7Array.sort(array, new Comparater<Student>(){
@override
public int compare(Student o1, Student o2){
//制定比较规则
return o2.getAge()-o1.getAge();//年龄降序排序
}
})==Java集合(Collection)框架====、Map框架==
==框架统一于util包内==
Collection集合的遍历:迭代器Iterator,foreach,lambda表达式(Collection不支持索引)
List集合:查询快,线性表(有序,有索引,可重复)
Set集合:增删改查都快,不重复集合
Map集合:键值对
Queue集合:队列Collection特点:集合大小不固定,可以动态变化,类型也可以不固定(对基本数据类型的引用只能用包装类),非常适合做增删操作
List集合
ArrayList列表、LinkedList链表
Vecto****r与ArrayList原理相同,但使用了synchronized方法保证线程安全,因此性能略差
Stack继承自Vector,拓展了push、pop、peek(取堆栈顶点)、empty、search操作以实现后进先出的堆栈结构==ArrayList构造方法==
方法名 方法功能 ArrayList() 构造一个初始容量10的空列表 ArrayList(Collection c) 构造一个包含指定collection的元素的列表 ArrayList(int capacity) 构造一个指定初始容量的空列表
List接口常用方法(Arraylist同用) void add(int index, Object obj)
Inserts obj 插入到调用列表中的索引通过索引处。达到或超出插入点任何预先存在的要素被上移。因此,不会有元素被覆盖。boolean addAll(int index, Collection c)
插入c的所有元素入索引通过索引处的调用列表。等于或超出插入点任何预先存在的要素被上移。因此,没有任何元素被覆盖。如果调用列表更改并返回true,否则返回false。Object get(int index)
返回存储调用集合中指定索引处的对象。int indexOf(Object obj)
返回调用列表obj的第一个实例的索引。如果obj不是列表中的一个元素,则返回-1。int lastIndexOf(Object obj)
返回调用列表obj的最后一个实例的索引。如果obj不是列表中的一个元素,则返回-1。ListIterator listIterator( )
返回一个迭代器调用列表的开始。List Iterator list Iterator(int index)
返回一个迭代器调用列表开头的在指定索引处。Object remove(int index)
从调用列表删除index位置的元素,并返回被删除的元素。结果列表中被压缩。也就是说,随后的元素的索引减一。Object set(int index, Object obj)
赋予obj转换通过索引调用列表中指定的位置。List subList(int start, int end)
返回一个列表,其中包括在调用列表,从开始元素end-1。在返回列表中的元素也被调用对象的引用。泛型深入
泛型类
public class myClass<E>{...}
→创建类对象时myClass<包装类> list = new myClass<>()
,限定对象的成员类型泛型方法
public <T> void show(T t)
,使用泛型接受一切类型的参数,更具有通用性泛型接口
public interface Data<E>{...}
,让实现类选择当前功能需要的数据类型泛型通配符、上下限
?
在 使用泛型 的时候代表一切类型(ETKV是在定义泛型使用的)
Set集合
HashSet
无序的底层原理:哈希表(数组+链表+红黑树)
哈希值:根据对象地址,按某种规则计算得到的int数值,可以直接用Object类下的
public int hashCode()
获取Step4的equals()方法和hashCode()方法需要重写
JDK8以后,如果链表长度超过8,会自动转为红黑树
去重的底层原理:先判断Hash值,再判断equals(),Override Object类中的hashCode和equals才能正确去重
LinkedHashSet
TreeSet
包装类按照大小、首字符编号等排序,自定义对象需要自定义排序规则 👇
浮点型比较直接用
Double.compare()
==Map集合==
键值对集合(双列集合),每个元素有key,value俩值{key1=value1,key2=value2,…}
特点:HashMap、TreeMap、LinkedHashMap作为重点,key无序不重复(后覆盖前),value可以有多个key。
- HashMap:无序、不重复、无索引
- LinkedHashMap:有序、不重复、无索引
常用API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14map.put("key_1",1); // 添加键值对,已有 key 则覆盖 value
map.putIfAbsent("key_2",2); // 添加键值对,已有 key 则不操作
map.remove("key_1"); // 删除键值对(按值)
map.remove("key_2",2); // 删除键值对(按键值)
map.get("key_1"); // 获取值, key 不存在返回null
map.getOrDefault("key_2",-1); // 获取值, key 不存在返回默认值
map.containsKey("key_1"); // 判断 key 是否存在
map.containsValue(1); // 判断 value 是否存在
map.keySet();
map.values();==遍历的3种方式==
键找值
Step1. 拿keySet集合
Step2. 遍历每个键,用键提取值,value = maps.get(key)
键值对
Step1. 将Map转化为Set集合,使用Map的实体类型
Set<Map.Entry<String,Integer>> entries = maps.entrySet();
Step2. foreach遍历for(Map.Entry<String,Integer> entry : entries){entry.getKey / entry.getValue}
Lambda
利用Map中的
forEach(BiConsumer<? super K,? super V> action)
方法
实现:maps.foreach((k,v) ->{...})
HashMap
TreeMap
LinkedHashMap
补充:集合工具类Collections
不属于Collection家族,是一个辅助工具,具体参照API文档,addAll、shuffle、sort等
补充:集合的嵌套
· 正则表达式
** 用规定的字符制定规则,用来校验数据格式的合法性**
==API文档regex.pattern==

String的一些方法(如split、replace)都可以使用正则表达式传参
==正则表达式爬取信息==
1 |
|
· Stream流体系
不可变集合(list,set,map)
List<T> lists = list.of(...)
不允许add,set
操作Stream流
目的:结合Lambda表达式,简化集合和数组操作的API(不会影响原集合结构(无增删改))
1
2
3
4
5
6
7
8
9
10
11
12
13// Map集合获取流
Map<String, Integer> maps = new HashMap<>();
//key stream
Stream<String> a = maps.KeySet().stream();
//value stream
Stream<String> b = maps.values().stream();
//key-value steam
Stream<Maps.Entry<String,Integer>> c = maps.entrySet().stream();
//数组获取流
Arrays.stream()
//或者
Stream<String> a = Stream.of()Stream流的常用API
方法名称 方法作用 方法种类 是否支持链式调用 count 统计个数 终结方法 否 forEach 逐一处理 终结方法 否 filter 过滤 函数拼接 是 limit 取用前几个 函数拼接 是 skip 跳过前几个 函数拼接 是 map 映射 函数拼接 是 concat 组合 函数拼接 是 注:终结方法:返回值类型不再是Stream接口本身类型的方法
非终结方法/延迟方法:返回值类型仍然是Stream接口自身类型的方法,除了终结方法都是延迟方法。Stream流 to Collection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18List<String> list2 = new ArrayList<>();
list2.add("张老三");
list2.add("张小三");
list2.add("李四");
list2.add("赵五");
list2.add("张六");
list2.add("王八");
// 需求:过滤出姓张的并且长度为3的元素
Stream<String> stream = list2.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3);
// stream 收集到单列集合中
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
// stream 收集到单列集合中
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set);
· 异常处理
Error
异常是系统级别的严重错误,是不可操作的;Exception
异常分为运行时异常RuntimeException
和编译时异常other
异常处理方式
throws :
function throws Exceptions{...}
try catch :
1
2
3
4
5
6
7
8
9
10
11
12try{
//需要监视的代码段
}catch(异常类型1 e){
//处理方式
e.printStackTrace();
}catch(异常类型2 e){
//处理方式
}
//企业级写法
catch(Exception e){
e.printStackTrace();
}调用者决定 :function将exception throw给上层调用者,上层调用者再做try catch决定如何处理
RuntimeException可以在最外层try catch
自定义异常
自定义编译时异常
定义一个异常类继承Exception -> 重写构造器 -> 出现异常的地方用
throw new
抛出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//出现异常的地方用`throw new`抛出
public static void main(String arg[]){
try{checkAge(-23)}
catch(xyfAgeIllegalException e){e.printStackTrace();}
}
public static void checkAge(int age) throws xyfAgeIllegalException{
if (age<0||age>200){
throw new xyfAgeIllegalException(age + "is illegal!");
}
else{
sout("年龄合法!");
}
}
//重写构造器
public class xyfAgeIllegalException extends Exception{
public xyfAgeIllegalException(){}
public xyfAgeIllegalException(String message){super(message);}
}自定义运行时异常
定义一个异常类继承RuntimeException -> 重写构造器 -> 出现异常的地方用
throw new
抛出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//出现异常的地方用`throw new`抛出
public static void main(String arg[]){
try{checkAge(-23)}
catch(xyfAgeIllegalRuntimeException e){e.printStackTrace();}
}
public static void checkAge(int age) throws xyfAgeIllegalRuntimeException{
if (age<0||age>200){
throw new xyfAgeIllegalRuntimeException(age + "is illegal!");
}
else{
sout("年龄合法!");
}
}
//重写构造器
public class xyfAgeIllegalRuntimeException extends RuntimeException{
public xyfAgeIllegalException(){}
public xyfAgeIllegalException(String message){super(message);}
}
· 日志技术
Advantage: 可以将日志信息写入文件或数据库 + 不需修改代码,灵活性好 + 多线程性能较好
Logback框架
三个技术模块
logback-core:为其他俩模块奠定基础
logback-classic:Log4j的改良版本,且完整实现了slf4j的API(日志规范)
logback-access:与Tomcat && Jetty等Servlet容器集成,以提供HTTP访问日志功能Logback入门
S1. 导入jar包(logback-classic.jar, logback-core.jar, slf4j-api.jar),至新建的lib文件夹下
S2. logback.xml拷贝到src目录下
S3. 代码中获取日志的对象public static final logger LOGGER = LoggerFactory.getLogger("类对象");
S4. 使用日志对象输出日志Logback配置
通过 logback.xml 配置,可以设置日志输出位置和格式
==日志输出位置和格式设置==
可以设置输出位置(CONSOLE or FILE)和日志信息的详细格式 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<appender name="CONSOLE" class="...">
<target>System.out</target> <!--.err打红色 -->
<encoder>
<!--format输出。%d-日期 %thread-线程名 %-5level-level从左显示5个字符宽度 %msg-日志消息-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c[%thread] : %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--输出路径-->
<file>C:/code/code1.log</file>
<rollingPolicy class="...">
<fileNamePattern>C:/code/code-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!--
<root> -> <appender-ref>不配置就不记录
level用来设置打印级别,有TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,default is debug
-->
<root level="ALL">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>==日志级别设置==
日志级别有 TRACE<DEBUG<INFO<WARN<ERROR ,ALL,OFF,默认是debug,只输出级别高于设置级别的信息
· IO文件操作
File类可以定位和操作文件,但不能读写文件内容,I/O流技术可以读写文件
一些预备知识点:File类使用,方法递归,字符集,I/O流,字节流&&字符流
File类
代表了操作系统的文件对象(文件,文件夹)
主要功能: 定位文件,删除文件,获取文件信息,创建文件
创建File对象:
File f = new File("C:/file/b.xlsx")
👉pathname支持 绝对路径 和 相对路径 (相对工程目录)
字节长度:f.length()
获取绝对路径:f.getAbsolutePath()
获取定义的路径:f.getPath()
获取文件名称:f.getName()
获取文件的最后修改时间:long time = f.lastModified()
获得时间毫秒值
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format()
转换成人话
判断是文件还是文件夹:.isFile() .isDirectory()
创建新文件:
f.createNewFile()
//几乎不用,会自动创建
创建一级目录:f.mkdir()
创建多级目录:f.mkdirs()
删除文件(夹):f.delete()
//只能删除空文件夹获取当前目录下所有一级文件名称,返回到字符串数组:
public String list()
—————————–对象,返回文件对象数组:public File listFiles()
//调用者不存在/为文件时返回null,空文件夹时返回空数组(包括隐藏文件)方法递归Recursion
复杂的问题 划分为 较小规模的问题
基线情况 + 递归情况 + 递归公式向基线情况靠近
递归经典问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
x是第x天,f(x)是第x天摘的桃子
f(x) - f(x)/2 - 1 = f(x+1)
*/
public static void main(String[] args) {
System.out.println(Peaches(1));
}
private static int Peaches(int day){
if(day == 10){
return 1;
}
else {
return 2*Peaches(day+1)+2;
}
}非规律递归–文件搜索
遍历一级文件对象,判断是否是文件 → 如果是文件,是否是目标 → 如果是文件夹,返回步骤1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public static void main(String[] args) {
//X盘中找codeblocks.exe
searchFile(new File("X:/codeblocks"),"codeblocks.exe");
}
/**
* @param dir 被搜索的源目录
* @param fileName 被搜索的文件名称
*/
public static void searchFile(File dir,String fileName){
if (dir!=null&&dir.isDirectory()){
//可以找
File[] files = dir.listFiles();
//防止拿了空文件夹
if (files!=null&&files.length>0){
for (File file : files) {
//进去之后判断当前是文件还是文件夹
if (file.isFile()){
if (file.getName().contains(fileName)){
System.out.println("找到了"+file.getAbsolutePath());
}
}else{
searchFile(file,fileName);
}
}
}
}else {
System.out.println(dir.getAbsolutePath()+"不是文件夹");
}
}非规律递归–啤酒问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//定义一个static成员变量用于存储可以买的酒数量
public static int totalNum;
public static int lastBottleNum;
public static int lastCoverNum;
public static void main(String[] args) {
buy(10);
System.out.println("总数:" + totalNum);
System.out.println("剩余盖子:" + lastCoverNum);
System.out.println("剩余瓶子:" + lastBottleNum);
}
private static void buy(int money) {
int buyNum = money / 2; //第一次直接可以买的酒数量
totalNum += buyNum;
//盖子和瓶子换算成钱
//统计本轮总的盖子数和瓶子数
int coverNum = lastCoverNum + buyNum; //盖子数 = 上一次的盖子数+传进来的money可以直接买的酒数
int bottleNUm = lastBottleNum + buyNum; //类上
//统计可以换算的钱
int allMoney = 0;
if (coverNum>=4){
allMoney += (coverNum/4)*2;
}
lastCoverNum = coverNum%4;
if (bottleNUm>=2){
allMoney += (bottleNUm/2)*2;
}
lastBottleNum = bottleNUm%2;
if (allMoney>=2){
buy(allMoney);
}
}
字符集
ASCII,GBK(中国码表,兼容ASCII表,包含了几万个汉字,一个中文以两个字节存储),Unicode(万国码,容纳大多数国家的符号和文字,utf-8后中文以三个字节存储)
编码解码操作
编码 –
getBytes("GBK...")
编码,返回字节数组解码 – S
tring rs = new String(字节数组,"GBK...")
编解码前后的字符集必须一致,否则乱码
IO流
磁盘与内存间的输入输出流,用以读写数据
按传输的最小单位划分:字节流(音视频文件),字符流(纯文本文件)
字节流
1
2
3
4
5
6
7
8
9
10
11
12
13/**
一次读一个字节的FileInputStream(性能差,可能乱码)
*/
//
//创建一个文件字节输入流管道
InputStream is = new FileInputStream(”path/File“);
//读取一个字节,返回
int b1 = is.read();
//读取下一个,返回(未读到返回-1)
int b2 = is.read();
//循环形式
int b;
while((b = is.read())!=-1){...}1
2
3
4
5
6
7
8
9
10/**
一次读一个字节数组的FileInputStream(可能乱码)
*/
InputStream is = new FileInputStream(”path/File“);
//定义一个字节数组,用于读取()
byte[] buffer = new byte[3];
int len;
while((len = is.read(buffer)) != -1){
sout.new String(buffer,0,len);
}1
2
3
4
5
6
7/**
一次读完全部字节的FileInputStream(solve the messy code problem)
*/
InputStream is = new FileInputStream(”path/File“);
//set buffer长度为文件字节长度(line6,7 is the same)
byte[] buffer = new byte[(int)f.length()]
byte[] buffer = is.readAllBytes();方法 说明 public void write(int a) 写一个字节出去 public void write(byte[] buffer) 写一个字节数组 public void write(byte[] buffer,int pos,int len) 写一个字节数组的一部分出去 flush() 刷新流,可以继续写 close() 关闭流,释放资源(关闭前刷新,一旦关闭,无法写数据) 1
2
3
4
5
6
7
8/**
FileOutputStream
*/
OutputStream os = new FileOutputStream(”path/File“,true);//true可以保证向文件追加数据而不是先清空再写
byte[] buffer = "something".getBytes();
os.write("/r/n".getBytes());//换行
os.write(buffer);
os.close();文件拷贝
Step1. 根据数据源创建字节输入流对象
Step2. 根据目的地创建字节输出流对象
Step3. 读写数据,复制视频
Step4. 释放资源1
2
3
4
5
6
7
8InputStream is = new FileInputStream(”path/File“);
OutputStream os = new FileOutputStream(”path/File“);
byte[] buffer = new byte[1024];
int len;
while(len=is.read(buffer)!=-1){
os.write(buffer,0,len);
os.close();
is.close();资源释放的方式
try-catch-finally/try-with-resource
因为finally语句一定会执行(除非JVM退出),因此适合做资源释放的工作.close()
如果finally中有return的话,上面语句的return不会执行1
2
3
4
5
6
7// JDK7更新的资源释放方式
try(
//定义流对象,只能放置资源对象(implements了Closeable/AutoCloseable接口的对象),用完自动关闭
InputStream is = new FileInputStream(”path/File“);
OutputStream os = new FileOutputStream(”path/File“);
){...}
catch{...}字符流
按单个字符读取,读取中文不会乱码(编码格式一致),更合适
构造器:
public FileReader(file/path)
、public FileWriter(file/path,true)
method statement public int read() 每次读取一个字符,读完返回-1 public int read(char[] buffer) 读取字符数组,返回字符个数,读完返回-1 void write(…) 可以写字符,字符数组(一部分),字符串(一部分) flush() 刷新 close() 关闭流,释放 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
一次读一个字节的FileReader(性能差)
*/
Reader fr = new FileReader("path/file");
while ((code=fr.read())!=-1){
sout.(char)code;
}
/**
一次读一个字节数组的FileReader
*/
char[] buffer = new char[1024];
int len;
while(len=fr.read(buffer)!=-1){
String rs = new String(buffer,0,len);
sout.rs;
}
缓冲流
==自带缓冲区,提高原始流的性能==
字节缓冲流
BufferedInputStream
、BufferedOutputStream
(自带了8kb的缓冲池)
InputStream bis = new BufferedInputStream(is)
直接将FileInputStream包装成BufferedInputStream字符缓冲流
BufferedReader
,多了一个readLine
方法
BufferedWriter
,多了一个newLine
方法
代码与文件编码不一致的乱码问题
字符输入转换流(InputStreamReader)
InputStreamReader(InputStream in, String charsetName)
字符输出转换流(OutputStreamReader)=“String”.getBytes()
控制写出的字符使用的编码
OutputStreamWriter(OutputStream out, Charset cs)
序列化对象
对象序列化
定义:以内存为基准,将内存的对象存储到磁盘文件中
使用对象字节输出流ObjectOutputStream
构造器 statement public ObjectOutputStream(OutputStream out) 低级字节输出流包装成高级的对象字节输出流 对象class必须implements Serializable才能序列化
对象反序列化
定义:以内存为基准,将磁盘文件中的对象数据恢复成内存中的对象
使用对象字节输入流ObjectInputStream
构造器 statement public ObjectInputStream(InputStream in) 低级字节输入流包装成高级的对象字节输入流
private transient password
敏感信息可以用transient修饰,不参与序列化可以定义final字段来规定序列化的版本号
打印流
两种:FileOutputStream -> PrintStream 或者 FileWriter -> PrintWriter
最强大的写数据到文件流,可以实现所见即所得
构造器 statement public PrintStream(OutputStream os) 直接通向字节输出流管道 public PrintStream(File f) 直接通向文件对象 public PrintStream(String filepath) 直接通向文件路径 … … method statement public print(xxx xx) 打印任意类型数据 打印功能上PrintStream与PrintWriter无异(归属有异,写数据上PrintStream 只能写字节数据)
Properties
Map -> HashTable -> Properties
其实就是一个Map集合,但不当集合用(因为HashMap更好用)
核心作用:Properties代表一个属性文件,可以把自己对象中的键值对信息存入一个属性文件中
属性文件:.properties结尾的文件,里面的内容都是key=value,后续做系统配置信息
method:
properties.store()
、properties.getProperties()
、properties.setProperties()
Commons-IO
apache基金会提供的有关IO操作的类库,主要的类有FileUtils,IOUtils
线程
多线程的创建、Thread类的常用方法、线程安全、线程同步、线程通信、线程池、定时器、线程状态…
创建Thread
方案一(继承Thread类)
① 定义一个myThread子类==继承==java.lang.Thread,重写run()
② 创建myThread类的对象
③ 调用线程对象的start()方法启动线程
缺点:继承Thread类,无法继承其他类,不利于扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//多线程创建方式一:继承Thread类
public class createThread {
public static void main(String[] args) {
Thread thread = new myThread();
thread.start();
task;
}
}
class myThread extends Thread{
// 重写run()
@Override
public void run() {
task;
}
}方案二(实现Runnable接口)
① 定义一个线程任务类MyRunnable实现Runnable接口,重写run()
② 创建MyRunnable任务对象
③ 把MyRunnable的任务对象交给Thread处理
④ 调用线程对象的start()方法启动线程
缺点:多了一层对象包装,线程的执行结果无法直接return(第一种也无法直接return)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 实现Runnable接口,创建线程
public class createThread2 {
public static void main(String[] args) {
//创建任务对象
Runnable target = new MyRunnable();
//把任务对象交给Thread处理
Thread t = new Thread(target);
//启动线程
t.start();
task;
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
task;
}
}另一种写法(匿名内部类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class createThreadOther {
public static void main(String[] args) {
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程执行输出:"+i);
}
}
};
Thread t = new Thread(target);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:"+i);
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13//lambda表达式简化
public class createThreadOther {
public static void main(String[] args) {
new Thread(() -> {
for(int i=0;i<10;i++){
System.out.println("子线程执行输出:"+i);
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:"+i);
}
}
}方案三(JDK5.0 Callable&FutureTask,解决不能直接return线程执行结果的问题)
① 得到任务对象
- 定义类实现Callable接口,重写call(),封装要做的事
- 用FutureTask把Callable对象封装成线程任务对象
② 线程任务对象交给Thread处理
③ 调用Thread的start()启动线程,执行任务
④ 线程执行完毕后,用FutureTask的get()获取任务执行的结果
缺点:编码复杂
优点:扩展性强,实现接口的同时可继承其他类;可以得到线程执行的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// Callable+FutureTask
public class createThread3 {
public static void main(String[] args) {
// 3.创建Callable任务对象
Callable<String> call = new MyCallable(100);
// 4.把Callable对象封装成FutureTask对象(FutureTask是Runable的对象)
FutureTask<String> f1 = new FutureTask<>(call);
// 5.交给线程
Thread t1 = new Thread(f1);
t1.start();
try {
String rs1 = f1.get();
System.out.println("第一个结果是:"+rs1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 1.定义任务类 实现Callable接口
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
// 2.重写call(),线程的任务方法
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum+=i;
}
return "子线程返回的结果是:"+sum;
}
}
Thread的常用方法
如何区分不同线程
method statement getName() 获取当前线程名 setName(String name) 修改线程名 currentThread() 获取当前线程对象 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package com.xyf.thread.APIofThread;
public class distinguishThread {
public static void main(String[] args) {
Thread t1 = new MyTHread();
t1.setName("no1");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyTHread();
t2.setName("no2");
t2.start();
System.out.println(t2.getName());
//to get main thread
Thread m = Thread.currentThread();
System.out.println(m.getName());
for (int i = 0; i < 5; i++) {
System.out.println("main线程输出:"+i);
}
}
}
class MyTHread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"子线程输出:"+i);
}
}
}也可以在MyThread类中修改构造器
Thread的构造器
constructor statement public Thread(Stirng name) 为当前线程指定名称 public Thread(Runable target) 封装Runnable对象成为线程对象 public Thread(Runable target,Stirng name) 封装Runnable对象成为,并指定名称 1
2
3
4public MyThread(String name){
//调用父类Thread的构造器
super(name);
}线程的休眠方法
method statement public static void sleep(long time) 线程休眠time时间再执行,单位ms
线程安全
多个Thread同时操作同一个共享资源时可能会出现的业务安全问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57class Account{
private String cardID;
private double money;
public Account(String cardID, double money) {
this.cardID = cardID;
this.money = money;
}
public Account() {
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawMoney(double money) {
//1.判断谁来取钱
String name = Thread.currentThread().getName();
//2.判断账户钱够不够
if (this.money>=money){
//3.取钱,更新余额
System.out.println(name+"取钱成功,取出:"+money+"元");
this.money-=money;
System.out.println(name+"取钱后剩余:"+this.money);
}else {
System.out.println(name+"取钱失败,余额不足");
}
}
}
public class DrawThread extends Thread{
//取钱线程类
private Account account;
public DrawThread(Account account,String name){
super(name);
this.account=account;
}
@Override
public void run() {
account.drawMoney(100000);
}
}
public class run{
public static void main(String[] args) {
//账户实例(用户共享账户)
Account a = new Account("123456",100000);
//创建两个用户线程
new DrawThread(a,"Tom").start();
new DrawThread(a,"Ivan").start();
}
}线程同步
核心思想
锁 lock
将共享资源上锁,一次只能一个Thread进入,访问完毕后解锁,Other thread才能进
==上锁方法一:同步代码块==
Function:把出现线程安全的code上锁
Principal:每次只能一个Thread进入,执行完毕后自动解锁,其他Thread才能执行
1
2
3
4
5synchronized(同步锁对象){
// ↑使用共享资源作为锁对象(实例方法用this,静态方法用类名.class)
//操作共享资源的代码
...
}==上锁方法二:同步方法==
Function:把出现线程安全的function上锁
Principal:same
1
2
3
4修饰符 synchronized 返回值类型 方法名称(para1,para2,...){
//操作共享资源的代码
...
}底层原理:其实方法前加
synchronized
与同步代码块方法是一样的,底层有隐式锁对象锁住整个方法代码,实例-this,静态-类名.class==上锁方法三:Lock锁==
JDK5后提供了Lock锁对象,Lock是接口不能实例化,用其实现类
public ReentrantLock()
构建Lock锁对象
lock()/unlock()
方法上/解锁,最好放在try-finally结构中保证始终解锁
lock.tryLock(1, TimeUnit.SECONDS)
,程序可以通过trylock的返回值做一些额外处理,而不是无限等待下去
线程池
一个复用线程的技术,工作线程 + 任务队列
创建线程池
JDK5起提供了一个代表线程池的接口:
ExecutorService
方法一(推荐)
使用
ExecutorService
的实现类==ThreadPoolExecutor
==自创建一个线程池对象ThreadPoolExecutor提供的有参构造器的参数含义
参数 含义 corePoolSize 指定线程池核心线程数量
“正式工”maximumPoolSize 最大线程数
“正式工+临时工”keepAliveTime 指定临时线程的存活时间 unit 临时线程存活的时间单位(秒,分,时,天) TimeUnit.SECONDS
…workQueue 指定线程池的任务队列 new ArrayBlockingQueue<>(3)
threadFactory 指定线程池的线程工厂
“负责招聘的HR”Executors.defaultThreadFactory()
handler 任务拒绝策略 new ThreadPoolExecutor.AbortPolicy()
方法二
使用线程池的工具类
Executors
,调用其方法返回不同的线程池对象方法 说明 pulic static ExecutorService newFixedThreadPool(int nThreads)
创建固定线程数量的线程池,如果线程因异常结束,系统会创建新线程替代 pulic static ExecutorService newSingleThreadExecutor()
创建只有一个线程的线程池对象,如果线程因异常结束,系统会创建新线程替代 pulic static ExecutorService newcachedThreadPool()
线程数随任务数增加,执行完毕且空闲60s的线程会被回收 pulic static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个线程池,在给定延迟后执行任务,或是定期执行任务 Executors可能存在的问题(alibaba开发手册)
1)FixedThreadPool
和SingleThreadPool
:
允许的请求队列长度为Integer.MAX_VALUE,可能会导致堆积大量的请求,从而导致OOM
2)cachedThreadPool
和ScheduledThreadPool
:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
Future类
Future是一个用于获取异步结果的接口、
一般来说,当我们执行一个长时间运行的任务时,使用Future就可以让我们暂时去处理其他的任务,等长任务执行完毕再返回其结果。
经常会使用到Future的场景有:1. 计算密集场景。2. 处理大数据量。3. 远程方法调用等。ExecutorService的常用方法
方法名 说明 void execute(Runable command)
执行Runable任务 Future<T> submit(Callable<T> task)
执行Callable任务,返回未来任务对象 void shutdown()
等全部任务执行完成后,关闭线程池 List<Runable> shutDownNow()
立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务 临时线程
新的任务提交时,==核心线程都在执行==,==任务队列也满了==,且==支持创建临时线程==时,才会创建临时线程
任务拒绝策略
策略 说明 ThreadPoolExecutor.AbortPolicy()
丢弃任务 并抛出异常,为默认策略 ThreadPoolExecutor.DiscardPolicy()
丢弃任务 不抛出异常 ThreadPoolExecutor.DiscardOldestPolicy()
丢弃等待最久的任务,把当前任务加入队列中 ThreadPoolExecutor.CallerRunsPolicy()
由主线程负责调用任务的 run()
方法,从而绕过线程池直接执行线程并发 & 并行
并发
process中的thread是由CPU调度的,但CPU核心数有限,为了保证所有线程执行,CPU会轮询
由于CPU切换速度很快,给人的感觉是线程同时执行。这就是并发并行
同一时刻,多个线程被CPU执行
网络编程
概念
IP
Java中使用
InetAddress
类实现IP地址常用方法 说明 public static InetAddress getLocalHost() throws UnknownHostException 获取本机ip,返回InetAddress对象 public String getHostName() 获取主机名 public String getHostAddress() 获取ip public static InetAddress getByName(String host) throws UnknownHostException 根据ip或域名,返回InetAddress对象 public boolean isReachable(int timeout) throws IOException 判断主机毫秒内与调用方法的ip是否能连通 端口
周知端口:0 - 1023,被预先定义的应用占用
注册端口:1024 - 49151,分配给用户进程或某些应用程序
动态端口:49152 - 65535,动态分配协议
UDP(User Datagram Protocol,用户数据报协议):无连接,不可靠
TCP(Transmission Control Protocol,传输控制协议):面向连接,可靠,三次握手四次挥手
UDP通信
不事先建立连接,发送端将数据(<=64KB)、接收端IP等信息封装为一个数据包,发出去就不跟踪了
java.net.DatagramSocket
包用于实现UDP通信构造器 说明 public DatagramSocket() 创建客户端socket对象,系统会随机分配一个端口号 public DatagramSocket(int port) 创建服务端的socket对象,并指定端口号 方法 说明 public void send(DatagramPacket dp) 发送数据包 public void receive(DatagramPacket dp) 使用数据包接收数据 其中,DatagramPacket是一种数据包类
构造器 说明 public DatagramPacket(byte buf[], int length, InetAddress address, int port) 创建客户端socket对象,系统会随机分配一个端口号 public DatagramPacket(byte buf[], int length) 创建服务端的socket对象,并指定端口号 一发一收
客户端(发送)
1
2
3
4
5
6
7// 1. 创建发送端对象
DatagramSocket socket = new DatagramSocket();
// 2. 创建数据包
byte[] dataInBytes = "我是要发送的数据".getBytes();
DatagramPacket packet = new DatagramPacket(dataInBytes, dataInBytes.length, InetAddress.getByName("ip地址"), 8080);
// 3. 发送数据包
socket.send(packet);服务端(接收)
1
2
3
4
5
6
7
8// 1. 创建服务端对象
DatagramSocket socket = new DatagramSocket(8080);
// 2. 创建一个用于接收的数据包
byte[] buf = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 3. 接收数据,将数据封装到数据包对象
socket.receive();
String data = new String(buf, 0, packet.getLength());多发多收
客户端(发送)
思路:使用while(true)
不断接收用户输入,直到用户输入exit1
2
3
4
5
6
7
8
9
10
11
12
13// 1. 创建发送端对象
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while(true){
// 2. 创建数据包
String msg = sc.nextLine();
if("msg".equals("exit")){...;break;}
byte[] dataInBytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(dataInBytes, dataInBytes.length, InetAddress.getByName("ip地址"), 8080);
// 3. 发送数据包
socket.send(packet);
socket.close();
}服务端(接收)
思路:while(true)
不断执行receive
方法1
2
3
4
5
6
7
8
9
10// 1. 创建服务端对象
DatagramSocket socket = new DatagramSocket(8080);
// 2. 创建一个用于接收的数据包
byte[] buf = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while(true){
// 3. 接收数据,将数据封装到数据包对象
socket.receive();
String data = new String(buf, 0, packet.getLength());
}
TCP通信
面向连接,可靠通信,三次握手,四次挥手
java.net.Socket
类实现TCP通信,基于IO流管道通信构造器 说明 public Socket(String host, int port) 根据指定服务器ip、端口号请求与服务器建立连接,连接通过获得了客户端的socket 方法 说明 public OutputStream getOutputStream() 获得字节输出流对象 public InputStream getInputStream() 获得字节输入流对象 一发一收
客户端(发送)
1
2
3
4
5
6
7
8
9
10// 1. 创建Socket管道对象,请求与服务端的Socket连接
Socket socket = new Socket("ip", 8080);
// 2. 从socket管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3. 特殊数据流
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(1);
dos.writeUTF("要发送的UTF编码字符");
// 4. 释放资源
socket.close();服务端(接收)
服务端是通过
java.net.ServerSocket
实现的构造器 说明 public ServerSocket(int port) 为服务端程序注册端口 方法 说明 public Socket accept() 阻塞等待客户端的连接请求,与客户端连接成功后,返回服务端侧的Socket对象 1
2
3
4
5
6
7
8
9
10
11// 1. 创建一个ServerSocket对象
ServerSocket ss = new ServerSocket(8080);
// 2. 等待客户端连接
Socket socket = ss.accept();
// 3. 获取输入流,读取客户端发送的信息
InputStream is = socket.getInputStream();
// 4. 将字节输入流包装为特殊数据输入流
DataInputStream dis = new DataInputStream(is);
// 5. 读取数据
int data1 = dis.readInt();
String data2 = dis.readUTF();多发多收
客户端(发送)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 1. 创建Socket管道对象,请求与服务端的Socket连接
Socket socket = new Socket("127.0.0.1", 8080);
// 2. 从socket管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3. 特殊数据流
DataOutputStream dos = new DataOutputStream(os);
// 4. while(True)接输入数据
Scanner sc = new Scanner(System.in);
while(True){
String msg = sc.nextLine();
if(msg.equals("exit")){...}
dos.writeUTF();
dos.flush();
}
// 4. 释放资源
socket.close();服务端(接收)
只要在读数据时用死循环即可
TCP同时接收多客户端消息❗
问题:上述所有的通信方法,服务端不能支持接收多个客户端的消息
思路:主线程不断接收客户端管道,每接收一个管道,便交由一个独立的子线程专门处理该管道的消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24main{
Socket socket = ss.accept();
new ServerReader(socket).start();
}
public class ServerReader extends Thread{ // Thread实现了Runnable,继承Thread相等于实现了Runnable
private Socket socket;
public ServerReader(Socket socket){
this.socket = socket;
}
@Override
public void run(){
// 读取管道的消息
try{
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while(true){
String msg = dis.readUTF();
}
} catch (Exception e){
e.printStackTrace();
sout("服务端下线了");
}
}
}每次请求都开一个新线程,资源浪费,如何解决?
短连接的业务场景,如B/S架构,可以考虑使用线程池进行优化
1
2
3
4
5
6
7
8
9ServerSocket ss = new ServerSocket(8080);
// 创建线程池
ExecutorService pool = new ThreadPoolExecutor(corePoolSize:3, maximumPoolSize:10, keepAliveTime:10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
while(True){
Socket socket = ss.accept();
// 把客户端管道包装成一个任务交给线程池处理
pool.execute(new ServerReader(socket));
}
高级技术
Junit单元测试
针对最小的功能单元:方法function,编写测试代码进行测试
为需要测试的业务类,定义对应的测试类,并为每个业务方法编写对应的测试方法(要求:public,无参,无返回值,@Test注解)
在测试方法中编写测试用例,要尽可能全面覆盖做断言
Assert
,对结果进行勘误反射
定义
加载类,并允许以编程的方式解剖类中的各种成分(如成员变量/方法/构造器)
步骤
① 获取类对象
Class
Class c1 = 类名.class
调用Class提供的方法:public static Class forName(String package)
Object提供的方法:public Class getClass(); Class c = 对象.getClass();
② 获取类的构造器对象Constructor
Constructor con = c1.
getDeclaredConstructor()
获取某类的构造器对象之后,可以拿构造器再创建对象,功能上等于Dog dog = new Dog(),也就是反射
Dog dog = (dog)con.newInstance()
③ 获取类的成员变量对象Field
Field[] fields = c1.
getDeclaredFields()
获取的成员变量对象可以为其设置值,也可以获取值 field.set(dog, "1")
,String hobby = (String)field.get(dog)
④ 获取类的成员方法对象Method
Method[] methods = c1.
getDeclaredMethods()
method.invoke(dog)
如何应用
得到一个类的全部成分,并操作
破坏封装性(暴力反射)
绕过泛型的约束适合做Java的框架,主流框架都会用反射设计功能
注解
让调用程序根据注解信息来决定如何执行此程序
注解可以加在类、构造器、方法、成员变量、参数等上
自定义注解
1
2
3
4
5
6
7
8
9
10
11public @interface 注解名 {
String str();
int age() default 10;
String[] address();
}
@注解名1(str="str", age=1, address={"",""})
// value是一个特殊属性,使用时若只有一个value属性,则写注解时可以不写属性名
public @interface 注解名1 {
String value();
}
@注解名1("str")注解原理
注解本身是一个接口,继承自
Annotation
类,我们定义的注解成员,实际上是抽象方法元注解
1
2
3@元注解
@元注解
public @interface 注解(){}常见的元注解 说明 @Target(ElementType.xx) 声明被修饰的注解能在哪些位置生效
1. TYPE ,类 / 接口
2. FIELD ,成员变量
3. METHOD ,成员方法
4. PARAMETER,方法参数
5. CONSTRUCTOR,构造器
6. LOCAL_VARIABLE,局部变量@Retention(RetentionPolicy.xx) 声明注解的保留周期
1. SOURCE ,作用在源码阶段,字节码中不存在
2. CLASS,默认值,作用在字节码中,run时不存在
3. RUNTIME,一直保留到运行阶段(常用)注解解析
判断类、方法、成员变量上是否有注解,并把注解内容解析
先用反射获得Class对象,,因为反射对象都实现了
AnnotationElement
接口,所以他们都有解析注解的能力 AnnotationElement
接口说明 public Annotation[] getDeclaredAnnotations() 获取当前对象上的注解 public T getDeclaredAnnotation(Class annotationClass) 获取指定的注解对象 public boolean isAnnotationPresent(Class annotationClass) 判断当前对象上是否存在某个注解
动态代理
动态代理是一种设计模式
代理
对象实现的任务太多,可以通过代理转移部分功能
代理通过实现接口来获得被代理对象的功能
java.lang.reflect.proxy
类提供了为对象生成代理的方法 ↓
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:指定类加载器,加载生成的代理类
参数二:指定接口,用于指定生成的代理有哪些方法
参数三:指定生成对象的功能1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47public interface starservice(){
void sing(String name);
String dance();
}
public class star implements starservice{
private String name;
@Override
public void sing(String name){
"啦啦啦~".sout;
}
public void dance(String name){
"跳~".sout;
}
}
public class ProxyUtil {
public static starservice createProxy(star s){
starservice proxy = (starservice)Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
new Class[]{starservice.class}或者s.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Obejcet invoke(Object proxy, Method method, Object[] args) throws Throwable{
// 参数1:proxy接收到的对象本身
// 参数2:method代表正在被代理的方法 sing/dance
// 参数3:args代表正在被代理方法的参数
String methodName = method.getName();
// 代理功能
if("sing".equals(methodName)){...}
// 原对象功能
Object res = method.invoke(s, args);
return res;
}
});
return proxy;
}
}
main{
Star star = new Star("刘德华");
// 创建代理对象
starservice star = ProxyUtil.createProxy(star);
// 代理对象proxy底层会调用重写的invoke方法
proxy.sing("歌名");
proxy.dance();
}