文件上传优化:
文件名称需要优化:
服务端,保存文件的名称如果名称固定,那么最终会导致服务器硬盘,只会保留一个文件,对上传的文件名称优化
//文件名称定义规则
"beautiful"+System.currentTimeMillis()+new Randow().nextInt(1000000)+".jpg"
//可以保证下载的文件名不重复。
服务端接收文件的优化:
服务器端接受一个文件之后就关闭了,后面的其他客户端无法上传文件,使用循环进行改进,可以不断地接收不同的客户端传输过来的文件。
//使用循环
while(true){
Socket socket=serverSocket.accept();
.....
....
}
服务器端接收客户端文件的效率的优化:
服务器端,在接受文件的时候,加入某个客户端给你传一个大文件,此时就不能再接收其他用户的文件,所以可以用多线程技术优化接收效率。
while(true){
Socket socket=serverSocket.accept();
//使用多线程技术,提高程序的效率
//有一个客户端上传文件,就开启一个线程,完成文件的上传
new Thread(new Runnable(){
//重写run方法,设定线程任务
@Override
public void run(){
//使用网络字节输入流对象
InputStream is=socket.getInputStream();
// 指定一个目录
File file=new File("D:\\upload");
if(!file.exits()){
file.mkdirs();
//防止同名的文件被覆盖
String filename="beautiful"+System.currentTimeMillis()+new
Randow().nextInt(1000000)+".jpg";
//构建一个本地的文件字节输出流对象
FileOutputStream fos=new FileOutputStream(file+"\\"+filename);
//一读一写完成文件的上传,最终把文件写入到服务器端的硬盘当中。
...
}
}).start();
}
//服务器不关闭
//serverSocket.close();
函数式接口:
概念:函数式接口在java中指的是,有且仅有一个抽象方法的接口就称为函数式接口。
函数式接口,适用于函数式编程的在Java当中的函数式编程体现在Lambda表达式,所以函数式接口就是用来服务Lambad表达式的。只有确保接口当中有且仅有一个抽象方法,那么在java中的Lambda才能顺利的进行推导。
备注:“语法糖”是指使用更加便利方便,但是原理不变代码语法。就比如遍历集合的时候使用for -each其实底层使用的是迭代器。这便是"语法糖"。
格式:
只有确保接口当中有且仅有一个抽象方法即可:
修饰符 interface InterfaceName{
//只能定义一个抽象方法
public abstract 返回值类型 方法名称(参数列表);
//还可以定义其他的非抽象方法
}
例如:
public interface A{
public abstrac void show01();
public default void show02(){
//...
}
//void show03();有且仅有一个抽象方法才成为函数式接口
}
@Functionallnterface注解
与@Override注解作用类似,java8中专门为函数式接口引入的一个新的注解@Functionallnterface,该注解主要定义在接口上。一旦在接口上使用该注解,编译器会强制检查该接口是不是一个函数式接口,该接口中是不是有且仅有一个抽象方法,如果不是,编译报错。
//函数式接口注解
@FunctionalInterface
//作用:可以检测接口是否为一个函数式接口 是:编译通过 否:编译失败
interface A {
//定义一个抽象方法
void method();
// void method2();
default void me() {
System.out.println("你好呀");
}
}
自定义函数式接口的用途
对于自定义的函数式接口,一般用于方法的参数和返回值上。
函数式编程:
能够在兼顾Java的面向对象特性的基础上,通过Lambda表达式与后面的方法引用,为开发者打开函数式编程的大门。
Lambda的延迟加载
有些场景的代码运行执行后,结果不一定会被使用到,从而造成性能的浪费,而Lambda表达式时延迟执行的,正好可以解决此问题,提升性能。
实际上使用内部类也可以达到这样的效果,只是将代码操作延迟到另外一个对象当中用过调用方法来完成。后面的代码的执行取决于前面的条件的判断结果。
public static void main(String[] args) {
// 定义一些日志信息
String message1 = "执行mysqld.exe操作";
String message2 = "执行java.exe操作";
String message3 = "执行tomcat.exe操作";
// 调用showLog方法,参数是一个函数式接口--BuildLogMessage接口,所以可以使用Lambda表达式
/* showLog(2, () -> {
// 返回一个拼接好的字符串
return message1 + message2 + message3;
});*/
// 简化Lambda表达式
/*
使用Lambda表达式作为参数传递,
只有满足条件,日志的等级小于等于3
才会调用此接口BuildLogMessage当中的方法buildLogMessage
才会进行字符串的拼接动作
如果条件不满足,日志的等级大于3
那么BuildLogMessage接口当中的方法buildLogMessage也不会执行
所以拼接字符串的动作也不会执行
所以不会存在性能上的浪费。
*/
showLog(4, () -> {
System.out.println("前面的日志等级大于3,此处不执行!");
return message1 + message2 + message3;
});
}
备注:实际上使用内部类也可以达到这样的效果,只是将代码操作延迟到另外一个对象当中通过调用方法来完成。
后面的代码的执行取决于前面的条件的判断结果。
使用Lambda作为方法的参数和返回值:
在Java当中,Lambda表达式是作为匿名内部类的替代品,如果一个方法的参数是一个函数式接口类型,那么可以使用Lambda表达式进行替代。
java.lang.Runnable接口就是一个函数式接口。代码如下:
public class Demo01Lambda{
//定义一个方法,开启线程的方法
public static void startThread(Runnable r){
new Thread(r).start;
}
public static void main(String[] args){
startThread(()->{
System.out.println("开启一个新的线程,线程任务被执行了!");
});
//优化Lambda
startThread(()->System.out.println("开启新线程");
}
}
如果一个方法的返回值类型是一个函数式接口,那么我们可以直接使用一个Lambda表达式。
java.util.Comparator接口是一个函数式接口;
代码如下:
public class Demo02Lambda{
//定义一个方法,方法的返回值类型是一个函数式接口类型Comparator
public static Comparator<String> createComparator(){
//返回值就是一个函数式接口
return new Comparator(){
@Override
public int compare(String o1,String o2){
//自定义比较的规则,升序/降序
return o1.length()-o2.length();
}
};
//使用Lambda表达式 字符串的长度升序
return (o1,o2) -> o1.length()-o2.length();
public static void main(String[] args){
String[] strs = {"aaa","s","bbb","dddd"};
Arrays.sort(strs,createComparator());
System.out.println(Arrays.toString(str));
}
}
常用的函数式接口:
JDK提供了大量常用的函数式接口,丰富Lambda表达式的使用场景,他们主要在java.util.function包中被提供
Supplier接口
java.util.function.Supplier接口,该接口有且仅有一个无参的方法:T get().用来获取一个泛型参数指定类型的对象数据。由于该接口是一个函数式接口,所以我们可以使用Lambda表达式来操作它。
Supplier<T>接口被称为生产型接口,指定接口的泛型是什么类型,那么接口中的get()方法就会生产什么类型的数据。
// 定义一个方法,方法的参数传递一个Supplier<T>接口,泛型指定String,get方法就会返回一个String
public static String getString(Supplier<String> sup) {
return sup.get();
}
// 定义一个方法,方法的参数传递一个Supplier<T>接口,泛型我指定为Integer,get方法就会返回一个int
public static int getNum(Supplier<Integer> sup) {
return sup.get();
}
public static void main(String[] args) {
// 调用getString方法,方法的参数传递Supplier<T>是一个函数式接口,那么我们就可以使用Lambda
/* String str = getString(() -> {
// 生产一个字符串并返回
return "你好Java";
});
System.out.println(str);*/
// 求一个int类型的数组中的最值
int[] arr = {10,20,5,8,3,50};
int maxNum = getNum(() -> {
// 求出数组的最大值
int max = arr[0];
for (int i : arr) {
// 判断
if (max < i) {
max = i;
}
}
return max;
});
// 输出最大值
System.out.println(maxNum);// 50
}
Consumer接口:
java.util.function.Consumer<T>接口刚好和Supplier接口相反的,他不是用来生产一个数据,而是用来消费一个数据。
数据的类型由泛型来指定。
accept方法:
消费一个指定泛型的数据。
代码如下:
*
accept()方法,消费一个指定类型的数据
Consumer接口是一个消费型接口,泛型指定什么数据类型,使用accept方法就消费什么类型的数据
至于具体怎么消费,需要自定义(统计,求和,输出....)
*/
public class DemoConsumer {
//定义一个方法,方法的参数传递一个Consumer<T>接口,传递一个字符串变量
public static void consumer(String string,Consumer<String> con){
//使用消费型接口对象,消费传递的字符串值
con.accept(string);
}
public static void main(String[] args) {
//来调用消费方法consumer
consumer("abcdsdgfsf", (name)-> System.out.println(
new StringBuffer(name.toUpperCase()).reverse().toString()));
}
}
默认的方法:andThen
如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现这样的效果,消费数据的时候,首先做一个消费的操作,再做一个消费的操作,实现组合。可以通过Consuner接口当中的默认方法:andThen来实现。
代码如下:
public static void main(String[] args) {
consumers("crazy-韩先森-加油奥里给", (name1) ->
//消费规则
//截取传入的字符串
//final String s = name1.substring(0, 6);
System.out.println(new String(name1.substring(0, 6).toString()))
, (name2) -> {
//定义消费的规则,分成字符串数组展示
String[] strings = name2.split("-");
System.out.println(Arrays.toString(strings));//{crazy,韩先森,加油奥里给}
});
}
//定义一个方法,方法的参数传递一个字符串和两个Consumer<T>接口,Consumer接口的泛型指定为字符串
public static void consumers(String s, Consumer<String> con1, Consumer<String> con2) {
//
// con1.accept(s);
// con2.accept(s);
//andThen连续消费 default Consumer<String> andThen
con1.andThen(con2).accept(s);
//规则con1连接con2,先执行con1消费数据,再执行con2数据
/*
crazy-
[crazy, 韩先森, 加油奥里给]
*/
}
通过查看源码得知,andThen方法不允许传入一个null对象否则就会抛出空指针异常。
要想把两次消费的动作连接起来,需要传入两个Consumer接口,通过andThen方法一步一步执行消费动作。
练习:
/*
练习:定义一个字符串数组,存储每一个人的信息:"张三,20,郑州市",存储5个人的信息。使用Consumer接口按照指定的格式进行打印输出:姓名:张三;年龄:20;地址:郑州市
要求将打印姓名的动作作为第一个Consumer接口的规则
将打印年龄的动作作为第二个Consumer接口的规则
将打印地址的动作作为第三个Consumer接口的规则;
*/
String[] s = {"张1,20,郑州市",
"张2,21,郑州市1"
, "张3,22,郑州市2"
, "张4,23,郑州市3"
, "张5,24,郑州市4"};
//Lambda表达式表示
consumer3(s, (name) -> System.out.print("姓名:" + name.split(",")[0] + "; ")
, (age) -> System.out.print("年龄:" + age.split(",")[1] + "; ")
, (address) -> System.out.println("地址:" + address.split(",")[2]));
}
public static void consumer3(String[] strings,
Consumer<String> consumer,
Consumer<String> consumer2,
Consumer<String> consumer3) {
for (String string : strings) {
//先消費consumer再消费consumer2,再消费consumer3
consumer.andThen(consumer2).andThen(consumer3).accept(string);
}
}
Stream流:
在java1.8中,由于Lambda表达式这种函数式编程,JDK引入了一个全新的概念“Stream流”,用于解决已有集合类库的一些弊端的。
给定你一些集合的数据
public class DemoStream{
public static void main(String[] args){
//构建一个集合
List<String> list = new ArrayList<String>();
list.add("123465");
list.add("acb1353");
list.add("abd144");
list.add("afg125");
list.add("ahb175");
list.add("asd1657");
//需要字符串包含数字2的元素取出来
List<String> list2 = new ArrayList<String>();
for(String str:list){
if(str.contains("2")){
list2.add(str);
}
//在结果字符串集合中把字母串长度不能超过6个的元素取出来.
List<String> list3 = new ArrayList<String>();
for(String str:list2){
if(str.length()<=6){
list3.add(str);
}
}
//遍历查看最终想要的元素集合
for(String str: list3){
System.out.println(str);
}
}
}
当我们需要集合当中的元素进行操作的时候,总是需要对集合循环遍历,再次循环遍历...一定要这样做吗?
不一定,它只是用来找到你需要元素的一种方式,并不是目的。目的是想要取出想要的元素并且循环打印展示出来。以往的方式就是每次循环都需要从头开始遍历,下一次循环还是从头开始。
Stream流对象中的API方法。
java1.8可以使用Lambda表达式来优化你遍历集合的方式。
public class DemoStream{
public static void main(String[] args){
List<String> list = new ArrayList<String>();
list.add("123465");
list.add("acb1353");
list.add("abd144");
list.add("afg125");
list.add("ahb175");
list.add("asd1657");
//需要字符串包含数字2的元素取出来
//在结果字符串集合中把字母串长度不能超过6个的元素取出来.
//遍历查看最终想要的元素集合
list.stream().filter(str->str.contains("2")).filter(str->str.length()
<=6).forEach(str->System.out.println(str));
}
}
一般我们把流式思想称之为"生产流水线"。
流式思想概述:
整体来看,流式思想类似于工厂中的"生产流水线"。
当需要对多个元素进行操作的时候,尤其是多步操作,考虑到性能以及遍历性,首先需要考虑一个“模型”步骤方案。然后按照设计的方案去执行。
比如你做某种类型的商品机进行操作,你需要进行过滤,映射,跳过,计数等操作。这也是我们对集合中的元素操作的步骤,这一套步骤我们称之为一种处理方案,而方案就是一种“函数模型”。方案中操作的每一个步骤,我们都可以称之为"流"。调用指定的API方法,从一个流中转换为另一个流。都有对应的API方法,filter,map,skip,count都是对函数模型进行操作。
当我们使用一个流的时候。通常包含三个基本步骤:
1.获取一个数据源 2.进行数据转换 3.执行需要的操作获取想要的操作结果。每次转换原有的Stream对象,会返回新的Stream对象。这样我们就可以像链条一样进行操作。
Stream流和以往的Collection集合有所不同。Stream操作有两个基础的特征:
1.中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格,对中间进行优化,比如可以进行延迟执行和短路。
2.内部迭代:以前遍历集合都是迭代器或者增强for循环,显示的在集合外部进行迭代,这叫做外部迭代。Stream流提供了内部迭代的方法,这个流可以直接调用遍历的方法。
Stream流其实是一个集合元素的函数模型,不是集合,也不是数据结构,其本身并不存储任何元素或者是地址值。
Stream流,是一个来自数据源的元素队列:
元素是特定类型的对象,形成一个队列。java中的Stream并不会存储元素,而是按需计算。
数据源 流的来源,可以是集合,也可以是数组等容器。
获取流对象
java.util.stream.Stream<T>是JDK1.8引入的新特性,较为常用的接口(本身并不是函数式接口)
获取一个流对象,有以下常见的操作:
所有的Collection集合都可以通过stream()默认的方法来获取。
Stream接口里面含有一个静态方法of也可以获取对应的流对象。
根据Collection集合或者是of来获取流对象
只要是Collection集合的实现类或者子接口都可以调用stream默认方法获取流对象
public static void main(String[] args) {
// 把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
HashSet<Integer> set = new HashSet<>();
Stream<Integer> stream2 = set.stream();
HashMap<String, String> map = new HashMap<>();
// map中的key存储到一个set中
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
// 把map中的value值存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
// 把map中的key和value值一起存储到entry(键与值的映射)中
Set<Entry<String,String>> entrySet = map.entrySet();
Stream<Entry<String, String>> stream5 = entrySet.stream();
// 把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1,2,3,4,5,6);
stream6.filter(num -> num > 3).filter(num -> num % 2 == 0).forEach(num ->System.out.println(num));
// 可变参数是一个数组
String[] arr = {"a","b","c","d"};
Stream<String> stream7 = Stream.of(arr);
}
Stream 流中的常用方法:
流模型中的操作很多,大致上可以把其中的api方法分成两部分:
延迟方法:返回值类型都是Stream接口自身,因此可以支持链式操作。
终结方法:返回值就不是Stream接口自身,因此不能再进行链式操作。比如:count方法和forEach方法
forEach方法
void forEach(Consumer<T> consumer);//借助于该函数式接口中的方法accept方法
//Consumer<T>是一个消费性接口,用来消费一个指定泛型的数据
代码如下:
//获取一个数据
Stream<String> stream6 = Stream.of("abc","abnc","abmc","jabc","anbc");
//转换数据
//执行操作获取想要的结果
stream6.forEach(str->{
if(str.contains("n")){
System.out.println(str);
}
});
过滤:filter
可以通过filter方法将一个流转换成另外一个子集流。
Stream<T> filter(Predicate<? super T>predicate)返回由与此给定谓词匹配的此流的元素组成的流。
//借助于Predicate函数式接口当中的抽象方法 test(T t) 对数据进行过滤
该方法接收一个函数式接口Predicate,可以使用Lambda表达式进行条件的筛选。
Predicate接口:
java.util.stream.Preticate函数式接口,其中唯一的抽象方法
boolean test(T t),该方法会返回布尔类型值,代表指定的条件是否满足,如果条件满足返回true,那么Stream流的方法filter将集合或者数组其中的元素保留下来,如果条件不满足返回false,那么filter方法会舍弃该元素。
public static void main(String[] args) {
// 1、 准备一个数据源
// 获取该数据源
String[] arr = {"小孙","小王","小赵","老王","涂少","老刘"};
// 2. 数据转换
// 使用Stream流中的方法filter,对姓涂的人过滤掉
Stream<String> stream = Stream.of(arr);
Stream<String> stream2 = stream.filter(name -> !name.contains("涂"));
Stream<String> stream3 = stream2.filter(name -> name.startsWith("小"));
stream3.forEach(name ->System.out.println(name));
stream2.filter(name -> !name.contains("少")).forEach(name ->System.out.println(name));
/* Stream流属于管道流,每次只能被消费一次
* 第一个Stream流调用完毕后,数据就会被转换到下一个Stream上
* 而这时第一个Stream流已经使用完毕,就会关闭了。
* 所以第一个Stream就不能再调用方法了。
* 如果你强制调用方法,程序就会抛出非法状态异常
* java.lang.IllegalStateException: stream has already been operated upon or closed
* stream.filter(name -> !name.contains("涂"))
.filter(name -> name.startsWith("小"))
.forEach(name ->System.out.println(name));
*/
}
映射:map
如果你需要将流中的数据映射到另外一个流中,可以使用map方法。
<R> Stream<R> map(Function<? super T,? extends R> mapper)返回由给定函数应用于此流的元素的结果组成的流。
该方法接受一个函数式接口Function作为方法参数。可以将当前流中的T数据转换成另外一种R类型的数据。
Function接口
java.util.stream.Function函数式接口,其中唯一的抽象方法:
R apply(T t)
//可以将一种T类型的数据转换成R类型的数据,那么这种转换的动作,我们称之为"映射";
public static void name2() {
// 1.准备一个数据源
// 获取数据
// 把String字符串的整数--》int 类型的整数
Stream<String> stream = Stream.of("123", "124", "1243", "1423", "1263");
// 2.数据转换,把字符串类型的数据转换成int类型的数据 由于Function是一个函数式接口,所以可以使用Lambda表达式
// apply(T t)
Stream<Integer> map=stream.map((String str)->{
return Integer.valueOf(str);
});
//Stream<Integer> map = stream.map(str -> Integer.valueOf(str));
//遍历
map.forEach(num -> System.out.println(num));
}
统计个数:count
可以像Collection集合中的size()一样,统计流中的元素个数,通过count方法来实现。
//返回此流中的元素个数
long count();
该方法返回一个long类型的值代表流中的元素个数(区别于size()(返回值为int值))
public static void name(){
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
//统计个数
long count = stream.count();
System.out.println(count);//6
}
取用流中前几个元素的方法 :limit
limit()方法可以对流中的数据进行限制-->截取操作,需要一个参数max,设定取用流中前max个数。
Stream<T> limit(long maxSize)//返回由此流的元素组成的流,截取长度不能超过maxSize.
参数是一个long类型的,截取的长度不能超过流中最大元素个数;否则不进行操作。
public void demo3(){
//准备一个数据
//获取数据
Stream<Integer> stream = Stream.of(12,13,23,43,24,67);
//想要截取流中的前五个元素
Stream<Integer> stream2 = stream.limit(3);
//查看流中的元素个数
System.out.println(stream2.count());//3
}
跳过前几个:skip
如果你希望跳过前几个元素,取用后几个元素,可以使用skip方法来实现。
Stream<T> skip(long n)//在丢弃流的第一个n元素后,返回由该流的n元素组成的流。
如果流中的当前个数小于n,你将会的得到一个长度为0的空流,反之流中的个数大于n,则会跳过前n个元素。
public static void name3() {
//1.获取数据
String[] source = {"123","124","235","acd","gh","gjk"};
Stream<String> stream = Stream.of(source);
//跳过前三个元素
Stream<String> stream2= stream.skip(3);
//Stream<String> stream2= stream.skip(source.length);//空流
//
stream2.forEach(name->System.out.println(name));//"acd","gh","gjk"
// long count = stream2.count();
// System.out.println(count);
}
组合:concat
如果有两个流,希望合并成一个流,那么就可以使用concat静态方法
//创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素。
static <T> Stream<T> concat(Stream<? extends T>) a,Stream<? extends T> b)
public void demo(){
//准备两个流
//获取两次数据流
Stream<Integer> stream = Stream.of(12,13,45,56,78,1);
Stream<Integer> stream2 = Stream.of(1,3,5,6,7);
//合并流
Stream<Integer> stream3 = Stream.concat(stream,stream2);
stream3.forEach(name->System.ouy.println(name));
//结果:12,13,45,56,78,1,1,3,5,6,7
}
方法引用: