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变成Vdefault <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 通过对象名引用
创建一个函数式接口
@FunctionalInterfacepublic 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)); //使用Lambdaint number2 = method(-10, Math::abs); //使用方法引用System.out.println(number1);System.out.println(number2);}}
5.3 通过super引用
定义一个函数式接口
@FunctionalInterfacepublic interface Greetable {void greet();}
定义一个父类
public class Human {public void sayHello(){System.out.println("Hello, I am Human.");}}
子类进行方法引用
public class Man extends Human {@Overridepublic void sayHello() {System.out.println("Hello, I am Man.");}public void method(Greetable g) {g.greet();}public void show() {//1 使用Lambdamethod(()->{Human h = new Human();h.sayHello();});//2 使用supermethod(() -> super.sayHello());//3 使用方法引用method(super::sayHello);}public static void main(String[] args) {new Man().show();}}
5.4 通过this引用
定义一个函数式接口
@FunctionalInterfacepublic 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()); //使用lambdamarry(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;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';}}
创建一个返回Person类对象的接口
@FunctionalInterfacepublic 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));}}
数组的构造器引用
创建一个定义数组的函数式接口
@FunctionalInterfacepublic 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);}}}
