Java 回顾
Java 8 (又称之为 jdk 1.8)是 Java 语言开发的一个主要版本。
Java 8 是Oracle 公司于2014年3月发布,可以看成是自 Java 5 以来最具革命性的版本。 Java 8 为 Java 语言、编译器、类库、开发工具与 JVM 带来了大量的新特性。
第一章、Lambda 表达式
Lambda 是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);
可以写出更加简洁、更灵活的代码;
作为一种更紧凑的代码风格,使得 Java 语言表达能力得到提升
1、Lambda 表达式的语法
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
- 左侧:指定了 Lambda 表达式需要的所有参数
- 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能
口诀:
- 写死小括号,拷贝右箭头,落地大括号
- 左右遇一括号省
- 左侧推断类型省
2、类型推断
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
3、几种语法格式
1.无参数,无返回值
public class Test {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello World!");
};
task.run();
}
}
2.有一个参数,无返回值
@Test
public void test01() {
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello World!");
}
3.有两个以上的参数,有返回值,并且Lambda体中有多条语句
@Test
public void test02() {
Comparator<Integer> comparator = (o1, o2) -> {
System.out.println(o1 - o2);
return Integer.compare(o1, o2);
};
comparator.compare(10, 20);
}
4、有两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写)
@Test
public void test03() {
BiConsumer<String, String> consumer = (s1, s2) -> System.out.println(s1 + s2);
consumer.accept("Hello ", "World!");
}
5.若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
Comparator<Integer> comparator = (o1, o2) -> o1.compareTo(o2);
6、Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x, y);
4、函数式接口
Java 内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 | T | void | 对类型T的对象应用操作:void accept(T t) |
Supplier 供给型接口 | 无 | T | 返回类型为T的对象:T get() |
Function<T, R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t) |
Predicate 断言型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t) |
消费型接口
//1、消费型接口
@Test
public void test01() {
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello World!");
}
供给型接口
//2、供给型借口
@Test
public void test02() {
Supplier<String> supplier = () -> new String("Hello World!");
System.out.println(supplier.get());
}
函数型接口
//3、函数型接口
@Test
public void test03() {
Function<String, String> function = s -> s + "World!";
System.out.println(function.apply("Hello "));
}
断言型接口
//4、断言型接口
@Test
public void test04() {
Predicate<String> predicate = s -> s.contains("Do you love me ?");
System.out.println(predicate.test("Do you love me ?") ? true : false);
}
5、方法引用
若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用(可以将方法理解为 Lambda 表达式的另外一种表现形式)
语法格式:
- 对象 :: 实例方法
- 类名 :: 静态方法
- 类名 :: 实例方法
注意:
- 方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致
- 若 Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName :: MethodName
1.对象 :: 实例方法
public class Test01 {
private Student student = new Student("牛马", 18);
//对象的引用 :: 实例方法名
@Test
public void test01() {
PrintStream out = System.out;
Consumer<String> consumer = out::println;
consumer.accept("Hello World");
}
@Test
public void test02() {
Supplier<String> supplier = student::getName;
System.out.println(supplier.get());
}
}
2.类名 :: 静态方法
public class Test02 {
//类名 :: 静态方法
@Test
public void test01() {
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!");
}
@Test
public void test02() {
Comparator<Integer> comparator = Integer::compare;
int res = comparator.compare(10, 20);
System.out.println(res);
}
@Test
public void test03() {
BiFunction<Integer, Integer, Integer> function = Math::max;
Integer res = function.apply(1, 2);
System.out.println(res);
}
}
3.类名 :: 实例方法
public class Test03 {
//类名 :: 实例方法
@Test
public void test01() {
Comparator<String> comparator = String::compareTo;
int res = comparator.compare("abc", "abc");
System.out.println(res);
}
@Test
public void test02() {
BiPredicate<String, String> predicate = String::equals;
boolean res = predicate.test("Hello", "Hello");
System.out.println(res);
}
}
构造器引用和数组引用
public class Test04 {
@Test
public void test01() {
Function<Integer, int[]> function = int[]::new;
int[] nums = function.apply(10);
System.out.println(nums);
System.out.println("数组的长度为:" + nums.length);
}
@Test
public void test02() {
BiFunction<String, Integer, Student> function = Student::new;
Student student = function.apply("张三", 18);
System.out.println(student);
}
@Test
public void test03() {
Function<String,Student> function = Student::new;
Student student = function.apply("张三");
System.out.println(student);
}
}
第二章、强大的Steam API
产生一个全新的流,和原来的数据源没有关系(数据源不受影响,类似于不可变类的设计)
1、概述
流(Stream)到底是什么?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲得是数据,流讲的是计算。
注意:
-
Stream 自己不会存储元素
-
Stream 不会改变源对象。相反,会返回一个持有结果的信Stream
-
Stream 操作时延迟执行的。这意味着它们会等到需要的结果的时候才执行
2、操作 Stream 流
步骤:
- 创建 Stream
- 一个数据源(如:集合、数组),获取一个流
- 中间操作
- 一个中间操作链,对数据源的数据进行处理
- 终止操作
- 一个终止操作,执行中间操作链,并产生结果
1. 创建 Stream 流
Java 8 中的 Collection 接口被扩展,提供了两个获取流的方法
- default Stream stream() : 返回一个顺序流(串行)
- default Stream parallelStream() : 返回一个并行流
public class MyTest {
@Test
public void test01() {
// 集合获取顺序流
List<Integer> list = new ArrayList<>();
Stream<Integer> stream1 = list.stream();
// 集合获取并行流
Stream<Integer> stream2 = list.parallelStream();
// 数组获取流
int[] nums = {1, 2};
IntStream stream3 = Arrays.stream(nums);
// 通过stream静态方法获取流
Stream<Integer> stream4 = Stream.of(1, 2, 3);
// 获取无限流
Stream<Integer> iterate = Stream.iterate(0, t -> t + 1);
// 获取生成流
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
}
}
2. 中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
Employee 类
public class Employee {
private String name;
private Integer age;
private Double salary;
public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (name != null ? !name.equals(employee.name) : employee.name != null) return false;
if (age != null ? !age.equals(employee.age) : employee.age != null) return false;
return salary != null ? salary.equals(employee.salary) : employee.salary == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (age != null ? age.hashCode() : 0);
result = 31 * result + (salary != null ? salary.hashCode() : 0);
return result;
}
public static List<Employee> getEmployeeList() {
List<Employee> list = new ArrayList<>();
list.add(new Employee("赵云", 31, 5000.1));
list.add(new Employee("张飞", 34, 7000.3));
list.add(new Employee("张飞", 34, 7000.3));
list.add(new Employee("关羽", 24, 7200.3));
list.add(new Employee("项羽", 32, 300.3));
list.add(new Employee("刘邦", 24, 1000.3));
list.add(new Employee("典韦", 14, 2000.3));
list.add(new Employee("郭嘉", 64, 6000.3));
list.add(new Employee("程昱", 54, 70.3));
return list;
}
}
**(1)筛选 / 切片 **
方法 | 描述 |
---|---|
limit | 截断流,使元素不超过给定数量 |
skip(n) | 跳过元素,返回一个舍弃了前 n 个元素的流;如果流中元素不足 n 个,则返回一个空流; |
distinct | 筛选,通过流所生成的 hashCode() 与 equals() 去除重复元素 |
filter | 接受 Lambda,从流中排除某些元素 |
@Test
public void test01() {
List<Employee> list = Employee.getEmployeeList();
// 限制元素个数
list.stream().limit(3).forEach(System.out::println);
System.out.println("-------------------------------------");
// 跳过元素
list.stream().skip(1).distinct().forEach(System.out::println);
System.out.println("-------------------------------------");
// filter 过滤元素
list.stream().filter(e-> e.getSalary() > 3000).forEach(System.out::println);
}
**(2)映射 **
方法 | 描述 |
---|---|
map(Function f) | 接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
mapToDouble(ToDoubleFunction f) | 接受一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream |
mapToInt(ToIntFunction f) | 接受一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream |
mapToLong(ToLongFunction f) | 接受一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream |
floatMap(Function f) | 接受一个函数作为参数,将流中的每个值都换为另一个流,然后把所有流连接成一个流 |
@Test
public void test02() {
List<Employee> employees = Employee.getEmployeeList();
// 映射为double流
employees.stream().mapToDouble(Employee::getSalary).forEach(System.out::println);
// 映射为int流
employees.stream().mapToInt(e -> e.getAge()).forEach(System.out::println);
// 从Employee流映射为Person流
employees.stream().map(employee -> new Person(employee.getName(), employee.getAge())).forEach(System.out::println);
}
**(3)排序 **
方法 | 描述 |
---|---|
sorted() | 自然排序 |
sorted(Comparator c) | 定制排序 |
@Test
public void test03() {
List<Employee> employees = Employee.getEmployeeList();
// 先转换为Person流,Person实现了Comparable接口无需提供比较器
employees.stream().map(e -> new Person(e.getName(), e.getAge())).sorted().forEach(System.out::println);
// 定制排序
employees.stream().sorted((s1, s2) -> s1.getAge() - s2.getAge()).forEach(System.out::println);
}
终止操作
**(1)查找 / 匹配 **
方法 | 描述 |
---|---|
allMatch | 检查是否匹配所有元素 |
anyMatch | 检查是否至少匹配一个元素 |
noneMatch | 检查是否没有匹配所有元素 |
findFirst | 返回第一个元素 |
findAny | 返回当前流中的任意元素 |
count | 返回流中元素的总个数 |
max | 返回流中的最大值 |
min | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代) |
@Test
public void test04() {
List<Employee> employees = Employee.getEmployeeList();
// 判断所有员工的年龄是否都小于34
boolean res = employees.stream().allMatch(e -> e.getAge() < 34);
System.out.println(res);
// 判断是否有员工的薪水大于10000
boolean flag = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(flag);
// 获取第一个以"马"字开头的员工
Optional<Employee> first = employees.stream().filter(e -> e.getName().startsWith("马")).findFirst();
System.out.println(first);
// 获取年龄最小的员工
Optional<Person> min = employees.stream().map(e -> new Person(e.getName(), e.getAge())).min(Person::compareTo);
System.out.println(min);
}
**(2)规约 **
方法 | 描述 |
---|---|
reduce(T identify, BinaryOperator b) | 可以将流中的主句反复结合起来,得到一个值。返回T |
reduce(BinaryOperator b) | 可以将流中的数据反复结合起来,得到一个值,返回Optional |
@Test
public void test05() {
List<Integer> list = Arrays.asList(1, 2, 3, 4);
Optional<Integer> res1 = list.stream().reduce(Integer::sum); // 10
System.out.println(res1);
Integer reduce = list.stream().reduce(2, (a, b) -> a + b); // 12
System.out.println(reduce);
}
**(3)规约 **
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
@Test
public void test06() {
List<Employee> list = Employee.getEmployeeList();
List<Person> res = list.stream().filter(employee -> employee.getAge() < 34).map(e -> new Person(e.getName(), e.getAge())).sorted().collect(Collectors.toList());
res.forEach(System.out::println);
}
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例
第三章、Optional 类
1. 定义
Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或者不存在,原来用一个 null 表示一个值不存在,现在用 Optional 可以更好表达这个概念,并且可以避免空指针异常
2. 常用方法
方法 | 描述 |
---|---|
Optional.of(T t) | 创建一个 Optional 实例 |
Optional.empty(T t) | 创建一个空的 Optional 实例 |
Optional.ofNullable(T t) | 若 t 不为null,创建 Optional 实例,否则空实例 |
isPresent() | 判断是否包含某值 |
orElse(T t) | 如果调用对象包含值,返回值,否则返回t |
orElseGet(Supplier s) | 如果调用对象包含值,返回该值,否则返回s获取的值 |
map(Function f) | 如果有值对其处理,并返回处理后的 Optional,否则返回 Optioal.empty() |
flatmap(Function mapper) | 与map相似,要求返回值必须是 Optional |
第四章、IO 流与网络编程
1、IO 流的分类
- 按操作数据单位不同分为:字节流(8bit)、字符流(16bit)
- 按数据流的流向不同分为:输入流、输出流
- 按流的角色的不同分为:节点流、处理流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
- Java 的 IO 流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的
- 由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀
- 缓冲流作用在结点流的基础上
2、IO 流的体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4ZVtrNd-1683859372003)(C:\Users\12086\AppData\Roaming\Typora\typora-user-images\image-20230108171555064.png)]
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream | BufferedInputStream |
OutputStream | FileOutputStream | BufferedOutputStream |
Reader | FileReader | BufferedReader |
Writer | FileWriter | BufferedWriter |
3、常用类的 API 使用
3.1 FileReader的基本读入操作
// 将hello.txt文件读入程序中,并输出到控制台
public class FileReaderTest {
public static void main(String[] args) {
File file = new File("demo01\\hello.txt");
FileReader fileReader = null;
try {
fileReader = new FileReader(file);
int data;
while ((data = fileReader.read()) != -1) {
System.out.print((char)data);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流的时候,要判断流是否为空
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
说明:
- read()的理解:返回一个读入的字符,如果到达文件末尾,返回-1
- 异常的处理:为了保证流资源一定可以执行关闭操作,需要使用try-catch-finally处理
- 读入的文件一定要存在,否则会报FileNotFoundException
3.2 FileWriter的基本写入操作
public class FileWriterTest {
public static void main(String[] args) throws IOException {
FileWriter fileWriter = null;
try {
File file = new File("demo01\\hello1.txt");
fileWriter = new FileWriter(file);
String msg = "I have a dream.";
fileWriter.write(msg);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileWriter != null) {
fileWriter.close();
}
}
}
}
说明:
- 对文件的写入操作,对应的 File 可以不存在,并不会报异常
3.2 FileInputStream的基本读入操作
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
try {
File file = new File("demo01\\hello.txt");
fis = new FileInputStream(file);
byte[] bytes = new byte[5];
int len;
while ((len = fis.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
fis.close();
}
}
}
}
说明:使用字节流FileInputStream处理文本文件,可能出现乱码
3.4 FileOutputStream的基本写入操作
public class FileOutputStreamTest {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
File file = new File("demo01\\hello.txt");
fos = new FileOutputStream(file, true);
fos.write("\nI love you.".getBytes());
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4、其他一些流
- 转换流:实现字节流和字符流的转化
- 数据流:用于读取或写出基本数据类型的变量或字符串
- DataInputStream / DataOutputStream
- 注:读取不同类型的数据的顺序要与当初写入文件时保存的数据一致
- 标准的输入输出流
- System.in : 标准的输入流,默认从键盘输入
- System.out : 标准的输出流,默认从控制台输出
- 修改默认的输入输出行为:System类的setIn / setOut 方式重新指定输入和输出的流
- 打印流
- PrintStream 和 PrintWriter
- 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
- System.out 返回的是 PrintStream 的实例
- 对象流
- ObjectInputStream / ObjectOutputStream
- 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原出来
- 注意:对象流不能序列化 static 和 transient 修饰的成员变量
5、网络编程概述
计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源
网络编程的目的:
直接或间接地通过网络协议与其他计算机实现数据交换,进行通讯
网络编程中的两个主要问题:
- 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 找到主机后如何可靠高效地进行数据传输
如何实现网络中的主机互相通信?
- 通信双方地址
- IP
- 端口号
- 一定的规则(网络通信协议)
- OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
- TCP / IP 参考模型(或
TCP / IP
协议):事实上的国际标准
6、IP 和 端口号
端口号标识正在计算机上运行的进程(程序)
- 不同的进行有不同的端口号
- 被规定为一个16位的整数0~65535
- 端口分类:
公认端口
:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Talnet占用端口23)注册端口
:1024~49151.分配给用户进程或应用进程(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)动态 / 私有端口
:49152~65535
- 端口与IP地址的组合得出一个网络套接字:Socket
7、网络协议概述
7.1 TCP / IP 协议簇
- 传输协议中有两个非常重要的协议:
- 传输控制协议TCP(Transmission Control Protocol)
- 用户数据报协议UDP(User Datagram Protocol)
- TCP / IP 以其中两个主要协议:传输控制协议TCP和网络互联协议IP而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议
- IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信
- TCP / IP 协议模型从更实用的角度出发,形成了高效的四层体系结构,即
物理链路层
、IP层
、传输层和应用层
7.2 TCP 和 UDP
TCP 协议:
- 实用TCP协议前,需要先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端
- 在连接中可以进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP 协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送方不管对方是否准备好,接受方收到也不确认,所以是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
8、UDP编程
- UDP数据报通过数据报套接字 DatagramSocket 发送和接受,系统不保证 UDP 数据报一定能够安全送到安全目的地,也不能确定什么时候可以抵达
- DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接受端的 IP 地址和端口号
- UDP 协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接
9、URL 编程
URL (Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址
URI、URL、URN的区别
- URI(同一资源标识符):用来唯一的标识一个资源
- URL(统一资源定位符):它是一种具体情况的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源
- URN(统一资源命名):是通过名字来标识资源,比如mailto:java-net@java.sum.com
- URI 是以一种抽象的,高层次概念定义统一资源标识,而 URL 和 URN 则是具体的资源标识的方式
- URL 和 URN 都是一种 URI
和应用层`
7.2 TCP 和 UDP
TCP 协议:
- 实用TCP协议前,需要先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端
- 在连接中可以进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP 协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送方不管对方是否准备好,接受方收到也不确认,所以是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
8、UDP编程
- UDP数据报通过数据报套接字 DatagramSocket 发送和接受,系统不保证 UDP 数据报一定能够安全送到安全目的地,也不能确定什么时候可以抵达
- DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接受端的 IP 地址和端口号
- UDP 协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接
9、URL 编程
URL (Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址
URI、URL、URN的区别
- URI(同一资源标识符):用来唯一的标识一个资源
- URL(统一资源定位符):它是一种具体情况的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源
- URN(统一资源命名):是通过名字来标识资源,比如mailto:java-net@java.sum.com
- URI 是以一种抽象的,高层次概念定义统一资源标识,而 URL 和 URN 则是具体的资源标识的方式
- URL 和 URN 都是一种 URI