-
理解CookieHTTP协议自身是“无状态”协议,但是在实际开发中,我们很多时候是需要知道请求之间的关联关系的。 上述图中的 "令牌" 通常就存储在 Cookie 字段中.此时在服务器这边就需要记录"令牌"信息, 以及令牌对应的⽤⼾信息, 这个就是 Session 机制所做的⼯作.理解Session我们先来了解⼀下什么是会话.会话: 对话的意思 在计算机领域, 会话是⼀个客⼾与服务器之间的不中断的请求响应. 对客⼾的每个请求,服务器能够识别出请求来⾃于同⼀个客⼾. 当⼀个未知的客⼾向Web应⽤程序发送第⼀个请求时就开始了⼀个会话.当客⼾明确结束会话或服务器在⼀个时限内没有接受到客⼾的任何请求时,会话就结束了.服务器同⼀时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是从属于哪个⽤⼾, 也就是属于哪个会话, 就需要在服务器这边记录每个会话以及与⽤⼾的信息的对应关系.Session是服务器为了保存⽤⼾信息⽽创建的⼀个特殊的对象. Session的本质就是⼀个 "哈希表", 存储了⼀些键值对结构. Key 就是SessionID, Value 就是⽤⼾信息(⽤⼾信息可以根据需求灵活设计). SessionId是由服务器⽣成的⼀个 "唯⼀性字符串", 从 Session 机制的⻆度来看, 这个唯⼀性字符串称为 "SessionId". 但是站在整个登录流程中看待, 也可以把这个唯⼀性字符串称为 "token".上述例⼦中的令牌ID, 就可以看做是SessionId, 只不过令牌除了ID之外, 还会带⼀些其他信息, ⽐如时间, 签名等. 1. 当⽤⼾登陆的时候, 服务器在 Session 中新增⼀个新记录, 并把 sessionId返回给客⼾端. (通过HTTP 响应中的 Set-Cookie 字段返回).2. 客⼾端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId. (通过 HTTP 请求中的Cookie 字段带上).3. 服务器收到请求之后, 根据请求中的 sessionId在 Session 信息中获取到对应的⽤⼾信息, 再进⾏后续操作.找不到则重新创建Session, 并把SessionID返回. Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.Cookie和Session的区别• Cookie 是客⼾端保存⽤⼾信息的⼀种机制. Session 是服务器端保存⽤⼾信息的⼀种机制.• Cookie 和 Session之间主要是通过 SessionId 关联起来的, SessionId 是 Cookie 和 Session 之间的桥梁• Cookie 和 Session 经常会在⼀起配合使⽤. 但是不是必须配合.• 完全可以⽤ Cookie 来保存⼀些数据在客⼾端. 这些数据不⼀定是⽤⼾⾝份信息, 也不⼀定是SessionId• Session 中的sessionId 也不需要⾮得通过 Cookie/Set-Cookie 传递, ⽐如通过URL传递.1. 存储位置Cookie:存储在客户端(浏览器)上。当服务器响应一个HTTP请求时,它可以在响应头中包含一个Set-Cookie字段,浏览器会保存这个Cookie,并在后续的请求中通过Cookie请求头将Cookie发送回服务器。Session:存储在服务器端。服务器为每个用户会话创建一个唯一的标识符(通常是Session ID),这个标识符被发送到客户端(通常是通过Cookie,但也可以通过URL重写等方式),客户端在后续的请求中携带这个标识符,服务器通过这个标识符来识别用户会话。2. 安全性Cookie:由于存储在客户端,因此相对容易受到攻击,如跨站脚本攻击(XSS)可以读取或修改Cookie。但是,可以通过设置HttpOnly和Secure标志来增加安全性,HttpOnly标志可以防止JavaScript访问Cookie,Secure标志则要求Cookie仅通过HTTPS发送。Session:存储在服务器端,因此相对更安全。但是,如果Session ID被泄露(例如,通过URL重写并泄露在日志中),则可能面临会话劫持的风险。3. 容量限制Cookie:由于存储在客户端,其大小受到浏览器和服务器设置的限制。大多数浏览器对每个Cookie的大小和每个域名下的Cookie总数都有限制。Session:存储在服务器端,因此其大小限制主要取决于服务器的内存和配置,通常远大于Cookie的限制。4. 生命周期Cookie:可以设置过期时间(Expires/Max-Age),也可以不设置(会话Cookie,浏览器关闭时失效)。Session:通常依赖于服务器端的配置和Session的存储方式(如内存、数据库等)。如果服务器配置了Session的超时时间,则Session在达到超时时间后会被销毁。5. 使用场景Cookie:适用于存储少量数据,如用户偏好设置、登录状态等。由于存储在客户端,可以跨多个页面和请求持久化数据。Session:适用于存储大量数据,如用户信息、购物车内容等。由于存储在服务器端,可以更安全地管理用户会话。获取Cookie首先先设置Cookie 再获取: @RequestMapping("/getC") public String getCookie(HttpServletRequest request){ //获取参数// String name = request.getParameter("name"); Cookie[] cookies = request.getCookies(); if (cookies!=null){ Arrays.stream(cookies).forEach(ck -> System.out.println(ck.getName()+":"+ck.getValue())); } return "获取Cookie"; }更为简洁的代码:@RequestMapping("/getC2")public String getCookie2(@CookieValue("name") String name){ return "从Cookie中获取值, name:"+name;}获取SessionSession是服务器端的机制, 我们需要先存储, 才能再获取.Session 也是基于HttpServletRequest 来存储和获取的.设置Session @RequestMapping("/setSess") public String setSess(HttpServletRequest request){ //从cookie中获取到了sessionID, 根据sessionID获取Session对象, 如果没有获取到, 会创建一个session对象 HttpSession session = request.getSession(); session.setAttribute("name", "zhangsan"); return "设置session成功"; }再获取: @RequestMapping("/getSess") public String getSess(HttpServletRequest request){ //从cookie中获取到了sessionID, 根据session获取Session对象 HttpSession session = request.getSession(); String name = (String)session.getAttribute("name"); return "从session中获取name:"+name; } 获取Session更为简洁的代码: @RequestMapping("/getSess2") public String getSess2(HttpSession session){ String name = (String)session.getAttribute("name"); return "从session中获取name:"+name; } @RequestMapping("/getSess3") public String getSess3(@SessionAttribute("name") String name){ return "从session中获取name:"+name; }获取Header获取User-Agent @RequestMapping("/getHeader") public String getHeader(HttpServletRequest request){ String userAgent = request.getHeader("User-Agent"); return "从header中获取信息, userAgent:"+userAgent; }更为简洁的代码: @RequestMapping("/getHeader2") public String getHeader2(@RequestHeader("User-Agent") String userAgent){ return "从header中获取信息, userAgent:"+userAgent; ———————————————— 原文链接:https://blog.csdn.net/wmh_1234567/article/details/141640649
-
前言相信大家都有去过图书馆吧,那么在借阅图书和归还的时候,都有一个系统来记录并操作这些过程,所以今天就带着大家利用之前的所学知识来简单实现一下图书管理系统的基本逻辑构造一. 图书管理系统的核心图书管理系统的核心包括三个部分:书籍的信息(书本属性)、操作书籍的人(管理员和读者)、 对书籍的操作(借阅、归还)所以在这里我们分别创建3个包 book、operation、user 来实现各个部分的操作: 二. 图书管理系统基本框架2.1 book包我们在book这个包中创建2个类:Book、Booklist,Book用来描述书籍的基本信息,Booklist充当书架,里面用来记录书架中书籍的信息。 2.1.1 Book(书籍类)首先在Book中我们要创建变量来记录书籍的基本信息: private String name; //书名 private String author; //作者 private int price; //价格 private String type; //书籍类型 private boolean isborrow; //是否被借出并且这里的书籍基本信息,我们是用private访问修饰限定符修饰变量的,其他类想要直接访问是访问不了的,所以这里需要我们创建方法间接访问。public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public String getType() { return type; } public void setType(String type) { this.type = type; } public boolean isIsborrow() { return isborrow; } public void setIsborrow(boolean isborrow) { this.isborrow = isborrow; }接下来我们来写Book类的构造方法:那么当我们每次新增一本书的时候 ,该书籍默认就是未借出的状态,默认初始值就是false,所以不需要在构造方法中进行初始化public Book(String name, String author, int price, String type) { //初始化书的属性 this.name = name; this.author = author; this.price = price; this.type = type; }这时候书籍的信息属性都能够被记录了,那么我们想看看这些书籍的信息该怎么办呢?没错打印出来,此时我们需要对 ToString 方法进行重写: 2.1.2 Booklist (书架类)能够记录书籍信息的Book类创建完成后,我们来创建一个书架类(Booklist),书架类的作用就是能够存放每一本书,并且记录每一本书的借阅状态。 private Book [] books;//表示书架可以存放10本书 private int usedbooks;//表示books数组中存放了几本书那么我们依然需要通过间接访问才能访问书架中的基本信息,需要自己提供方法间接访问: public int getUsedbooks() { return usedbooks;//返回有效书本数量 } public void setUsedbooks(int usedbooks) { this.usedbooks = usedbooks;//更改有效书本数量 } public Book getbook(int pos){ return books[pos]; //返回书架中 第pos本书 } public void setBooks(int pos,Book book){ this.books[pos]=book; //更改书架中的书 }那么我们还是写一下Booklist类的构造方法,默认书架中已经存放了书籍:public BookList() { this.books = new Book [10]; //默认创建存放10本书大小的数组 this.books[0]=new Book("三国演义","罗贯中",20,"四大名著"); this.books[1]=new Book("西游记","吴承恩",22,"四大名著"); this.books[2]=new Book("红楼梦","曹雪芹",24,"四大名著"); this.books[3]=new Book("水浒传","施耐庵",26,"四大名著"); this.usedbooks=4;//初始化有效书本数量为4本 } 那么在book这个包的操作就告一段落,后续随着思路的深入再进行补充~2.2 user包在user包中我们新建三个类来描述管理员和用户,那么管理员和用户也是拥有相同属性的,而第三个类User,我们就定义成抽象类,让管理员类与用户类去继承,这样就可以省略一些重复的代码啦2.2.1 User类那么管理员与用户共同拥有的特征就是都有名字,那么我们就在User类中定义一个名字属性的变量,并且通过构造方法初始化 管理员 或 用户 的名称:public abstract class User { public String name; //名字 public User(String name) { this.name = name; //初始化名称 }}2.2.2 Administrator(管理员类)因为我们的管理员类继承了User类,所以需要在构造方法中调用父类的构造方法:public class Administrator extends User{ public Administrator(String name) { super(name);//通过父类初始化管理员名称 }}2.2.3 Visitor(用户类)同理用户类也继承了User类,所以也需要构造方法调用父类的构造方法:public class Visitor extends User{ public Visitor(String name) { super(name); //通过父类初始化用户名称 }}那么书籍的属性和操作者的基本框架我们已经实现了,接下来该实现一下图书管理系统的功能了~2.3 图书管理系统操作菜单管理员菜单1.查找图书2.新增图书3.删除图书4.显示图书0.退出系统用户菜单1.查找图书2.借阅图书3.归还图书0.退出系统接下来,我们就针对这些功能创建一个包,实现这些功能。2.4 operation(操作包)在这个包中我们去实现上述描述的相关操作,接着我们创建一个接口来实现多态,从而降低代码的复杂度operate接口:public interface Operate { //服务接口 void work(BookList books); //功能的实现我们都要利用到书架,所以这里参数给定一个书架对象}接下来我们创建实现各个功能的类: Addbook类:(新增书籍)public class Addbook implements Operate{ @Override public void work(BookList books) { System.out.println("增加书籍..."); }}Borrowbook类:(借阅书籍)public class Borrowbook implements Operate { @Override public void work(BookList books) { System.out.println("借阅书籍...."); }} Deletebook类:(删除书籍)public class Deletebook implements Operate{ @Override public void work(BookList books) { System.out.println("删除书籍..."); }}Exitbook类:(退出系统)public class Exitbook implements Operate{ @Override public void work(BookList books) { System.out.println("退出系统....."); }}Findbook类:(查阅书籍)public class Findbook implements Operate{ @Override public void work(BookList books) { System.out.println("查找书籍...."); ]}Returnbook类:(归还书籍)public class Retuenbook implements Operate{ @Override public void work(BookList books) { System.out.println("归还书籍....");Showbook类:(显示书籍)public class Showbook implements Operate { @Override public void work(BookList books) { System.out.println("显示书籍...."); }}接下来就要慢慢的去将每个类的功能具体的实现,并且将每个类联系起来运作图书管理系统。既然知道了管理员和用户的菜单后,我们就分别给管理员和用户添加一下菜单:在Administrator类添加管理员菜单:public void menu() { System.out.println("欢迎 "+this.name+" 管理员来到图书管理系统"); System.out.println("<<**************管理员操作菜单******************>>"); System.out.println("1. 查找图书 "); System.out.println("2. 新增图书 "); System.out.println("3. 删除图书 "); System.out.println("4. 显示图书"); System.out.println("0. 退出系统 "); System.out.println("<<******************************************>>");}在Visitor类中添加用户菜单:public void menu(){ System.out.println("欢迎 "+this.name+" 读者来到图书管理系统"); System.out.println("<<***************游客读者菜单*************>>"); System.out.println("1. 查找图书 "); System.out.println("2. 借阅图书 "); System.out.println("3. 归还图书 "); System.out.println("0. 退出系统 "); System.out.println("<<**************************************>>");} 那么我们现在创建一个Main类,在类中写上我们的main方法实现一下基本逻辑:public class Main { public static User login(){ //登录系统过程 Scanner scanner=new Scanner(System.in); System.out.println("请输入你的名字:"); String name = scanner.nextLine(); System.out.println("请输入你要登入的帐号:1.管理员登录 -----> 2.游客登录 ----->"); int choice=scanner.nextInt(); if(choice==1){ return new Administrator(name); //如果选择了1,就实例化一个管理员对象,并返回 }else{ return new Visitor(name); //如果选择了2,就实例化一个用户对象,并返回 } //此时的返回值我们不能确定返回的是管理员对象还是用户对象,所以这里用向上转型返回User类型的对象 } public static void main(String[] args) { User user=login(); //进入登录系统 user.menu(); //打印菜单 }} 但是此时login方法的返回对象我们使用User类型的对象进行接收的,此时我们是不能使用user.menu()的,那么该怎么办呢?我们之前将User抽象出来作为管理员类和用户类的父类,那么此时我们在User类中写一个menu()方法就可以实现多态,打印菜单啦~public abstract class User { public String name; //用户名称 public User(String name) { this.name = name; //初始化用户名称 } public abstract int menu(); //抽象方法实现多态,打印菜单}那么此时只需要将子类做出相应的改动,改变成重写的形式就可以了: 这就实现了打印菜单的功能,但是光打印选择不了功能可不行呀,所以我们要在菜单中加上一个选择功能: @Override public int menu() { System.out.println("欢迎 "+this.name+" 管理员来到图书管理系统"); System.out.println("<<**************管理员操作菜单******************>>"); System.out.println("1. 查找图书 "); System.out.println("2. 新增图书 "); System.out.println("3. 删除图书 "); System.out.println("4. 显示图书"); System.out.println("0. 退出系统 "); System.out.println("<<******************************************>>"); //选择功能 Scanner scanner=new Scanner(System.in); System.out.println("请选择您需要的服务:-->>"); int choice=scanner.nextInt(); return choice; }通过输入标号来选择我们想要的服务,那么我们应该返回这个标号,所以menu()方法的返回值也要改成 int 类型,Visitor类的menu()方法也是同理。那么能够选择服务后,我们应该根据对象(管理员或用户)选择该调用哪一个菜单方法:Administrator(管理员类):public Administrator(String name) { super(name);//通过父类初始化管理员名称 this.operate=new Operate[]{ //创建 管理员账户提供的服务 new Exitbook(), new Findbook(), new Addbook(), new Deletebook(), new Showbook() }; }Visitor(用户类):public class Visitor extends User{ public Visitor(String name) { super(name); //通过父类初始化用户名称 this.operate=new Operate[]{ //创建 游客账户提供的服务 new Exitbook(), new Findbook(), new Borrowbook(), new Retuenbook() }; } 那么当我们分别在它们的构造方法中创建一个Operate类型的数组后,里面存放Operate接口实现的方法,那么在new一个管理员对象或者用户对象时,系统就会为这个数组分配内存: 那么我们需要在User类中加上这么一段代码利用动态绑定调用管理员菜单中的方法还是调用用户菜单中的方法: public Operate []operate; //创建服务数组,子类通过数组下标调用 各个服务 public void Dooperate(int choice,BookList books){ this.operate[choice].work(books); //调用 游客/管理员 所选择的服务 }this.operate[ choice ]就是我们new的那个对象中构造方法中下标为 choice 的那个类,而后面的 .work(books)就是调用的对应类的work方法。那么现在我们在main方法中打印菜单的时候就需要有个变量来接收选择的服务啦,再通过new对应的对象调用上面写的 Dooperate方法调用所选择的服务。public static void main(String[] args) { BookList bookList=new BookList(); //实例化书架 User user=login(); //进入登录系统 while(true) { int chioce = user.menu(); //接收选择服务的选项 user.Dooperate(chioce, bookList); //调用 游客/管理员 提供的服务 } }而为了实现用户输入0时才退出系统,所以这里的while循环我们设置为死循环。此时基本框架就搭建完成啦~我们来看看测试效果:那么我们来画图分析一下运行的过程:1. 通过主函数调用login方法: 2. 通过login方法返回的对象调用该对象类中的menu()方法 3. 最后通过调用user.Dooperate方法一步一步实现所选择的服务 三. 实现服务 3.1 显示所有书籍那么在实现显示书籍功能之前,我们应该在Booklist类中写一个方法让Showbook类中的work方法能够通过数组下标打印书籍的信息: public Book getbook(int pos){ return this.books[pos]; //返回书架中 第pos本书 }接下来我们就可以实现显示书籍这个服务啦~在Showbook类中实现: public class Showbook implements Operate { @Override public void work(BookList books) { System.out.println("显示书籍...."); int count=books.getUsedbooks(); for(int i=0;i<count;i++) { //通过循环遍历数组;调用books.getbook(i)方法获取指定的书籍对象 System.out.println(books.getbook(i).toString()); } System.out.println(); }}3.2 查找书籍查找书籍的过程与显示所有书籍相似,也是通过循环遍历数组,利用equal()方法进行对比,实现服务。在Findbook类中实现:public class Findbook implements Operate{ @Override public void work(BookList books) { System.out.println("查找书籍...."); Scanner scanner=new Scanner(System.in); System.out.print("请输入要查找书籍的名字:-->"); String name=scanner.nextLine(); int count=books.getUsedbooks(); for(int i=0;i<count;i++) { Book book=books.getbook(i); if(book.getName().equals(name)){ System.out.println("找到了,书本内容如下:"); System.out.println(book); System.out.println(); return; } } System.out.println("很抱歉,没有查找到你要查找的书籍"); System.out.println("后续会尽快联系管理员添加~"); System.out.println(); }} 3.3 退出系统在Exitbook类中实现:public class Exitbook implements Operate{ @Override public void work(BookList books) { System.out.println("退出系统....."); int count=books.getUsedbooks(); for(int i=0;i<count;i++) { books.setBooks(i,null); // } books.setUsedbooks(0); //将有效书籍数量 制为空 System.exit(0); }}书架中存放的书籍都是对象,那么在退出系统之前我们应该将书籍对象都进行回收,避免发生内存泄漏,并且将书架中的书本数量变成0,最后在结尾加上 System.exit(0); 就可以结束程序,退出系统了。3.4 增加书籍在增加书籍之前,我们应该先判断这个书籍是否已经在书架中存放,如果书架中没有那么就添加到书架中在Addbook类中实现:public class Addbook implements Operate{ @Override public void work(BookList books) { System.out.println("增加书籍..."); int count=books.getUsedbooks(); Scanner scanner=new Scanner(System.in); System.out.print("请输入新添加的书名:--->"); String name=scanner.nextLine(); System.out.print("请输入新添加书籍的作者:--->"); String author=scanner.nextLine(); System.out.print("请输入书籍的价格:--->"); int price=scanner.nextInt(); scanner.nextLine(); System.out.print("请输入书籍的类型:--->"); String type=scanner.nextLine(); for(int i=0;i<count;i++) { if(books.getbook(i).getName().equals(name)){ System.out.println("该书籍已经存在了,不需要添加-----"); System.out.println(); return ; } } Book book=new Book(name,author,price,type); books.setBooks(count,book); System.out.println("添加成功---"); books.setUsedbooks(count+1); System.out.println(); }}那么在Booklist类中我们就要提供一个方法用来新增书籍: public void setBooks(int pos,Book book){ this.books[pos]=book; //更改书架中的书 }3.5 删除书籍在删除书籍中我们有两个点需要注意:当我们删除了某本书籍后,需要将该书籍后面的书籍往前移动,并且将移动完最后的那个空间进行回收,并且更改书架中书籍的数量在Deletebook类中实现:public class Deletebook implements Operate{ @Override public void work(BookList books) { System.out.println("删除书籍..."); System.out.print("请输入你要删除的书籍:--->"); Scanner scanner=new Scanner(System.in); String name =scanner.nextLine(); int count=books.getUsedbooks(); int index=-1; boolean flag=false; for (int i = 0; i <count ; i++) { if(books.getbook(i).getName().equals(name)){ index=i; flag=true; break; } } if(flag==false) { System.out.println("没有找到你要删除的书籍-----"); System.out.println(); return ; } if (books.getbook(index).isIsborrow()==true){ System.out.println("该书本已经被借出,暂时不能进行操作-----"); System.out.println(); return; }else{ for(int j=index;j<count-1;j++) { books.setBooks(j,books.getbook(j+1)); } System.out.println("删除成功------->"); books.setUsedbooks(count-1); books.setBooks(count-1,null); } }}那么在删除书籍的时候为了实现将后面的书籍往前面移动,我们需要在Booklist类中调用setBooks方法来实现这个过程: public void setBooks(int pos,Book book){ this.books[pos]=book; //更改书架中的书 }3.6 借阅书籍借阅书籍的时候我们应该判断该书籍是否在书架上存在,如果存在再判断该书籍是否被借出,如果没有被借出,那么就可以进行借阅在Borrowbook类中实现:ublic class Borrowbook implements Operate { @Override public void work(BookList books) { System.out.println("借阅书籍...."); System.out.print("请输入你要借阅的图书:-->"); Scanner scanner = new Scanner(System.in); String name = scanner.nextLine(); int count = books.getUsedbooks(); for (int i = 0; i < count; i++) { if (books.getbook(i).getName().equals(name)) { if (books.getbook(i).isIsborrow() == false) { books.getbook(i).setIsborrow(true); System.out.println("借阅成功---"); System.out.println(books.getbook(i)); System.out.println(); return; }else{ System.out.println("这本书已经被借出,请等待读者归还后再进行借阅---"); System.out.println(); return ; } } } System.out.println("查找不到你要借阅的书籍,后续会联系管理员尽快上架!!!"); System.out.println(); }}这个时候我们需要在Book类中提供一个方法,返回当前书籍是否被借出的状态,并且还要提供一个方法改变书籍是否被借出的状态,在Book类中实现: public boolean isIsborrow() { return isborrow; } public void setIsborrow(boolean isborrow) { this.isborrow = isborrow; } 3.7 归还书籍在归还之前我们需要判断一下这本书是否在书架上存在过,如果存在再进行判断这本书是否有被借出,如果有被借出则可以归还在Returnbook类中实现: public class Retuenbook implements Operate{ @Override public void work(BookList books) { System.out.println("归还书籍...."); System.out.print("请输入你要归还的图书:-->"); Scanner scanner = new Scanner(System.in); String name = scanner.nextLine(); int count = books.getUsedbooks(); for (int i = 0; i < count; i++) { if (books.getbook(i).getName().equals(name)) { if (books.getbook(i).isIsborrow() == true) { books.getbook(i).setIsborrow(false); System.out.println("归还成功---欢迎再次借阅"); System.out.println(books.getbook(i)); System.out.println(); return; }else{ System.out.println("这本书没有被借出,不需要归还---"); System.out.println(); return ; } } } System.out.println("查找不到你要归还的书籍,你可能是在其他图书馆借阅的书籍---"); System.out.println(); }}如果将书籍归还后我们需要将书籍是否借出的状态进行改变,在Book类中实现:public boolean isIsborrow() { return isborrow; } public void setIsborrow(boolean isborrow) { this.isborrow = isborrow; }四. 总代码Main类:import book.BookList;import user.Administrator;import user.User;import user.VipPerson;import user.Visitor;import java.util.Scanner; public class Main { public static User login(){ //登录系统过程 Scanner scanner=new Scanner(System.in); System.out.println("请输入你的名字:"); String name = scanner.nextLine(); System.out.println("请输入你要登入的帐号:1.管理员登录 -----> 2.游客登录 ----->"); int choice=scanner.nextInt(); if(choice==1){ return new Administrator(name); //如果选择了1,就创建一个管理员对象,并返回 }else if(choice==2){ return new Visitor(name); //如果选择了2,就创建一个游客对象,并返回 }else{ return new VipPerson(name); } //此时的返回值我们不能确定返回的是管理员对象还是用户对象,所以这里用向上转型返回User类型的对象 } public static void main(String[] args) { BookList bookList=new BookList(); //实例化书架 User user=login(); //进入登录系统 while(true) { int chioce = user.menu(); //接收选择服务的选项 user.Dooperate(chioce, bookList); //调用 游客/管理员 提供的服务 } }}User类:package user;import book.BookList;import operation.Operate; public abstract class User { public String name; //用户名称 public Operate []operate; //创建服务数组,子类通过数组下标调用 各个服务 public User(String name) { this.name = name; //初始化用户名称 } public abstract int menu(); //服务菜单 public void Dooperate(int choice,BookList books){ this.operate[choice].work(books); //调用 游客/管理员 所选择的服务 }} Administrator类:package user;import operation.*;import java.util.Scanner;public class Administrator extends User{ public Administrator(String name) { super(name);//通过父类初始化管理员名称 this.operate=new Operate[]{ //创建 管理员账户提供的服务 new Exitbook(), new Findbook(), new Addbook(), new Deletebook(), new Showbook() }; } @Override public int menu() { System.out.println("欢迎 "+this.name+" 管理员来到图书管理系统"); System.out.println("<<**************管理员操作菜单******************>>"); System.out.println("1. 查找图书 "); System.out.println("2. 新增图书 "); System.out.println("3. 删除图书 "); System.out.println("4. 显示图书"); System.out.println("0. 退出系统 "); System.out.println("<<******************************************>>"); //选择功能 Scanner scanner=new Scanner(System.in); System.out.println("请选择您需要的服务:-->>"); int choice=scanner.nextInt(); return choice; }}Visitor类:package user;import operation.*;import java.util.Scanner;public class Visitor extends User{ public Visitor(String name) { super(name); //通过父类初始化用户名称 this.operate=new Operate[]{ //创建 游客账户提供的服务 new Exitbook(), new Findbook(), new Borrowbook(), new Retuenbook() }; } public int menu(){ System.out.println("欢迎 "+this.name+" 读者来到图书管理系统"); System.out.println("<<***************游客读者菜单*************>>"); System.out.println("1. 查找图书 "); System.out.println("2. 借阅图书 "); System.out.println("3. 归还图书 "); System.out.println("0. 退出系统 "); System.out.println("<<**************************************>>"); Scanner scanner=new Scanner(System.in); System.out.println("请选择您需要的服务:-->>"); int choice=scanner.nextInt(); return choice; }}Book类:package book;public class Book { //书的属性 private String name; //书名 private String author; //作者 private int price; //价格 private String type; //书籍类型 private boolean isborrow; //是否被借出 public Book(String name, String author, int price, String type) { //初始化书的属性 this.name = name; this.author = author; this.price = price; this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public String getType() { return type; } public void setType(String type) { this.type = type; } public boolean isIsborrow() { return isborrow; } public void setIsborrow(boolean isborrow) { this.isborrow = isborrow; } @Override public String toString() { return "Book{" + "书名='" + name + '\'' + ", 作者='" + author + '\'' + ", 价格=" + price + ", 类型='" + type + '\'' + (isborrow==true?" ,已经借出":" ,未被借出") + '}'; }}Booklist类:package book;public class BookList { private Book [] books;//表示书架可以存放10本书 private int usedbooks;//表示books数组中存放了几本书 public BookList() { this.books = new Book [10]; //默认创建存放10本书大小的数组 this.books[0]=new Book("三国演义","罗贯中",20,"四大名著"); this.books[1]=new Book("西游记","吴承恩",22,"四大名著"); this.books[2]=new Book("红楼梦","曹雪芹",24,"四大名著"); this.books[3]=new Book("水浒传","施耐庵",26,"四大名著"); this.usedbooks=4;//初始化有效书本数量为4本 } public int getUsedbooks() { return usedbooks;//返回有效书本数量 } public void setUsedbooks(int usedbooks) { this.usedbooks = usedbooks;//更改有效书本数量 } public Book getbook(int pos){ return this.books[pos]; //返回书架中 第pos本书 } public void setBooks(int pos,Book book){ this.books[pos]=book; //更改书架中的书 }} 那么operation这个包中各个类的代码都在 实现服务 那里写啦,这里就不在重新上传了~结语以上就是利用我们过去的所学知识实现的图书管理系统的基本逻辑构造,在此感谢大家的观看!!———————————————— 原文链接:https://blog.csdn.net/2402_86304740/article/details/143254119
-
引言在当今数字化浪潮中,深度学习作为人工智能领域的核心驱动力,正以前所未有的速度改变着我们的生活和工作方式。从图像识别到自然语言处理,从医疗诊断到金融预测,深度学习的应用场景无处不在,展现出巨大的潜力和价值。Java作为一门广泛应用于企业级开发的编程语言,以其稳定性、可移植性和丰富的类库资源,在软件开发领域占据着重要地位。然而,传统的Java开发在面对深度学习复杂的模型构建和大规模数据处理时,往往显得力不从心。DL4J(Deeplearning4j)的出现,为Java开发者打开了一扇通往深度学习世界的大门。DL4J是一个专为Java和Scala设计的深度学习框架,它将深度学习的强大功能与Java的企业级特性完美结合。通过DL4J,Java开发者无需深入掌握复杂的底层数学原理和编程语言,就能够利用Java的生态优势,快速搭建和训练深度学习模型。在过去的一年里,我深入研究和实践了Java DL4J深度学习,积累了丰富的经验和见解。在本文,我们将对这一年在Java DL4J深度学习领域的技术探索进行全面总结,让为我们一起来回顾DL4J的整个概况吧!一、Java DL4J深度学习概述1.1 DL4J框架简介DL4J是基于 Java 和 Scala的分布式深度学习库,它构建在ND4J(一个用于 Java 和 Scala 的数值运算库)之上,提供了丰富的神经网络模型和工具,支持多种深度学习任务,如卷积神经网络(CNN)用于图像识别、循环神经网络(RNN)及其变体(如LSTM、GRU)用于处理序列数据等。DL4J的设计目标是让Java开发者能够像使用传统Java库一样轻松地进行深度学习开发,同时保持高性能和可扩展性。1.2 与其他深度学习框架的比较与Python的TensorFlow和PyTorch等热门深度学习框架相比,DL4J具有独特的优势。首先,在语言层面,Java的静态类型系统和强大的企业级生态使其更适合构建大规模、高可靠性的深度学习应用,尤其在对稳定性和安全性要求较高的企业级场景中。其次,DL4J提供了与Java生态系统的无缝集成,方便与其他Java技术栈(如Spring框架、Hadoop等)结合使用,实现端到端的解决方案。然而,Python的深度学习框架由于其简洁的语法和庞大的社区支持,在快速原型开发和研究领域更为流行。DL4J(Deeplearning4j)则在生产环境部署和企业级应用开发方面展现出明显的优势。1.3 DL4J 的优势1.3.1 与 Java 生态系统的无缝集成由于 DL4J 是用 Java 编写的,它可以与现有的 Java 项目轻松集成,利用 Java 丰富的类库和工具,提高开发效率。1.3.2 分布式计算支持DL4J 支持分布式训练,能够充分利用集群计算资源,加速模型训练过程,适用于大规模数据集的深度学习任务。1.3.3 高度可定制DL4J 提供了丰富的 API,开发者可以根据具体需求灵活定制神经网络结构、优化算法和训练参数,实现个性化的深度学习模型。二、开发环境搭建2.1 安装Java JDK首先,确保系统安装了合适版本的Java JDK(Java Development Kit)。DL4J支持Java 8及以上版本。可以从Oracle官方网站或OpenJDK官网下载并安装相应的JDK。安装完成后,配置系统环境变量JAVA_HOME,指向JDK的安装目录,并将%JAVA_HOME%\bin添加到系统的PATH环境变量中,以便在命令行中能够正确识别java和javac命令。2.2 配置Maven项目Maven是Java项目中常用的构建工具,用于管理项目的依赖和构建过程。创建一个新的Maven项目,可以使用Maven的命令行工具或集成开发环境(IDE)如Eclipse、IntelliJ IDEA等。在项目的pom.xml文件中,需要引入DL4J相关的依赖。<!-- 引入DL4J(deeplearning4j)核心依赖 --><dependency> <groupId>org.deeplearning4j</groupId> <artifactId>deeplearning4j-core</artifactId> <version>1.0.0-beta7</version></dependency><!-- 引入ND4J后端依赖,这里以CPU后端为例。Nd4j 是 DL4J 的底层数值运算库,为 DL4J 提供了高效的矩阵运算支持。 --><dependency> <groupId>org.nd4j</groupId> <artifactId>nd4j-native-platform</artifactId> <version>1.0.0-beta7</version></dependency><!-- 引入数据加载和预处理相关依赖 --><dependency> <groupId>org.deeplearning4j</groupId> <artifactId>deeplearning4j-ui</artifactId> <version>1.0.0-beta7</version></dependency><dependency> <groupId>org.deeplearning4j</groupId> <artifactId>deeplearning4j-datavec</artifactId> <version>1.0.0-beta7</version></dependency>上述代码中:deeplearning4j-core是DL4J的核心库,包含了深度学习模型构建、训练和评估的基本功能。nd4j-native-platform是ND4J的本地平台实现,提供了数值计算的底层支持。这里选择了CPU版本,如果需要使用GPU加速,可以引入相应的GPU版本依赖。deeplearning4j-ui提供了可视化工具,方便监控模型训练过程。deeplearning4j-datavec用于数据加载、预处理和转换,是构建深度学习模型的重要环节。2.3 选择合适的IDE选择一个功能强大的IDE对于开发效率至关重要。IntelliJ IDEA以其丰富的Java开发功能和对Maven项目的良好支持,成为许多Java开发者的首选。在IntelliJ IDEA中,导入创建好的Maven项目,IDE会自动下载并解析pom.xml中定义的依赖。同时,IDE提供了代码自动完成、调试等功能,方便开发者编写和测试DL4J应用程序。三、深度学习基础概念3.1 神经网络神经网络是深度学习的核心概念之一,它模仿人类神经系统的结构和工作方式。一个简单的神经网络由输入层、隐藏层和输出层组成。输入层接收外部数据,隐藏层对数据进行特征提取和转换,输出层根据隐藏层的处理结果产生最终的预测或分类结果。例如,在一个手写数字识别的神经网络中,输入层接收图像的像素值,隐藏层通过一系列的神经元计算提取图像中的特征,如线条、轮廓等,输出层则根据这些特征判断图像中的数字是 0 到 9 中的哪一个。3.2 神经元与激活函数神经元是神经网络的基本计算单元,它接收多个输入信号,并通过加权求和的方式将这些输入信号组合起来,再经过激活函数的处理得到输出。激活函数的作用是为神经网络引入非线性因素,使得神经网络能够学习到复杂的非线性关系。常见的激活函数有 sigmoid 函数、ReLU(Rectified Linear Unit)函数等。sigmoid 函数将输入值映射到 0 到 1 之间,其公式为:σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}}σ(x)= 1+e −xReLU 函数则更为简单,当输入大于 0 时,输出等于输入;当输入小于等于 0 时,输出为 0,其公式为:f ( x ) = max ( 0 , x ) f(x) = \max(0, x)f(x)=max(0,x)3.3 反向传播算法反向传播算法是神经网络训练的核心算法,它用于计算损失函数关于网络参数(权重和偏置)的梯度,以便通过梯度下降等优化算法更新参数,使得损失函数最小化。反向传播算法的基本思想是从输出层开始,根据损失函数计算输出层的误差,然后将误差反向传播到隐藏层,依次计算每个隐藏层的误差,最后根据误差计算梯度并更新参数。四、核心概念与模型构建4.1 神经网络基础神经网络是深度学习的核心概念,它由大量的神经元组成,通过模拟人类大脑的神经元结构和工作方式来处理数据。在DL4J中,神经网络的基本构建块是Layer(层)。常见的层类型包括:输入层(Input Layer):负责接收输入数据,数据以张量(Tensor)的形式传入。例如,对于图像识别任务,输入层可以接收一个三维张量,分别表示图像的高度、宽度和通道数(如RGB图像通道数为3)。// 定义输入层InputLayer inputLayer = new InputLayer.Builder() .nIn(inputSize) .build();上述代码中,inputSize表示输入数据的维度,通过InputLayer.Builder来配置输入层的参数并构建输入层对象。全连接层(Fully Connected Layer):也称为密集层(Dense Layer),层中的每个神经元都与前一层的所有神经元相连。它通过权重矩阵和偏置项对输入数据进行线性变换,然后通过激活函数引入非线性。// 定义全连接层DenseLayer denseLayer = new DenseLayer.Builder() .nIn(inputSize) .nOut(outputSize) .activation("relu") .build();这里,nIn表示输入维度,nOut表示输出维度,activation指定激活函数,如relu(修正线性单元)。激活函数(Activation Function):用于引入非线性,使神经网络能够学习复杂的模式。常见的激活函数有Sigmoid、Tanh、ReLU等。不同的激活函数具有不同的特性和适用场景。例如,ReLU函数在处理大规模数据时具有计算效率高、不易出现梯度消失等优点。4.2 卷积神经网络(CNN)CNN是专门为处理具有网格结构数据(如图像、音频)而设计的神经网络。它通过卷积层、池化层和全连接层的组合来自动提取数据的特征。卷积层(Convolutional Layer):使用卷积核(Filter)对输入数据进行卷积操作,提取局部特征。卷积核在输入数据上滑动,每次滑动计算卷积核与局部数据的点积,得到卷积结果。// 定义卷积层ConvolutionLayer convolutionLayer = new ConvolutionLayer.Builder() .kernelSize(3, 3) .stride(1, 1) .nIn(inputChannels) .nOut(outputChannels) .activation("relu") .build();其中,kernelSize指定卷积核的大小,stride表示卷积核滑动的步长,nIn是输入通道数,nOut是输出通道数。池化层(Pooling Layer):用于对卷积层的输出进行下采样,减少数据维度,同时保留主要特征。常见的池化方法有最大池化(Max Pooling)和平均池化(Average Pooling)。// 定义最大池化层SubsamplingLayer poolingLayer = new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX) .kernelSize(2, 2) .stride(2, 2) .build();这里选择了最大池化,kernelSize和stride的含义与卷积层类似。4.3 循环神经网络(RNN)及其变体RNN适用于处理序列数据,如时间序列、文本等。它通过引入反馈机制,能够记住过去的信息并用于当前的决策。然而,传统RNN存在梯度消失和梯度爆炸的问题,限制了其在长序列数据处理中的应用。为了解决这些问题,出现了一些RNN的变体,如长短期记忆网络(LSTM)和门控循环单元(GRU)。LSTM:LSTM通过引入记忆单元(Cell)和多个门控机制(输入门、遗忘门、输出门)来有效地控制信息的流动,从而能够处理长序列数据。// 定义LSTM层LSTM.Builder lstmBuilder = new LSTM.Builder() .nIn(inputSize) .nOut(outputSize) .build();GRU:GRU是LSTM的简化版本,它将输入门和遗忘门合并为一个更新门,减少了模型的参数数量,同时在性能上与LSTM相当。// 定义GRU层GRU.Builder gruBuilder = new GRU.Builder() .nIn(inputSize) .nOut(outputSize) .build();五、数据处理与加载5.1 数据预处理在将数据输入到深度学习模型之前,需要进行预处理,以提高模型的训练效果和效率。常见的数据预处理步骤包括:数据归一化(Normalization):将数据的特征值缩放到一定范围内,如[0, 1]或[-1, 1]。这有助于加速模型的收敛和提高泛化能力。在DL4J中,可以使用DataNormalization接口及其实现类进行数据归一化。// 使用MinMaxScaler进行数据归一化MinMaxScaler scaler = new MinMaxScaler(0, 1);scaler.fit(data);INDArray normalizedData = scaler.transform(data);这里,data是输入的数据集,MinMaxScaler将数据缩放到[0, 1]区间。数据标准化(Standardization):将数据的特征值转换为均值为0,标准差为1的分布。这可以通过计算数据的均值和标准差,并对每个特征值进行相应的变换来实现。// 使用StandardScaler进行数据标准化StandardScaler scaler = new StandardScaler();scaler.fit(data);INDArray standardizedData = scaler.transform(data);5.2 数据加载DL4J提供了DataVec库来加载和处理各种格式的数据。对于常见的数据集格式,如CSV、图像文件等,都有相应的加载器。加载CSV数据:可以使用CSVRecordReader来读取CSV文件中的数据。// 创建CSVRecordReaderCSVRecordReader recordReader = new CSVRecordReader();recordReader.initialize(new FileSplit(new File("data.csv")));// 创建DataSetIteratorDataSetIterator iterator = new CSVDataSetIterator(recordReader, batchSize, labelIndex, numClasses);这里,batchSize表示每次加载的数据批次大小,labelIndex是标签所在的列索引,numClasses是分类问题中的类别数。加载图像数据:对于图像数据,可以使用ImageLoader和ImageRecordReader来加载和预处理图像。// 创建ImageRecordReaderImageRecordReader recordReader = new ImageRecordReader(height, width, channels, new LabelsSource() { @Override public List<String> getLabels() { return Arrays.asList("class1", "class2", "class3"); }});recordReader.initialize(new FileSplit(new File("images")));// 创建DataSetIteratorDataSetIterator iterator = new ImageDataSetIterator(recordReader, batchSize, 1, numClasses);其中,height、width和channels分别表示图像的高度、宽度和通道数。六、模型训练与优化6.1 定义损失函数损失函数(Loss Function)用于衡量模型预测结果与真实标签之间的差异,是模型训练的目标函数。常见的损失函数有:均方误差(Mean Squared Error,MSE):适用于回归问题,计算预测值与真实值之间误差的平方的平均值。// 使用均方误差损失函数LossFunction lossFunction = LossFunction.MSE;12交叉熵损失(Cross Entropy Loss):常用于分类问题,衡量两个概率分布之间的差异。在多分类问题中,通常使用Softmax交叉熵损失。// 使用Softmax交叉熵损失函数LossFunction lossFunction = LossFunction.NEGATIVELOGLIKELIHOOD;6.2 选择优化器优化器用于调整模型的参数,以最小化损失函数。DL4J提供了多种优化器,如随机梯度下降(SGD)、Adagrad、Adadelta、Adam等。随机梯度下降(SGD):最基本的优化器,每次迭代使用一个小批量的数据计算梯度并更新参数。// 使用随机梯度下降优化器Optimizer optimizer = new SGD.Builder() .learningRate(0.01) .build();这里,learningRate是学习率,控制每次参数更新的步长。Adam优化器:结合了Adagrad和Adadelta的优点,自适应调整学习率,在许多情况下表现良好。// 使用Adam优化器Optimizer optimizer = new Adam.Builder() .learningRate(0.001) .build();6.3 模型训练在定义好模型结构、损失函数和优化器后,就可以进行模型训练了。训练过程通常包括多个epoch(轮次),在每个epoch中,模型对训练数据进行多次迭代,不断调整参数以降低损失。// 创建MultiLayerNetwork模型MultiLayerNetwork model = new MultiLayerNetwork(new NeuralNetConfiguration.Builder() .list() .layer(0, inputLayer) .layer(1, denseLayer) .layer(2, outputLayer) .build());model.init();// 定义训练配置TrainingConfig trainingConfig = new TrainingConfig.Builder() .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT) .lossFunction(lossFunction) .optimizer(optimizer) .build();// 创建Trainer对象进行训练Trainer trainer = model.trainer(trainingConfig);for (int epoch = 0; epoch < numEpochs; epoch++) { trainer.fit(trainingData);}上述代码中,MultiLayerNetwork是DL4J中用于构建多层神经网络的类,TrainingConfig配置了训练的相关参数,Trainer负责执行训练过程。七、模型评估与调优7.1 模型评估指标在训练完成后,需要对模型的性能进行评估。常见的评估指标有:准确率(Accuracy):分类问题中,预测正确的样本数占总样本数的比例。// 计算准确率Evaluation evaluation = new Evaluation(numClasses);INDArray output = model.output(testData.getFeatures());evaluation.eval(testData.getLabels(), output);System.out.println(evaluation.stats());这里,Evaluation类用于计算各种评估指标,testData是测试数据集。召回率(Recall):在分类问题中,召回率衡量模型正确预测出的正例占所有正例的比例。F1值(F1-Score):F1值是准确率和召回率的调和平均数,综合反映了模型的性能。7.2 超参数调优除了上述方法,还有一些高级的超参数调优技巧。例如,学习率调度(Learning Rate Scheduling)是一种动态调整学习率的策略。在训练初期,较大的学习率有助于模型快速收敛到一个较好的解空间;而在训练后期,较小的学习率可以防止模型在最优解附近振荡,从而提高模型的精度。在 DL4J 中,可以使用 LearningRatePolicy 来实现不同的学习率调度策略。例如,StepDecay 策略会在指定的步数后按一定比例降低学习率:// 每 1000 步将学习率降低为原来的 0.1 倍LearningRatePolicy learningRatePolicy = new StepDecay(1000, 0.1);MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder() .learningRate(0.01) .learningRatePolicy(learningRatePolicy) // 其他配置参数 .build();随机搜索和网格搜索虽然有效,但在高维超参数空间中效率较低。而模拟退火(Simulated Annealing)算法则提供了一种在超参数空间中更智能的搜索方式。它基于物理退火过程的思想,在搜索过程中以一定概率接受较差的解,从而避免陷入局部最优。虽然在 DL4J 中没有直接的内置实现,但可以通过自定义搜索算法来结合 DL4J 使用。7.3 模型监控与早期停止为了实时监控模型的训练过程,DL4J 提供了丰富的回调函数(Callback)机制。例如,IterationListener 接口可以用于在每次迭代结束时执行特定的操作,如记录损失值和准确率:public class MyIterationListener implements IterationListener { @Override public void iterationDone(IterationEvent iterationEvent) { int iteration = iterationEvent.getIteration(); double loss = iterationEvent.getNet().calculateScore(); System.out.println("Iteration " + iteration + ": Loss = " + loss); }}// 在训练时添加监听器MultiLayerNetwork network = new MultiLayerNetwork(conf);network.init();network.setListeners(new MyIterationListener());network.fit(trainingData);早期停止机制可以通过 EpochListener 来实现。我们可以记录验证集上的性能,并在性能不再提升时停止训练:public class EarlyStoppingListener implements EpochListener { private int noImprovementCount = 0; private int patience = 10; private double bestValidationScore = Double.MAX_VALUE; @Override public void onEpochEnd(EpochEvent epochEvent) { double validationScore = epochEvent.getNet().calculateScore(validationData); if (validationScore < bestValidationScore) { bestValidationScore = validationScore; noImprovementCount = 0; } else { noImprovementCount++; if (noImprovementCount >= patience) { System.out.println("Early stopping triggered."); epochEvent.getNet().setListeners(new ArrayList<>()); // 停止训练 } } }}// 添加早期停止监听器network.setListeners(new EarlyStoppingListener());network.fit(trainingData);八、模型部署与集成8.1 模型部署到生产环境将训练好的 DL4J 模型部署到生产环境,首先要考虑模型的序列化和反序列化。DL4J 支持将 MultiLayerNetwork 模型保存为二进制文件,以便在不同环境中加载使用。// 保存模型MultiLayerNetwork model = // 训练好的模型try (OutputStream os = new FileOutputStream("model.zip")) { ModelSerializer.writeModel(model, os, true);} catch (IOException e) { e.printStackTrace();}在生产环境中加载模型进行预测:// 加载模型MultiLayerNetwork loadedModel;try (InputStream is = new FileInputStream("model.zip")) { loadedModel = ModelSerializer.restoreMultiLayerNetwork(is);} catch (IOException e) { e.printStackTrace(); return;}// 进行预测INDArray input = Nd4j.create(new double[]{/* 输入数据 */});INDArray output = loadedModel.output(input);对于生产环境中的实时预测服务,我们可以使用 Java 的 Servlet 或更现代化的框架如 Spring Boot 来构建 RESTful API。以下是一个简单的Spring Boot 示例,用于接收输入数据并返回模型预测结果:import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;import org.nd4j.linalg.api.ndarray.INDArray;import org.nd4j.linalg.factory.Nd4j;@SpringBootApplication@RestControllerpublic class ModelDeploymentApplication { private static MultiLayerNetwork loadedModel; static { try (InputStream is = new FileInputStream("model.zip")) { loadedModel = ModelSerializer.restoreMultiLayerNetwork(is); } catch (IOException e) { e.printStackTrace(); } } @PostMapping("/predict") public double[] predict(@RequestBody double[] inputData) { INDArray input = Nd4j.create(inputData); INDArray output = loadedModel.output(input); return output.toDoubleVector(); } public static void main(String[] args) { SpringApplication.run(ModelDeploymentApplication.class, args); }}8.2 与其他系统的集成在实际项目中,深度学习模型通常需要与其他系统进行集成。例如,与企业的数据库系统集成,以获取训练数据或存储预测结果。假设我们使用 MySQL 数据库,使用 JDBC 来读取数据用于模型训练:import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.Statement;public class DatabaseReader { public static INDArray readDataFromDatabase() { try { Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/your_database", "username", "password"); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT * FROM your_table"); int rows = 0; while (resultSet.next()) { rows++; } resultSet.beforeFirst(); int cols = resultSet.getMetaData().getColumnCount(); INDArray data = Nd4j.create(rows, cols); int rowIndex = 0; while (resultSet.next()) { for (int colIndex = 1; colIndex <= cols; colIndex++) { data.putScalar(new int[]{rowIndex, colIndex - 1}, resultSet.getDouble(colIndex)); } rowIndex++; } connection.close(); return data; } catch (Exception e) { e.printStackTrace(); return null; } }}将预测结果存储回数据库:import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;public class DatabaseWriter { public static void writePredictionsToDatabase(double[] predictions) { try { Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/your_database", "username", "password"); String sql = "INSERT INTO prediction_results (prediction) VALUES (?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); for (double prediction : predictions) { preparedStatement.setDouble(1, prediction); preparedStatement.executeUpdate(); } connection.close(); } catch (Exception e) { e.printStackTrace(); } }}九、年度总结与展望在过去一年对 Java DL4J 深度学习的实践探索中,我们经历了从理论学习到实际项目落地的完整过程。从最初搭建简单的神经网络模型,到通过不断优化和调优构建复杂且高效的深度学习架构,每一步都积累了宝贵的经验。在技术实现方面,我们熟练掌握了 DL4J 的核心 API,能够根据不同的业务需求灵活构建、训练和评估模型。通过模型评估与调优策略,我们显著提升了模型的性能和泛化能力,使其在面对各种实际数据时都能表现出色。然而,实践过程并非一帆风顺。在处理大规模数据时,内存管理和计算资源的优化成为了关键挑战。通过采用分布式计算框架和数据预处理技术,我们有效地缓解了这些问题,但仍需不断探索更高效的解决方案。深度学习领域的快速发展为我们提供了广阔的创新空间。我们计划进一步探索 DL4J 在新兴领域的应用,如强化学习与深度学习的结合,以实现更智能的决策系统。同时,随着硬件技术的持续进步,我们将致力于优化模型在新型硬件设备上的运行效率,充分发挥 GPU、TPU 等加速设备的潜力。此外,模型的可解释性和安全性也将成为重要的研究方向。在实际应用中,尤其是在医疗、金融等关键领域,理解模型的决策过程以及确保数据和模型的安全性至关重要。我们将积极探索相关技术,如特征重要性分析、对抗攻击防御等,以提升模型的可信度和可靠性。通过持续学习和实践,我们坚信能够在 Java DL4J 深度学习领域不断取得新的突破,为解决实际问题提供更强大、更可靠的技术支持,为推动行业发展贡献自己的力量。————————————————原文链接:https://blog.csdn.net/lilinhai548/article/details/145224986
-
引言:亲爱的 Java 和 大数据爱好者们,大家好!在 Java 大数据技术的探索之路上,我们已经搭建起了一套完整且稳固的技术体系。从《Java 大视界 – 基于 Java 的大数据分布式索引技术探秘(50)》中,我们了解到分布式索引技术借助分布式哈希表等精妙设计,实现了海量数据的高效存储与毫秒级检索,为大数据应用筑牢了根基。而在《Java 大视界 – Java 与大数据流式机器学习:理论与实战(51)》里,我们踏入了实时数据处理的前沿,领略到 Java 与流式机器学习融合在金融风险实时监测、工业物联网设备故障预警等场景中的卓越效能。如今,随着数字化进程的迅猛推进,数据已然成为关键资产,数据安全的重要性愈发凸显。Java 大数据安全多方计算技术应运而生,它宛如一把钥匙,开启了跨机构、跨领域安全数据合作的大门,在严守数据隐私的同时深度挖掘数据价值,为大数据时代注入新的活力与机遇。 正文:一、安全多方计算技术基础1.1 安全多方计算的概念与原理安全多方计算(Secure Multi-Party Computation,MPC)作为现代密码学领域的重要成果,其核心在于允许多个参与方在不暴露各自私有数据的情况下,协同完成既定计算任务。这一过程依赖于一系列精妙的密码学协议。以混淆电路(Garbled Circuit)协议为例,它通过对电路进行加密混淆,使得参与方在不知晓其他方输入的情况下完成计算。假设参与方 A 持有数据 x ,参与方 B 持有数据 y ,双方希望计算函数 f(x, y) ,却不想让对方知晓自己的数据。通过混淆电路协议,A 将数据 x 加密后发送给 B,B 在不知 x 具体值的情况下,结合自身数据 y 进行计算,最终得出 f(x, y) 的结果,且 A 和 B 均无法获取对方原始数据。如图 1 所示,清晰展示了混淆电路协议的工作流程:AB使用加密算法(如AES)准备数据x并加密发送加密后的数据x准备数据y结合接收到的加密数据x与自身数据y进行计算(基于混淆电路协议,使用特定计算规则)返回计算结果f(x,y)返回错误信息alt[计算成功][计算失败]AB不经意传输(Oblivious Transfer)协议则保证接收方只能获取特定信息,而发送方无法得知接收方获取的具体内容。在医疗数据查询场景中,医院 A 拥有大量患者病历数据,患者 B 希望查询自己的病历,却不想让医院 A 知道查询的是哪一份。通过不经意传输协议,患者 B 能在不暴露查询目标的情况下获取病历信息,医院 A 也无法知晓患者 B 的查询行为。具体步骤如下表所示:步骤 描述1 发送方准备多个数据项,并利用非对称加密算法对每个数据项进行加密处理,生成密文数据集合。2 接收方生成随机选择信息,例如生成一个随机数作为索引,用于指定要获取的数据项。3 发送方根据接收方的选择信息,以一种特殊方式将加密数据发送给接收方,接收方只能利用自己的私钥解密出自己选择的数据项,而发送方无法得知接收方的选择。1.2 与传统数据处理方式的区别传统数据处理模式常采用集中式架构,将所有数据汇聚到一个中心节点进行处理。这种方式虽便于管理和计算,但存在巨大安全隐患。一旦中心节点遭受攻击,数据泄露风险极高,还可能面临数据合规性难题,如违反《通用数据保护条例》(GDPR)等法规。例如,某知名社交平台曾因数据中心被攻击,导致数亿用户个人信息泄露,引发了严重的信任危机,用户对该平台的信任度大幅下降,平台也面临着巨额的罚款和业务整改。安全多方计算采用分布式计算架构,数据分散存储在各参与方本地,仅在计算时通过加密协议以密文形式交互。整个传输和计算过程如同被层层加密的黑盒,外界难以窥探其中奥秘,极大地增强了数据隐私保护能力。为更直观呈现二者差异,以下通过详细对比表格说明:对比项 传统数据处理方式 安全多方计算数据存储位置 集中于中心节点,易成为攻击目标 分散在各参与方本地,降低整体风险,即使部分节点数据泄露,也不会影响全局数据处理方式 集中计算,依赖中心节点性能,一旦中心节点出现故障,计算任务将中断 分布式计算,各节点协同工作,利用并行处理能力,提高计算效率和容错性数据安全性 中心节点安全漏洞可能导致大规模数据泄露,数据在传输和存储过程中面临较高风险 密文传输与计算,隐私保护机制严密,采用多种加密算法和协议确保数据安全应用场景 适用于数据敏感度低、追求处理效率的简单场景,如一般性的数据分析和报表生成 主要应用于对数据隐私和合规要求极高的复杂场景,如医疗、金融、政务等领域的数据共享与分析二、Java 在安全多方计算中的技术实现2.1 基于 Java 的密码学库应用Java 凭借丰富且强大的密码学库,为安全多方计算提供了坚实技术支撑。Java Cryptography Architecture(JCA)和 Java Cryptography Extension(JCE)是其中的核心组件,涵盖加密、解密、数字签名、密钥管理等全方位密码学功能。以 AES(Advanced Encryption Standard)算法为例,它是一种对称加密算法,广泛应用于数据加密场景。以下是使用 AES 算法结合 GCM(Galois/Counter Mode)模式进行数据加密和解密的 Java 代码示例,GCM 模式不仅提供数据保密性,还具备完整性验证功能:import javax.crypto.Cipher;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.spec.GCMParameterSpec;import java.nio.charset.StandardCharsets;import java.security.SecureRandom;public class AESExample { public static void main(String[] args) throws Exception { // 生成256位AES密钥,密钥长度越长,安全性越高 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(256); SecretKey secretKey = keyGenerator.generateKey(); // 创建加密器,使用AES/GCM/NoPadding模式 Cipher encryptCipher = Cipher.getInstance("AES/GCM/NoPadding"); // 生成12字节的初始化向量(IV),用于加密过程的随机化 byte[] iv = new byte[12]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(iv); // 创建GCM参数规范,设置认证标签长度为128位 GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec); // 待加密的敏感数据 String originalData = "sensitive data"; // 执行加密操作 byte[] encryptedData = encryptCipher.doFinal(originalData.getBytes(StandardCharsets.UTF_8)); // 创建解密器,使用相同的模式和参数 Cipher decryptCipher = Cipher.getInstance("AES/GCM/NoPadding"); decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); // 执行解密操作 byte[] decryptedData = decryptCipher.doFinal(encryptedData); // 将解密后的字节数组转换为字符串 String decryptedString = new String(decryptedData, StandardCharsets.UTF_8); System.out.println("Original Data: " + originalData); System.out.println("Decrypted Data: " + decryptedString); }}为帮助读者更好理解代码,对每一步操作详细解释如下:生成密钥:使用KeyGenerator生成 256 位的 AES 密钥,密钥长度决定加密强度,越长越安全。128 位密钥在面对强大的暴力破解攻击时,可能在较短时间内被破解,而 256 位密钥的破解难度呈指数级增长,大大提高了数据的安全性。根据密码学研究,256 位 AES 密钥在目前的计算能力下,破解时间可能长达数百年甚至更久。创建加密器:选择AES/GCM/NoPadding模式创建加密器,同时生成 12 字节的初始化向量(IV),用于加密过程的随机化,防止相同明文加密后结果相同。IV 就像加密过程中的 “随机种子”,即使相同的明文,在不同 IV 下加密结果也不同,有效增强了加密的安全性。在实际应用中,IV 的随机性和唯一性至关重要,否则可能会被攻击者利用来破解加密数据。执行加密:将待加密的数据转换为字节数组,调用加密器的doFinal方法进行加密,得到加密后的数据。这一步是加密的核心操作,doFinal方法会根据之前设置的密钥、模式和参数,对数据进行加密处理。在加密过程中,数据会被分成多个块进行处理,每个块都会经过复杂的加密运算,最终生成密文。创建解密器:使用相同的模式和参数创建解密器,确保能够正确解密。解密器的配置必须与加密器一致,才能准确还原原始数据。如果模式或参数不一致,解密将无法成功,可能会得到错误的结果或无法解密。执行解密:将加密后的数据传入解密器,得到解密后的字节数组,再转换为字符串。经过解密器处理后,加密数据被还原为原始数据,以字符串形式呈现,方便查看和使用。在解密过程中,解密器会按照加密时的逆过程对密文进行处理,将其还原为原始的明文数据。2.2 分布式计算框架与安全多方计算的融合在大数据时代,数据规模和计算复杂度呈指数级增长,分布式计算框架成为处理海量数据的关键工具。Apache Spark 作为业界领先的分布式计算框架,与安全多方计算的融合为大规模数据的安全处理开辟了新路径。下面通过一个更详细的代码示例,展示如何在 Spark 中初步实现安全多方计算的逻辑。假设我们有两个参与方的数据,分别存储在不同的 RDD 中,我们希望在不暴露原始数据的情况下计算它们的交集(简化的安全多方计算场景)。这里使用 Paillier 同态加密算法(基于 Java 实现的简单版本)来对数据进行加密处理,以保证数据安全传输和计算。首先,引入相关依赖(假设使用 Maven 管理项目):<dependencies> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.68</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_2.12</artifactId> <version>3.3.1</version> </dependency></dependencies>然后是 Java 代码实现:import org.apache.spark.SparkConf;import org.apache.spark.api.java.JavaRDD;import org.apache.spark.api.java.JavaSparkContext;import org.bouncycastle.crypto.AsymmetricCipherKeyPair;import org.bouncycastle.crypto.generators.PaillierKeyGenerator;import org.bouncycastle.crypto.params.PaillierKeyGenerationParameters;import org.bouncycastle.crypto.params.PaillierPrivateKeyParameters;import org.bouncycastle.crypto.params.PaillierPublicKeyParameters;import org.bouncycastle.math.ec.WNafUtil;import java.math.BigInteger;import java.security.SecureRandom;import java.util.Arrays;import java.util.List;public class SparkMPCExample { public static void main(String[] args) { SparkConf conf = new SparkConf().setAppName("SparkMPCExample").setMaster("local[*]"); JavaSparkContext sc = new JavaSparkContext(conf); // 生成Paillier密钥对 AsymmetricCipherKeyPair keyPair = generatePaillierKeyPair(); PaillierPublicKeyParameters publicKey = (PaillierPublicKeyParameters) keyPair.getPublic(); PaillierPrivateKeyParameters privateKey = (PaillierPrivateKeyParameters) keyPair.getPrivate(); // 模拟参与方1的数据 List<Integer> data1 = Arrays.asList(1, 2, 3, 4, 5); JavaRDD<BigInteger> encryptedData1 = sc.parallelize(data1) .map(num -> encrypt(num, publicKey)); // 模拟参与方2的数据 List<Integer> data2 = Arrays.asList(3, 4, 5, 6, 7); JavaRDD<BigInteger> encryptedData2 = sc.parallelize(data2) .map(num -> encrypt(num, publicKey)); // 模拟安全多方计算求交集(这里只是简单模拟,实际会更复杂) JavaRDD<BigInteger> intersection = encryptedData1.intersection(encryptedData2); // 解密结果(假设只有一方有私钥可以解密) intersection.map(encrypted -> decrypt(encrypted, privateKey)) .collect() .forEach(System.out::println); sc.stop(); } private static AsymmetricCipherKeyPair generatePaillierKeyPair() { PaillierKeyGenerator keyGen = new PaillierKeyGenerator(); keyGen.init(new PaillierKeyGenerationParameters(new SecureRandom(), 1024)); return keyGen.generateKeyPair(); } private static BigInteger encrypt(int num, PaillierPublicKeyParameters publicKey) { BigInteger plaintext = BigInteger.valueOf(num); return publicKey.encrypt(plaintext); } private static int decrypt(BigInteger encrypted, PaillierPrivateKeyParameters privateKey) { BigInteger decrypted = privateKey.decrypt(encrypted); return decrypted.intValue(); }}代码解释如下:初始化 Spark:创建SparkConf和JavaSparkContext,配置应用名称和运行模式。SparkConf用于设置 Spark 应用的各种参数,如应用名称、运行模式等;JavaSparkContext是与 Spark 集群交互的入口,负责创建和管理 RDD 等分布式数据集。在实际应用中,还可以根据集群的资源情况和计算任务的需求,对 SparkConf 进行更多的配置,如设置内存分配、线程数等。生成密钥对:使用PaillierKeyGenerator生成 Paillier 密钥对,包括公钥和私钥。密钥对是安全多方计算的基础,公钥用于加密数据,私钥用于解密数据,确保数据的安全性和隐私性。在生成密钥对时,需要选择合适的密钥长度和随机数生成器,以保证密钥的安全性。数据加密:将参与方 1 和参与方 2 的数据分别转换为JavaRDD,并使用公钥对数据进行加密。JavaRDD是 Spark 中分布式弹性数据集,通过map操作将数据转换为加密后的形式,实现数据在分布式环境下的安全传输。在实际应用中,可能会遇到数据格式不统一、数据量过大等问题,需要进行相应的数据预处理和优化。计算交集:对加密后的数据调用intersection方法,模拟安全多方计算求交集。这一步是在加密数据上进行计算,确保原始数据不被泄露。在实际场景中,计算交集可能会涉及到更复杂的逻辑和算法,需要根据具体需求进行优化。解密结果:使用私钥对交集结果进行解密,并打印输出。只有拥有私钥的一方才能解密出计算结果,保证了结果的安全性和隐私性。在实际应用中,需要妥善保管私钥,防止私钥泄露导致数据安全问题。通过将安全多方计算协议无缝集成到 Spark 的分布式计算流程中,各节点能够在保护数据隐私的前提下协同工作。例如,在电商行业的联合营销分析中,多个电商平台拥有各自的用户浏览、购买、评价等数据。利用 Spark 的分布式计算能力和安全多方计算技术,这些平台可以联合分析用户行为数据,挖掘潜在的市场需求和用户偏好,同时确保各自平台的用户隐私数据不被泄露。具体实现时,可将安全多方计算的加密、解密和计算逻辑封装成 Spark 的自定义算子(Operator),融入 Spark 的 RDD(Resilient Distributed Datasets)或 DataFrame 处理流程中,实现高效、安全的分布式计算。如图 2 所示,展示了安全多方计算与 Spark 融合的架构图:三、安全多方计算的高级应用场景3.1 医疗数据共享与联合研究医疗领域积累了海量的患者数据,这些数据蕴含着巨大的医学价值,但由于患者隐私保护和严格的法规限制,数据共享与联合研究面临重重困难。安全多方计算技术为这一困境提供了破局之道。多家医疗机构可以借助安全多方计算,在不泄露患者个人隐私的前提下,联合开展疾病研究、药物研发等工作。例如,针对罕见病的研究,不同地区的医院可以联合分析患者的基因数据、临床症状、治疗记录等信息,通过安全多方计算挖掘疾病的潜在致病基因和有效的治疗方案。据权威研究表明,采用安全多方计算进行医疗数据联合研究后,疾病研究的效率提高了 30%,新药研发周期平均缩短了 20%,为攻克疑难病症带来了新的希望。在一项针对罕见病的跨国联合研究项目中,来自 5 个国家的 10 家顶级医院参与其中。通过安全多方计算技术,这些医院在不泄露患者隐私的情况下,共享了超过 5000 份患者病例数据。经过联合分析,研究团队成功发现了一种与该罕见病相关的新基因靶点,基于此开发的新型治疗药物已进入临床试验阶段,有望为全球数千名患者带来治愈的可能。从社会人文角度来看,安全多方计算促进医疗数据共享,让更多患者受益于先进医疗研究成果,减少医疗资源分配不均带来的影响,体现了技术对人文关怀的促进作用。例如,偏远地区的患者可以通过安全多方计算参与到国际前沿的医疗研究中,获得更精准的诊断和治疗方案。某偏远地区的医院与国际知名医疗机构合作,利用安全多方计算技术共享患者数据,成功为一位罕见病患者制定了个性化治疗方案,使患者病情得到有效控制,生活质量显著提高。这种技术的应用不仅改善了患者的健康状况,还增强了患者对医疗系统的信任,体现了科技发展对社会公平和人文关怀的积极影响。3.2 金融风控联合建模在金融领域,风险评估和风控建模是保障金融稳定的核心任务,而这需要大量多维度的数据支持。不同金融机构如银行、保险公司、消费金融公司等持有各自客户的信用数据、消费行为数据、资产数据等,但出于数据安全和商业竞争的考虑,难以直接共享数据。安全多方计算技术使得金融机构能够在不泄露客户敏感信息的情况下,联合进行风险评估模型的训练和优化。通过安全多方计算,各机构可以共同分析客户数据,建立更精准的风险评估模型,有效降低金融风险。在实际应用中,某大型金融集团采用安全多方计算进行金融风控联合建模后,风险评估的准确率提高了 15%,不良贷款率降低了 10%,显著提升了金融风险管理水平。该金融集团旗下拥有银行、证券、保险等多个子公司,以往各子公司独立进行风险评估,数据孤立且模型不够精准。引入安全多方计算技术后,各子公司在保护客户隐私的前提下,共享部分数据进行联合建模。例如,银行提供客户的信贷记录,保险公司提供客户的理赔数据,证券子公司提供客户的投资行为数据。通过整合这些多维度数据,新的风险评估模型能够更全面地评估客户风险,为金融决策提供更可靠的依据。随着金融科技的发展,安全多方计算与区块链、人工智能等前沿技术的融合趋势逐渐显现。区块链可用于确保参与方身份验证和数据不可篡改,人工智能则能助力更精准的风险预测,这种跨领域融合为金融行业带来创新变革,提升金融服务的普惠性和安全性。例如,在普惠金融领域,通过安全多方计算和区块链技术,金融机构可以更准确地评估小微企业的信用风险,为其提供更合理的贷款额度和利率,促进小微企业的发展。某地区多家银行和小额贷款公司合作开展普惠金融项目,它们利用安全多方计算技术,整合各自掌握的小微企业的交易流水、纳税记录、社保缴纳等数据,同时借助区块链确保数据来源可靠且不可篡改。经过联合分析,原本被传统金融机构拒之门外的许多小微企业获得了合理的贷款,贷款额度平均提升了 20%,利率降低了 15%,有力地推动了当地小微企业的发展,促进了就业和经济增长。此外,人工智能算法能够对安全多方计算产生的海量金融数据进行深度挖掘,发现潜在的风险模式和市场趋势。例如,利用机器学习算法对客户的消费行为、还款记录等数据进行分析,提前预测客户的违约风险,为金融机构及时采取风险防范措施提供支持。四、安全多方计算面临的挑战与解决方案4.1 性能效率问题安全多方计算由于涉及复杂的密码学计算和频繁的网络通信,计算和通信开销较大,导致性能效率成为制约其广泛应用的瓶颈。为提升性能,可从以下几个方面着手:优化密码学算法:采用更高效的同态加密算法变体,如基于格的同态加密算法,在保证安全性的前提下,大幅减少加密和解密的计算量。基于格的同态加密算法利用格的数学性质,能够在较短的计算时间内完成加密和解密操作,相比传统同态加密算法效率更高。例如,在处理大规模数据时,传统同态加密算法可能需要数小时才能完成加密,而基于格的同态加密算法可以将时间缩短至几十分钟,大大提高了计算效率。研究表明,在处理 10GB 的数据集时,基于格的同态加密算法的加密时间仅为传统算法的 1/5,且在安全性上能够抵御量子计算攻击。分布式计算架构优化:设计更合理的分布式集群架构,利用多节点的并行计算能力,将复杂计算任务分解为多个子任务并行执行,提高整体计算效率。例如,采用主从架构结合分布式缓存技术,减少数据传输次数,提高计算速度。在一个包含 100 个节点的分布式集群中,通过优化架构和使用分布式缓存,数据传输时间减少了 50%,计算任务的完成时间缩短了 30%。通过合理分配计算任务和优化数据存储方式,使各节点能够充分发挥其计算能力,避免出现节点负载不均衡的情况。网络通信优化:采用高速网络通信协议和优化的数据传输策略,减少数据传输延迟和带宽消耗,如使用 UDP 协议进行部分数据传输,并结合数据压缩技术降低传输数据量。在数据传输前,对数据进行压缩处理,可有效减少传输时间和带宽占用。实验表明,对 1GB 的数据进行压缩后再传输,传输时间可缩短 70%,带宽占用降低 80%。选择合适的压缩算法,如 Snappy、Gzip 等,根据数据特点和网络环境进行优化配置,以实现最佳的传输效果。4.2 安全漏洞与攻击防范安全多方计算系统面临着诸多安全威胁,如中间人攻击、恶意参与者攻击、数据泄露攻击等。为有效防范这些攻击,需采取以下措施:强化密码学协议安全性:持续研究和改进密码学协议,修复潜在的安全漏洞,增强协议的抗攻击能力,如对混淆电路协议进行优化,防止电路结构被破解。通过引入随机化机制和多重加密技术,提高混淆电路协议的安全性。例如,在混淆电路协议中加入随机噪声,使得攻击者难以通过分析电路结构获取原始数据。研究人员不断提出新的密码学协议和改进方案,如基于不经意传输扩展的混淆电路协议,进一步提高了协议的安全性和效率。多方认证机制:引入严格的多方认证机制,确保参与计算的各方身份真实可靠,防止恶意节点混入。可采用基于数字证书的认证方式,结合区块链技术实现身份信息的不可篡改和可追溯。区块链的分布式账本特性,能将各方身份信息以加密形式存储在多个节点,确保数据的真实性和完整性。一旦身份信息被篡改,区块链的共识机制会立即检测到异常,保障计算环境的安全可靠。例如,在一个多方参与的医疗数据共享项目中,通过基于区块链的数字证书认证,成功阻止了一次恶意节点试图冒充医疗机构参与计算的攻击。利用区块链的智能合约技术,实现自动化的身份验证和权限管理,提高认证的效率和安全性。审计跟踪技术:建立完善的审计跟踪系统,记录计算过程中的关键操作和数据流向,便于及时发现和追溯潜在的安全问题。系统可以记录每次数据加密、传输、计算以及解密的时间、参与方、操作类型等关键信息。通过对审计日志的实时分析,能够快速检测到异常行为。例如,当监测到某个参与方在短时间内发起大量不合理的计算请求,或者数据传输量远超正常范围时,系统可自动触发警报,安全团队能迅速介入,对该参与方进行进一步审查,查明异常原因,采取相应的防范措施,如暂时中断该参与方的计算任务,对其身份和操作进行详细核实。采用大数据分析技术对审计日志进行深度挖掘,发现潜在的安全威胁和异常模式,提前预警并防范安全风险。五、技术发展的多维洞察5.1 前沿技术趋势下的安全多方计算在科技飞速发展的当下,量子计算技术的崛起给传统密码学带来了前所未有的挑战,安全多方计算自然也无法置身事外。基于量子 - resistant 密码学的安全多方计算协议,正成为学术界和产业界共同关注的焦点。以格密码为例,它基于复杂的格理论构建,独特的数学结构赋予其卓越的抗量子攻击特性。在未来量子计算普及的时代,格密码有望成为安全多方计算的中流砥柱,确保数据在计算和传输过程中的安全性。目前,许多科研团队正在研究基于格密码的安全多方计算协议的优化与应用拓展,力求在保障安全性的同时,提升计算效率和实用性。例如,某知名科研机构的研究团队成功将基于格密码的安全多方计算协议应用于金融数据的跨境传输与计算,在保证数据安全的同时,实现了比传统协议快 2 倍的计算速度。该研究成果为金融机构在全球化业务中保护数据安全提供了新的解决方案,降低了计算成本和时间成本。与此同时,联邦学习与安全多方计算的融合也在不断深化。联邦学习允许多个参与方在不直接共享原始数据的情况下协同训练模型,而安全多方计算则为这种协作提供了坚实的数据隐私保护屏障。在医疗领域,多家医院可以利用联邦学习与安全多方计算的结合,共同训练疾病诊断模型,在保护患者隐私的同时,提升模型的准确性和泛化能力。通过整合不同医院的病例数据进行联合建模,模型能够学习到更广泛的疾病特征,从而提高诊断的准确性,为患者提供更可靠的医疗服务。据某医学研究报告显示,采用联邦学习与安全多方计算结合的方式训练的疾病诊断模型,准确率比单一医院训练的模型提高了 12%。在实际应用中,通过联邦学习与安全多方计算的融合,能够整合不同地区、不同医院的医疗数据,解决数据孤岛问题,提高医疗资源的利用效率。5.2 跨领域融合驱动的创新变革安全多方计算与区块链的融合,为数据处理带来了全新的信任机制。区块链的去中心化特性确保了计算过程不受单一节点控制,不可篡改的账本则保证了数据和计算结果的真实性与可追溯性。在政务数据共享中,各部门可以通过这种融合技术,安全地共享和协同处理数据,提高政务服务的效率和透明度。不同政府部门之间共享公民的社保、税务、医疗等数据时,利用安全多方计算保护公民隐私,同时借助区块链确保数据的准确性和完整性,避免数据被恶意篡改,提升政府部门间的协作效率,为公民提供更便捷的一站式政务服务。例如,某城市通过采用安全多方计算与区块链融合技术,实现了社保、医保、民政等部门的数据共享,办理社保相关业务的时间从原来的平均 7 个工作日缩短至 3 个工作日。该城市的市民在办理社保转移、医保报销等业务时,不再需要在多个部门之间来回奔波提交材料,只需在一个平台上即可完成所有业务办理,大大提高了政务服务的便捷性和满意度。安全多方计算与人工智能的融合同样展现出巨大潜力。人工智能算法能够对安全多方计算产生的海量数据进行深度挖掘,提取其中的潜在价值。例如,在智能安防领域,通过安全多方计算共享监控数据,利用人工智能进行图像识别和行为分析,既能保障数据隐私,又能提高安防系统的智能化水平。将不同区域的监控数据在安全多方计算的框架下进行整合分析,人工智能算法可以实时监测异常行为,如人员聚集、异常闯入等,及时发出警报,有效提升公共安全保障能力。在某大型活动安保项目中,采用安全多方计算与人工智能融合技术,成功预警并处理了多起潜在的安全事件,保障了活动的顺利进行。通过人工智能算法对监控数据进行实时分析,能够快速准确地识别出异常行为,为安保人员提供及时的决策支持,提高安保工作的效率和效果。5.3 技术演进对社会人文的深远影响安全多方计算技术的发展,深刻地改变着社会的隐私观念和数据治理模式。在个人层面,它为人们参与线上服务提供了更可靠的隐私保护。当个人在进行在线医疗咨询、金融交易等活动时,涉及敏感信息的数据通过安全多方计算进行处理,大大降低了隐私泄露的风险,增强了人们对数字社会的信任。用户在进行线上医疗问诊时,个人的病历、症状等敏感信息在加密状态下传输和处理,医生能够获取必要的诊断信息,却无法获取患者的其他隐私细节,保障了患者的隐私安全,让患者更放心地使用在线医疗服务。据某市场调研机构的调查显示,在采用安全多方计算技术处理数据的在线医疗平台上,用户满意度提升了 25%。这表明安全多方计算技术不仅保护了用户的隐私,还提升了用户体验,促进了在线医疗服务的发展。从全球视角来看,安全多方计算促进了数据的合法合规流通,打破了数据孤岛。不同国家和地区的科研机构可以借助这一技术共享科研数据,加速科研进展,推动人类社会在医学、环保、能源等多个领域的共同进步。在医学研究中,跨国合作研究罕见病时,各国科研机构可以通过安全多方计算共享患者数据和研究成果,共同探索疾病的治疗方法,缩短研发周期,为全球患者带来福音。在商业领域,它也为企业间的合作创新提供了可能,促进了全球商业生态的繁荣与发展 ,体现了技术对社会公平和创新活力的积极推动作用。例如,某国际科研合作项目通过安全多方计算技术,整合了来自 5 个国家的科研数据,成功研发出一种新型药物,为全球患者带来了新的治疗选择。该项目的成功不仅展示了安全多方计算技术在科研合作中的重要作用,还促进了不同国家科研机构之间的交流与合作,推动了全球医学研究的发展。结束语:亲爱的 Java 和 大数据爱好者们,通过对 Java 大数据安全多方计算的深度剖析,我们全面掌握了其核心技术、Java 实现方法、丰富的应用场景、面临的挑战及解决方案,以及技术发展的多维洞察。安全多方计算作为大数据时代数据安全的坚固盾牌,有力地推动了跨领域数据合作与创新发展。然而,随着技术的不断演进和应用场景的日益复杂,我们仍需持续探索和创新,不断优化技术性能,强化安全防护体系。————————————————原文链接:https://blog.csdn.net/atgfg/article/details/145233339
-
大家好,2025年开年的第一篇合集,本次带来的是Python,Java,MySql,Golang,JSON,等等希望可以帮到大家。1.Python判断for循环最后一次的方法【转】https://bbs.huaweicloud.com/forum/thread-0248173698858425071-1-1.html2.使用Python实现高效的端口扫描器【转】https://bbs.huaweicloud.com/forum/thread-0248173699028101072-1-1.html3.使用Python实现操作mongodb详解【转】https://bbs.huaweicloud.com/forum/thread-02109173699263711070-1-1.html4.一文详解Python中数据清洗与处理的常用方法【转】https://bbs.huaweicloud.com/forum/thread-02109173699342905071-1-1.html5.Go中sync.Once源码的深度讲解【转】https://bbs.huaweicloud.com/forum/thread-0271173699402065058-1-1.html6.从源码解析golang Timer定时器体系【转】https://bbs.huaweicloud.com/forum/thread-0251173701525255062-1-1.html7.golang1.23版本之前 Timer Reset方法无法正确使用【转】https://bbs.huaweicloud.com/forum/thread-02127173701584637057-1-1.html8.Python文件读写实用方法小结【转】https://bbs.huaweicloud.com/forum/thread-02104173701685566070-1-1.html9.mysql外键创建不成功/失效如何处理【转】https://bbs.huaweicloud.com/forum/thread-02109173701958630072-1-1.html10.Redis的Zset类型及相关命令详细讲解【转】https://bbs.huaweicloud.com/forum/thread-02109173702031434073-1-1.html11.大数据小内存排序问题如何巧妙解决【转】https://bbs.huaweicloud.com/forum/thread-02127173702077058058-1-1.html12.Redis多种内存淘汰策略及配置技巧分享【转】https://bbs.huaweicloud.com/forum/thread-0272173702166312062-1-1.html13.MySQL通过binlog实现恢复数据【转】https://bbs.huaweicloud.com/forum/thread-02109173702268081074-1-1.html14.MySQL如何将一个表的字段更新到另一个表中【转】https://bbs.huaweicloud.com/forum/thread-0272173702328248063-1-1.html15.JSON字符串转成java的Map对象详细步骤【转】https://bbs.huaweicloud.com/forum/thread-02109173702572327075-1-1.html
-
大数据小内存排序问题,很经典,很常见,类似的还有比如 “如何对上百万考试的成绩进行排序” 等等。三种方法:数据库排序(对数据库设备要求较高)分治法(常见思路)位图法(Bitmap)方法概要数据库排序(对数据库设备要求较高)操作:将数据全部导入数据库,建立索引,数据库对数据进行排序,提取出数据。特点:操作简单, 运算速度较慢,对数据库设备要求较高。分治法(常见思路)操作:操作与归并排序的思想类似,都是分治。将数据进行分块,然后对每个数据块进行内部的排序(假如是对int形数据升序)。和归并排序类似,每个数据块取第一个数据(当前块的最小数据),然后比较取出的数据,取其最小加入结果集。重复2操作,直到取完所有数据,此时排序完毕。特点:位图法(Bitmap)操作:基本思想就是利用一位(bit)代表一个数字,例如第 3 位上为 1,则说明 3 这个数字出现过,若为0,则说明 3 这个数字没有出现过。很简单~ java.util 封装了 BitSet 这样一个类,是位图法的典型实现。特点:可读性差(不是一般的差 🤔)位图存储的元素个数虽然比一般做法多,但是存储的元素大小受限于存储空间的大小。要想定义存储空间大小就需要实现知道存储的元素到底有多少对于有符号类型的数据,需要用 2 位来表示,比如 第 0 位和第 1 位表示 0 这个数据,第 2 位和第 3 位表示 1 这个数据......,这会让位图能存储的元素个数,元素值大小上限减半只知道元素是否出现,无法知道出现的具体次数
-
个示例中,我们使用一个volatile修饰的布尔变量作为线程间的标志位,来控制一个线程的执行和停止。 public class VolatileExample { // 使用volatile修饰的布尔变量,作为线程间的标志位 private volatile boolean running = true; public static void main(String[] args) { VolatileExample example = new VolatileExample(); // 启动一个工作线程 Thread workerThread = new Thread(example::doWork); workerThread.start(); // 主线程休眠一段时间后,修改标志位以停止工作线程 try { Thread.sleep(5000); // 休眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } example.stopWork(); // 确保工作线程已停止 try { workerThread.join(); // 等待工作线程结束 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Worker thread has stopped."); } // 工作线程执行的方法 private void doWork() { while (running) { // 模拟工作负载 System.out.println("Worker is running..."); try { Thread.sleep(1000); // 每秒打印一次 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 保留中断状态 break; // 退出循环 } } System.out.println("Worker has stopped working."); } // 修改标志位以停止工作线程 public void stopWork() { running = false; // 修改volatile变量,确保对所有线程可见 } } 在这个示例中,running变量被声明为volatile,以确保当主线程修改其值时,工作线程能够立即感知到这个变化。工作线程在doWork方法中不断检查running变量的值,并根据其值决定是否继续执行模拟的工作负载。主线程在启动工作线程后,休眠5秒钟,然后调用stopWork方法来修改running变量的值,从而通知工作线程停止工作。六、结论volatile关键字在Java并发编程中扮演着重要角色,它确保了变量的可见性和禁止了指令的重排序。然而,它也存在一些局限性,如无法保证复合操作的原子性和可能增加内存开销等。因此,在使用volatile关键字时,需要谨慎考虑其适用场景和限制条件。在更复杂的并发场景中,可能需要使用synchronized关键字或其他并发工具类来确保线程安全。通过深入理解volatile关键字的特性和使用场景,并结合实际的代码示例进行实践,我们可以更好地利用Java并发编程的强大功能,构建出高效、稳定、可扩展的并发应用程序。
-
一、线程库 在Linux中,内核中并没有很明确的线程概念,而是只有轻量级进程的概念!!因此OS并没有给我们提供线程的系统调用,只会给我们提供轻量级进程的系统调用——>可是我们的用户只认识线程而不认识什么轻量级进程啊!!而且使用起来的学习成本也很高啊! 因此就有大佬在应用层为轻量级进程接口进行封装,为用户提供直接的线程接口(pthread线程库) pthread线程库又叫原生线程库,几乎所有的Linux平台都是默认自带这个库的,但是他对于g++来说属于第三方库,链接这些线程函数库时要使用编译器命令的“-lpthread”选项!二、线程创建pthread_create功能:创建一个新的线程int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);参数thread:返回线程ID(输出型参数)attr:设置线程的属性,attr为NULL表示使用默认属性(一般设为NULL)start_routine:是个函数地址,线程启动后要执行的函数(其实就是通过要执行的函数来给线程划分地址空间)arg:传给线程启动函数的参数(可以通过类传多个)返回值:成功返回0;失败返回错误码(pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通 过返回值返回) pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码(局部存储)。对于pthreads函数的错误, 建议通过返回值来判定,因为读取返回值要比读取线程内的errno变量的开销更小 2.1 简单看看多线程 为什么-l就可以了呢??——>因为这个库已经默认安装在系统路径下了,编译器知道他在哪,只是不知道要链接哪个库而已!! 如果我们想查看所有的轻量级进程的话 可以用 ps -aL(a是all的意思,L是轻的意思) 我们会发现主线程的PID和LWP(cpu调度的基本单位)是一样的,这应该也是用来让CPU区分切换的是主线程还是次线程的一个标识!!监视线程的方法: 2.2 全局变量 所有的线程都可以看到全局变量 我们会发现以前的进程间通信,无论是管道、共享内存、消息队列……他们让两个进程看到同一份代码和资源的方法都比较麻烦,可以线程天生就具有看到同一份资源的能力,所以也给我们的通信提供了很好的应用场景和技术准备!! 问题:为什么我们不研究多进程并发,而是研究多线程并发呢??——>因为多进程的写时拷贝、通信……都很麻烦,而线程的共享性更容易实现,他是先进性的表现,但是方便的同时也伴随着线程之间的相互影响、健壮性差等问题,因此这些需要我们程序员在代码上去解决这类问题!!2.3 tid vs LWP 我们会发现tid和LWP差距非常大,因为lwp是操作系统的轻量级进程的概念,只需要OS知道就行,而tid是给用户使用的!(本质是一个地址)! 2.4 线程函数参数返回值为啥都是void*以往进程返回是通过返回错误码来告知我们错误信息,可以线程中的函数为什么会是void*呢??因为不止可以传整形、字符串……还可以传类对象!! (类里面可以放很多内置类型,其实就相当于可以传很多参数,以及返回很多返回值)即使你只想传一个整形或者字符串,你也可以封装在类里面传,能传类的话尽量传类,因为他具有可扩展性!未来想增加别的类型就很方便! 比方说我们要计算1-100相加,我们可以写个request的类传递给他1-100的区间,然后再写个Respond的类帮助我们把运行结果返回回来!! 要注意一定不要在主线程里面创建局部变量传递给次线程!! 如果我们主线程要传类对象给次线程,就必须在堆区开辟空间,这样虽然td指针被释放了,但是我们可以通过args把这个指针传递给线程,这样每个线程就可以去访问自己在堆中的对象了! 其实堆区的资源大家都看得到,比如我2号线程也可以去看1号线程堆区的数据,但是这样没有意义!!所以线程可以看到全部的堆空间,但是每个线程访问的是堆的不同位置!!问题:可是我们为什么不直接在线程里去写这个参数,而是要让主线程通过类传递过去呢??——> 因为主线程可能需要给不止一个次线程分配任务,比如说我想让1线程算1-100,让2线程算101-200…… 也就是可以让每个线程并行地去共同完成同一个任务,而我只需要讲需要处理的数据通过类告诉他们就行,最后我再对结果进行汇总(主线程重分配和管理,次线程重实践)——>甚至你还可以把方法都写进类里面!!这样你的线程就更简洁了!!——>你次线程需要什么类,需要什么方法,我可以通过类来告诉你!!你只管调用就行! 三、线程等待pthread_ join你主线程把我新线程创建出来了,你不得管我吗??万一我还没退你先退了怎么办??——>所以我们要尽量保证主线程最后退!怎么让主线程最后退呢??你可能会想到写个死循环,然后把工作都交给次线程去干,这是这样真的好吗??我只是想让你管理我,不是想让你当甩手掌柜然后自己啥代码也不执行,而且我要是自己退了,你就搁那傻傻循环啥也不管吗??你难道不关心我的运行结果吗??你难道不需要释放我的空间吗??——>所以你主线程必须要等待子线程!!(1、将已经退出的线程的空间释放掉 2、创建新的线程时不会复用刚在退出线程的地址空间)功能:等待线程结束int pthread_join(pthread_t thread, void **value_ptr);参数:thread:线程IDvalue_ptr:它指向一个指针,后者指向线程的返回值(得知新线程的运行情况)返回值:成功返回0;失败返回错误码 调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的 终止状态是不同的 总结如下:1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。 2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。 3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传pthread_exit的参数。4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。 问题:为什么是void**呢?? ——> 因为OS作为管理者也需要知道执行结果,这个执行结果会先被携带结构体里,然后我们可以通过二级指针将我们自己的void*变量地址传递给他,然后把他拷贝过来!!四、线程分离pthread_detach和pthread_self 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join(需要由主线程回收)操作,否则无法释放 资源,从而造成系统泄漏。 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。 讲个小故事理解分离:比如说五六十年代那个时候,很多家庭里面会一次性有很多小孩由父母管理,然后这些小孩长大以后,有其中一个小孩跟父亲特别不对付(一种情况是小孩自己提出分家——子线程自己想分离 还有一种情况是父亲嫌弃你让你离开——父线程要求你分离),而这个小孩虽然可以共享家里的一部分资源,但是其实已经不是一家人了!!所以不管你以后怎么样了,父亲都不会管你了(就相当于线程分离之后,虽然他还可以用到进程的公共资源,并且他也有自己idea资源,但是父线程已经不关心他了 此时以后不管怎么都没人管他 只能自生自灭由OS去回收他)所以可以由线程组内的其他线程对目标线程进行分离,也可以是线程自己分离!! pthread_detach:int pthread_detach(pthread_t thread); pthread_self pthread_t pthread_self(void); 可以获得线程自身的ID joinable和分离是冲突的,一个线程不能既是joinable又是分离的。——本质上就是将我们线程库中我们认为的tcp结构体里的一个关于线程是否分离的标记位给改了!!五、线程终止pthread_exit和pthread_cancel只终止某个线程而不终止整个进程,可以有三种方法:1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。2. 线程可以调用pthread_ exit终止自己。3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程pthread_exit函数功能:线程终止 void pthread_exit(void *value_ptr);参数value_ptr:value_ptr不要指向一个局部变量(独立栈空间会被释放)。返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(线程都终止了返回没有意义) pthread_cancel函数功能:取消一个执行中的线程 int pthread_cancel(pthread_t thread);参数thread:线程ID返回值:成功返回0;失败返回错误码六、c++的线程库 C++其实也有自己的线程库thread ! 可实际上他的底层也是封装的pthread的原生线程库!也需要指定链接! 而以往我们在windows系统下在vs中使用线程库,我们其实并不需要这样,这是因为windows下他有自己专门的线程库,因为windows实现的时候就是有专门的tcb结构体,所以我们包cpp头文件的时候,他的底层其实是windows类型的系统调用!!——>cpp具有跨平台性,根据不同的平台(Linux和windows),他用的是条件编译,外面虽然呈现出来的头文件和接口是一样的,但是不同的平台内部封装所使用的系统调用是不一样的!!——>所以在Linux下的cpp底层封装的是Linux的原生线程库(由于是用的进程模拟线程,所以并没有专门的tcb结构体,他的系统调用接口只有轻量级进程的概念,所以又封装了一个原生线程库给我们,而使用第三方库都需要链接,cpp底层也是这个原生线程库,所以也要链接) 而windows下的线程库就是原生windows下的系统调用,所以他并没有第三方库的概念!! ——>所以你平时写代码在不同的环境下没有感觉,是写库文件的设计者帮助你把这种差异给屏蔽掉了!! 所以我们平时刚推荐使用语言里的库方法而非系统调用接口,因为这样代码就不具备可移植性和跨平台性了!! 七、用户级线程vs内核级LWP用户级线程和内核级LWP是1:1的关系 线程共享进程数据,但也拥有自己的一部分数据:线程ID一组寄存器(保存上下文)独立栈 (完成调用链)errno (局部存储)信号屏蔽字调度优先级 进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:文件描述符表每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录用户id和组id1、 线程的概念是库给我们维护的!!所以线程库注定要维护多个线程属性集合!——>先描述再组织2、不用维护线程的执行流,这是由OS的轻量级进程完成的(已经帮我们封装了) 3、原生线程库必然要被加载到内存中,因此我们的线程属性集合也应该在线程库中维护4、线程控制块就是库帮我们维护的一个用户级线程结构体tcb,而pthread_t类型的线程ID,本质 就是一个进程地址空间上的一个地址,就是指向他的。 7.1 独立线程栈 执行流的本质就是独立的调用链!! 所以每个线程都需要建立自己独立的调用链,所以就必须得有一个独立的栈结构——>支持我们在应用层来完成我们整个调用链对应的临时空间的开辟和释放——>这样对于局部变量来说,就可以保持线程的独立性 虽然是独立栈,但其实其他线程想要访问在技术角度也是可以做到的(定义一个全局的指针,然后在某一个线程中让他保存其中的一个局部变量的地址,然后主线程再当全部线程创建完成之后,再去查看这个全局的指针变量),因为线程与线程之间几乎没有秘密!! 7.2 局部存储 如果我们想要一个只属于线程的全局变量呢??——>通过局部存储(他会被存储在一个区域中 )! 问题:可是这看上去很鸡肋啊!!干嘛要定义这种私有的全局变量啊,我直接在自己的独立栈定义局部变量不就行了??——>可是如果你的线程内部将来也调用函数了呢??比如说你想让别的函数也能够知道你线程的id或者是其他属性,那你还得把这个局部变量通过参数传递给他!! 所以局部存储私有的全局变量最核心的意义就是可以让该线程独立栈内部调用链上所有的函数都可以看得到这些信息,而不需要传参或者是频繁地调用系统调用!———————————————— 原文链接:https://blog.csdn.net/weixin_51142926/article/details/142829794
-
引言在 Java 开发过程中,经常会遇到“找不到或无法加载主类”(Error: Could not find or load main class)的错误。这个错误通常表示 JVM 无法找到指定的主类,可能是由于类路径(Classpath)设置不正确、类文件缺失、编译错误等原因引起的。1. 错误描述当运行 Java 应用程序时,如果 JVM 无法找到指定的主类,会抛出以下错误:Error: Could not find or load main class <ClassName>其中 <ClassName> 是你尝试运行的主类名称。2. 常见原因以下是导致“找不到或无法加载主类”错误的一些常见原因:2.1 类路径设置错误类路径未包含主类:确保类路径(Classpath)中包含了主类所在的目录或 JAR 文件。类路径格式错误:确保类路径的格式正确,特别是多个路径之间的分隔符(Windows 使用 ;,Linux 使用 :)。2.2 类文件缺失编译错误:确保所有 Java 源文件已经成功编译,并且生成了相应的 .class 文件。文件路径错误:确保主类文件位于正确的目录中,且文件名和类名一致。2.3 主类声明错误缺少 public static void main(String[] args) 方法:确保主类中有一个 public static void main(String[] args) 方法,这是 JVM 入口点。类名拼写错误:确保命令行中指定的类名与实际类名完全一致,包括大小写。2.4 JAR 文件问题JAR 文件损坏:确保 JAR 文件没有损坏,并且包含所需的类文件。MANIFEST 文件错误:如果使用 JAR 文件,确保 MANIFEST 文件中的 Main-Class 属性正确指定了主类。3. 诊断方法以下是诊断“找不到或无法加载主类”错误的一些方法:3.1 检查类路径打印类路径:在命令行中使用 echo %CLASSPATH%(Windows)或 echo $CLASSPATH(Linux)命令,检查当前的类路径设置。手动验证:确保类路径中包含了主类所在的目录或 JAR 文件。3.2 检查类文件编译源文件:重新编译所有 Java 源文件,确保生成了 .class 文件。检查文件路径:确保主类文件位于正确的目录中,且文件名和类名一致。3.3 检查主类声明查看源代码:打开主类的源代码文件,确保有 public static void main(String[] args) 方法。检查类名:确保命令行中指定的类名与实际类名完全一致,包括大小写。3.4 检查 JAR 文件验证 JAR 文件:使用 jar tf <jar-file> 命令检查 JAR 文件中的内容,确保包含所需的类文件。检查 MANIFEST 文件:打开 JAR 文件中的 MANIFEST.MF 文件,确保 Main-Class 属性正确指定了主类。4. 解决方案根据诊断结果,采取相应的解决方案:4.1 修正类路径设置类路径:在命令行中使用 -cp 或 -classpath 参数指定类路径。例如:java -cp .;path/to/classes com.example.MainClass环境变量:确保 CLASSPATH 环境变量正确设置。例如,在 Windows 中:set CLASSPATH=.;path\to\classes4.2 重新编译类文件编译源文件:使用 javac 命令重新编译所有 Java 源文件。例如:javac -d . com/example/MainClass.java4.3 修正主类声明添加 main 方法:确保主类中有一个 public static void main(String[] args) 方法。例如:package com.example; public class MainClass { public static void main(String[] args) { System.out.println("Hello, World!"); }}检查类名:确保命令行中指定的类名与实际类名完全一致,包括大小写。例如:java com.example.MainClass4.4 修复 JAR 文件重新打包 JAR 文件:使用 jar 命令重新打包 JAR 文件。例如:jar cvf myapp.jar -C path/to/classes .更新 MANIFEST 文件:确保 MANIFEST.MF 文件中的 Main-Class 属性正确指定了主类。例如:Main-Class: com.example.MainClass5. 示例以下是一个完整的示例,展示了如何编译和运行一个简单的 Java 应用程序:5.1 创建源文件创建一个名为 MainClass.java 的文件,内容如下:package com.example; public class MainClass { public static void main(String[] args) { System.out.println("Hello, World!"); }}5.2 编译源文件在命令行中导航到源文件所在目录,编译源文件:mkdir -p com/examplemv MainClass.java com/example/javac -d . com/example/MainClass.java5.3 运行应用程序确保类路径设置正确,运行应用程序:java -cp . com.example.MainClass6. 总结“找不到或无法加载主类”错误通常是由于类路径设置错误、类文件缺失、主类声明错误或 JAR 文件问题引起的。通过仔细检查类路径、类文件、主类声明和 JAR 文件,可以快速定位和解决这个问题。———————————————— 原文链接:https://blog.csdn.net/2401_85648342/article/details/143706066
-
在 Java 中,处理日期和时间是开发中常见的任务之一,特别是在涉及到多个时区、日期格式、时间计算等需求时。Java 提供了多种方式来处理日期和时间,其中 LocalDateTime、DateTime 和 Date 是三种常见的日期时间类。尽管它们看起来有些相似,但它们的设计理念和应用场景却各有不同。本文将深入分析这三者的区别,帮助大家更好地理解它们的使用场景。一、LocalDateTime:新的 Java 8 日期时间 API1.1 LocalDateTime 简介LocalDateTime 是 Java 8 中引入的 java.time 包的一部分,它代表了没有时区信息的日期和时间。它只包含 年、月、日、时、分、秒、纳秒 信息,不涉及与时区或具体的时间点相关的数据。1.2 设计理念LocalDateTime 设计的目标是解决传统 java.util.Date 类中存在的许多问题,提供一个清晰、直观的 API 来处理日期和时间。由于它没有时区信息,它非常适合表示 本地时间,例如在某个特定地点的时间,且不受时区转换的影响。1.3 适用场景本地日期时间处理:例如,我们只关心某个事件发生的日期和时间,但不关心该事件发生的时区。与数据库交互:当你存储和操作不涉及时区的日期时间(比如某些日历系统或事务记录)时,LocalDateTime 是一个理想的选择。1.4 示例代码import java.time.LocalDateTime;import java.time.format.DateTimeFormatter; public class LocalDateTimeExample { public static void main(String[] args) { LocalDateTime now = LocalDateTime.now(); // 获取当前的本地日期和时间 System.out.println("当前本地日期时间: " + now); // 格式化输出 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); System.out.println("格式化后的日期时间: " + now.format(formatter)); }}输出结果:当前本地日期时间: 2024-11-13T12:45:30.123456789格式化后的日期时间: 2024-11-13 12:45:30二、DateTime:没有明确标准的类2.1 DateTime 的模糊性在 Java 标准库中,并没有直接命名为 DateTime 的类。通常,在一些库或框架中,DateTime 用来指代 日期和时间的组合。例如,Java 8 的 ZonedDateTime 和 LocalDateTime 都可能被通称为 DateTime,尽管它们是不同的类。因此,DateTime 并没有明确的标准定义,通常只是作为一个通用术语,用来描述所有涉及日期和时间的类。2.2 适用场景在一些第三方库(如 Joda-Time)或框架中,DateTime 被广泛用于表示日期和时间。如果使用这些库,可能会遇到 DateTime 这个类。在 Java 中,如果遇到 DateTime,我们可能需要进一步查看它具体是哪个类,例如 ZonedDateTime 或 OffsetDateTime。三、Date:老旧的日期时间类3.1 Date 简介java.util.Date 是 Java 中最早的日期和时间类之一。它代表自 1970 年 1 月 1 日 00:00:00 UTC 以来的毫秒数,直到 Java 8 被新的日期时间 API 取代。Date 类最初设计上存在诸多缺陷,因此不再推荐用于新的项目开发。3.2 设计缺陷时区问题:Date 类本身并不包含时区信息,且会根据所在的系统时区进行转换,导致时区处理不够精确。不便的 API:许多方法(如 getYear())的返回值并不直观,而且对日期的操作不便,缺少流畅的日期计算方法。精度不足:Date 的精度仅限于毫秒,不能处理纳秒级别的时间。3.3 适用场景兼容旧代码:如果你需要与旧版的 API 进行交互,Date 可能仍然是不可避免的。时间戳:由于 Date 内部使用毫秒表示时间,因此它仍然适用于一些需要表示时间戳的场景。3.4 示例代码import java.util.Date; public class DateExample { public static void main(String[] args) { Date date = new Date(); // 获取当前日期和时间 System.out.println("当前日期时间: " + date); }}输出结果:当前日期时间: Wed Nov 13 12:45:30 GMT 2024四、如何选择合适的日期时间类?特性 LocalDateTime DateTime(通常指 ZonedDateTime、OffsetDateTime 等) Date(旧的 java.util.Date)时区信息 无时区信息 ZonedDateTime 有时区信息,LocalDateTime 没有时区信息 Date 依赖于系统时区精确度 纳秒精度 视具体类型而定,如 ZonedDateTime 支持到纳秒精度 毫秒精度设计方式 Java 8 引入的新日期时间 API,推荐使用 DateTime 不是标准类,通常是指 LocalDateTime 或 ZonedDateTime 等 旧的 API,设计不够清晰,容易出错功能 只包含日期和时间,没有时区 包含日期和时间,可以有时区(如 ZonedDateTime) 仅表示一个时间点,设计较为原始使用推荐 推荐用于不需要时区的日期时间操作 ZonedDateTime 或 OffsetDateTime 用于处理带时区的日期时间 不推荐使用,除非需要兼容旧代码1. LocalDateTime:适用于本地日期和时间的场景,不需要时区处理。2. ZonedDateTime:适用于需要时区处理的场景,适合跨时区的日期时间计算。3. Date:仅在兼容旧代码或需要处理时间戳时使用,不推荐在新项目中使用。在 Java 8 之后,我更推荐使用新的日期时间 API(java.time 包中的类),这些类设计更加清晰,功能更强大,避免了 java.util.Date 中的很多问题。———————————————— 原文链接:https://blog.csdn.net/Y_1215/article/details/143755420
-
在Java中,Stream是一种用于处理集合数据的强大工具。它提供了一种函数式编程的方式来对数据进行操作和转换。Stream中的peek方法是一种非终端操作,它允许你在流的每个元素上执行一个操作,而不会改变流的内容。peek方法的语法如下:Stream<T> peek(Consumer<? super T> action)其中,action是一个接收一个元素并执行操作的函数。peek方法的主要作用是在流的每个元素上执行一个操作,比如打印元素的值、记录日志、调试等。它通常用于调试和观察流的中间状态,而不会对流的内容进行修改。下面是一个使用peek方法的简单示例:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> doubledNumbers = numbers.stream() .peek(n -> System.out.println("Processing number: " + n)) .map(n -> n * 2) .collect(Collectors.toList());在上面的示例中,我们创建了一个整数列表numbers,然后通过流的方式对每个元素进行处理。在流的peek操作中,我们打印了每个数字的值。然后,我们使用map操作将每个数字乘以2,并将结果收集到一个新的列表中。当我们运行上面的代码时,会看到以下输出:Processing number: 1 Processing number: 2 Processing number: 3 Processing number: 4 Processing number: 5通过使用peek方法,我们可以观察到流中每个元素的处理过程。这对于调试和理解流的中间状态非常有用。需要注意的是,peek方法是一个中间操作,它不会触发流的终端操作。如果你希望对流的内容进行修改或者获取最终的结果,你需要在peek方法之后添加一个终端操作,比如collect、forEach等。总结起来,peek方法是一个在流的每个元素上执行操作的非终端操作。它通常用于调试和观察流的中间状态,而不会对流的内容进行修改。原文链接:https://gitcode.csdn.net/65e957c41a836825ed78f822.html
-
Java 8 中引入了Stream API,极大地简化了集合操作,使得开发者可以使用流的方式进行数据处理。Stream 提供了一系列非常强大的操作方法,其中之一就是 peek() 方法。peek() 是一个中间操作,它可以用来在操作流的过程中查看元素的处理状态。本文将详细介绍 peek() 方法的使用场景和原理,并配合代码示例帮助大家深入理解。一、peek() 方法简介peek() 方法的定义在 java.util.stream.Stream 接口中,其签名如下:Stream<T> peek(Consumer<? super T> action);作用:peek() 是一个中间操作,它允许我们在流的每个元素上执行一个操作,但并不会改变流中的元素或中断流的处理。常用作调试工具,用来在流的各个操作步骤中查看流中的数据。它接收一个 Consumer 函数作为参数,Consumer 函数可以对每个流中的元素执行某些动作。特点:peek() 不会消耗流,只是执行一个旁路行为。因为是中间操作,它不会触发终端操作,因此在调用完 peek() 后,还需要调用诸如 forEach()、collect() 这类终端操作来触发流的处理。示例代码:import java.util.Arrays;import java.util.List;import java.util.stream.Collectors;public class PeekExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 使用peek方法调试流操作过程 List<Integer> result = numbers.stream() .filter(n -> n % 2 == 0) // 过滤出偶数 .peek(n -> System.out.println("Filtered: " + n)) // 查看过滤结果 .map(n -> n * n) // 对偶数进行平方 .peek(n -> System.out.println("Mapped: " + n)) // 查看映射结果 .collect(Collectors.toList()); // 收集结果 System.out.println("最终结果: " + result); }}输出结果:Filtered: 2Mapped: 4Filtered: 4Mapped: 16最终结果: [4, 16]在上面的示例中,peek() 用来查看流中元素的处理情况,展示了在经过 filter() 和 map() 操作后的数据变化。二、peek() 方法的常见使用场景2.1 调试流操作peek() 的主要用途之一是调试。当我们处理复杂的流操作链时,可能很难理解每个中间操作的效果。这时,可以通过 peek() 来查看流中的数据在每个操作后的变化,以便找到问题或验证逻辑是否正确。import java.util.Arrays;import java.util.List;public class DebugWithPeek { public static void main(String[] args) { List<String> words = Arrays.asList("apple", "banana", "cherry", "date"); words.stream() .filter(w -> w.length() > 4) .peek(w -> System.out.println("Filtered: " + w)) .map(String::toUpperCase) .peek(w -> System.out.println("Mapped to upper case: " + w)) .forEach(System.out::println); }}输出结果:Filtered: appleMapped to upper case: APPLEFiltered: bananaMapped to upper case: BANANAFiltered: cherryMapped to upper case: CHERRYAPPLEBANANACHERRY可以看到,peek() 方法被用于调试,以便我们看到 filter() 和 map() 操作后的字符串。2.2 记录日志在实际应用中,peek() 还可以用于记录流操作的执行过程,比如将流中每个元素的处理结果写入日志。这在数据处理链条较长时,尤为有用。import java.util.Arrays;import java.util.List;import java.util.logging.Logger;public class LogWithPeek { private static final Logger logger = Logger.getLogger(LogWithPeek.class.getName()); public static void main(String[] args) { List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50); numbers.stream() .filter(n -> n > 20) .peek(n -> logger.info("After filter: " + n)) .map(n -> n / 2) .peek(n -> logger.info("After map: " + n)) .forEach(System.out::println); }}在这个例子中,peek() 被用于记录日志,通过 Logger 的 info() 方法记录流中每个元素的处理状态。2.3 数据检查与验证peek() 还可以用来对流中的数据进行检查与验证。当你想确认流中数据是否符合某种规则,但不希望中断流的处理时,peek() 是一个非常好的选择。import java.util.Arrays;import java.util.List;public class DataValidationWithPeek { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); names.stream() .filter(name -> name.length() > 3) .peek(name -> { if (name.startsWith("C")) { System.out.println("注意!名字以C开头: " + name); } }) .forEach(System.out::println); }}在这个示例中,peek() 方法用于检查名字是否以字母C开头,而不影响流的其他操作。三、与forEach()的区别peek() 和 forEach() 看似相似,都是用来对流中的元素进行操作,但它们有明显的区别:peek() 是中间操作,而 forEach() 是终端操作。peek() 通常用于调试或数据检查,因为它不会中断流的链式操作;而 forEach() 是用来最终消费流的元素。示例代码:import java.util.Arrays;import java.util.List;public class PeekVsForEach { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 使用peek()作为中间操作 numbers.stream() .peek(n -> System.out.println("Peeked: " + n)) .map(n -> n * 2) .forEach(System.out::println); System.out.println("--------"); // 使用forEach()作为终端操作 numbers.stream() .map(n -> n * 2) .forEach(n -> System.out.println("ForEach: " + n)); }}输出结果:Peeked: 1Peeked: 2Peeked: 3Peeked: 4Peeked: 5--------ForEach: 2ForEach: 4ForEach: 6ForEach: 8ForEach: 10可以看到,peek() 用于在流操作中查看每个元素,而 forEach() 用于最终消费元素。四、注意事项惰性求值:peek() 是中间操作,具有惰性,只有在终端操作(如 forEach()、collect())调用时,流的处理才会被执行。不可用于修改流元素:peek() 不能修改流中的元素,它只用于执行副作用操作。如果需要修改元素的值,应使用 map() 方法。适用场景:peek() 最适合用于调试或监控流的中间状态,不应该滥用,否则可能会导致代码可读性降低。五、总结在Java的Stream API中,peek() 方法是一个强大的工具,它允许我们在流的处理中观察和调试数据,特别是在数据处理链比较长的情况下,它可以帮助我们跟踪流中元素的状态和变化。但需要注意的是,peek() 不能用于修改流的元素,更多地是用作调试、记录日志和数据检查的手段。通过丰富的代码示例,我们了解了peek() 的常见使用场景和注意事项。在实际开发中,合理使用peek() 可以极大地帮助我们调试和监控流操作,希望本文能帮助你深入理解并掌握peek()的使用。———————————————— 原文链接:https://blog.csdn.net/qq_42978535/article/details/142763452
-
Java中的数据库性能优化:索引、查询和连接池管理大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们来探讨Java中的数据库性能优化,包括索引、查询优化和连接池管理。这些技术在提高应用程序的响应速度和系统的整体性能方面至关重要。一、索引索引是数据库性能优化的重要手段之一。它通过加快查询速度,提高数据检索效率。以下是关于索引的一些技术细节和Java中的应用示例。1. 创建索引在数据库中创建索引可以显著提高查询性能。以MySQL为例,可以使用以下SQL语句创建索引:CREATE INDEX idx_user_name ON users (name);1在Java中,使用JPA(Java Persistence API)可以在实体类上定义索引:package cn.juwatech.demo;import javax.persistence.*;@Entity@Table(name = "users", indexes = {@Index(name = "idx_user_name", columnList = "name")})public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;// getters and setters}2. 使用合适的索引类型不同类型的索引适用于不同的场景。常见的索引类型有B树索引、哈希索引等。在选择索引类型时,应根据查询模式进行选择。例如,B树索引适用于范围查询,而哈希索引适用于等值查询。二、查询优化编写高效的SQL查询是数据库性能优化的关键。以下是一些常见的查询优化策略和Java中的应用示例。1. 避免全表扫描全表扫描会导致性能瓶颈,尤其是在数据量较大的情况下。使用索引可以避免全表扫描,提高查询效率。例如:SELECT * FROM users WHERE name = 'John';1如果在name列上有索引,上述查询会使用索引扫描而不是全表扫描。2. 使用批量操作批量操作可以减少数据库的往返次数,提高性能。在Java中,可以使用JDBC的批量操作功能:package cn.juwatech.demo;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;public class BatchUpdateExample {public static void main(String[] args) throws Exception {Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");String sql = "INSERT INTO users (name) VALUES (?)";PreparedStatement statement = connection.prepareStatement(sql);for (int i = 1; i <= 1000; i++) {statement.setString(1, "User" + i);statement.addBatch();}statement.executeBatch();statement.close();connection.close();}}3. 避免N+1查询问题在使用ORM(如Hibernate)时,N+1查询问题会导致大量的SQL查询,从而影响性能。使用fetch策略可以解决这个问题:package cn.juwatech.demo;import javax.persistence.*;import java.util.List;@Entitypublic class Department {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;@OneToMany(fetch = FetchType.LAZY, mappedBy = "department")private List employees;// getters and setters}@Entitypublic class Employee {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;@ManyToOne@JoinColumn(name = "department_id")private Department department;// getters and setters}三、连接池管理数据库连接池通过复用数据库连接,减少了连接的建立和关闭操作,从而提高了应用程序的性能。以下是常用的连接池管理工具及其配置示例。1. HikariCPHikariCP是一个高性能的JDBC连接池,广泛应用于Java项目中。以下是HikariCP的配置示例:package cn.juwatech.demo;import com.zaxxer.hikari.HikariConfig;import com.zaxxer.hikari.HikariDataSource;import javax.sql.DataSource;public class HikariCPExample {public static DataSource getDataSource() {HikariConfig config = new HikariConfig();config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");config.setUsername("user");config.setPassword("password");config.setMaximumPoolSize(10);return new HikariDataSource(config);}}2. Spring Boot中使用HikariCPSpring Boot默认使用HikariCP作为连接池。以下是Spring Boot配置示例:package cn.juwatech.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.beans.factory.annotation.Value;import com.zaxxer.hikari.HikariConfig;import com.zaxxer.hikari.HikariDataSource;import javax.sql.DataSource;@SpringBootApplicationpublic class SpringBootHikariCPApplication {public static void main(String[] args) {SpringApplication.run(SpringBootHikariCPApplication.class, args);}@Beanpublic DataSource dataSource(@Value("${spring.datasource.url}") String url,@Value("${spring.datasource.username}") String username,@Value("${spring.datasource.password}") String password) {HikariConfig config = new HikariConfig();config.setJdbcUrl(url);config.setUsername(username);config.setPassword(password);config.setMaximumPoolSize(10);return new HikariDataSource(config);}}总结通过合理使用索引、优化查询和管理连接池,可以显著提高Java应用程序的数据库性能。这些技术在处理大规模数据和高并发请求时尤为重要————————————————原文链接:https://blog.csdn.net/weixin_44409190/article/details/140782820
-
前言最近在解决用户反馈的时候发现有些用户查询效率太慢或者直接就查询sql超时(这里配的是默认15秒),然后就在考虑如何在百万级用户的情况下优化性能。一、查询效率缓慢原因Java层面第一层面是java代码原因,最常见的就是java写了太多的暴力循环(例如多重嵌套for循环),导致时间复杂度太高,就导致后端返回结果的时间过于缓慢,让用户体验十分差。mysql层面(数据库)第二层方面就是数据库原因,这里说mysql的情况,最常见的就是有些热数据字段没加索引导致慢查询甚至超时,sql写的不合理各种子查询联表查询,数据库表的设计不合理,还有一种情况是用户的数据量太大(主要原因);二、解决方案Java层面1、优化代码:减少不必要的循环,一般我是把某个业务理解透了,在for循环里边进行适当的continue或break关键字减少循环,如果算法比较厉害的人,可以把时间复杂度降到O(n)甚至O(logN),例如查找某个数用二分查找,也可以用递归通过空间换取性能的方式(注意堆栈溢出)2、异步操作:对某些不需要立马得到结果的数据,可以利用异步操作让用户感觉到加载迅速,先返回一个结果给用户,其余的数据用异步操作。这边可以使用多线程实现异步操作,我发现spring这里是有@Async关键字到方法上实现异步操作,创建线程池和线程都不需要自己担心,多线程的配置可以在配置文件上直接配置即可;还有一种方式是使用消息队列,例如rocketMQ,感兴趣的可以去看看消息队列的相关文章。数据库层面(mysql)1、花更多的钱:有土豪式买更多的数据库服务器,提供更多的节点,这样访问压力小的时候查询性能会更高。2、分库分表:这里分表一般都是水平分表(只根据数据量分),垂直分表的话需要把业务吃的很透,表设计十分合理难度较大,一般可以设定一定的公式进行水平分表,然后对应的用户查询对应的表数据,这样表里面的数据量也比较少,查询性能比较快。3、表设计:咱们也可以从表设计层面进行优化,例如对热数据字段加索引增加查询效率;或者在一些的查询主表里面加一些冗余字段,减少联表查询,不过加了之后需要在对该表进行新增、修改操作的业务都需要把该冗余字段给补上,需要十分熟悉业务。4、sql优化:一般都是sql写的不合理导致索引失效然后sql查询超时,最常见的就是减少子查询,子查询会导致生成一个临时表就相当于又查了一次数据,其余的情况可以利用mybaits的标签if test减少联表,但是这个也是需要十分熟悉业务,并且很多时候你不敢改造这些用了多年的sql。5、缓存:有些重复查询操作,咱们可以直接去缓存里面去拿,这样效率十分高效,公司是使用了redis,但是缓存也需要考虑很多地方,例如缓存击穿,缓存和数据库存放的数据不一致,这是非常致命的问题,一般缓存只能用来一些影响较小但是查询比较多的数据,例如商城的购物车咱们就可以利用缓存,也可以用es做缓存,这种一般都是针对一些列表数据的全文搜索,但这个类似于加冗余字段,各种操作生成的数据都需要同步到es。6、异步:跟上文的java类似,也是利用消息队列进行异步操作。总结目前想到的方案就这些,写的比较乱,这里是记载我目前想法,暂时就没有写实例来体现了,大家有更好的方法可以在评论区指出,我会去科普知识补充上去。———————————————— 原文链接:https://blog.csdn.net/qq_42706375/article/details/127484115
-
代码优化的目标1.减少代码的体积2.提高代码的运行效率一.代码层面 1. for循环中不要利用 + 号去拼接字符串 在循环次数比较多的for循环中,我们也不要利用 + 号去拼接字符串。具体例子如下: for(int i=0;i<1000;++){ String str+=i; } *.三者在执行速度方面的比较:StringBuilder > StringBuffer > String 对于三者使用的总结: 1.如果要操作少量的数据用 = String2.单线程操作字符串缓冲区 下操作大量数=StringBuilder3.多线程操作字符串缓冲区 下操作大量数据 = StringBufferfor循环建议写法,尽量减少对变量的重复计算,明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:for (int i = 0, int length = list.size(); i < length; i++)这样,在list.size()很大的时候,就减少了很多的消耗。2.System.arraycopy > clone > Arrays.copyOf > for综上所述,当复制大量数据时,使用System.arraycopy()命令。3.切勿把异常放置在循环体内try-catch语句本身性能不高,如果再放到循环体中,无非是雪上加霜。因此在开发中,我们要极力避免。eg.for(int i=0;i<10;i++){try{}catch{}}正确做法try{for(int i=0;i<10;i++){ } }catch{12}尽量指定类、方法的final修饰符带有final修饰符的类是不可派生的,为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写。如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,具体参见Java运行期优化。此举能够使性能平均提高50%。5.尽可能使用局部变量调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。6.及时关闭流Java编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。7.懒加载的策略尽量采用懒加载的策略,即在需要的时候才创建例如:String str = “aaa”;if (i == 1){ list.add(str);}建议替换为:if (i == 1){ String str = “aaa”; list.add(str);}循环内不要不断创建对象引用例如:for (int i = 1; i <= count; i++){Object obj = new Object();}这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:Object obj = null;for (int i = 0; i <= count; i++){obj = new Object();}这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。9.性能开销尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销。10.尽量在合适的场合使用单例使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:控制资源的使用,通过线程同步来控制资源的并发访问控制实例的产生,以达到节约资源的目的控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信11.尽量避免随意使用静态变量要知道,当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的,如:public class A{private static B b = new B();}此时静态变量b的生命周期与A类相同,如果A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止12.及时清除不再需要的会话为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,当会话不再需要时,应当及时调用HttpSession的invalidate()方法清除会话。13.使用同步代码块替代同步方法这点在多线程模块中的synchronized锁方法块一文中已经讲得很清楚了,除非能确定一整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。14.将常量声明为static final,并以大写命名这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量15.不要创建一些不使用的对象,不要导入一些不使用的类这毫无意义,如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”,那么请删除这些无用的内容程序运行过程中避免使用反射关于,请参见反射。反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存—-用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。17.使用数据库连接池和线程池这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程。18.使用带缓冲的输入输出流进行IO操作带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率。19.不要让public方法中有太多的形参public方法即对外提供的方法,如果给这些方法太多形参的话主要有两点坏处:违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合参数太多势必导致方法调用的出错概率增加至于这个”太多”指的是多少个,3、4个吧。比如我们用JDBC写一个insertStudentInfo方法,有10个学生信息字段要插如Student表中,可以把这10个参数封装在一个实体类中,作为insert方法的形参20.字符串变量和字符串常量equals的时候将字符串常量写在前面String str = “123”;if (“123”.equals(str)){…}这么做主要是可以避免空指针异常21.转换最快的方式把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、**String.valueOf(数据)次之、数据+”“最慢。所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单:String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断Integer.toString()方法就不说了,直接调用了i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串原文链接:https://blog.csdn.net/xss_lala/article/details/79605537
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签