赞
踩
需求分析文档、概要设计文档、详细设计文档、编码和测试、安装和调试、维护和升级
1、开闭原则(Open Close Principle)
对扩展开放对修改关闭,为了使程序的扩展性好,易于维护和升级。
2、里氏代换原则(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现,多使用多态的方式。(父类类型的引用指向子类的对象)
3、依赖倒转原则(Dependence Inversion Principle)
尽量多依赖抽象类或接口而不是具体实现类,对子类具有强制性和规范性
4、接口隔离原则(Interface Segregation Principle)
尽量多使用小接口而不是大接口,避免接口的污染,降低类之间耦合度。
5、迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少与其他实体之间发生相互作用,使系统功能模块相对独立。 高内聚,低耦合。
6、合成复用原则(Composite Reuse Principle)
尽量多使用合成/聚合的方式,而不是继承的方式。
/** * @auther weiwei * @date 2021/5/1 21:08 * @description 开闭原则 */ public abstract class Person { private String name; //private int age; public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public abstract void show(); } public class SubPerson extends Person{ private int age; @Override public void show() { } } public interface Animal { void run(); // 描述奔跑行为的抽象方法 void fly(); // 描述飞行行为的抽象方法 } public interface RunAnimal { void run(); // 描述奔跑行为的抽象方法 } public interface FlyAnimal { void fly(); // 描述飞行行为的抽象方法 } /** * @auther weiwei * @date 2021/5/1 21:21 * @description 接口隔离原则 避免接口的污染,降低类之间耦合度(关联) */ public class Dog implements RunAnimal{ @Override public void run() { } } /** * @auther weiwei * @date 2021/5/2 19:20 * @description 合成复用原则 */ public class A { public void show(){ System.out.println("这是A类中的show方法!"); } } public class B /*extends A*/ { private A a; // 合成复用原则 public B(A a){ this.a = a; } public void test(){ // 调用A类中的show方法,怎么实现? //show(); 继承方式,不推荐 a.show(); } }
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
设计模式就是一种用于固定场合的固定套路。
1、创建型模式——单例设计模式、工厂方法模式、抽象工厂模式、…
2、结构型模式——装饰器模式、代理模式、…
3、行为型模式——模板设计模式、…
单例设计模式主要分为:饿汉式 和 懒汉式,懒汉式需要对多线程进行同步处理。
/** * @auther weiwei * @date 2021/5/2 20:12 * @description 懒汉式 */ public class Singleton { // 2.声明本类类型的引用指向本类类型的对象并使用private static关键字修饰 private static Singleton sin = null; // 1.私有化构造方法,使用private关键字修饰 private Singleton(){} // 3.提供公有的get方法负责将上述对象返回出去,使用public static关键字修饰 public static /*synchronized*/ Singleton getInstance(){ /*synchronized (Singleton.class){ if(null == sin){ sin = new Singleton(); } return sin; }*/ if (null == sin){ synchronized (Singleton.class){ if(null == sin){ sin = new Singleton(); } } } return sin; } }
普通工厂方法模式就是建立一个工厂类,对实现了同一接口的不同实现类进行实例的创建。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVmgZAKi-1620391959360)(E:\MarkDown\拉勾笔记\普通工厂模式类图结构)]
在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,并且可能出现空指针异常。
/** * @auther weiwei * @date 2021/5/2 20:34 * @description 普通工厂类 */ public interface Sender { // 自定义抽象方法来描述发送的行为 void send(); } public class MailSender implements Sender{ @Override public void send() { System.out.println("正在发送邮件..."); } } public class SmsSender implements Sender{ @Override public void send() { System.out.println("正在发送信息..."); } } public class SendFactory { // 自定义成员方法实现对象的创建 public Sender produce(String type){ if ("mail".equals(type)){ return new MailSender(); } if ("sms".equals(type)){ return new SmsSender(); } return null; } } public class SendFactoryTest { public static void main(String[] args) { // 缺点:代码复杂,可读性略差 // 优点:扩展性和可维护性更强! 尤其是在创建大量对象的前提下 // 1.声明工厂类类型的引用指向工厂类类型的对象 SendFactory sf = new SendFactory(); // 2.调用生产方法来实现对象的创建 Sender mail = sf.produce("mail"); //Sender mail = sf.produce("maill"); //空指针异常 // 3.使用对象调用方法模拟发送的行为 mail.send(); System.out.println("------------------------------------"); // 优点:代码简单,可读性强 在创建单个对象时有优势 // 缺点:扩展性和可维护性落差 Sender sender1 = new MailSender(); sender1.send(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bvc9W5tY-1620391959365)(E:\MarkDown\拉勾笔记\多个工厂方法模式)]
在多个工厂方法模式中,为了能够正确创建对象,先需要创建工厂类的对象才能调用工厂类中的生产方法。
/** * @auther weiwei * @date 2021/5/2 20:34 * @description 普通工厂类 */ public interface Sender { // 自定义抽象方法来描述发送的行为 void send(); } public class MailSender implements Sender{ @Override public void send() { System.out.println("正在发送邮件..."); } } public class SmsSender implements Sender{ @Override public void send() { System.out.println("正在发送信息..."); } } public class SendFactory { // 自定义成员方法实现对象的创建 public Sender produce(String type){ if ("mail".equals(type)){ return new MailSender(); } if ("sms".equals(type)){ return new SmsSender(); } return null; } public Sender produceMail(){ return new MailSender(); } public Sender produceSms(){ return new SmsSender(); } } public class SendFactoryTest { public static void main(String[] args) { // 缺点:代码复杂,可读性略差 // 优点:扩展性和可维护性更强! 尤其是在创建大量对象的前提下 // 1.声明工厂类类型的引用指向工厂类类型的对象 SendFactory sf = new SendFactory(); // 2.调用生产方法来实现对象的创建 //Sender mail = sf.produce("mail"); //Sender mail = sf.produce("maill"); //空指针异常 Sender mail = sf.produceMail(); // 3.使用对象调用方法模拟发送的行为 mail.send(); System.out.println("------------------------------------"); // 优点:代码简单,可读性强 在创建单个对象时有优势 // 缺点:扩展性和可维护性落差 Sender sender1 = new MailSender(); sender1.send(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3NrvozlS-1620391959368)(E:\MarkDown\拉勾笔记\静态工厂方法模式)]
工厂方法模式适合:凡是出现了大量的产品需要创建且具有共同的接口时,可以通过工厂方法模式进行创建。
类的创建依赖工厂类,即如果想要拓展程序生产新的产品,就必须对工厂类的代码进行修改,这就违背了开闭原则。
/** * @auther weiwei * @date 2021/5/2 20:34 * @description 普通工厂类 */ public interface Sender { // 自定义抽象方法来描述发送的行为 void send(); } public class MailSender implements Sender{ @Override public void send() { System.out.println("正在发送邮件..."); } } public class SmsSender implements Sender{ @Override public void send() { System.out.println("正在发送信息..."); } } public class SendFactory { // 自定义成员方法实现对象的创建 public Sender produce(String type){ if ("mail".equals(type)){ return new MailSender(); } if ("sms".equals(type)){ return new SmsSender(); } return null; } public static Sender produceMail(){ return new MailSender(); } public static Sender produceSms(){ return new SmsSender(); } } public class SendFactoryTest { public static void main(String[] args) { // 缺点:代码复杂,可读性略差 // 优点:扩展性和可维护性更强! 尤其是在创建大量对象的前提下 // 1.声明工厂类类型的引用指向工厂类类型的对象 //SendFactory sf = new SendFactory(); // 普通工厂模式 // 2.调用生产方法来实现对象的创建 //Sender mail = sf.produce("mail"); //Sender mail = sf.produce("maill"); // 空指针异常 //Sender mail = sf.produceMail(); // 多个工厂方法模式 Sender mail = SendFactory.produceMail(); // 静态工厂方法模式 // 3.使用对象调用方法模拟发送的行为 mail.send(); System.out.println("------------------------------------"); // 优点:代码简单,可读性强 在创建单个对象时有优势 // 缺点:扩展性和可维护性落差 Sender sender1 = new MailSender(); sender1.send(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nptNBNK2-1620391959371)(E:\MarkDown\拉勾笔记\抽象工厂模式)]
/** * @auther weiwei * @date 2021/5/2 20:34 * @description 普通工厂类 */ public interface Sender { // 自定义抽象方法来描述发送的行为 void send(); } public class MailSender implements Sender{ @Override public void send() { System.out.println("正在发送邮件..."); } } public class SmsSender implements Sender{ @Override public void send() { System.out.println("正在发送信息..."); } } /** * @auther weiwei * @date 2021/5/2 21:08 * @description 增加一个发送包裹类 */ public class PacketSender implements Sender{ @Override public void send() { System.out.println("正在发送包裹..."); } } public class SendFactory { // 自定义成员方法实现对象的创建 public Sender produce(String type){ if ("mail".equals(type)){ return new MailSender(); } if ("sms".equals(type)){ return new SmsSender(); } return null; } public static Sender produceMail(){ return new MailSender(); } public static Sender produceSms(){ return new SmsSender(); } } /** * @auther weiwei * @date 2021/5/2 21:02 * @description 抽象工厂模式 */ public interface Provider { // 自定义抽象方法描述产品的生成 Sender produce(); } public class MailSendFactory implements Provider{ @Override public Sender produce() { return new MailSender(); } } public class SmsSendFactory implements Provider{ @Override public Sender produce() { return new SmsSender(); } } public class PacketSendFactory implements Provider{ @Override public Sender produce() { return new PacketSender(); } } public class SendFactoryTest { public static void main(String[] args) { // 缺点:代码复杂,可读性略差 // 优点:扩展性和可维护性更强! 尤其是在创建大量对象的前提下 // 1.声明工厂类类型的引用指向工厂类类型的对象 //SendFactory sf = new SendFactory(); // 普通工厂模式 // 2.调用生产方法来实现对象的创建 //Sender mail = sf.produce("mail"); //Sender mail = sf.produce("maill"); // 空指针异常 //Sender mail = sf.produceMail(); // 多个工厂方法模式 Sender mail = SendFactory.produceMail(); // 静态工厂方法模式 // 3.使用对象调用方法模拟发送的行为 mail.send(); System.out.println("------------------------------------"); // 优点:代码简单,可读性强 在创建单个对象时有优势 // 缺点:扩展性和可维护性落差 Sender sender1 = new MailSender(); sender1.send(); System.out.println("------------------------------------"); Provider provider = new MailSendFactory(); // 抽象工厂模式 Sender mail2 = provider.produce(); mail2.send(); System.out.println("------------------------------------"); // 增加一个发送包裹类 Provider provider1 = new PacketSendFactory(); Sender sender = provider1.produce(); sender.send(); } }
装饰器模式就是给一个对象动态的增加一些新功能,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxES97iX-1620391959375)(E:\MarkDown\拉勾笔记\装饰器模式)]
1、可以实现一个类功能的扩展。
2、可以动态的增加功能,而且还能动态撤销(继承不行)。
3、缺点:产生过多相似的对象,不易排错。
/** * @auther weiwei * @date 2021/5/2 21:14 * @description */ public interface Sourceable { // 自定义抽象方法 void method(); } public class Source implements Sourceable{ @Override public void method() { System.out.println("素颜原来可以如此之美!"); } } /** * @auther weiwei * @date 2021/5/2 21:18 * @description // 装饰器模式 */ public class Decorator implements Sourceable{ private Sourceable source; public Decorator(Sourceable source){ this.source = source; } @Override public void method() { source.method(); // 保证原有功能不变 System.out.println("化妆之后你会更美!"); } } public class SourceableTest { public static void main(String[] args) { Sourceable sourceable = new Source(); sourceable.method(); System.out.println("---------------------------------"); // 使用装饰类实现功能 Sourceable sourceable1 = new Decorator(sourceable); sourceable1.method(); } }
代理模式就是找一个代理类替原对象进行一些操作。
比如我们在租房子的时候找中介,再如我们打官司需要请律师,中介和律师在这里就是我们的代理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dwAH3t6S-1620391959376)(E:\MarkDown\拉勾笔记\代理模式)]
如果在使用的时候需要对原有的方法进行改进,可以采用一个代理类调用原有方法,并且对产生的结果进行控制,这种方式就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护。
装饰器模式通常的做法是将原始对象作为一个参数传给装饰者的构造器,而代理模式通常在一个代理类中创建一个被代理类的对象。
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。
/** * @auther weiwei * @date 2021/5/2 21:14 * @description */ public interface Sourceable { // 自定义抽象方法 void method(); } public class Source implements Sourceable{ @Override public void method() { System.out.println("素颜原来可以如此之美!"); } } /** * @auther weiwei * @date 2021/5/2 21:18 * @description // 装饰器模式 */ public class Decorator implements Sourceable{ private Sourceable source; public Decorator(Sourceable source){ this.source = source; } @Override public void method() { source.method(); // 保证原有功能不变 System.out.println("化妆之后你会更美!"); } } /** * @auther weiwei * @date 2021/5/2 21:24 * @description 代理模式 */ public class Proxy implements Sourceable{ private Source source; public Proxy(){ source = new Source(); } @Override public void method() { source.method(); System.out.println("我和装饰器模式其实是不一样的!"); } } public class SourceableTest { public static void main(String[] args) { Sourceable sourceable = new Source(); sourceable.method(); System.out.println("---------------------------------"); // 使用装饰类实现功能 Sourceable sourceable1 = new Decorator(sourceable); sourceable1.method(); System.out.println("---------------------------------"); // 代理模式 Sourceable sourceable2 = new Proxy(); sourceable2.method(); } }
模板方法模式主要指一个抽象类中封装了一个固定流程,流程中的具体步骤可以由不同子类进行不同的实现,通过抽象类让固定的流程产生不同的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wUvCrp6w-1620391959378)(E:\MarkDown\拉勾笔记\模板方法模式)]
将多个子类共有且逻辑基本相同的内容提取出来实现代码复用。
不同的子类实现不同的效果形成多态,有助于后期维护。
/** * @auther weiwei * @date 2021/5/2 21:30 * @description 模板方法模式 */ public abstract class AbstractCalculator { // 自定义成员方法事项将参数指定的表达式按照参数指定的规则进行切割并返回计算结果 1+1 + public int splitExpression(String exp, String op){ String[] sArr = exp.split(op); return calculate(Integer.parseInt(sArr[0]), Integer.parseInt(sArr[1])); } // 自定义抽象方法实现运算 public abstract int calculate(int ia, int ib); } public class Plus extends AbstractCalculator{ @Override public int calculate(int ia, int ib) { return ia + ib; } } public class Minus extends AbstractCalculator{ @Override public int calculate(int ia, int ib) { return ia - ib; } } public class AbstractCalculatorTest { public static void main(String[] args) { AbstractCalculator abstractCalculator = new Plus(); int res = abstractCalculator.splitExpression("1+1", "\\+"); System.out.println("最终的运算结果是:" + res); // 2 } }
Java8是 Java 语言的一个重要版本,该版本于2014年3月发布,是自Java5以来最具革命性的版本,这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。
1、函数式接口主要指只包含一个抽象方法的接口,如:java.lang.Runnable、java.util.Comparator接口等。
2、Java8提供@FunctionalInterface注解来定义函数式接口,若定义的接口不符合函数式的规范便会报错。
3、Java8中增加了java.util.function包,该包包含了常用的函数式接口,具体如下:
接口名称 | 方法声明 | 功能介绍 |
---|---|---|
Consumer | void accept(T t) | 根据指定的参数执行操作 |
Supplier | T get() | 得到一个返回值 |
Function<T,R> | R apply(T t) | 根据指定的参数执行操作并返回 |
Predicate | boolean test(T t) | 判断指定的参数是否满足条件 |
public class FunctionalInterfaceTest { public static void main(String[] args) { // 1.匿名内部类语法格式: 父类/接口类型 引用变量名 = new 父类/接口类型(){ 方法的重写 }; Runnable runnable = new Runnable() { @Override public void run() { System.out.println("我是既没有参数又没有返回值的方法!"); } }; runnable.run(); // 我是既没有参数又没有返回值的方法! System.out.println("-----------------------------------------"); Consumer consumer = new Consumer() { @Override public void accept(Object o) { System.out.println(o + "有参但没有返回值的方法就是我!"); } }; consumer.accept("友情提示:"); // 友情提示:有参但没有返回值的方法就是我! System.out.println("-----------------------------------------"); Supplier supplier = new Supplier() { @Override public Object get() { return "无参有返回值!"; } }; System.out.println(supplier.get()); // 无参有返回值! System.out.println("-----------------------------------------"); Function function = new Function() { @Override public Object apply(Object o) { return o; } }; System.out.println(function.apply("有参有返回值的方法!")); // 有参有返回值的方法 System.out.println("-----------------------------------------"); Comparator comparator = new Comparator() { @Override public int compare(Object o1, Object o2) { return 0; } }; System.out.println(comparator.compare(10, 20)); // 0 System.out.println("-----------------------------------------"); Predicate predicate = new Predicate() { @Override public boolean test(Object o) { return false; } }; System.out.println(predicate.test("hello")); // false } }
1、Lambda 表达式是实例化函数式接口的重要方式,使用 Lambda 表达式可以使代码变的更加简洁紧凑。
2、lambda表达式:参数列表、箭头符号->和方法体组成,而方法体中可以是表达式,也可以是语句块。
3、语法格式:(参数列表) -> { 方法体; } - 其中()、参数类型、{} 以及return关键字 可以省略。
public class FunctionalInterfaceTest { public static void main(String[] args) { // 1.匿名内部类语法格式: 父类/接口类型 引用变量名 = new 父类/接口类型(){ 方法的重写 }; Runnable runnable = new Runnable() { @Override public void run() { System.out.println("我是既没有参数又没有返回值的方法!"); } }; runnable.run(); // 我是既没有参数又没有返回值的方法! // 使用lambda表达式实现函数式接口对象的创建:(参数列表) -> {方法体;} //Runnable runnable1 = () -> { System.out.println(); }; Runnable runnable1 = () -> System.out.println("我是既没有参数又没有返回值的方法!"); runnable1.run(); System.out.println("-----------------------------------------"); Consumer consumer = new Consumer() { @Override public void accept(Object o) { System.out.println(o + "有参但没有返回值的方法就是我!"); } }; consumer.accept("友情提示:"); // 友情提示:有参但没有返回值的方法就是我! //Consumer consumer1 = (Object o) -> {System.out.println(o + "有参但没有返回值的方法就是我!");}; //Consumer consumer1 = (o) -> System.out.println(o + "有参但没有返回值的方法就是我!"); // 省略了()、参数类型、{}, 自动类型推断 Consumer consumer1 = o -> System.out.println(o + "有参但没有返回值的方法就是我!"); consumer1.accept("友情提示:"); System.out.println("-----------------------------------------"); Supplier supplier = new Supplier() { @Override public Object get() { return "无参有返回值!"; } }; System.out.println(supplier.get()); // 无参有返回值! //Supplier supplier1 = () -> { return "无参有返回值!"; }; Supplier supplier1 = () -> "无参有返回值!"; System.out.println(supplier1.get()); System.out.println("-----------------------------------------"); Function function = new Function() { @Override public Object apply(Object o) { return o; } }; System.out.println(function.apply("有参有返回值的方法!")); // 有参有返回值的方法 // return 和 {}都可以省略 Function function1 = o -> o; System.out.println(function1.apply("有参有返回值的方法!")); System.out.println("-----------------------------------------"); Comparator comparator = new Comparator() { @Override public int compare(Object o1, Object o2) { return 0; } }; System.out.println(comparator.compare(10, 20)); // 0 Comparator comparator1 = (o1, o2) -> 0; System.out.println(comparator1.compare(10, 20)); System.out.println("-----------------------------------------"); Predicate predicate = new Predicate() { @Override public boolean test(Object o) { return false; } }; System.out.println(predicate.test("hello")); // false Predicate predicate1 = o -> false; System.out.println(predicate1.test("hello")); } }
1、方法引用主要指通过方法的名字来指向一个方法而不需要为方法引用提供方法体,该方法的调用交给函数式接口执行。
2、方法引用使用一对冒号 :: 将类或对象与方法名进行连接,通常使用方式如下:
a.对象的非静态方法引用 ObjectName :: MethodName
b.类的静态方法引用 ClassName :: StaticMethodName
c.类的非静态方法引用 ClassName :: MethodName
d.构造器的引用 ClassName :: new
e.数组的引用 TypeName[] :: new
3、方法引用是在特定场景下lambda表达式的一种简化表示,可以进一步简化代码的编写使代码更加紧凑简洁,从而减少冗余代码。
/** * @auther weiwei * @date 2021/5/3 14:33 * @description 对象的非静态方法引用 ObjectName :: MethodName (1-7) * 类的静态方法引用 ClassName :: StaticMethodName (8-9) * 类的非静态方法引用 ClassName :: MethodName (10) * 构造器的引用 ClassName :: new (11-12) * 数组的引用 TypeName[] :: new (13) */ public class MethodReferenceTest { public static void main(String[] args) { // 1.使用匿名内部类的方式通过函数式接口Runnable中的方法实现对Person类正在show方法的调用 Person perosn = new Person("张飞",30); Runnable runnable = new Runnable() { @Override public void run() { perosn.show(); } }; runnable.run(); // 没事出来秀一下哦! System.out.println("-----------------------------------------------------------"); // 2.使用lambda表达式的方式实现Person类中show方法的调用 Runnable runnable1 = () -> perosn.show(); runnable1.run(); // 没事出来秀一下哦! System.out.println("-----------------------------------------------------------"); // 3.使用方法引用的方式实现Person类中show方法的调用 Runnable runnable2 = perosn::show; runnable2.run(); System.out.println("-----------------------------------------------------------"); // 4.使用匿名内部类的方式通过函数式接口Consumer中的方法来实现Person类中setName方法的调用 Consumer<String> consumer = new Consumer<String>() { @Override public void accept(String s) { perosn.setName(s); } }; consumer.accept("关羽"); System.out.println("person = " + perosn); // 关羽 30 System.out.println("-----------------------------------------------------------"); // 5.使用lambda表达式实现Person类中setName方法的调用 Consumer<String> consumer1 = s -> perosn.setName(s); consumer1.accept("刘备"); System.out.println("person = " + perosn); // 刘备 30 System.out.println("-----------------------------------------------------------"); // 6.使用方法引用的方式实现Person类中setName方法的调用 Consumer<String> consumer2 = perosn::setName; consumer2.accept("张飞"); System.out.println("person = " + perosn); // 张飞 30 System.out.println("-----------------------------------------------------------"); // 7.使用匿名内部类的方式通过函数式接口Consumer中的方法来实现Person类中getName方法的调用 Supplier<String> supplier = new Supplier<String>() { @Override public String get() { return perosn.getName(); } }; System.out.println(supplier.get()); // 张飞 Supplier<String> supplier1 = () -> perosn.getName(); System.out.println(supplier1.get()); // 张飞 Supplier<String> supplier2 = perosn::getName; System.out.println(supplier2.get()); // 张飞 System.out.println("-----------------------------------------------------------"); // 8.使用匿名内部类的方式通过函数式接口Function中的方法实现Integer类中parseInt方法的调用 Function<String, Integer> function = new Function<String, Integer>() { @Override public Integer apply(String s) { return Integer.parseInt(s); } }; System.out.println(function.apply("12345")); // 12345 Function<String, Integer> function1 = (s) -> Integer.parseInt(s); System.out.println(function1.apply("12345")); // 12345 Function<String, Integer> function2 = Integer::parseInt; System.out.println(function2.apply("12345")); // 12345 System.out.println("-----------------------------------------------------------"); // 9.使用匿名内部类的方式通过函数式接口Comparator中的方法实现Integer类中compare方法的调用 Comparator<Integer> comparator = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); } }; System.out.println(comparator.compare(10, 20)); // -1 Comparator<Integer> comparator1 = (o1, o2) -> Integer.compare(o1, o2); System.out.println(comparator1.compare(10, 20)); // -1 Comparator<Integer> comparator2 = Integer::compare; System.out.println(comparator2.compare(10, 20)); // -1 System.out.println("-----------------------------------------------------------"); // 10.使用匿名内部类的方式通过类名来调用非静态方法 // 其中一个参数对象作为调用对象来调用方法时,可以使用上述方式 更抽象 Comparator<Integer> comparator3 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }; System.out.println(comparator3.compare(20, 10)); // 1 Comparator<Integer> comparator4 = (o1, o2) -> o1.compareTo(o2); System.out.println(comparator4.compare(20, 10)); // 1 Comparator<Integer> comparator5 = Integer::compareTo; System.out.println(comparator5.compare(20, 10)); // 1 System.out.println("-----------------------------------------------------------"); // 11.使用匿名内部类的方式通过Supplier函数式接口创建Person类型的对象并返回 Supplier<Person> supplier3 = new Supplier<Person>() { @Override public Person get() { return new Person(); } }; System.out.println(supplier3.get()); // null 0 Supplier<Person> supplier4 = () -> new Person(); System.out.println(supplier4.get()); // null 0 Supplier<Person> supplier5 = Person::new; System.out.println(supplier5.get()); // null 0 System.out.println("-----------------------------------------------------------"); // 12.使用匿名内部类的方式通过BiFunction函数式接口采用有参方式创建Person类型的对象并返回 BiFunction<String, Integer, Person> biFunction = new BiFunction<String, Integer, Person>() { @Override public Person apply(String s, Integer integer) { return new Person(s, integer); } }; System.out.println(biFunction.apply("张飞", 30)); // 张飞 30 BiFunction<String, Integer, Person> biFunction1 = (s, integer) -> new Person(s,integer); System.out.println(biFunction1.apply("张飞", 30)); // 张飞 30 BiFunction<String, Integer, Person> biFunction2 = Person::new; System.out.println(biFunction2.apply("张飞", 30)); // 张飞 30 System.out.println("-----------------------------------------------------------"); // 13.使用匿名内部类的方式通过Function函数式接口采用有参方式创建指定数量的Person类型的对象数组并返回 Function<Integer, Person[]> function3 = new Function<Integer, Person[]>() { @Override public Person[] apply(Integer integer) { return new Person[integer]; } }; Person[] pArr = function3.apply(3); System.out.println(Arrays.toString(pArr)); Function<Integer, Person[]> function4 = integer -> new Person[integer]; System.out.println(Arrays.toString(function4.apply(4))); Function<Integer, Person[]> function5 = Person[]::new; System.out.println(Arrays.toString(function5.apply(5))); } }
案例题目:
准备一个List集合并放入Person类型的对象,将集合中所有成年人过滤出来放到另外一个集合并打印出来。
public class ListPersonTest { public static void main(String[] args) { // 1.准备一个List集合并放入Person类型的对象后打印 List<Person> list = new LinkedList<>(); list.add(new Person("张飞",30)); list.add(new Person("小乔",17)); list.add(new Person("周瑜",20)); list.add(new Person("张飞",30)); list.add(new Person("关羽",35)); list.add(new Person("刘备",40)); for (Person tp : list) { System.out.println(tp); } System.out.println("------------------------------------"); // 2.将List集合中所有成年人过滤出来并放入另外一个集合中打印 List<Person> list1 = new LinkedList<>(); for (Person tp : list) { if (tp.getAge() >= 18){ list1.add(tp); } } for (Person tp : list1) { System.out.println(tp); } } }
1、java.util.stream.Stream接口是对集合功能的增强,可以对集合元素进行复杂的查找、过滤、筛选等操作。
2、Stream接口借助于Lambda 表达式极大的提高编程效率和程序可读性,同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势。
1、创建Stream,通过一个数据源来获取一个流。
2、转换Stream,每次转换返回一个新的Stream对象。
3、对Stream进行聚合操作并产生结果。
方式一:通过调用集合的默认方法来获取流,如:default Stream stream()
方式二:通过数组工具类中的静态方法来获取流,如:static IntStream stream(int[] array)
方式三:通过Stream接口的静态方法来获取流,如:static Stream of(T… values)
方式四:通过Stream接口的静态方法来获取流,static Stream generate(Supplier<? extends T> s)
筛选与切片的常用方法:
方法声明 | 功能介绍 |
---|---|
Stream filter(Predicate<? super T> predicate) | 返回一个包含匹配元素的流 |
Stream distinct() | 返回不包含重复元素的流 |
Stream limit(long maxSize) | 返回不超过给定元素数量的流 |
Stream skip(long n) | 返回丢弃前n个元素后的流 |
映射的常用方法:
方法声明 | 功能介绍 |
---|---|
Stream map(Function<? super T,? extends R> mapper) | 返回每个处理过元素组成的流 |
Stream flatMap(Function<? super T,? extends Stream<? extends R>> mapper) | 返回每个被替换过元素组成的流,并将所有流合成一个流 |
排序的常用方法:
方法声明 | 功能介绍 |
---|---|
Stream sorted() | 返回经过自然排序后元素组成的流 |
Stream sorted(Comparator<? super T> comparator) | 返回经过比较器排序后元素组成的流 |
匹配与查找的常用方法:
方法声明 | 功能介绍 |
---|---|
Optional findFirst() | 返回该流的第一个元素 |
boolean allMatch(Predicate<? super T> predicate) | 返回所有元素是否匹配 |
boolean noneMatch(Predicate<? super T> predicate) | 返回没有元素是否匹配 |
Optional max(Comparator<? super T> comparator) | 根据比较器返回最大元素 |
Optional min(Comparator<? super T> comparator) | 根据比较器返回最小元素 |
long count() | 返回元素的个数 |
void forEach(Consumer<? super T> action) | 对流中每个元素执行操作 |
规约的常用方法:
方法声明 | 功能介绍 |
---|---|
Optional reduce(BinaryOperator accumulator) | 返回结合后的元素值 |
收集的常用方法:
方法声明 | 功能介绍 |
---|---|
<R,A> R collect(Collector<? super T,A,R> collector) | 使用收集器对元素进行处理 |
public class Person implements Comparable<Person>{ private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void show(){ System.out.println("没事出来秀一下哦!"); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Person o) { //return getName().compareTo(o.getName()); return getAge() - o.getAge(); } } public class ListPersonTest { public static void main(String[] args) { // 1.准备一个List集合并放入Person类型的对象后打印 List<Person> list = new LinkedList<>(); list.add(new Person("张飞",30)); list.add(new Person("小乔",17)); list.add(new Person("周瑜",20)); list.add(new Person("张飞",30)); list.add(new Person("关羽",35)); list.add(new Person("刘备",40)); for (Person tp : list) { System.out.println(tp); } System.out.println("------------------------------------"); // 2.将List集合中所有成年人过滤出来并放入另外一个集合中打印 List<Person> list1 = new LinkedList<>(); for (Person tp : list) { if (tp.getAge() >= 18){ list1.add(tp); } } for (Person tp : list1) { System.out.println(tp); } System.out.println("------------------------------------"); // 3.使用Stream接口实现上述功能 list.stream().filter(new Predicate<Person>() { @Override public boolean test(Person person) { return person.getAge() >= 18; } }).forEach(new Consumer<Person>() { @Override public void accept(Person person) { System.out.println(person); } }); System.out.println("------------------------------------"); // 4.使用lambda表达式对上述代码进行优化 //list.stream().filter(person -> person.getAge() >= 18).forEach(person -> System.out.println(person)); list.stream().filter(person -> person.getAge() >= 18).forEach(System.out::println); System.out.println("------------------------------------"); // 5.实现对集合中的元素通过流跳过2个元素后再取3个元素 list.stream().skip(2).limit(3).forEach(System.out::println); System.out.println("------------------------------------"); // 6.实现将集合中所有元素中的年龄获取出来并打印 list.stream().map(new Function<Person, Integer>() { @Override public Integer apply(Person person) { return person.getAge(); } }).forEach(System.out::println); //list.stream().map(person -> person.getAge()).forEach(System.out::println); list.stream().map(Person::getAge).forEach(System.out::println); System.out.println("------------------------------------"); // 7.实现集合中所有元素的自然排序并打印 list.stream().sorted().forEach(System.out::println); System.out.println("------------------------------------"); // 比较器排序 list.stream().sorted(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.compareTo(o2); } }).forEach(System.out::println); System.out.println("------------------------------------"); // 8.判断集合中的元素是否没有元素年龄是大于45岁的 boolean b1 = list.stream().noneMatch(new Predicate<Person>() { @Override public boolean test(Person person) { return person.getAge() > 45; } }); System.out.println("b1 = " + b1); // true b1 = list.stream().noneMatch(person -> person.getAge() > 45); System.out.println("b1 = " + b1); // true System.out.println("------------------------------------"); // 9.按照指定的比较器规则获取集合所有元素中的最大值 Optional<Person> max = list.stream().max(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getAge() - o2.getAge(); } }); System.out.println("按照年龄排序后的最大值是:" + max); max = list.stream().max(((o1, o2) -> o1.getAge() - o2.getAge())); System.out.println("按照年龄排序后的最大值是:" + max); System.out.println("------------------------------------"); // 10.实现将集合中所有元素的年龄映射出来并进行累加后打印 Optional<Integer> reduce = list.stream().map(Person::getAge).reduce(new BinaryOperator<Integer>() { @Override public Integer apply(Integer integer, Integer integer2) { return integer + integer2; } }); System.out.println("最终所有年龄的累加和是:" + reduce); // 172 //reduce = list.stream().map(Person::getAge).reduce((integer, integer2) -> integer + integer2); reduce = list.stream().map(Person::getAge).reduce(Integer::sum); System.out.println("最终所有年龄的累加和是:" + reduce); // 172 System.out.println("------------------------------------"); // 11.实现将集合中所有元素的姓名映射出来并收集到集合中打印 list.stream().map(Person::getName).collect(Collectors.toList()).forEach(System.out::println); } }
案例题目
判断字符串是否为空,若不为空则打印字符串的长度,否则打印0。
public class OptionalTest {
public static void main(String[] args) {
//String str1 = "hello";
String str1 = null;
if (null != str1){
System.out.println("字符串长度是:" + str1.length()); // 5 空指针异常
}else {
System.out.println("字符串为空,因此长度为0!");
}
}
}
1、java.util.Optional类可以理解为一个简单的容器,其值可能是null或者不是null,代表一个值存在或不存在。
2、该类的引入很好的解决空指针异常,不用显式进行空值检测。
方法声明 | 功能介绍 |
---|---|
static Optional ofNullable(T value) | 根据参数指定数值来得到Optional类型的对象 |
Optional map(Function<? super T,? extends U> mapper) | 根据参数指定规则的结果来得到Optional类型的对象 |
T orElse(T other) | 若该值存在就返回,否则返回other的数值 |
public class OptionalTest { public static void main(String[] args) { //String str1 = "hello"; String str1 = null; if (null != str1){ System.out.println("字符串长度是:" + str1.length()); // 5 空指针异常 }else { System.out.println("字符串为空,因此长度为0!"); } System.out.println("--------------------------------------"); // Java8中使用Optinoal类实现空值的处理 // 1.将数据str1装到Optional对象代表的容器中 Optional<String> optional = Optional.ofNullable(str1); // 2.建立映射关系 使用字符串长度与字符串建立映射关系 /*Optional<Integer> integer = optional.map(new Function<String, Integer>() { @Override public Integer apply(String s) { return s.length(); } });*/ //Optional<Integer> integer = optional.map(s -> s.length()); Optional<Integer> integer = optional.map(String::length); // 3.若字符串为空则打印0,否则打印字符串的数值 System.out.println("integer = " + integer); // Optional.empty System.out.println(integer.orElse(0)); // 0 } }
1、Java9发布于2017年9月发布,带来了很多新特性,其中最主要的变化是模块化系统。
2、模块就是代码和数据的封装体,模块的代码被组织成多个包,每个包中包含Java类和接口,模块的数据则包括资源文件和其他静态信息。
在 module-info.java 文件中,我们可以用新的关键词module来声明一个模块,具体如下:
module 模块名称 {
}
1、减少内存的开销。
2、可简化各种类库和大型应用的 开发和维护。
3、安全性,可维护性,提高性能。
package com.wei; /** * @auther weiwei * @date 2021/5/3 16:40 * @description */ public class Person { } module java9 { // 实现将com.wei这个包暴露出去 exports com.wei; } module java92 { // 需要java9-01中的模块信息 requires java9; } import com.wei.Person; /** * @auther weiwei * @date 2021/5/3 16:40 * @description */ public class PersonTest { public static void main(String[] args) { Person person = new Person(); } }
在Java9中允许在匿名内部类的使用中使用钻石操作符。
public class DiamondTest {
public static void main(String[] args) {
// 实现匿名内部类和砖石操作符的搭配使用 砖石操作符:<>
//Comparator<Integer> comparator = new Comparator<Integer>() {
Comparator<Integer> comparator = new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return 0;
}
};
}
}
1、Java9的List、Set和Map集合中增加了静态工厂方法of实现不可变实例的创建。
2、不可变体现在无法添加、修改和删除它们的元素。
3、不允许添加null元素对象。
1、保证线程安全:在并发程序中既保证线程安全性,也大大增强了并发时的效率。
2、被不可信的类库使用时会很安全。
3、如果一个对象不需要支持修改操作,将会节省空间和时间的开销。
4、可以当作一个常量来对待,并且这个对象在以后也不会被改变。
public class CollectionTest { public static void main(String[] args) { // 创建List类型的不可变实例 List<Integer> list = List.of(1,2,3,4,5); //list.add(6); // 编译OK,运行发生UnsupportedOperationException 不支持此操作的异常 System.out.println(list); // [1,2,3,4,5] Set<Integer> set = Set.of(6,7,8); //set.add(null); // 编译OK,运行发生UnsupportedOperationException 不支持此操作的异常 Map<Integer,String> map = Map.of(1,"one",2,"two"); //map.put(3,"three"); // 编译OK,运行发生UnsupportedOperationException 不支持此操作的异常 } }
InputStream类中提供了transferTo方法实现将数据直接传输到OutputStream中。
public class InputStreamTest { public static void main(String[] args) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = new FileInputStream("e:/a.txt"); outputStream = new FileOutputStream("e:/b.txt"); inputStream.transferTo(outputStream); // 实现数据的复制,底层是read和write方法的调用 } catch (IOException e) { e.printStackTrace(); } finally { if (null != outputStream){ try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != inputStream){ try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
1、Java10于2018年3月发布,改进的关键点包括一个本地类型推断、一个垃圾回收的增强。
2、Java10计划只是一个短期版本,因此公开更新将在六个月内结束,9月份发布的Java11将是Java的长期支持(LTS)版本,LTS版本的发布每三年发布一次。
1、Java10可以使用var作为局部变量类型推断标识符,此符号仅适用于局部变量,增强for循环的索引,以及传统for循环的本地变量。
2、它不能使用于方法形式参数,构造函数形式参数,方法返回类型,字段,catch形式参数或任何其他类型的变量声明。
1、标识符var不是关键字,只是一个保留的类型名称。这意味着var用作变量,方法名或包名的代码不会受到影响,但var不能作为类或则接口的名字。
2、避免了信息冗余。
3、对齐了变量名。
4、更容易阅读。
public class VarTest { public static void main(String[] args) { // 由初始值可以推断出变量的类型,因此可以使用var取代 //int num = 10; var num = 10; //List<Integer> list = new LinkedList<>(); var list = new LinkedList<Integer>(); list.add(10); for (var v : list) { System.out.println(v); } for (var i = 0; i < 10; i++) {} } }
Java11于2018年9月正式发布,这是 Java 大版本周期变化 后的第一个长期支持版本,非常值得关注。
1、在Java11中可以使用java命令一次性进行编译和运行操作。
2、执行源文件中的第一个类必须包含主方法。
3、不可以使用其它源文件中自定义的类。
方法声明 | 功能介绍 |
---|---|
boolean isBlank() | 判断字符串是否为空或只包含空白代码点 |
Optional map(Function<? super T,? extends U> mapper) | 根据参数指定规则的结果来得到Optional类型的对象 |
T orElse(T other) | 若该值存在就返回,否则返回other的数值 |
1、需求分析文档
2、概要设计文档
3、详细设计文档
4、编码和测试
5、安装和调试
6、维护和升级
在线考试系统的主要功能分析如下:
1、用户模块:登录、修改密码、退出
2、考试模块:开始考试、查询成绩、导出成绩(选)
1、学员管理模块:增加学员、删除学员、修改学员、查找学员
2、考题管理模块:增加考题、删除考题、修改考题、查找考题、导入考题(选)
在线考试系统采用C(Client客户端)/S(Server服务器)架构进行设计,具体如下:
1、客户端(Client)——主要用于提供字符界面供用户选择并将处理结果显示出来。
2、服务器(Server)——主要用于针对字符界面的选择实现真正业务功能的处理。
3、数据库(Database)——主要用于进行数据的存取。
1、客户端和服务器之间采用基于tcp协议的编程模型进行通信。
客户端的对象输出流连接服务器的对象输入流。
服务器的对象输出流连接客户端的对象输入流。
2、客户端采用消息的类型作为具体业务的代表,伴随着账户信息等一并发送给服务器。
当客户端发来的消息类型为"managerCheck"时,则表示要实现管理员账户信息的校验功能。
当客户端发来的消息类型为"userCheck"时,则表示要实现学员账户信息的校验功能。
3、服务器采用消息的类型作为是否校验成功的标志发送给客户端。
当客户端收到的消息类型为"success"时,则表示账户信息正确。
当客户端收到的消息类型为"fail"时,则表示账户信息错误。
1、编写基于tcp协议的服务器端,也就是初始化服务器;
2、编写基于tcp协议的客户端,来连接服务器;
3、编写客户端的字符界面并提示客户进行业务的选择;
4、将客户的选择和输入的相关信息通过对象输出流发送给服务器;
5、服务器通过对象输入流接收客户端发来的消息并进行功能处理,将处理结果发送给客户端;
6、客户端通过对象输入流接收服务器的处理结果并给出提示;
1、当项目启动时,将文件中的所有学员账户信息全部读取出来放到一个List集合中。
2、客户端输入要增加学员的用户名和密码信息,通过对象输出流发送给服务器。
3、服务器接收客户端发来的消息,判断集合中是否存在该学员账户信息并实现具体添加功能。
4、服务器将增加学员功能的处理结果发送给客户端,客户端给出对应的提示。
5、当项目退出时,将集合中的所有学员账户信息整体写入到文件中
IO:Input OutPut(输入 输出)
IO技术的作用:解决设备和设备之间的数据传输问题
IO的应用场景:图片上传、下载、打印机打印信息表、解析XML…
1、NIO即 Java New IO
2、是1个全新的、 JDK 1.4 后提供的 IO API
3、Java API中提供了两套NIO,一套是针对标准输入输出NIO ,另一套就是网络编程NIO
1、NIO和IO有相同的作用和目的,但实现方式不同
2、可替代标准 Java IO 的 IO API
3、IO是以流的方式处理数据,而NIO是以块(缓冲区)的方式处理数据
1、NIO和IO最大的区别是数据打包和传输方式。
2、IO是以流的方式处理数据,而NIO是以块的方式处理数据。
面向流的IO一次一个字节的处理数据,一个输入流产生一个字节,一个输出流就消费一个字节。
面向块的IO系统以块的形式处理数据。每一个操作都在一步中产生或消费一个数据块。按块要比按流快得多
(举例:拿水龙头来比喻:流就像水龙头滴水,每次只有一滴;块就像水龙头往水壶放水,放满之后对一整个水壶的水进行操作)
Java IO和NIO 具备的新特性如下:
IO | NIO |
---|---|
面向流(Strem Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(无) | 选择器(Selectors) |
可简单认为:
1、IO是面向流的处理,NIO是面向块(缓冲区)的处理
2、面向流的I/O 系统一次一个字节地处理数据
3、一个面向块(缓冲区)的I/O系统以块的形式处理数据
Java NIO的核心组件 包括:
通道( Channel )
缓冲区( Buffer )
选择器( Selector )(多路复用器)
1、在NIO中并不是以流的方式来处理数据的,而是以buffer缓冲区和Channel管道配合使用来处理数据。
2、Selector是因为NIO可以使用异步的非阻塞模式才加入的东西
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2DEsTSc-1620391959380)(E:\MarkDown\拉勾笔记\NIO核心组件)]
3、简单理解一下:
Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物)
而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理!
要时刻记住:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区
Channel–>运输
Buffer–>数据
相对于传统IO而言,流是单向的。对于NIO而言,有了Channel管道这个概念,我们的读写都是双向的
(铁路上的火车能从广州去北京、自然就能从北京返还到广州)!
1、作用:缓冲区,用来存放具体要被传输的数据,比如文件、scoket 等。这里将数据装入 Buffer 再通过通道进行传输。
2、Buffer 就是一个数组,用来保存不同数据类型的数据
3、在 NIO 中,所有的缓冲区类型都继承于抽象类 Buffer,最常用的就是 ByteBuffer,对于 Java 中的基本类型,基本都有一个具体 Buffer 类型与之相对应,它们之间的继承关系如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96fY7VAo-1620391959382)(E:\MarkDown\拉勾笔记\Buffer缓冲区)]
ByteBuffer:存储字节数据到缓冲区
ShortBuffer:存储字符串数据到缓冲区
CharBuffer: 存储字符数据到缓冲区
IntBuffer:存储整数数据到缓冲区
LongBuffer:存储长整型数据到缓冲区
DoubleBuffer:存储小数到缓冲区
FloatBuffer:存储小数到缓冲区
对于 Java 中的基本数据类型,都有一个 Buffer 类型与之相对应,最常用的自然是ByteBuffer 类(二进制数据)
1、在堆中创建缓冲区:allocate(int capacity)
2、在系统内存创建缓冲区:allocateDirect(int capacity)
3、通过普通数组创建缓冲区:wrap(byte[] arr)
/** * @auther weiwei * @date 2021/5/6 18:01 * @description ByteBuffer创建的三种方式 */ public class ByteBufferCreate { public static void main(String[] args) { // 1.第一种创建方式:在堆中创建缓冲区:allocate(int capacity) 常用方式,推荐 ByteBuffer byteBuffer = ByteBuffer.allocate(10); // 2.第二种创建方式:在系统内存创建缓冲区:allocateDirect(int capacity) ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(10); // 3.第三种创建方式:通过普通数组创建缓冲区:wrap(byte[] arr) byte[] arr = {97,98,99}; ByteBuffer byteBuffer2 = ByteBuffer.wrap(arr); } }
1、拿到一个缓冲区我们往往会做什么?很简单,就是读取缓冲区的数据/写数据到缓冲区中。
所以,缓冲区的核心方法就是:
put(byte b) : 给数组添加元素
get() :获取一个元素
public class ByteBufferMethod { public static void main(String[] args) { // 创建buffer对象,并设置缓冲区能够容纳的数据元素的最大数量 ByteBuffer byteBuffer = ByteBuffer.allocate(10); // put(byte, b): 给数组添加元素 byteBuffer.put((byte) 10); byteBuffer.put((byte) 20); byteBuffer.put((byte) 30); // 把缓冲区数组转换成普通数组 byte[] array = byteBuffer.array(); // 打印 System.out.println(Arrays.toString(array)); // get():获取一个元素 byte b = byteBuffer.get(1); System.out.println("b = " + b); // 20 } }
2、Buffer类维护了4个核心变量属性来提供关于其所包含的数组的信息。它们是:
a、容量Capacity
缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。(不能被改变的原因也很简单,底层是数组嘛)
b、界限Limit
缓冲区中可以操作数据的大小,代表了当前缓冲区中一共有多少数据(从limit开始后面的位置不能操作)。
c、位置Position
下一个要被读或写的元素的位置。Position会自动由相应的 get( ) 和 put( ) 函数更新。
d、标记Mark
一个备忘位置。用于记录上一次读写的位置。
public class BufferCharacteristic { public static void main(String[] args) { // 1.创建buffer对象 ByteBuffer byteBuffer = ByteBuffer.allocate(10); System.out.println("初始化---->capacity---->" + byteBuffer.capacity()); // 10 System.out.println("初始化---->limit---->" + byteBuffer.limit()); // 10 System.out.println("初始化---->position---->" + byteBuffer.position()); // 0 System.out.println("------------------------------------------------"); // 写 String s = "JavaEE"; byteBuffer.put(s.getBytes()); System.out.println("put后---->capacity---->" + byteBuffer.capacity()); // 10 System.out.println("puy后---->limit---->" + byteBuffer.limit()); // 10 System.out.println("put后---->position---->" + byteBuffer.position()); // 6 System.out.println("------------------------------------------------"); // 读 // 切换成读模式,从buffer缓冲区读数据都要调用flip()方法 byteBuffer.flip(); System.out.println("flip后---->capacity---->" + byteBuffer.capacity()); // 10 System.out.println("flip后---->limit---->" + byteBuffer.limit()); // 10 System.out.println("flip后---->position---->" + byteBuffer.position()); // 6 System.out.println("------------------------------------------------"); System.out.println("--从buffer缓冲区读取数据--"); // 1.创建字节数组(因为只有limit这么多数据可读) byte[] bytes = new byte[byteBuffer.limit()]; // 2.将读取出来的数据装进字节数组中 byteBuffer.get(bytes); System.out.println("get后---->capacity---->" + byteBuffer.capacity()); // 10 System.out.println("get后---->limit---->" + byteBuffer.limit()); // 6 System.out.println("get后---->position---->" + byteBuffer.position()); // 6 // 3.输出数据 System.out.println(new String(bytes,0,bytes.length)); System.out.println("------------------------------------------------"); // 读取完数据后还想接着写入数据,调用clear方法清空缓冲区,核心变量回归写模式,重置核心变量的值 // 之前缓冲区的数据还会存在,处于被遗忘状态 byteBuffer.clear(); System.out.println("clear后---->capacity---->" + byteBuffer.capacity()); // 10 System.out.println("clear后---->limit---->" + byteBuffer.limit()); // 10 System.out.println("clear后---->position---->" + byteBuffer.position()); // 0 // char b = (char) byteBuffer.get(); // System.out.println(b); // J System.out.println("------------------------------------------------"); byteBuffer.put("lagou".getBytes()); System.out.println("第一次put后---->position---->" + byteBuffer.position()); // 5 byteBuffer.mark(); // 做一个标记:记录上一次读写位置position的值 5 byteBuffer.put("zimu".getBytes()); System.out.println("第二次put后---->position---->" + byteBuffer.position()); // 9 // 还原到标记位 byteBuffer.reset(); System.out.println("reset后---->position---->" + byteBuffer.position()); // 5 } }
1、通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。
2、标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中(白话: 就是数据传输用的通道,作用是打开到IO设备的连接,文件、套接字都行)
例:相当于一根管子,buffer中的数据可以通过管子写入被操作的资源当中,也可以将资源通过管子写入到buffer中去
通道(Channel):由java.nio.channels包定义的
Java 为 Channel 接口提供的最主要实现类如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-21jDpDmy-1620391959384)(E:\MarkDown\拉勾笔记\Channel API)]
FileChannel:用于读取、写入、映射和操作文件的通道。(本地资源)
DatagramChannel:通过 UDP 读写网络中的数据通道。(网络资源)
SocketChannel:通过 TCP 读写网络中的数据。(网络资源)常用
ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来 的连接都会创建一个SocketChannel。(网络资源)
/** * @auther weiwei * @date 2021/5/7 13:29 * @description 将E:\nio_img文件夹中的wx_lagou.jpg 复制到工程中 */ public class FileChannelCopy { public static void main(String[] args) throws IOException { // 1.创建输入输出流(依赖于IO流获取channel) FileInputStream fileInputStream = new FileInputStream("E:/nio_img/wx_lagou.jpg"); FileOutputStream fileOutputStream = new FileOutputStream("E:\\JavaSE\\src\\com\\wei\\Part5\\NIO\\复制.jpg"); // 2.通过IO流获取channel通道 FileChannel channel1 = fileInputStream.getChannel(); FileChannel channel2 = fileOutputStream.getChannel(); // 3.创建缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 4.循环读写 while(channel1.read(byteBuffer) != -1){ byteBuffer.flip(); // 切换读模式,改变position和limit的位置 channel2.write(byteBuffer); byteBuffer.clear(); // 还原位置 } // 5.关闭流 fileOutputStream.close(); fileInputStream.close(); } }
客户端
public class NIOClient { public static void main(String[] args) throws IOException { /** * Socket s = new Socket("127.0.0.1",9999); * OutputStream os = s.getOutputStream(); * os.write("你好呀".getBytes()); * os.close(); * s.close(); */ // 1.创建对象 SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("127.0.0.1",9999)); // 2.创建缓冲区数组 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 设置数据 byteBuffer.put("哈哈哈".getBytes()); byteBuffer.flip(); // 输出数据 sc.write(byteBuffer); // 关闭流 sc.close(); } }
服务器端
public class NIOServer { public static void main(String[] args) throws IOException { /** * ServerSocket ss = new ServerSocket(9999); * Socket s = ss.accept(); // 阻塞,等待客户端连接 * InputStream is = s.getInputStream(); * byte[] bytes = new byte[1024]; * int len = is.read(bytes); * System.out.println(new String(bytes,0,len)); * s.close(); * ss.close(); */ // 1.创建服务器端对象,监听对应的端口 ServerSocketChannel ssc = ServerSocketChannel.open(); // 绑定要监听的端口号 ssc.bind(new InetSocketAddress(9999)); // 2.连接客户端 阻塞 SocketChannel socketChannel = ssc.accept(); // 3.读取数据 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 读取到的字节长度 int len = socketChannel.read(byteBuffer); // 打印 System.out.println(new String(byteBuffer.array(),0,len)); } }
public class NIOServer { public static void main(String[] args) throws IOException, InterruptedException { /** * ServerSocket ss = new ServerSocket(9999); * Socket s = ss.accept(); // 阻塞,等待客户端连接 * InputStream is = s.getInputStream(); * byte[] bytes = new byte[1024]; * int len = is.read(bytes); * System.out.println(new String(bytes,0,len)); * s.close(); * ss.close(); */ // 1.创建服务器端对象,监听对应的端口 ServerSocketChannel ssc = ServerSocketChannel.open(); // 绑定要监听的端口号 ssc.bind(new InetSocketAddress(9999)); // 设置为非阻塞连接 ssc.configureBlocking(false); while (true) { // 2.连接客户端 阻塞 SocketChannel socketChannel = ssc.accept(); if (socketChannel != null) { // 3.读取数据 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 读取到的字节长度 int len = socketChannel.read(byteBuffer); // 打印 System.out.println(new String(byteBuffer.array(), 0, len)); // 结束循环 break; }else { // 没有连接到服务器的客户端 System.out.println("做一些别的事"); Thread.sleep(2000); } } } }
一个选择器可以同时监听多个服务器端口, 帮多个服务器端口同时等待客户端的访问
Channel和Buffer比较好理解 ,联系也比较密切,他们的关系简单来说就是:数据总是从通道中读到buffer缓冲区内,或者从buffer写入到通道中。
选择器(Selector) 是 Channel(通道)的多路复用器,Selector 可以同时监控多个通道的IO(输入输出)状况。(将多个channel注册到selector上)
选择器提供选择执行已经就绪的任务的能力。从底层来看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。
注意:并不是所有的Channel,都是可以被Selector 复用的。比方说,FileChannel就不能被选择器复用。为什么呢?
判断一个Channel 能被Selector 复用,有一个前提:判断他是否继承了一个抽象类SelectableChannel。如果继承了SelectableChannel,则可以被复用,否则不能。
SelectableChannel 的结构如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFV8CWPv-1620391959386)(E:\MarkDown\拉勾笔记\SelectableChannel)]
SelectableChannel类提供了实现通道的可选择性所需要的公共方法
答:不是。不是一对一的关系。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。
通道和选择器之间的关系,使用注册的方式完成。SelectableChannel可以被注册到Selector对象上,在注册的时候,需要指定通道的哪些操作,是Selector感兴趣的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aGNv7QwP-1620391959388)(E:\MarkDown\拉勾笔记\通道和选择器注册关系)]
1、使用Channel.register(Selector sel,int ops)方法,将一个通道注册到一个选择器时。
第一个参数:指定通道要注册的选择器是谁
第二个参数:指定选择器需要查询的通道操作
2、可以供选择器查询的通道操作,从类型来分,包括以下四种:
(1)接收 : SelectionKey.OP_ACCEPT(接收连接进行事件,表示服务器监听到了客户连接,服务器可以接收这个连接)
(2)连接 : SelectionKey.OP_CONNECT(连接就绪事件,表示客户与服务器连接已经建立成功)
(3)可写 : SelectionKey.OP_WRITE(写就绪事件,表示已经可以向通道写数据了)
(4)可读 : SelectionKey.OP_READ(读就绪事件,表示通达中已经有了可读数据,可以执行读操作了)
3、如果Selector对通道的多操作类型感兴趣,可以用“位或”操作符来实现:
int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ;
Channel和Selector的关系确定好后,并且一旦通道处于某种就绪的状态,就可以被选择器查询到。这个工作,使用选择器Selector的select()方法完成。select方法的作用,对感兴趣的通道操作,进行就绪状态的查询。
Selector可以不断的查询Channel中发生的操作的就绪状态。并且挑选感兴趣的操作就绪状态。一旦通道有操作的就绪状态达成,并且是Selector感兴趣的操作,就会被Selector选中,放入选择键集合中。
select() :选择器等待客户端连接的方法
阻塞问题:
1.在开始没有客户访问的时候是阻塞的
2.在有客户来访问的时候方法会变成非阻塞的
3.如果客户的访问被处理结束之后,又会恢复成阻塞的
selectedKeys() :选择器会把被连接的服务端对象放在Set集合中,这个方法就是返回一个Set集
合
Selector对象是通过调用静态工厂方法open()来实例化的,如下:
// 1、获取Selector选择器
Selector selector = Selector.open();
要实现Selector管理Channel,需要将channel注册到相应的Selector上,如下:
// 2、获取通道
ServerSocketChannel serverSocketChannel =
ServerSocketChannel.open();
// 3.设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 4、绑定连接
serverSocketChannel.bind(new
InetSocketAddress(SystemConfig.SOCKET_SERVER_PORT));
// 5、将通道注册到选择器上,并制定监听事件为:“接收”事件
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
上面通过调用通道的register()方法会将它注册到一个选择器上。
首先需要注意的是:
与Selector一起使用时,Channel必须处于非阻塞模式下,否则将抛出异常
IllegalBlockingModeException
public class SelectorServer { public static void main(String[] args) throws IOException { // 小目标:通道注册到选择器上 // 1.获取Selector选择器 Selector selector = Selector.open(); // 2.获取去通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(9999)); // 3.设置为非阻塞模式 ** (与Selector一起使用时,channel必须要在非阻塞模式下,如果是阻塞的回抛出异常) serverSocketChannel.configureBlocking(false); // 4.将通道注册到选择器上:指定监听事件为 “接收” 事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } }
1、万事俱备,下一步是查询就绪的操作。
2、通过Selector的 select() 方法,可以查询出已经就绪的通道操作,这些就绪的状态集合,包存在一个元素是SelectionKey对象的Set集合中。
3、select()方法返回的int值,表示有多少通道已经就绪
4、而一旦调用select()方法,并且返回值不为0时,下一步工干啥?
5、通过调用Selector的selectedKeys()方法来访问已选择键集合,然后迭代集合的每一个选择键元素,根据就绪操作的类型,完成对应的操作:
public class NIOClient { public static void main(String[] args) throws IOException { // 1.创建对象 SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("127.0.0.1",7777)); // 2.创建缓冲区数组 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 设置数据 byteBuffer.put("哈哈哈".getBytes()); byteBuffer.flip(); // 输出数据 sc.write(byteBuffer); // 关闭流 sc.close(); } }
public class SelectorServer { public static void main(String[] args) throws IOException { // 小目标:通道注册到选择器上 // 1.获取Selector选择器 Selector selector = Selector.open(); // 2.获取去通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ServerSocketChannel serverSocketChannel2 = ServerSocketChannel.open(); ServerSocketChannel serverSocketChannel3 = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(9999)); serverSocketChannel2.bind(new InetSocketAddress(8888)); serverSocketChannel3.bind(new InetSocketAddress(7777)); // 3.设置为非阻塞模式 ** (与Selector一起使用时,channel必须要在非阻塞模式下,如果是阻塞的回抛出异常) serverSocketChannel.configureBlocking(false); serverSocketChannel2.configureBlocking(false); serverSocketChannel3.configureBlocking(false); // 4.将通道注册到选择器上:指定监听事件为 “接收” 事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); serverSocketChannel2.register(selector, SelectionKey.OP_ACCEPT); serverSocketChannel3.register(selector, SelectionKey.OP_ACCEPT); // select():查询已经就绪的通道操作 返回值:表示有多少通道已经就绪 // 阻塞:阻塞到至少有一个通道上的时间就绪了 /*System.out.println(1); int select = selector.select(); System.out.println("就绪的通道操作:" + select);*/ // 5.采用轮询的方式,查询准备就绪事件 while (selector.select() > 0){ // 6.集合中就是所有已经准备就绪的操作 Set<SelectionKey> set = selector.selectedKeys(); Iterator<SelectionKey> selectionKeys = set.iterator(); while (selectionKeys.hasNext()){ // 7.已经准备就绪的事件 SelectionKey selectionKey = selectionKeys.next(); // 8.判断事件的类型 ----ACCEPT ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel)selectionKey.channel(); // 9.接收客户端发来的数据 SocketChannel socketChannel = serverSocketChannel1.accept(); // 10.读取数据 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int len = socketChannel.read(byteBuffer); // 11.打印 System.out.println(new String(byteBuffer.array(),0,len)); // 12.关闭资源 socketChannel.close(); } selectionKeys.remove(); } serverSocketChannel.close(); serverSocketChannel2.close(); serverSocketChannel3.close(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。