1 网络编程入门
1.1 软件结构
- C/S结构 :Client/Server结构,常见程序有QQ,迅雷等软件
B/S结构 :Browser/Server结构,常见浏览器有谷歌、Safari等
1.2 网络通信协议
网络通信协议 :计算机网络中,多台计算机实现连接必须遵守网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一的规定
TCP/IP协议 :是Internet最基本、最广泛的协议,定义了计算机如何连入因特网,以及数据传输的标准,采用4层分层模型,每一层都呼叫它的下一层嗦提供的协议来完成自己的需求
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接,也就是不会相互确认对方的身份。
- 数据被限制在64kb以内,超出此范围一次就不可发送
优点
- 消耗资源小,通信效率高,通产用于音频、视频和普通数据的传输如视频会议等,这种情况即使丢失一两个数据包也不会对接收结果产生影响
缺点
-
TCP
传输控制协议,TCP协议是面向连接的通信协议,提供了两台计算机之间可靠无差错的数据传输
三次握手 TCP协议准备就单,客户端与服务器之间的三次交互 第一次握手,客户端向服务器端发出连接请求,等待服务器确认
- 第二次握手,服务器端向客户端回送响应,通知客户端收到了连接请求
-
1.3 网络编程三要素
协议
IP地址
IPv4:4个字节表示计算机的地址
192.168.65.10
- IPv6:16个字节表示的计算机地址,每两个字节一组,分成8组十六进制数
ABCD:EF01:2345::6789
-
端口号
是一个逻辑端口,无法直接看到,当使用的网络软件一打开, 操作系统就会为网络软件分配一个随机的端口号
- 端口号由两个字节组成,取值范围在0-65535之间
- 1024之前的端口号已经被系统分配给已知的网络软件,网络软件的端口号不可重复,因此无法使用
常用端口号
- 80 网络端口(http)
- 3306 MySql端口
- 1521 Oracle端口
-
2 TCP通信程序
两端通信的步骤
服务端需要事先启动,等待客户端的连接
- 客户端主动连接服务端,连接成功才能通信,服务端不可以主动连接客户端
java中的类
- 客户端
java.net.Socket
向服务端发出连接请求 - 服务端
java.net.ServerSocket
开启一个服务,等待客户端的连接
服务器需要明确
- 多个客户端同时和服务器进行通信,服务器使用
accept
方法获取到请求的客户端对象 服务器没有IO流,服务器使用每个客户端Socket中提供的的IO流和客户端进行交互
2.1 Socket
此类实现客户端套接字,套接字是两台机器间通信的端点
1.构造方法public Socket(String host, int port)
创建一个套接字并将其连接到指主机上的指定端口号
2.成员方法
getOutputStream()
返回此套接字的输出流getInputStream()
返回此套接字的输入流-
2.2 ServerSocket
此类实现服务器套接字
1.构造方法 public ServerSocket(int port)
2.成员方法
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("src/Network/Upload/社会主义核心价值观.txt");
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
socket.shutdownOutput(); // 文件上传完毕后,给文件写上一个结束标记
InputStream is = socket.getInputStream();
while ((len = is.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, len));
}
fis.close();
socket.close();
}
}
服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
File dir = new File("/Users/stone/Musou/Java/Grammar/Upload");
if(!dir.exists()){
dir.mkdir();
}
FileOutputStream fos = new FileOutputStream(new File(dir,"社会主义核心价值观.txt"));
byte[] bytes = new byte[1024];
int len = 0;
while((len=is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
OutputStream os = socket.getOutputStream();
os.write("上传成功".getBytes(StandardCharsets.UTF_8));
fos.close();
socket.close();
}
}
2.4 模拟B/S
3 函数式编程
有且只有一个抽象方法的接口叫做函数式接口,函数式接口既可以作为参数,也可以作为返回值
3.1 性能浪费
使用Lambda有一个特点:延迟加载,因此可以被应用在日志的性能上面
创建一个拼接字符串的函数式接口
public interface MessageBuilder {
String buildMessage();
}
使用lambda表达式传递参数,只有在level为1时才会拼接字符串,进行日志的打印。使用普通方法传递参数,则将字符串先行拼接后再进行传参,若其level不为1,则会存在性能浪费的情况
public class Logger {
public static void showLog(int level, MessageBuilder mb){
if(level == 1){
System.out.println(mb.buildMessage());
}
}
public static void main(String[] args) {
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
showLog(2,()-> {
return msg1 + msg2 + msg3;
});
}
}
3.2 常见的函数式接口
在声明前添加@FunctionalInterface来检验是否为函数式接口
Supplier
java.util.function.Supplier<T>
仅包含一个 T get()
方法获取泛型指定类型的数据
import java.util.function.Supplier;
public class DemoSupplier {
public static int getMax(Supplier<Integer> sup) {
return sup.get();
}
public static void main(String[] args) {
int[] arr = {23, 12, 25, -3, 1, 6};
int maxNum = getMax(() -> {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) max = arr[i];
}
return max;
});
System.out.println(maxNum);
}
}
Consumer
Consumer<T>
中的 accept(T t)
方法消费一个对象
默认方法andThen
// 可以把两个consumer接口组合到一起,再对对象进行消费
/*
Consumer<String> con1;
Consumer<String> con2;
String s = "Hello";
con1.accept(s);
con2.accept(s);
//等价于 con1.andThen(con2).accept(s);
*/
default Consumer<T> andThen(Consumer<? super T> after){
Objects.requireNonNull(after);
return (T t) -> {accept(t); after.accept(t);};
}
使用
import java.util.function.Consumer;
public class DemoConsumer {
public static void method(String s, Consumer<String> con1, Consumer<String> con2){
con1.andThen(con2).accept(s);
}
public static void main(String[] args) {
method("Hello",(t)->System.out.println(t.toUpperCase()),(t)->System.out.println(t.toLowerCase()));
}
}
应用:格式化打印信息
import java.util.function.Consumer;
public class DemoConsumer02 {
public static void main(String[] args) {
String[] array = {"迪丽热巴,女", "古力娜扎,女", "玛尔扎哈,男"};
printInfo((s) -> {
System.out.print("姓名:" + s.split(",")[0]);
}, (s) -> {
System.out.println(",性别:" + s.split(",")[1]);
}, array);
}
public static void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info);
}
}
}
Predicate
Predicate<T>
包含一个抽象方法 boolean test(T t)
用于条件判断的场景
默认方法
//此方法用于判断两个条件同时满足
default Predicate<T> and(Predicate<? super T> other){
Objects.requireNonNull(other);
return(t) -> this.test(t) && other.test(t);
}
//此方法只需满足条件之一
default Predicate<T> or(Predicate<? super T> other){
Objects.requireNonNull(other);
return(t) -> this.test(t) || other.test(t);
}
//此方法用于取反
default Predicate<T> negate(){
return(t) -> !t.test();
}
Function
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据, R apply(T t)
根据T的参数获取类型R的结果
默认方法
//此方法用于将T变成R,再将R变成V
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
练习:将一个字符串转化为整数+10后返回字符串
package FunctionalInterface;
import java.util.function.Function;
public class DemoFunction {
public static String change(Function<String, Integer> f1, Function<Integer, String> f2, String s) {
// return f2.apply(f1.apply(s));
return f1.andThen(f2).apply(s);
}
public static void main(String[] args) {
String s = "1234";
String r = change((str) -> Integer.parseInt(str) + 10, (i) -> i.toString(), s);
System.out.println(r);
}
}
4 Stream流
4.1 传统方式遍历集合
package Stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DemoForEach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张三丰", "张强");
List<String> listA = new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")) listA.add(s);
}
List<String> listB = new ArrayList<>();
for (String s : listA) {
if(s.length() == 3) listB.add(s);
}
for (String s : listB) {
System.out.println(s);
}
}
}
4.2 Stream流遍历集合
使用list.stream().filter()方法
//代码一下就简化了
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DemoStream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张三丰", "张强");
list.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3).forEach(name -> System.out.println(name));
}
}
4.3 流式思想
Stream(流)是一个来自数据源的元素队列,是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
特点
- 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
- 数据源 :流的来源,可以是集合,数组等
Stream流属于管道流,只能被消费(使用)一次,方法调用完毕,数据则会流转到下一个Stream上
获取流
java.util.stream.Stream<T>
是Java8新加入的最常用的流接口(并不是一个函数式接口)
获取流的方式所有的
Collection
集合都可以通过Stream
默认方法获取流Stream
接口的静态方法of
可以获取数组对应的流
根据Collection获取流 Collection
接口的default方法 stream
可以用来获取流,所以其所有实现类均可以获取流
import java.util.*;
import java.util.stream.Stream;
public class DemoGetStream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String, String> map = new HashMap<>();
Set<String> keySet = map.keySet();
Collection<String> values = map.values();
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<String> stream3 = keySet.stream();
Stream<String> stream4 = values.stream();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
}
}
根据Stream接口获取流
import java.util.stream.Stream;
public class DemoGetStream02 {
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> stream2 = Stream.of(arr);
}
}
常用方法
流模型常用的API可以被分为两种
- 延迟方法 返回值类型仍然是
Stream
接口自身类型的方法,因此支持链式操作Stream<T> filter(Predicate<? super T> predicate)
将一个流转换成另一个子集流Stream<R> map(Function<? super T, ? extends R> mapper)
将流中元素映射到另一个流中,如:
Stream stream = Stream.of("1", "2", "3", "4");
stream.map((String s) -> Integer.parseInt(s)).forEach(i -> System.out.println(i));
Stream<T> limit(long maxSize)
对流进行截取,取用前n个Stream<T> skip(long n)
跳过前n个元素(如果当前流的长度小小于等于n,则返回长度为0的空流)static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
将流a和流b合并- 终结方法 返回值类型不再是
Stream
接口自身类型的方法,因此不再支持链式调用
- 终结方法 返回值类型不再是
long coun()
返回此流中的元素个数forEach
用于遍历流中的数据,参数是一个Consumer
接口5 方法引用
双冒号::
为引用运算符,它所在的表达式被称为方法引用,如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者,以下两种写法完全等效
- Lambda表达式写法
s -> System.out.println(s);
- 方法引用写法
System.out::println
注意:Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
5.1 通过对象名引用
创建一个函数式接口
@FunctionalInterface
public interface Printable {
void print(String str);
}
创建一个存在的方法
public class MethodReferenceObject {
public void printUppercase(String str){
System.out.println(str.toUpperCase());
}
}
使用方法引用优化Lambda
public class ObjectMethodReferenceTest {
public static void printString(String str, Printable p){
p.print(str);
}
public static void main(String[] args) {
String str = "world";
MethodReferenceObject obj = new MethodReferenceObject();
printString(str, obj::printUppercase);
}
}
5.2 通过类名引用
创建一个函数式接口
public interface Calcable {
int calAbs(int number);
}
通过类名进行方法引用
public class DemoTest {
public static int method(int number, Calcable c) {
return c.calAbs(number);
}
public static void main(String[] args) {
int number1 = method(-20, (n) -> Math.abs(n)); //使用Lambda
int number2 = method(-10, Math::abs); //使用方法引用
System.out.println(number1);
System.out.println(number2);
}
}
5.3 通过super引用
定义一个函数式接口
@FunctionalInterface
public interface Greetable {
void greet();
}
定义一个父类
public class Human {
public void sayHello(){
System.out.println("Hello, I am Human.");
}
}
子类进行方法引用
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("Hello, I am Man.");
}
public void method(Greetable g) {
g.greet();
}
public void show() {
//1 使用Lambda
method(()->{
Human h = new Human();
h.sayHello();
});
//2 使用super
method(() -> super.sayHello());
//3 使用方法引用
method(super::sayHello);
}
public static void main(String[] args) {
new Man().show();
}
}
5.4 通过this引用
定义一个函数式接口
@FunctionalInterface
public interface Richable {
void buy();
}
方法引用
public class Husband {
public void buyHouse() {
System.out.println("北京二环内买一套四合院");
}
public void marry(Richable r) {
r.buy();
}
public void soHappy() {
marry(() -> this.buyHouse()); //使用lambda
marry(this::buyHouse); //使用方法引用
}
public static void main(String[] args) {
new Husband().soHappy();
}
}
5.5 构造方法引用
类的构造器引用
创建一个Person类
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
创建一个返回Person类对象的接口
@FunctionalInterface
public interface PersonBuilder {
Person buildPerson();
}
使用构造器引用
public class Demo {
public static void main(String[] args) {
printName("迪丽热巴", (String name) -> {
return new Person(name);
});//使用lambda表达式
// 使用类的构造器引用
printName("古力娜扎", Person::new);
}
private static void printName(String name, PersonBuilder pb) {
System.out.println(pb.buildPerson(name));
}
}
数组的构造器引用
创建一个定义数组的函数式接口
@FunctionalInterface
public interface ArrayBuilder {
int[] buildArray(int length);
}
使用数组的方法引用
public class DemoArray {
public static int[] createArray(int length, ArrayBuilder ab) {
return ab.buildArray(length);
}
public static void main(String[] args) {
int[] array1 = createArray(10, (len) -> new int[len]); // 使用lambda表达式
int[] array2 = createArray(5, int[]::new); //使用方法引用
for (int i : array1) {
System.out.println(i);
}
for (int i : array2) {
System.out.println(i);
}
}
}