-
Spring Boot 提供了 RestTemplate 来辅助发起一个 REST 请求,默认通过 JDK 自带的 HttpURLConnection 来作为底层 HTTP 消息的发送方式,使用 JackSon 来序列化服务器返回的 JSON 数据。 RestTemplate 是核心类, 提供了所有访问 REST 服务的接口,尽管实际上可以使用 HTTP Client 类或者 java.net.URL来完成,但 RestTemplate 提供了阻STful 风格的 API。 Spring Boot 提供了 RestTemplateBuilder 来创建一个 RestTemplate。 RestTemplate定义11个基本操作方法,大致如下: delete(): 在特定的URL上对资源执行HTTP DELETE操作 exchange(): 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中 映射得到的 3.execute(): 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象(所有的get、post、delete、put、options、head、exchange方法最终调用的都是excute方法),例如: @Override public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = <span style="white-space:pre"> </span>new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables); } 4.getForEntity(): 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象 5.getForObject() :发送一个HTTP GET请求,返回的请求体将映射为一个对象 6.postForEntity() :POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得 到的 7.postForObject(): POST 数据到一个URL,返回根据响应体匹配形成的对象 8.headForHeaders(): 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头 9.optionsForAllow(): 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息 10.postForLocation() :POST 数据到一个URL,返回新创建资源的URL 11.put(): PUT 资源到特定的URL 实际上,由于Post 操作的非幂等性,它几乎可以代替其他的CRUD操作. 一、GET请求 在RestTemplate中,发送一个GET请求,我们可以通过如下两种方式: 第一种:getForEntity getForEntity方法的返回值是一个ResponseEntity<T>,ResponseEntity<T>是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。其重载方法如下: <T> ResponseEntity<T> getForObject(URI url, Class<T> responseType) throws RestClientException; <T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; 比如下面一个例子: @Autowired RestTemplateBuilder restTemplateBuilder; @RequestMapping("/gethello") public String getHello() { RestTemplate client = restTemplateBuilder.build (); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class); String body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); StringBuffer result = new StringBuffer(); result.append("responseEntity.getBody():").append(body).append("<hr>") .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>") .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>") .append("responseEntity.getHeaders():").append(headers).append("<hr>"); return result.toString(); } 有时候我在调用服务提供者提供的接口时,可能需要传递参数,有两种不同的方式,如下: @RequestMapping("/sayhello") public String sayHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "张三"); return responseEntity.getBody(); } @RequestMapping("/sayhello2") public String sayHello2() { Map<String, String> map = new HashMap<>(); map.put("name", "李四"); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map); return responseEntity.getBody(); } 可以用一个数字做占位符,最后是一个可变长度的参数,来一一替换前面的占位符 也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值 第一个调用地址也可以是一个URI而不是字符串,这个时候我们构建一个URI即可,参数神马的都包含在URI中了,如下: @RequestMapping("/sayhello3") public String sayHello3() { UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode(); URI uri = uriComponents.toUri(); ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class); return responseEntity.getBody(); } 通过Spring中提供的UriComponents来构建Uri即可。 当然,服务提供者不仅可以返回String,也可以返回一个自定义类型的对象,比如我的服务提供者中有如下方法: @RequestMapping(value = "/getbook1", method = RequestMethod.GET) public Book book1() { return new Book("三国演义", 90, "罗贯中", "花城出版社"); } 对于该方法我可以在服务消费者中通过如下方式来调用: @RequestMapping("/book1") public Book book1() { ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO- SERVICE/getbook1", Book.class); return responseEntity.getBody(); } 第二种:getForObject getForObject函数实际上是对getForEntity函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject,重载方法如下: <T> T getForObject(URI url, Class<T> responseType) throws RestClientException; <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; 二、POST请求 在RestTemplate中,POST请求可以通过如下三个方法来发起: 第一种:postForEntity、postForObject POST请求有postForObject()和postForEntity()两种方法,和GET请求的getForObject()和getForEntity()方法类似。getForLocation()是POST请求所特有的。 <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException; <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; 上面三个方法中,第一个参数都是资源要POST到的URL,第二个参数是要发送的对象,而第三个参数是预期返回的Java类型。在URL作为String类型的两个版本中,第四个参数指定了URL变量(要么是可变参数列表,要么是一个Map)。 该方法和get请求中的getForEntity方法类似,如下例子: @RequestMapping("/book3") public Book book3() { Book book = new Book(); book.setName("红楼梦"); ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class); return responseEntity.getBody(); } 第二种:postForLacation postForLacation()会在POST请求的请求体中发送一个资源到服务器端,返回的不再是资源对象,而是创建资源的位置。 postForLocation(String url, Object request, Object... uriVariables) throws RestClientException; postForLocation(String url, Object request, Map<String, ?> uriVariables) throws RestClientException; postForLocation(URI url, Object request) throws RestClientException; public String postSpitter(Spitter spitter) { RestTemplate rest = new RestTemplate(); return rest.postForLocation("http://localhost:8080/Spitter/spitters", spitter).toString(); } postForLocation也是提交新资源,提交成功之后,返回新资源的URI,postForLocation的参数和前面两种的参数基本一致,只不过该方法的返回值为Uri,这个只需要服务提供者返回一个Uri即可,该Uri表示新资源的位置。 三、PUT请求 在RestTemplate中,对PUT请求可以通过put方法进行调用实现,比如: RestTemplate restTemplate=new RestTemplate(); Longid=100011; User user=new User("didi",40); restTemplate.put("http://USER-SERVICE/user/{l}",user,id); put函数也实现了三种不同的重载方法: put(String url,Object request,Object... urlVariables) put(String url,Object request,Map urlVariables) put(URI url,Object request) put函数为void类型,所以没有返回内容,也就没有其他函数定义的responseType参数,除此之外的其他传入参数定义与用法与postForObject基本一致。 四、DELETE请求 在RestTemplate中,对DELETE请求可以通过delete方法进行调用实现,比如: RestTemplate restTemplate=new RestTemplate(); Longid=10001L; restTemplate.delete("http://USER-SERVICE/user/{1)",id); delete函数也实现了三种不同的重载方法: delete(String url,Object... urlVariables) delete(String url,Map urlVariables) delete(URI url) 由于我们在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息,就如put()方法一样,返回值类型为void。 说明:第三种重载方法,url指定DELETE请求的位置,urlVariables绑定url中的参数即可。 五、通用方法exchange() exchange方法可以在发送个服务器端的请求中设置头信息,其重载方法如下: <T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException; <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException; <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>(); headers.add("Accept", "application/json"); HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers); ResponseEntity<Spitter> response=rest.exchange("http://localhost:8080/Spitter/spitters/ {spitter}", HttpMethod.GET, requestEntity, Spitter.class, spitterId); 补充: 如果期望返回的类型是一个列表,如 List,不能简单调用 xxxForObject,因为存在泛型的类型擦除, RestTemplate 在反序列化的时候并不知道实际反序列化的类型,因此可以使用 ParameterizedTypeReference 来包含泛型类型,代码如下: RestTemplate client= restTemplateBuilder.build(); //根据条件查询一纽订单 String uri = base+"/orders?offset={offset }"; Integer offset = 1; //元参数 HttpEntity body = null; ParameterizedTypeReference<List<Order> typeRef = new ParameterizedTypeReference<List<Order>(){}; ResponseEntity<List<Order> rs=client.exchange(uri, HttpMethod.GET, body, typeRef, offset); List<Order> order = rs.getBody() ; 注意到 typeRef 定义是用{}结束的,这里创建了一个 ParameterizedTypeReference 子类,依 据在类定义中的泛型信息保留的原则, typeRef保留了期望返回的泛型 List。 exchange 是一个基础的 REST 调用接口,除了需要指明 HTTP Method,调用方法同其他方法类似。 除了使用ParameterizedTypeReference子类对象,也可以先将返回结果映射成 json字符串,然后通过 ObjectMapper 来转为指定类型(笔记<SpringBoot中的JSON>中有介绍 ) ———————————————— 原文链接:https://blog.csdn.net/fsy9595887/article/details/86420048
-
【问题来源】黑龙江农信社【问题简要】开发 vxml 版本自助语音流程时,使用grammer 标签 的 src 属性上送语法文件是,流程跟踪日志中,提示异常【问题类别】vxml 版本自主语音流程开发【AICC解决方案版本】AICC 版本:AICC 22.200.0【问题现象描述】页面中通过 grammar 标签 读取项目中一个 grammar.xml 文件流程文件中提示异常 There was a DEFINE_GRAMMAR failure 语法文件内容如下
-
1、GC触发的条件 Java中,GC的触发主要有两种方式: 显式触发:通过程序调用System.gc()或Runtime.getRuntime().gc()方法,向JVM发出建议进行垃圾回收的请求。但请注意,这仅仅是建议,JVM可以忽略这个请求。 隐式触发:由JVM根据内部算法和内存使用情况自动决定。当堆内存中的对象空间不足以满足新对象分配时,JVM会自动触发GC以尝试回收内存。 2、GCRoots的对象类型 在Java中,GC Roots是对象图遍历的起始点,它们是在垃圾回收过程中,被JVM视为存活的对象。GC Roots主要包括以下几种: 虚拟机栈中引用的对象:包括局部变量和参数等。 方法区中类静态属性引用的对象:类的静态变量引用的对象。 方法区中常量引用的对象:如字符串常量池中的对象。 本地方法栈中JNI(Java Native Interface)引用的对象:由JNI调用本地方法时,本地方法栈中引用的对象。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_41840843/article/details/140333907
-
static关键字 1修饰属性 Java的静态属性和类相关, 和具体的实例无关. 换句话说, 同一个类的不同实例共用同一个静态属性. 举例 class TestDemo{ public int a; public static int count; } public class Main{ public static void main(String[] args) { TestDemo t1 = new TestDemo(); t1.a++; TestDemo.count++; System.out.println(t1.a); System.out.println(TestDemo.count); System.out.println("============"); TestDemo t2 = new TestDemo(); t2.a++; TestDemo.count++; System.out.println(t2.a); System.out.println(TestDemo.count); } } 上面的代码输出结果是: 1 1 ============ 为什么呢?-----count成员属性被static修饰后,属于类不属于具体的对象,而a属于具体的对象。 2修饰方法 如果在任何方法上应用 static 关键字,此方法称为静态方法。 静态方法属于类,而不属于类的对象。 可以直接调用静态方法,而无需创建类的实例。 静态方法可以访问静态数据成员,并可以更改静态数据成员的值。 静态方法不可以访问非静态数据成员。 举例 class TestDemo{ public int a; public static int count; public static void change() { count = 100; //a = 10; error 不可以访问非静态数据成员 } } public class Main{ public static void main(String[] args) { TestDemo.change();//无需创建实例对象 就可以调用 System.out.println(TestDemo.count); } } final 被final修饰的成员属性叫做常量,属于对象,被final修饰后,后续不可被修改。 被final和static同时修饰的成员属性叫做静态常量,属于类本身,只有一份,后续不可被修改。 构造方法 基本语法 构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作. new 执行过程 为对象分配内存空间 调用对象的构造方法 语法规则 1.方法名称必须与类名称相同 2.构造方法没有返回值类型声明 3.每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)。 注意事项 如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数 若类中定义了构造方法,则默认的无参构造将不再生成. 构造方法支持重载. 规则和普通方法的重载一致 . this关键字 this表示当前对象引用(注意不是当前对象). 可以借助 this 来访问对象的字段和方法。 可以使用this调用构造函数,但是要放在第一行!!! 举例 class Person { private String name;//实例成员变量 private int age; private String sex; //默认构造函数 构造对象 public Person() { //this调用构造函数 this("bit", 12, "man");//必须放在第一行进行显示 } //这两个构造函数之间的关系为重载。 public Person(String name,int age,String sex) { this.name = name; this.age = age; this.sex = sex; } public void show() { System.out.println("name: "+name+" age: "+age+" sex: "+sex); } } public class Main{ public static void main(String[] args) { Person person = new Person();//调用不带参数的构造函数 person.show(); } } // 输出 结果 name: bit age: 12 sex: man 代码块 字段的初始化方式有: 1. 就地初始化 2. 使用构造方法初始化 3.使用代码块初始化 下面将讲解使用第3种方式的初始化。 定义 使用 {} 定义的一段代码. 根据代码块定义的位置以及关键字,又可分为以下四种: 普通代码块 构造块 静态块 同步代码块 普通代码块 定义在方法中的代码块。 举例 public class Main{ public static void main(String[] args) { { //直接使用{}定义,普通方法块 int x = 10 ; System.out.println("x1 = " +x); } int x = 100 ; System.out.println("x2 = " +x); } } // 执行结果 x1 = 10 x2 = 100 构造代码块 定义在类中的代码块(不加修饰符),也称实例代码块。 构造代码块一般用于初始化实例成员变量。 举例 class Person{ private String name;//实例成员变量 private int age; private String sex; public Person() { System.out.println("I am Person init()!"); } //实例代码块 { this.name = "bit"; this.age = 12; this.sex = "man"; System.out.println("I am instance init()!"); } public void show(){ System.out.println("name: "+name+" age: "+age+" sex: "+sex); } } public class Main { public static void main(String[] args) { Person p1 = new Person(); p1.show(); } } 运行结果: I am instance init()! I am Person init()! name: bit age: 12 sex: man 静态代码块 使用static定义的代码块。一般用于初始化静态成员属性。 静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。 静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。 举例 class Person{ private String name;//实例成员变量 private int age; private String sex; private static int count = 0;//静态成员变量 由类共享数据 方法区 public Person(){ System.out.println("I am Person init()!"); } //实例代码块 { this.name = "bit"; this.age = 12; this.sex = "man"; System.out.println("I am instance init()!"); } //静态代码块 static { count = 10;//只能访问静态数据成员 System.out.println("I am static init()!"); } public void show(){ System.out.println("name: "+name+" age: "+age+" sex: "+sex); } } public class Main { public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person();//静态代码块是否还会被执行? } } 匿名对象 匿名只是表示没有名字的对象. 没有引用的对象称为匿名对象. 匿名对象只能在创建对象时使用. 如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象. 举例 class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); } } public class Main { public static void main(String[] args) { new Person("caocao",19).show();//通过匿名对象调用方法 } } toString class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); } } public class Test { public static void main(String[] args) { Person person = new Person("caocao",19); person.show(); System.out.println(person); } } 为什么第二行打印结果是这样的呢? 为了更好的打印,需要重写Object的toString方法,即: class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); } //重写Object的toString方法 @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class Test { public static void main(String[] args) { Person person = new Person("caocao",19); person.show(); System.out.println(person); } } 总结 一个类可以产生无数的对象,类就是模板,对象就是具体的实例。 类中定义的属性,大概分为几类:类属性,对象属性。其中被static所修饰的数据属性称为类属性, static修饰的方法称为类方法,特点是不依赖于对象,我们只需要通过类名就可以调用其属性或者方法。 静态代码块优先实例代码块执行,实例代码块优先构造函数执行。 this关键字代表的是当前对象的引用。并不是当前对象。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/wmh_1234567/article/details/140722526
-
大家好,本次7月合计带来的内容包括 .Sql Server,mysql,java,python,nginx多个内容供您查找学习,感谢1.Sql Server查询卡顿的排查方法【转】 https://bbs.huaweicloud.com/forum/thread-02113157814182541065-1-1.html 2.mysql WITH RECURSIVE语法的具体使用【转】 https://bbs.huaweicloud.com/forum/thread-02127157734079592051-1-1.html 3.MySQL文件权限存在的安全问题和解决方案【转】 https://bbs.huaweicloud.com/forum/thread-02127157814439433059-1-1.html 4.MySQL的表约束的具体使用【转】 https://bbs.huaweicloud.com/forum/thread-02119157814488009063-1-1.html 5.MySQL建表语句基础及示例详解【转】 https://bbs.huaweicloud.com/forum/thread-02113157815352821066-1-1.html 6.MySQL分表和分区分表的区别小结【转】 https://bbs.huaweicloud.com/forum/thread-02119157815516348065-1-1.html 7.Redission实现分布式锁lock()和tryLock()方法的区别小结【转】 https://bbs.huaweicloud.com/forum/thread-02119157816143144066-1-1.html 8.无法启动Redis打开redis-server闪退的问题解决办法【转】 https://bbs.huaweicloud.com/forum/thread-0220157816373247058-1-1.html 9.Nginx出现404 Not Found nginx/1.23.4的完美解决方案【转】 https://bbs.huaweicloud.com/forum/thread-0220157816562623059-1-1.html 10.Nginx上传文件出现“ 413 (499 502 404) Request Entity Too Large错误解决【转】 https://bbs.huaweicloud.com/forum/thread-02127157817937530061-1-1.html 11.Nginx代理MySQL实现通过域名连接数据库的详细教程【转】 https://bbs.huaweicloud.com/forum/thread-02127157818132517062-1-1.html 12.Nginx实现灰度发布的常见方法小结【转】 https://bbs.huaweicloud.com/forum/thread-02127157818398915063-1-1.html 13.Vscode中launch.json与tasks.json文件的详细介绍【转】 https://bbs.huaweicloud.com/forum/thread-02127157818683109058-1-1.html 14.Python中网络请求的12种方式【转】 https://bbs.huaweicloud.com/forum/thread-02113157818977531068-1-1.html 15. pip install过程中出现error: subprocess-exited-with-error错误的解决办法【转】 https://bbs.huaweicloud.com/forum/thread-0286157819160557055-1-1.html 16.在Python代码中执行Linux命令的详细用法教程【转】 https://bbs.huaweicloud.com/forum/thread-02127157819336403064-1-1.html 17.ava 集合去重的三种方法【转】 https://bbs.huaweicloud.com/forum/thread-0220157820152566061-1-1.html
-
在Java中,如果你有一个集合(如List、Set等),其中包含自定义对象,并且你希望根据某个特定属性去重,只保留每个具有唯一属性值的对象的一个实例,你可以使用以下几种方法:方法1:使用HashSet如果对象的类实现了equals()和hashCode()方法,并且这两个方法是基于你想去重的那个属性来实现的,那么可以直接将列表转换为HashSet以达到去重的目的。123List<MyObject> list = ...; // 假设这是你的原始列表Set<MyObject> uniqueSet = new HashSet<>(list);List<MyObject> uniqueList = new ArrayList<>(uniqueSet);方法2:Stream API (Java 8及以上版本)通过Java 8引入的Stream API可以更方便地处理这种情况,尤其是当你想基于对象的某个属性进行去重时:1234567891011List<MyObject> list = ...;List<MyObject> uniqueList = list.stream() .collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(MyObject::getProperty))), ArrayList::new ));// 或者简化版本,如果MyObject直接或间接实现了Comparable接口:List<MyObject> uniqueList = list.stream() .distinct() .collect(Collectors.toList());上述代码中,我们首先将列表转换为流,然后使用Collectors.toCollection()收集到一个TreeSet中。TreeSet会自动根据传入的比较器对元素排序并去除重复项。这里假设getProperty()是获取对象属性的方法。最后将TreeSet转换回ArrayList。方法3:手动遍历并使用Map如果你想根据对象的某个属性保持第一个出现的对象,可以使用toMap方法:12345678List<MyObject> list = ...;Map<String, MyObject> map = list.stream() .collect(Collectors.toMap( MyObject::getProperty, // key extractor function Function.identity(), // value mapping function (o1, o2) -> o1 // merge function - 如果有冲突则保留第一个对象 ));List<MyObject> uniqueList = new ArrayList<>(map.values());这个方法会根据对象的属性值作为键存入Map中,由于Map不允许键重复,所以结果自然就是唯一的。通过merge函数指定当遇到相同键时保留第一个对象。请注意,这些示例假定MyObject是一个代表具体业务实体的类,而getProperty()是返回该类中用于判断是否重复的属性值的方法。根据实际情况调整类名和方法名。
-
在Java中,使用 poi-tl 插件可以非常方便地生成和操作Word文档,并且能够动态填充数据。要根据导出数据的类别数量动态显示两列或三列,可以按照以下步骤进行:1. 导入依赖首先,在您的项目中添加 poi-tl 和 poi 的依赖。以Maven为例,您需要在 pom.xml 文件中添加以下内容: <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.9.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency>2. 创建模板创建一个 Word 模板(例如 template.docx),其中包含占位符。在这里,我们假设有一个表格区域用来放置动态列的数据。示例模板(template.docx){{table}}3. Java 代码实现编写Java代码,根据数据的类别动态生成两列或三列的表格,并将数据填充到Word文档中。动态填充数据的Java示例 import com.deepoove.poi.XWPFTemplate;import com.deepoove.poi.data.RowRenderData;import com.deepoove.poi.data.TableRenderData;import java.io.FileOutputStream;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class DynamicWordExport {public static void main(String[] args) throws Exception {// 假设这是要填充的数据List<Map<String, String>> data = new ArrayList<>();data.add(new HashMap<String, String>() {{put("Name", "John");put("Age", "30");put("Gender", "Male");}});data.add(new HashMap<String, String>() {{put("Name", "Jane");put("Age", "28");put("Gender", "Female");}});// 根据数据动态生成 TableRenderDataTableRenderData tableData = generateTableData(data);// 填充模板Map<String, Object> templateData = new HashMap<>();templateData.put("table", tableData);XWPFTemplate template = XWPFTemplate.compile("template.docx").render(templateData);// 输出到文件FileOutputStream out = new FileOutputStream("output.docx");template.write(out);out.close();template.close();}private static TableRenderData generateTableData(List<Map<String, String>> data) {List<RowRenderData> rows = new ArrayList<>();// 表头行List<String> headers = new ArrayList<>(data.get(0).keySet());RowRenderData headerRow = RowRenderData.build(headers.toArray(new String[0]));rows.add(headerRow);// 数据行for (Map<String, String> entry : data) {List<String> rowValues = new ArrayList<>();for (String key : headers) {rowValues.add(entry.get(key));}RowRenderData row = RowRenderData.build(rowValues.toArray(new String[0]));rows.add(row);}return new TableRenderData(rows);}}4. 运行程序执行上述 Java 程序,将会生成一个包含动态数据的 Word 文档 output.docx。5. 根据列数调整表格布局如果要根据数据类别的多少动态显示两列或三列,可以在 generateTableData 方法中根据 headers 的数量进行条件判断: private static TableRenderData generateTableData(List<Map<String, String>> data) {List<RowRenderData> rows = new ArrayList<>();// 表头行List<String> headers = new ArrayList<>(data.get(0).keySet());RowRenderData headerRow = RowRenderData.build(headers.toArray(new String[0]));rows.add(headerRow);// 数据行for (Map<String, String> entry : data) {List<String> rowValues = new ArrayList<>();for (String key : headers) {rowValues.add(entry.get(key));}RowRenderData row = RowRenderData.build(rowValues.toArray(new String[0]));// 根据列数调整表格布局if (headers.size() == 2) {row.setCellWidth(Arrays.asList("50%", "50%"));} else if (headers.size() == 3) {row.setCellWidth(Arrays.asList("33%", "33%", "33%"));}rows.add(row);}return new TableRenderData(rows);}通过这种方法,可以根据数据的类别数量动态调整 Word 文档中的表格列数和布局。
-
介绍七夕魔方照片墙是一款创意应用,旨在让用户通过一个3D魔方展示他们的照片。每个面都可以显示不同的图片,用户可以旋转和翻转魔方来浏览所有的照片。这种方式不仅仅是简单地展示照片,而是将视觉效果与互动体验结合起来,使得照片展示更加生动有趣。应用使用场景个人相册展示: 用户可以将自己的旅游照片、家庭照片等按日期或主题放在魔方上展示。企业产品宣传: 企业可以将产品图片制作成魔方,用于展会或者网站上进行互动展示。艺术作品展示: 艺术家可以将自己的作品集成在魔方上,观众可以通过旋转魔方查看每一个细节。教育培训: 可以用于教学内容的展示,比如展示几何图形的各个面。原理解释七夕魔方照片墙基于三维图形渲染技术,将多个二维图像拼接在一个立方体的六个面上。通过用户的交互(如鼠标拖拽、触摸屏滑动),实现立方体的旋转变化,从而展现不同的面上的照片。算法原理流程图及解释+----------------+| Load Images |+--------+-------+ | v+--------+----------+| Initialize Cube || - Create 3D model || - Map images |+--------+----------+ | v+--------+-----------+| Render Cube || - Set up camera || - Apply textures |+--------+-----------+ | v+--------+-----------+| User Interaction || - Detect input || - Update rotation |+--------+-----------+ | v+--------+-----------+| Update Display || - Re-render cube |+--------------------+加载图片:从本地或远程资源加载用户选择的图片。初始化魔方:创建一个3D模型,并将图片映射到魔方的六个面上。渲染魔方:设置摄像机视角,将已贴好图像的3D魔方渲染到屏幕上。用户交互:检测用户输入(如鼠标拖拽、手指滑动等),并根据输入更新魔方的旋转状态。更新显示:重新渲染魔方,以反映最新的旋转状态。应用场景代码示例实现以下是一个利用 Java 和 JavaFX 实现七夕魔方照片墙的简单示例:import javafx.application.Application;import javafx.scene.*;import javafx.scene.image.Image;import javafx.scene.paint.PhongMaterial;import javafx.scene.shape.Box;import javafx.stage.Stage;import javafx.scene.transform.Rotate;public class PhotoCube extends Application { private static final double CUBE_SIZE = 200.0; private static final String[] IMAGES = { "image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg", "image5.jpg", "image6.jpg" }; @Override public void start(Stage primaryStage) throws Exception { Group root = new Group(); Scene scene = new Scene(root, 800, 600, true); PerspectiveCamera camera = new PerspectiveCamera(true); camera.setTranslateZ(-500); Box cube = createPhotoCube(); root.getChildren().add(cube); scene.setOnMouseDragged(event -> { cube.getTransforms().add(new Rotate(event.getSceneX(), Rotate.Y_AXIS)); cube.getTransforms().add(new Rotate(event.getSceneY(), Rotate.X_AXIS)); }); primaryStage.setTitle("七夕魔方照片墙"); primaryStage.setScene(scene); primaryStage.show(); } private Box createPhotoCube() { Box box = new Box(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE); PhongMaterial material = new PhongMaterial(); material.setDiffuseMap(new Image(IMAGES[0])); box.setMaterial(material); return box; } public static void main(String[] args) { launch(args); }}部署测试场景开发环境: 安装 JDK 和 JavaFX SDK。确保你的开发环境可以编译和运行 JavaFX 项目。构建项目: 使用 IDE(如 IntelliJ IDEA 或 Eclipse)创建并构建该项目。运行测试: 在本地运行应用程序,测试魔方的旋转和图片展示功能。材料链接JavaFX 官方文档JavaFX 示例项目Java 图形编程入门教程总结七夕魔方照片墙通过3D图形渲染和用户交互,为照片展示提供了一种创新和有趣的方式。它不仅适用于个人照片展示,还可以用于企业宣传、教育培训等多个领域。通过详细的算法解释和代码实现,我们了解了其工作原理和实现方法。未来展望未来,可以考虑加入更多的交互功能,例如:动画效果:增加自动旋转和缩放的动画效果,使展示更具动态性。多媒体支持:除了图片外,还可以支持视频和音频展示,增加内容的丰富性。云端存储和分享:集成云存储服务,让用户可以方便地上传和分享自己的魔方照片墙。这些改进将进一步提升用户体验和应用的实用性。
-
在开发过程中,我们可能会遇到需要生成word,或者通过模板word替换相应内容的需求。但在文档中插入图片时,如果段落格式设置不对,就会导致图片只显示一点点或者不显示。接下来就介绍一下java编辑word和插入图片需怎么处理。1.引入依赖首先我们在项目中引入Apache POI,用于读取和操作word,这里我使用的版本是4.1.2,版本可以根据项目需求自己选择。12345<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version></dependency> 2.编辑word这里是通过模板加入占位符,然后替换占位符的内容首先我们打开word模板文件12345678910String path = "***.docx";File file = new File(path);try { XWPFDocument template = new XWPFDocument(new FileInputStream(file)); // 替换内容 XWPFDocument outWord = PoiWordUtil.replaceWithPlaceholder(template, list); return outWord;} catch (IOException e) { log.error("读取模板文件失败", e);}替换相应内容1// 这里我定义了Placeholder来封装替换数据public static XWPFDocument replaceTextAndImage(XWPFDocument document, List<Placeholder> list) { for (XWPFParagraph xwpfParagraph : document.getParagraphs()) { String paragraphText = xwpfParagraph.getText(<em id="__mceDel"> if (StringUtils.isEmpty(paragraphText)) contin</em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"> for (Placeholder placeholder : list) {</em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"> String key = placeholder.getKey();</em></em></em></em></em></em></em><em><em><em><em><em><em><em> if (paragraphText.contains(key)) {<br> for (XWPFRun cellRun : xwpfParagraph.getRuns()) {<br> String text = cellRun.getText(0);<br> if (text != null && text.contains(key)) {<br> //获取占位符类型<br> String type = placeholder.getType();<br> //获取对应key的value<br> String value = placeholder.getValue();<br> if("0".equals(type)){<br> //把文本的内容,key替换为value<br> text = text.replace(key, value);<br> //把替换好的文本内容,保存到当前这个文本对象<br> cellRun.setText(text, 0);<br> }else {<br> text = text.replace(key, "");<br> cellRun.setText(text, 0);<br> if (StringUtils.isEmpty(value)) </em></em></em></em></em></em></em><em id="__mceDel"><em id="__mceDel">continue;</em></em><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"><em id="__mceDel"> try {<br> // 获取段落行距模式<br> int rule = xwpfParagraph.getSpacingLineRule().getValue();<br> // 如果段落行距为固定值,会导致图片显示不全,所以需要改成其他模式<br> if (LineSpacingRule.EXACT.getValue() == rule) {<br> // 设置段落行距为单倍行距<br> xwpfParagraph.setSpacingBetween(1);<br> }<br> // 获取文件流<br> InputStream imageStream = ImageUtils.getFile(value);</em></em></em></em></em></em></em><em id="__mceDel"> if (imageStream == null) continue;<br> // 通过BufferedImage获取图片信息<br> BufferedImage bufferedImage = ImageIO.read(imageStream);<br> int height = bufferedImage.getHeight();<br> int width = bufferedImage.getWidth();<br> // 这里需要重新获取流,之前的流已经被BufferedImage使用掉了<br> cellRun.addPicture(ImageUtils.getFile(value), XWPFDocument.PICTURE_TYPE_JPEG, "", Units.toEMU(width), Units.toEMU(height));</em> } catch (Exception e) {<br> e.printStackTrace();<br> }<br> }<br> }<br> }<br> }<br> }<br> }<br> return document;<br>}在插入图片时,如果段落的行距设置成了固定值,那么在显示图片时只能显示行距大小的部分,所以当插入图片的段落行距为固定值时,我们需要修改为其他模式,这样图片就能正常大小显示。然后我们使用cellRun.addPicture()来插入图片,这里我们可以通过BufferedImage来获取图片的尺寸大小。这样就解决了插入图片显示异常的问题了。转载自https://www.cnblogs.com/qq545505061/p/18302543
-
原子性的意义原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况。这意味着原子性操作是不可分割的,它们在执行过程中不会被其他操作中断或干扰。原子性的意义在于它保证了数据的一致性和程序的正确性。在多线程或多进程的环境中,当多个操作同时访问和修改共享数据时,如果没有原子性保证,可能会导致数据不一致或不确定的结果。例如,如果一个线程在读取某个数据时,另一个线程同时修改了这个数据,那么第一个线程读取到的数据可能是不正确的。通过确保操作的原子性,可以避免这种情况,从而维护数据的完整性和程序的正确执行。了解了上面的原子性的重要概念后,接下来一起聊一聊 volatile 关键字。volatile 关键字在 Java 中用于确保变量的更新对所有线程都是可见的,但它并不保证复合操作的原子性。这意味着当多个线程同时访问一个 volatile 变量时,可能会遇到读取不一致的问题,尽管它们不会看到部分更新的值。Volatile 的限制不保证原子性:volatile 变量的单个读写操作是原子的,但复合操作(如自增或同步块)不是原子的。不保证顺序性:volatile 变量的读写操作不会与其他操作(如非 volatile 变量的读写)发生重排序。一个例子用一个示例来解释会更清楚点,假如我们有一段代码是这样的:class Counter { private volatile int count = 0; void increment() { count++; } int getCount() { return count; } } 尽管 count 是 volatile 变量,但 increment 方法中的复合操作 count++(读取-增加-写入)不是原子的。因此,在多线程环境中,多个线程可能会同时读取相同的初始值,然后增加它,导致最终值低于预期。volatile 不保证原子性的代码验证以下是一个简单的 Java 程序,演示了 volatile 变量在多线程环境中不保证复合操作原子性的问题:public class VolatileTest { private static volatile int counter = 0; public static void main(String[] args) throws InterruptedException { int numberOfThreads = 10000; Thread[] threads = new Thread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 100; j++) { counter++; } }); threads[i].start(); } for (int i = 0; i < numberOfThreads; i++) { threads[i].join(); } System.out.println("Expected count: " + (numberOfThreads * 100)); System.out.println("Actual count: " + counter); } }在这个例子中:counter 是一个 volatile 变量。每个线程都会对 counter 执行 100 次自增操作。理论上,如果 counter++ 是原子的,最终的 counter 值应该是 10000 * 100。然而,由于 counter++ 包含三个操作:读取 counter 的值、增加 1、写回 counter 的值,这些操作不是原子的。因此,在多线程环境中,最终的 counter 值通常会小于预期值,这证明了 volatile 变量不保证复合操作的原子性。解决方案1. 使用 synchronized 方法或块:将访问 volatile 变量的方法或代码块声明为 synchronized,确保原子性和可见性。class Counter { private volatile int count = 0; synchronized void increment() { count++; } synchronized int getCount() { return count; } }2. 使用 AtomicInteger 类:java.util.concurrent.atomic 包中的 AtomicInteger 提供了原子操作,可以替代 volatile 变量。import java.util.concurrent.atomic.AtomicInteger; class Counter { private AtomicInteger count = new AtomicInteger(0); void increment() { count.incrementAndGet(); } int getCount() { return count.get(); } }3. 使用锁(如 ReentrantLock):使用显式锁(如 ReentrantLock)来同步访问 volatile 变量的代码块。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Counter { private volatile int count = 0; private final Lock lock = new ReentrantLock(); void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }使用volatile变量的正确使用场景如果操作是简单的读写,并且你只需要保证可见性,可以使用 volatile。但对于复合操作,可以使用上述其他方法来实现,通过这些方法,可以确保在多线程环境中对共享资源的正确同步和可见性。转载自https://www.cnblogs.com/wgjava/p/18311697
-
介绍在Spring Boot开发的动态世界中,确保数据完整性和跟踪变化是至关重要的。实现这一目标的一个强大工具是@Audited注解。本文深入探讨了该注解的复杂性、其目的、实现步骤以及如何利用其功能进行有效的实体审计。理解@AuditedSpring Boot中的@Audited注解用于审计实体,提供对数据随时间变化的详细记录。这在需要跟踪修改、用户操作或合规要求的情况下非常有价值。实现步骤1. 依赖项:要包含@Audited,需要在项目中添加spring-data-envers依赖。确保你的pom.xml或build.gradle反映这一添加。<!-- Maven Dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency>spring-boot-starter-data-jpa依赖包含了使用Spring Data JPA进行数据访问所需的组件。然而,如果你特别想在Spring Boot中使用@Audited注解启用实体审计,还需要包含hibernate-envers依赖。该依赖添加了对Hibernate Envers的支持,这是负责实体版本控制和审计的工具。2. 实体配置:将@Audited注解应用于你想要审计的实体类。import org.hibernate.envers.Audited; @Entity @Audited public class YourEntity { // 你的实体字段和方法 }3. application.yml配置:确保你的application.yml或application.properties包含Hibernate Envers所需的配置。spring: data: jpa: repositories: enabled: true auditing: enabled: true4. 审计表字段:Hibernate Envers生成的审计表通常包括REV(修订号)、REVTYPE(修订类型)、AUDIT_TIMESTAMP(审计时间戳)等字段。这些字段共同存储对审计实体的历史更改。Spring Boot会自动创建审计表(例如,‘YourEntity_AUD’)以存储元数据。探索审计表中的字段:– REV:修订号(递增)– REVTYPE:修订类型(插入、更新、删除)– AUDITEDFIELD:审计字段值– MODIFIEDBY:进行更改的用户– MODIFIEDDATE:修改日期和时间5. 检索审计数据:使用Spring Data JPA仓库查询审计历史。import org.springframework.data.repository.history.RevisionRepository; import org.springframework.data.history.Revision; import java.util.List; public interface YourEntityAuditRepository extends RevisionRepository<YourEntity, Long, Integer> { List<Revision<Integer, YourEntity>> findRevisionsById(Long entityId); } 在这个例子中:– YourEntityAuditRepository扩展了RevisionRepository,这是一个处理修订的Spring Data JPA接口。– findRevisionsById方法允许你检索具有指定ID的实体的所有修订。然后,你可以在服务或控制器中使用此仓库查询审计历史:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class AuditService { private final YourEntityAuditRepository entityAuditRepository; @Autowired public AuditService(YourEntityAuditRepository entityAuditRepository) { this.entityAuditRepository = entityAuditRepository; } public List<Revision<Integer, YourEntity>> getEntityRevisions(Long entityId) { return entityAuditRepository.findRevisionsById(entityId); } }另一个例子使用Hibernate Envers查询具有给定ID的特定实体的审计历史List<YourEntity_AUD> revisions = auditReader.findRevisions(YourEntity.class, entityld);– auditReader:一个AuditReader实例,由Hibernate Envers提供。它允许你与实体的审计历史进行交互。– findRevisions:Hibernate Envers提供的方法,用于检索具有指定ID的给定实体的所有修订。– YourEntity.class:你想要检索审计历史的实体类。– entityId:你想要获取修订的实体的特定ID。– List<YourEntity_AUD>:结果是一个审计实体(YourEntity_AUD)的列表,列表中的每个条目代表实体的一个修订。在Hibernate Envers中,当你为一个实体启用审计时,它会生成一个带有“_AUD”后缀的相应审计实体(默认情况下)。这个审计实体会跟踪原始实体随时间变化的所有更改。因此,这行代码本质上是在查询具有给定ID的实体的所有修订的审计历史,并将结果存储在一个审计实体列表中。然后,可以使用此列表分析或显示实体在不同修订中的更改。转载自https://www.cnblogs.com/didispace/p/18322448
-
前言在Spring框架中,@PostConstruct注解用于在Bean初始化完成后立即执行某些方法。这个注解的作用是保证在依赖注入完成后,执行一些初始化工作。诞生背景@PostConstruct注解的诞生是为了提供一种标准化的、简单的方法来进行对象初始化工作。1. 简化初始化逻辑在传统的Java开发中,进行对象初始化通常需要在构造函数中完成。然而,构造函数的局限性使得一些初始化操作变得复杂,例如:构造函数不能依赖于容器注入的资源,因为这些资源在对象实例化时可能还没有准备好。复杂的初始化逻辑会导致构造函数变得冗长且难以维护。@Component public class MyService { private final SomeDependency dependency; public MyService(SomeDependency dependency) { this.dependency = dependency; // 这里不能进行复杂的初始化逻辑,因为依赖可能还没有完全准备好 } }@PostConstruct提供了一种更清晰的方式来处理初始化逻辑。依赖注入完成后,容器会自动调用标注了 @PostConstruct的方法,使得开发者可以在这时安全地访问所有依赖资源。简化逻辑代码示例import javax.annotation.PostConstruct; import org.springframework.stereotype.Component; @Component public class MyService { private final SomeDependency dependency; public MyService(SomeDependency dependency) { this.dependency = dependency; // 这里不能进行复杂的初始化逻辑,因为依赖可能还没有完全准备好 } @PostConstruct public void init() { // 在这里进行初始化逻辑 // 这时依赖已经完全准备好,可以安全地使用 dependency.setup(); System.out.println("MyService 初始化完成"); } // 其他业务方法 public void performTask() { System.out.println("执行任务"); } }2. 标准化的生命周期管理在Java EE和Spring这样的依赖注入框架中,Bean的生命周期管理是一个重要的特性。@PostConstruct和@PreDestroy注解是Java EE(JSR-250)标准的一部分,为生命周期管理提供了标准化的解决方案。这使得开发者可以在不同的框架中使用相同的注解,而不用依赖于特定框架的API。3. 提高代码的可读性和可维护性@PostConstruct注解可以将初始化逻辑从构造函数中分离出来,使代码更具可读性和可维护性。开发者可以更清晰地看到哪些方法是用于初始化的,哪些方法是用于业务逻辑的。这种分离还可以使单元测试更加容易,因为初始化逻辑可以被单独测试。4. 与依赖注入框架的结合在依赖注入框架(如Spring)中,Bean的依赖关系是由容器管理的。在Bean实例化和依赖注入完成之后,框架会自动调用 @PostConstruct方法。这种机制确保了所有依赖都已准备就绪,开发者可以安全地在 @PostConstruct方法中进行进一步的初始化操作。生命周期的详细介绍Bean实例化:Spring容器根据配置文件或注解扫描创建Bean实例。依赖注入:Spring容器进行依赖注入,将所需的依赖对象注入到Bean中。调用@PostConstruct方法:依赖注入完成后,Spring容器会调用标注了@PostConstruct的方法。这个方法通常用于执行初始化操作,比如配置资源、进行检查或执行一些准备工作。Bean可用:在调用了@PostConstruct方法后,Bean被标记为可用,并准备好接受请求或执行其预定的功能。Bean销毁(可选,使用@PreDestroy):当Spring容器关闭时,可以通过@PreDestroy注解来指定一个方法,在Bean被销毁之前执行清理工作。生命周期的代码示例import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.stereotype.Component; @Component public class MyBean { // 构造方法 public MyBean() { System.out.println("MyBean实例化"); } // @PostConstruct注解的方法 @PostConstruct public void init() { System.out.println("MyBean初始化 - @PostConstruct方法调用"); // 这里可以执行一些初始化操作 } // @PreDestroy注解的方法 @PreDestroy public void cleanup() { System.out.println("MyBean销毁之前 - @PreDestroy方法调用"); // 这里可以执行一些清理操作 } // 其他业务方法 public void performTask() { System.out.println("MyBean执行任务"); } }关键点总结依赖注入后执行:@PostConstruct方法在Bean的依赖注入完成之后执行,确保所有依赖都已经准备就绪。只执行一次:@PostConstruct方法在Bean的整个生命周期中只会执行一次。用于初始化逻辑:适合用来执行一些初始化逻辑,比如检查配置、初始化资源、启动辅助线程等。与Spring结合:@PostConstruct是JavaEE规范中的一部分,但在Spring中也得到了广泛应用,主要用于简化Bean的初始化工作。注意事项只适用于单个方法:一个类中只能有一个 @PostConstruct方法。方法签名:@PostConstruct方法不能有任何参数,也不能有返回值。异常处理:@PostConstruct方法如果抛出异常,会阻止Bean的创建,Spring容器会在启动时抛出异常。转载自https://www.cnblogs.com/hyg0513/p/18314560
-
扑克算牌公式及Java和C++软件制作要实现一个扑克的算牌公式,并开发相应的Java和C++程序,首先需要定义一些基本的步骤和概念。具体来说,我们需要:定义扑克数据结构:表示扑克牌和其属性。历史数据导入:从外部文件或数据库中导入历史数据。概率计算:基于历史数据计算剩余牌的出现次数和概率。界面及交互:提供用户接口来发牌并显示结果。1. 数据结构定义我们用简单的数据结构来表示扑克牌。在Java和C++中,可以分别使用类和结构体来实现。Java代码public class Card { public enum Suit {HEARTS, DIAMONDS, CLUBS, SPADES} private Suit suit; private int value; public Card(Suit suit, int value) { this.suit = suit; this.value = value; } public Suit getSuit() { return suit; } public int getValue() { return value; } @Override public String toString() { return suit.toString() + value; } }C++代码#include <string> #include <iostream> struct Card { enum Suit { HEARTS, DIAMONDS, CLUBS, SPADES } suit; int value; std::string toString() const { static const char* suitNames[] = {"Hearts", "Diamonds", "Clubs", "Spades"}; return suitNames[suit] + std::to_string(value); } };2. 历史数据导入假设历史数据存储在一个CSV文件中,我们可以编写函数来读取这些数据。Java代码import java.io.*; import java.util.*; public class HistoryData { public static List<List<Card>> loadHistory(String filename) throws IOException { List<List<Card>> history = new ArrayList<>(); BufferedReader br = new BufferedReader(new FileReader(filename)); String line; while ((line = br.readLine()) != null) { String[] parts = line.split(","); List<Card> hand = new ArrayList<>(); for (String part : parts) { String[] cardParts = part.split(" "); Card.Suit suit = Card.Suit.valueOf(cardParts[0].toUpperCase()); int value = Integer.parseInt(cardParts[1]); hand.add(new Card(suit, value)); } history.add(hand); } br.close(); return history; } }C++代码#include <vector> #include <fstream> #include <sstream> #include <iostream> std::vector<std::vector<Card>> loadHistory(const std::string& filename) { std::vector<std::vector<Card>> history; std::ifstream file(filename); std::string line; while (std::getline(file, line)) { std::istringstream ss(line); std::vector<Card> hand; std::string part; while (std::getline(ss, part, ',')) { std::istringstream cardStream(part); std::string suitStr; int value; cardStream >> suitStr >> value; Card::Suit suit; if (suitStr == "Hearts") suit = Card::HEARTS; else if (suitStr == "Diamonds") suit = Card::DIAMONDS; else if (suitStr == "Clubs") suit = Card::CLUBS; else if (suitStr == "Spades") suit = Card::SPADES; hand.push_back({suit, value}); } history.push_back(hand); } return history; }3. 概率计算基于导入的历史数据进行概率计算。Java代码import java.util.HashMap; import java.util.List; import java.util.Map; public class ProbabilityCalculator { public static Map<Card, Double> calculateProbabilities(List<List<Card>> history, List<Card> givenCards) { Map<Card, Integer> counts = new HashMap<>(); for (List<Card> hand : history) { boolean match = true; for (Card card : givenCards) { if (!hand.contains(card)) { match = false; break; } } if (match) { for (Card card : hand) { counts.put(card, counts.getOrDefault(card, 0) + 1); } } } int totalHands = history.size(); Map<Card, Double> probabilities = new HashMap<>(); for (Map.Entry<Card, Integer> entry : counts.entrySet()) { probabilities.put(entry.getKey(), entry.getValue() / (double) totalHands); } return probabilities; } }C++代码#include <unordered_map> #include <vector> std::unordered_map<Card, double, CardHash> calculateProbabilities( const std::vector<std::vector<Card>>& history, const std::vector<Card>& givenCards) { std::unordered_map<Card, int, CardHash> counts; int totalHands = 0; for (const auto& hand : history) { bool match = true; for (const auto& card : givenCards) { if (std::find(hand.begin(), hand.end(), card) == hand.end()) { match = false; break; } } if (match) { totalHands++; for (const auto& card : hand) { counts[card]++; } } } std::unordered_map<Card, double, CardHash> probabilities; for (const auto& entry : counts) { probabilities[entry.first] = entry.second / static_cast<double>(totalHands); } return probabilities; }4. 主程序及交互将所有部分整合到主程序中,并提供交互界面。Java代码import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Scanner; public class Main { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.println("Enter the path to the historical data file:"); String filepath = scanner.nextLine(); List<List<Card>> history = HistoryData.loadHistory(filepath); System.out.println("Enter the three cards (e.g., 'HEARTS 2', 'CLUBS 5', 'HEARTS 10'):"); List<Card> givenCards = new ArrayList<>(); for (int i = 0; i < 3; i++) { String input = scanner.nextLine(); String[] parts = input.split(" "); Card.Suit suit = Card.Suit.valueOf(parts[0].toUpperCase()); int value = Integer.parseInt(parts[1]); givenCards.add(new Card(suit, value)); } Map<Card, Double> probabilities = ProbabilityCalculator.calculateProbabilities(history, givenCards); System.out.println("Probabilities of remaining cards:"); for (Map.Entry<Card, Double> entry : probabilities.entrySet()) { System.out.printf("%s: %.2f%%\n", entry.getKey(), entry.getValue() * 100); } } }C++代码#include <iostream> #include <vector> #include <unordered_map> int main() { std::cout << "Enter the path to the historical data file:" << std::endl; std::string filepath; std::cin >> filepath; auto history = loadHistory(filepath); std::cout << "Enter the three cards (e.g., 'HEARTS 2', 'CLUBS 5', 'HEARTS 10'):" << std::endl; std::vector<Card> givenCards; for (int i = 0; i < 3; ++i) { std::string suitStr; int value; std::cin >> suitStr >> value; Card::Suit suit; if (suitStr == "Hearts") suit = Card::HEARTS; else if (suitStr == "Diamonds") suit = Card::DIAMONDS; else if (suitStr == "Clubs") suit = Card::CLUBS; else if (suitStr == "Spades") suit = Card::SPADES; givenCards.push_back({suit, value}); } auto probabilities = calculateProbabilities(history, givenCards); std::cout << "Probabilities of remaining cards:" << std::endl; for (const auto& entry : probabilities) { std::cout << entry.first.toString() << ": " << entry.second * 100 << "%" << std::endl; } return 0; }总结通过上述步骤,我们实现了一个扑克算牌工具,可以根据给定的三张牌计算后续牌的出现概率。该工具使用Java和C++分别实现了数据结构、历史数据导入、概率计算以及用户交互。
-
前言 快速排序算法可以分为两部分来看: 第一部分:将枢轴元素移动到最终位置 第二部分:分别处理枢轴元素左右两边的元素 tips:上面的第一、二步是不断递归的过程。读者可以去某站看一下王道的数据结构课 建议:1.学习算法最重要的是理解算法的每一步,而不是记住算法。 2.建议读者学习算法的时候,自己手动一步一步地运行算法。 1. 快速排序简介 快速排序是一种分治法(Divide and Conquer)的排序算法,由英国计算机科学家Tony Hoare于1960年提出。其基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有元素均比另一部分的元素小,然后分别对这两部分继续进行排序,最终达到整个序列有序的效果。 2. 快速排序的基本原理 快速排序的基本原理可以总结为以下三个步骤: 2.1 选择基准元素 从待排序的数组中选择一个元素作为基准元素。选择基准元素的方式有多种,常见的方法包括选择第一个元素、最后一个元素或者随机选择一个元素。 2.2 分割操作 将数组中比基准元素小的元素移到基准元素的左边,比基准元素大的元素移到右边。这个过程称为分割操作。 2.3 递归排序 递归地对基准元素左右两侧的子数组进行快速排序。 3. Java中的快速排序实现 下面是一个简单的Java实现快速排序的例子: public class QuickSort { public static void quickSort(int[] arr, int low, int high) { if (low < high) { // 找到划分点 int pivotIndex = partition(arr, low, high); // 递归地对左右两部分进行快速排序 quickSort(arr, low, pivotIndex - 1); quickSort(arr, pivotIndex + 1, high); } } private static int partition(int[] arr, int low, int high) { // 选择最后一个元素作为基准 int pivot = arr[high]; int i = (low - 1); // 小于基准值的索引 for (int j = low; j < high; j++) { // 当前元素小于或等于基准值时 if (arr[j] <= pivot) { i++; // 交换元素,使得小于基准值的元素移到左边 int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 把基准值放到正确的位置上(即i+1) int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; } public static void main(String[] args) { int[] arrayToSort = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6}; int n = arrayToSort.length; quickSort(arrayToSort, 0, n - 1); System.out.println("Sorted array: "); for (int num : arrayToSort) { System.out.print(num + " "); } } } 在上述代码中: quickSort 方法是用于递归调用的主要方法,它接受数组、以及待排序区间的起始和结束索引。 partition 方法负责执行分区操作,返回基准元素最终应该所在的位置。 在 partition 方法内部,使用了一个指针 i 来跟踪小于基准值的所有元素的边界,并通过遍历整个区间来进行元素的移动和交换,最后确保基准元素处于正确位置,并返回其索引。 通过这样的方式,快速排序能够以平均时间复杂度为 的效率完成对数据的排序。 4.时空复杂度 4.1 时间复杂度: 最好情况(平均情况): 当每次划分都能将数组大致均等地分为两部分时,快速排序的时间复杂度为 ,其中n是待排序数组中的元素个数。这是由于每次递归调用都会减少问题规模大约一半,并且需要层递归。 最坏情况: 在最坏情况下,即输入数组已经有序或接近有序时,每次划分只能将数组划分为一个元素和剩余元素两部分,这会导致递归树退化为线性结构,时间复杂度上升到 。不过,通过随机化选择基准元素或采用三数取中等优化方法,可以在实际应用中避免这种情况的发生,使得快速排序在实践中通常能保持接近其平均时间复杂度。 4.2 空间复杂度: 原地排序: 快速排序在理想情况下只需要常数级别的额外空间,即空间复杂度为 O(1)。这是因为交换操作可以直接在原数组上进行,不需要额外存储空间。 递归栈空间: 然而,在实际实现中,快速排序是递归实现的,因此递归调用会占用一定的栈空间。在最坏的情况下,递归深度可达 n 层,所以空间复杂度可能达到 。然而,在平均情况下,由于快速排序的平衡特性,递归树的高度约为,故空间复杂度通常是。 4.3 总结: 总结来说,快速排序在理想情况下具有非常高的效率,尤其在处理大型数据集时表现出色。但要注意的是,对于小规模数据或者近乎有序的数据,其他排序算法(如插入排序)可能会更优。同时,为了保证算法性能稳定,通常会对基准元素的选择进行优化以减小最坏情况发生的概率。 5.优缺点 5.1 优点: 高效性: 在平均情况下,快速排序的时间复杂度为,这使得它在处理大规模数据时表现出极高的效率,并且是实践中最快的通用内部排序算法之一。 原地排序(In-place): 快速排序不需要额外的存储空间来保存临时数组,只需要一个很小的栈空间用于递归调用,所以空间复杂度在理想情况下可以达到。 广泛应用: 由于其高效的性能和灵活的实现,快速排序被广泛应用于实际系统中的各种排序场景,尤其是在内存受限或需要实时排序的情况下。 适应性强: 对于不同的输入数据分布有较好的适应性,尤其是当采用随机化版本选择基准元素时,能有效地避免最坏情况的发生。 易于理解与实现: 基本思想简单直观,通过分治策略将大问题划分为小问题,便于理解和编程实现。 5.2 缺点: 最坏情况下的性能: 当待排序的数据已经部分有序或者完全有序时,快速排序可能退化成的时间复杂度。不过,可以通过优化基准元素的选择(例如三数取中、随机化选取等方法)来减少这种情况发生的概率。 不稳定排序: 快速排序不是稳定的排序算法,即相等元素的相对顺序可能会在排序过程中改变。 递归深度: 由于使用了递归,如果数据规模很大且递归树不平衡时,可能会导致栈溢出的问题。虽然通过尾递归优化或其他非递归实现方式可以缓解这个问题,但这也增加了实现的复杂性。 边界条件处理: 对于非常小的数据集(如小于一定阈值),快速排序可能不如插入排序等其他简单的排序算法高效,因此通常会结合其他算法进行混合排序以提升整体性能。 6.现实中的应用 快速排序算法在现实中的应用非常广泛,因其高效性、通用性和易于实现的特点,被大量用于各种软件和硬件系统中。以下是一些典型的应用场景: 数据处理与分析: 在大数据处理、数据分析以及数据库管理系统中,快速排序是内部排序操作的核心算法之一,尤其适用于大规模数据集的排序,比如SQL查询语句执行过程中的ORDER BY子句。 编程语言标准库: 许多现代编程语言的标准库或内置函数都包含了快速排序算法,例如Java的Arrays类、C++的std::sort函数等,为开发者提供了现成的排序工具。 操作系统内核: 操作系统内核在处理内存管理、文件系统排序目录结构、进程调度时可能使用快速排序或其他优化过的排序算法来提高效率。 机器学习与人工智能: 在机器学习领域,快速排序可应用于特征选择、模型训练前的数据预处理阶段,例如对输入样本进行排序以构建更高效的索引结构。 编译器优化: 编译器在符号表管理和代码生成阶段,可能会用到快速排序对变量名、函数名等进行排序,以加速查找和比较操作。 科学计算与工程应用: 在数值计算、信号处理、图像处理等领域,快速排序可用于数组和矩阵元素的排序,尤其是在处理稀疏矩阵和大型向量时。 嵌入式系统与实时系统: 尽管快速排序在最坏情况下的性能不理想,但在许多实际应用中通过随机化选取基准值等方法可以有效避免性能退化,因此也被用于嵌入式系统或实时系统的数据排序。 竞赛编程与算法研究: 在ACM国际大学生程序设计竞赛(ACM-ICPC)以及其他算法竞赛中,快速排序作为基础排序算法,经常出现在解题方案中。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_56154577/article/details/136596325
-
应用介绍 RustDesk 是一款可以平替 TeamViewer 的开源软件,旨在提供安全便捷的自建方案。 描述 跨平台支持:RustDesk可以在Windows、Linux、MacOS以及Android等多个平台上使用,为用户提供了极大的便利,无需担心设备兼容性问题。 安全性:它采用了安全的加密传输技术,确保用户数据的安全。在数据传输过程中,RustDesk会对数据进行加密,以防止数据泄露或被篡改。 高性能和低延迟:RustDesk的高性能和低延迟特性使得用户在进行远程操作时几乎感觉不到延迟,大大提高了用户体验。 多功能性:RustDesk支持多屏幕显示,对于需要同时操作多个屏幕的用户来说非常实用。此外,它还支持文件传输、剪贴板共享以及多种连接方式,包括基于IP的连接和基于域名的连接,以满足不同用户的需求。 易用性:相较于其他远程工具,RustDesk无需繁琐配置,用户只需在受控机上安装RustDesk并获取其ID和密码,然后在主控机上输入这些信息,即可快速建立连接。这种简洁的操作方式使得用户无需专业知识即可轻松上手。 灵活性:用户可以选择使用RustDesk的官方服务器或自建服务器,同时,如果用户有自己的云服务器且服务器带宽足够,那么使用RustDesk的体验将会更加流畅。 特性: 随时随地访问任何设备 支持 Windows、macOS、Linux、iOS、Android、Web 等多个平台。 支持 VP8 / VP9 / AV1 软件编解码器和 H264 / H265 硬件编解码器。 完全掌控数据,轻松自建。 P2P 连接,端到端加密。 在 Windows上可以非管理员不安装运行,根据需要在本地或远程提升权限。 操作简单 前期准备 本文将通过Linux宝塔面板Docker部署RustDesk服务器 应用部署 创建应用目录,我的地址/www/server/rustdesk,为了保证后续操作成功,希望与我的目录设置统一。 创建docker-compose.yml配置文件,这里我已经帮大家写好了,直接复制修改即可 version: '3' networks: rustdesk-net: external: false services: hbbs: container_name: hbbs ports: - 21115:21115 - 21116:21116 # 自定义 hbbs 映射端口 - 21116:21116/udp # 自定义 hbbs 映射端口 image: rustdesk/rustdesk-server:latest # 注意这里要加:latest,防止docker镜像缓存未更新的问题 command: hbbs -r xxx.xxx.com:21117 -k _ # 填入个人域名或 IP + hbbr 暴露端口,这里填写你解析后的域名或服务器ip都行, -k _意为使用key进行认证 volumes: - /www/server/rustdesk:/root # 自定义挂载目录 networks: - rustdesk-net depends_on: - hbbr restart: unless-stopped deploy: resources: limits: memory: 64M hbbr: container_name: hbbr ports: - 21117:21117 # 自定义 hbbr 映射端口 image: rustdesk/rustdesk-server:latest #注意,这里同样需要拉取最新镜像 command: hbbr -k _ #这里的key也是需要key认证的话则要加 volumes: - /www/server/rustdesk:/root # 自定义挂载目录 networks: - rustdesk-net restart: unless-stopped deploy: resources: limits: memory: 64M 注意: hbbr 与 hbbs 的挂载目录必须为同一个,否则后面链接会提示对方已挂断提示 如果你想所有人都可以通过你的服务器来进行链接rustdesk,则需要删掉配置文件中 -k _ 配置文件中xxx.xxx.com需要替换为解析到目标服务器的域名或目标服务器公网ip地址 设置服务器安全组开放端口规则 注意: 将自己服务器与上方配置文件中有关所有端口全部放开21115,21116/TCP,21116/UDP,21117 开放宝塔面板中端口,同上 执行配置文件下载并启动容器 docker-compose up -d 1 查看启动容器运行中 docker ps -a 1 设备链接 下载rustdesk客户端并完成服务器对接 DownloadFile 下载完成进入软件,打开软件网络设置 注意: ID服务器为文件中配置的个人域名或ip。格式例:www.baidu.com or 127.0.0.1 如果配置文件中增加了上述提到的-k _配置,则需要找到文件挂载目录/www/server/rustdesk中的id_ed25519.pub文件,将内容复制即可。 如果没有配置则不需要进行配置 错误解决 如果完全按照以上操作步骤进行部署任然出现了错误请看这里👇 ID不存在 配置了key的情况下,如果两台电脑key不匹配则会提示此问题。将两边电脑key配置相同即可 链接被对方关闭 这个问题就是前面提到的,hbbr 和 hbbs的挂载目录必须相同。更新配置文件,删除旧容器,重新启动即可 结语 真的很推荐有自己服务器的自己去使用这个。真的比收费某些远程工具好用很多。也流畅很多。 如果部署出现其他问题请留言或私信我~ ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_45602658/article/details/138702681
上滑加载中
推荐直播
-
华为云码道-AI时代应用开发利器2026/03/18 周三 19:00-20:00
童得力,华为云开发者生态运营总监/姚圣伟,华为云HCDE开发者专家
本次直播由华为专家带你实战应用开发,看华为云码道(CodeArts)代码智能体如何在AI时代让你的创意应用快速落地。更有华为云HCDE开发者专家带你用码道玩转JiuwenClaw,让小艺成为你的AI助理。
回顾中 -
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中 -
码道新技能,AI 新生产力——从自动视频生成到开源项目解析2026/04/08 周三 19:00-21:00
童得力-华为云开发者生态运营总监/何文强-无人机企业AI提效负责人
本次华为云码道 Skill 实战活动,聚焦两大 AI 开发场景:通过实战教学,带你打造 AI 编程自动生成视频 Skill,并实现对 GitHub 热门开源项目的智能知识抽取,手把手掌握 Skill 开发全流程,用 AI 提升研发效率与内容生产力。
回顾中
热门标签