[scode type="share" size="simple"]在Java中，动态代理是一种机制，允许在运行时创建代理对象来代替原始对象。代理对象可以拦截并处理原始对象的方法调用，以实现额外的逻辑或行为。[/scode]动态代理主要依赖于Java的反射机制。通过动态代理，我们可以在不修改原始对象的情况下，对其方法进行增强或拦截。这对于实现横切关注点（cross-cutting concerns）非常有用，如日志记录、性能监控、事务管理等。在Java中，动态代理通常涉及两个主要角色：接口（Interface）：定义了原始对象和代理对象之间的公共方法。InvocationHandler接口：定义了代理对象的调用处理程序。它包含一个invoke()方法，该方法在代理对象上调用方法时被调用。在invoke()方法中，可以实现对原始方法的增强逻辑。使用动态代理时，我们需要以下步骤：定义接口：定义原始对象和代理对象之间的公共方法。创建InvocationHandler实现类：实现InvocationHandler接口，并在invoke()方法中编写对原始方法的增强逻辑。使用Proxy类创建代理对象：使用Proxy类的静态方法newProxyInstance()，传入ClassLoader、接口数组和InvocationHandler对象，来创建代理对象。调用代理对象的方法：通过代理对象调用方法时，将会触发InvocationHandler的invoke()方法，从而执行增强逻辑。动态代理在Java中被广泛应用于AOP（面向切面编程）等场景，它提供了一种灵活的方式来实现横切关注点的功能。下面是一个简单的案例来演示Java中的动态代理：假设我们有一个接口 Calculator，定义了一个计算器的基本功能，包括加法、减法、乘法和除法：public interface Calculator { int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); int divide(int a, int b); }现在，我们希望在每次调用这些方法时记录日志。我们可以使用动态代理来实现这个功能。首先，我们创建一个实现 InvocationHandler 接口的类 CalculatorInvocationHandler：import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class CalculatorInvocationHandler implements InvocationHandler { private Calculator target; public CalculatorInvocationHandler(Calculator target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("调用方法：" + method.getName()); Object result = method.invoke(target, args); System.out.println("方法调用结果：" + result); return result; } }然后，我们使用 Proxy 类的 newProxyInstance() 方法来创建代理对象：import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { Calculator calculator = new BasicCalculator(); CalculatorInvocationHandler invocationHandler = new CalculatorInvocationHandler(calculator); Calculator proxy = (Calculator) Proxy.newProxyInstance( calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(), invocationHandler); // 使用代理对象调用方法 int sum = proxy.add(2, 3); int difference = proxy.subtract(5, 2); int product = proxy.multiply(4, 6); int quotient = proxy.divide(10, 2); } }在上面的示例中，我们创建了一个原始对象 BasicCalculator，并将其传递给 CalculatorInvocationHandler。然后，通过 Proxy.newProxyInstance() 创建了一个代理对象 proxy，该代理对象会在调用方法时触发 invoke() 方法，在 invoke() 方法中打印日志并调用原始对象的方法。当我们运行上述代码时，会输出如下日志：调用方法：add 方法调用结果：5 调用方法：subtract 方法调用结果：3 调用方法：multiply 方法调用结果：24 调用方法：divide 方法调用结果：5可以看到，代理对象成功拦截了原始对象的方法调用，并在调用前后打印了日志。这个案例展示了如何使用动态代理来实现对原始对象方法的增强，其中代理对象负责拦截方法调用并执行额外的逻辑。
[scode type="share" size="simple"]摘要：CompletableFuture是Java中用于处理异步操作的强大工具，它提供了丰富的API方法和灵活的功能，可以简化并发编程的复杂性。本文将介绍CompletableFuture的API文档、用途、案例和详细说明，帮助读者深入理解和灵活运用CompletableFuture。[/scode]1. CompletableFuture的API文档CompletableFuture类提供了许多API方法来处理异步任务。以下是CompletableFuture类的一些常用API方法：创建CompletableFuture对象的静态方法：static CompletableFuture<Void> runAsync(Runnable runnable)static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)static CompletableFuture supplyAsync(Supplier supplier)static CompletableFuture supplyAsync(Supplier supplier, Executor executor)异步任务完成时的处理方法：CompletableFuture<T> thenApply(Function<? super T, ? extends U> fn)CompletableFuture<Void> thenAccept(Consumer<? super T> action)CompletableFuture<Void> thenRun(Runnable action)组合多个CompletableFuture对象的方法：CompletableFuture thenCompose(Function<? super T, ? extends CompletionStage> fn)CompletableFuture<Void> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn)异常处理方法：CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)CompletableFuture<T> handle(BiFunction<? super T, Throwable, ? extends U> fn)并行执行多个CompletableFuture对象的方法：static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)2. CompletableFuture的用途CompletableFuture在异步编程中有广泛的应用，它可以帮助开发人员处理异步操作的完成、组合和转换。下面是CompletableFuture的一些常见用途：异步任务的执行和结果获取：通过CompletableFuture的静态方法supplyAsync和runAsync，可以创建异步任务并获取其执行结果。异步任务完成时的处理：使用thenApply、thenAccept和thenRun等方法，可以在异步任务完成时对结果进行转换、消费或执行指定的操作。多个异步任务的组合：通过thenCompose和thenCombine等方法，可以将多个CompletableFuture对象进行组合，实现任务的串行或并行执行，并对结果进行处理。异常处理：使用exceptionally和handle等方法，可以对异步任务发生的异常进行处理，并返回默认值或进行其他操作。并行执行多个异步任务：通过allOf和anyOf等方法，可以并行执行多个CompletableFuture对象，并在它们全部完成或任意一个完成时进行相应的操作。3. CompletableFuture的案例和说明下面是几个使用CompletableFuture的案例和详细说明：3.1 异步任务的执行和结果获取import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // 创建CompletableFuture对象，执行异步任务并返回结果 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, CompletableFuture!"); // 获取异步任务的结果 String result = future.join(); System.out.println(result); } }说明：上述代码通过supplyAsync方法创建了一个CompletableFuture对象，执行异步任务并返回结果。使用join方法获取异步任务的结果。3.2 异步任务完成时的处理import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // 创建CompletableFuture对象，执行异步任务并返回结果 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, CompletableFuture!"); // 当异步任务完成时，对结果进行转换 CompletableFuture<Integer> transformedFuture = future.thenApply(s -> s.length()); // 获取转换后的结果 int result = transformedFuture.join(); System.out.println(result); } }说明：上述代码通过thenApply方法，在异步任务完成时对结果进行转换。使用join方法获取转换后的结果。3.3 组合多个CompletableFuture对象import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // 创建两个CompletableFuture对象，执行异步任务并返回结果 CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10); CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20); // 将两个CompletableFuture对象进行组合，计算它们的和 CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (x, y) -> x + y); // 获取组合后的结果 int result = combinedFuture.join(); System.out.println(result); } }说明：上述代码通过thenCombine方法，将两个CompletableFuture对象进行组合，计算它们的和。使用join方法获取组合后的结果。3.4 异常处理import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // 创建CompletableFuture对象，执行可能会抛出异常的异步任务 CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { // 模拟一个可能会抛出异常的操作 if (Math.random() < 0.5) { throw new RuntimeException("Oops, something went wrong!"); } return 42; }); // 当异步任务发生异常时，进行异常处理并返回默认值 CompletableFuture<Integer> handledFuture = future.exceptionally(ex -> { System.out.println("Exception occurred: " + ex.getMessage()); return -1; }); // 获取处理后的结果 int result = handledFuture.join(); System.out.println(result); } }说明：上述代码通过exceptionally方法，在异步任务发生异常时进行异常处理，并返回默认值。使用join方法获取处理后的结果。3.5 并行执行多个CompletableFuture对象import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // 创建多个CompletableFuture对象，执行异步任务并返回结果 CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10); CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20); CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> 30); // 当所有CompletableFuture对象都完成时，执行指定的操作 CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3); allFutures.thenRun(() -> System.out.println("All futures completed!")); } }说明：上述代码通过allOf方法，并行执行多个CompletableFuture对象，并在它们全部完成时执行指定的操作。结论CompletableFuture是Java中强大的异步编程工具，它提供了丰富的API方法和灵活的功能，可以简化并发编程的复杂性。通过使用CompletableFuture，开发人员可以更方便地处理异步任务的完成、组合和转换。本文介绍了CompletableFuture的API文档、用途、案例和详细说明，希望能帮助读者深入理解和灵活运用CompletableFuture。
【开发技巧】Java根据详细地址获取对应经纬度
[scode type="blue" size="simple"]Q：稳不稳定？A：稳不稳定我不知道，不过我永远相信gov.cn后缀域名网站的稳定性！[/scode]获取密钥去天地图注册开发者账号：http://lbs.tianditu.gov.cn/我测试在代码中新建的项目需要选择服务端新建之后复制密钥在Java中的代码首先导入Maven坐标<dependency> <groupId>org.snmp4j</groupId> <artifactId>snmp4j</artifactId> <version>2.8.18</version> </dependency>需要用到的工具类import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import java.io.*; import java.net.URI; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @Slf4j public class HttpClientUtil { /** * 封装HTTP POST方法 * * @param * @param * @return * @throws ClientProtocolException * @throws IOException */ public static String post(String url, Map<String, String> paramMap) throws ClientProtocolException, IOException { HttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url); //设置请求和传输超时时间 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); httpPost.setConfig(requestConfig); List<NameValuePair> formParams = setHttpParams(paramMap); UrlEncodedFormEntity param = new UrlEncodedFormEntity(formParams, "UTF-8"); httpPost.setEntity(param); HttpResponse response = httpClient.execute(httpPost); log.info("************{}", response); String httpEntityContent = getHttpEntityContent(response); log.info("************{}", httpEntityContent); httpPost.abort(); log.info("************{}", httpEntityContent); return httpEntityContent; } /** * 封装HTTP POST方法 * * @param * @param （如JSON串） * @return * @throws ClientProtocolException * @throws IOException */ public static String post(String url, String data) throws ClientProtocolException, IOException { HttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url); //设置请求和传输超时时间 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); httpPost.setConfig(requestConfig); httpPost.setHeader("Content-Type", "text/json; charset=utf-8"); httpPost.setEntity(new StringEntity(URLEncoder.encode(data, "UTF-8"))); HttpResponse response = httpClient.execute(httpPost); String httpEntityContent = getHttpEntityContent(response); httpPost.abort(); return httpEntityContent; } /** * 封装HTTP GET方法 * * @param * @return * @throws ClientProtocolException * @throws IOException */ public static String get(String url) throws ClientProtocolException, IOException { HttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(); //设置请求和传输超时时间 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); httpGet.setConfig(requestConfig); httpGet.setURI(URI.create(url)); HttpResponse response = httpClient.execute(httpGet); String httpEntityContent = getHttpEntityContent(response); httpGet.abort(); return httpEntityContent; } /** * 封装HTTP GET方法 * * @param * @param * @return * @throws ClientProtocolException * @throws IOException */ public static String get(String url, Map<String, String> paramMap) throws ClientProtocolException, IOException { HttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(); //设置请求和传输超时时间 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); httpGet.setConfig(requestConfig); List<NameValuePair> formparams = setHttpParams(paramMap); String param = URLEncodedUtils.format(formparams, "UTF-8"); httpGet.setURI(URI.create(url + "?" + param)); HttpResponse response = httpClient.execute(httpGet); String httpEntityContent = getHttpEntityContent(response); httpGet.abort(); return httpEntityContent; } /** * 封装HTTP PUT方法 * * @param * @param * @return * @throws ClientProtocolException * @throws IOException */ public static String put(String url, Map<String, String> paramMap) throws ClientProtocolException, IOException { HttpClient httpClient = HttpClients.createDefault(); HttpPut httpPut = new HttpPut(url); //设置请求和传输超时时间 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); httpPut.setConfig(requestConfig); List<NameValuePair> formparams = setHttpParams(paramMap); UrlEncodedFormEntity param = new UrlEncodedFormEntity(formparams, "UTF-8"); httpPut.setEntity(param); HttpResponse response = httpClient.execute(httpPut); String httpEntityContent = getHttpEntityContent(response); httpPut.abort(); return httpEntityContent; } /** * 封装HTTP DELETE方法 * * @param * @return * @throws ClientProtocolException * @throws IOException */ public static String delete(String url) throws ClientProtocolException, IOException { HttpClient httpClient = HttpClients.createDefault(); HttpDelete httpDelete = new HttpDelete(); //设置请求和传输超时时间 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); httpDelete.setConfig(requestConfig); httpDelete.setURI(URI.create(url)); HttpResponse response = httpClient.execute(httpDelete); String httpEntityContent = getHttpEntityContent(response); httpDelete.abort(); return httpEntityContent; } /** * 封装HTTP DELETE方法 * * @param * @param * @return * @throws ClientProtocolException * @throws IOException */ public static String delete(String url, Map<String, String> paramMap) throws ClientProtocolException, IOException { HttpClient httpClient = HttpClients.createDefault(); HttpDelete httpDelete = new HttpDelete(); //设置请求和传输超时时间 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); httpDelete.setConfig(requestConfig); List<NameValuePair> formparams = setHttpParams(paramMap); String param = URLEncodedUtils.format(formparams, "UTF-8"); httpDelete.setURI(URI.create(url + "?" + param)); HttpResponse response = httpClient.execute(httpDelete); String httpEntityContent = getHttpEntityContent(response); httpDelete.abort(); return httpEntityContent; } /** * 设置请求参数 * * @param * @return */ private static List<NameValuePair> setHttpParams(Map<String, String> paramMap) { List<NameValuePair> formparams = new ArrayList<NameValuePair>(); Set<Map.Entry<String, String>> set = paramMap.entrySet(); for (Map.Entry<String, String> entry : set) { formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } return formparams; } /** * 获得响应HTTP实体内容 * * @param response * @return * @throws IOException * @throws UnsupportedEncodingException */ private static String getHttpEntityContent(HttpResponse response) throws IOException, UnsupportedEncodingException { HttpEntity entity = response.getEntity(); if (entity != null) { InputStream is = entity.getContent(); BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); String line = br.readLine(); StringBuilder sb = new StringBuilder(); while (line != null) { sb.append(line + "\n"); line = br.readLine(); } return sb.toString(); } return ""; } }主要的代码逻辑package com.klay.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.junit.platform.commons.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AddressUtil { private static final Logger log = LoggerFactory.getLogger(AddressUtil.class); public static String getLatAndLngByAddr(String addr) { try { String result = ""; addr = addr.replace(" ", "").replace("#", "").replace("中国", ""); String queryStr = "http://api.tianditu.gov.cn/geocoder?ds=%7B'keyWord':'" + addr + "'%7D&tk=" + "你获取的key"; String info = com.klay.utils.HttpClientUtil.get(queryStr); if (StringUtils.isNotBlank(info)) { JSONObject resultJson = JSON.parseObject(info); if (!"无结果".equals(resultJson.get("msg"))) { JSONObject locationObj = (JSONObject) resultJson.get("location"); //纬度 String lat = locationObj.get("lat") + ""; //经度 String lng = locationObj.get("lon") + ""; result = "经度：" + lng + "\n纬度：" + lat; } } return result; } catch (Exception e) { return "无"; } } public static void main(String[] args) { System.out.println(getLatAndLngByAddr("福田区梅林一村二期94栋15A室")); } }
【开发技巧】Mybatis官方推荐的批量插入方法
1.背景介绍在写crud时使用mybatis写批量插入用的foreach标签，报错 Packet for query is too large (9601829 > 4194304).当遇到比较多的数据时，就会出现这个错误，比如excel导入时。2.推荐方法在官网中：https://mybatis.org/mybatis-dynamic-sql/docs/insert.html查阅了资料发现：官网推荐的批量方法如下try(SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) { SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class); List<SimpleTableRecord> records = getRecordsToInsert(); // not shown BatchInsert<SimpleTableRecord> batchInsert = insert(records) .into(simpleTable) .map(id).toProperty("id") .map(firstName).toProperty("firstName") .map(lastName).toProperty("lastName") .map(birthDate).toProperty("birthDate") .map(employed).toProperty("employed") .map(occupation).toProperty("occupation") .build() .render(RenderingStrategies.MYBATIS3); batchInsert.insertStatements().forEach(mapper::insert); session.commit(); }对应的SpringBoot方式如下注意如果SimpleTableMapper方法继承了Mapper，不要写insert方法，不然冲突了，Mapper已经存在insert方法。@Mapper public interface SimpleTableMapper { void insert(SimpleTableRecord record); }@Service public class SimpleTableService { @Autowired private SimpleTableMapper mapper; @Autowired private SqlSessionFactory sqlSessionFactory; public void insertRecords(List<SimpleTableRecord> records) { try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) { for (SimpleTableRecord record : records) { mapper.insert(record); } session.commit(); } }3.优势使用批量插入相比于逐条插入的方式，具有以下优势：提高插入效率：批量插入可以减少与数据库的通信次数，一次性将多条记录发送给数据库，从而有效提高插入的效率。减少事务开销：在批量插入中，可以将多个插入操作合并为一个事务，减少了事务的开销。相比于逐条插入需要每条数据都开启和关闭事务，批量插入可以显著降低事务管理的开销。降低系统负载：由于批量插入减少了与数据库的交互次数，减轻了系统的负担和网络传输的压力，有助于提高整个系统的性能和响应速度。简化代码逻辑：通过批量插入，可以将多个插入操作合并到一起，简化了代码逻辑。不再需要手动遍历进行逐条插入，降低了代码的复杂性和维护成本。综上所述，批量插入在大规模数据插入时具有明显的性能优势，并且可以降低系统负载和简化代码逻辑。对于需要频繁插入大量数据的场景，使用批量插入是一个更加高效和可靠的选择。
1、概述注解：注解（Annotation），即元数据。是代码级别的说明。JDK1.5及以后版本引入了该特性，与类、接口、枚举属于同一级别。它可以声明在包、类、字段、方法、局部变量、方法参数上，以对这些元素进行说明。使用方式：@注解名称[scode type="blue" size="simple"]可以在编译时选择注解是否只存在于源码中，或者是保留在字节码(.class)文件中或者是出现在运行中。[/scode][scode type="green" size="simple"]注释是给程序员看的注解是给程序看的注解是元数据[/scode]2、作用编写文档：通过注解生成文档代码分析：通过注解对代码进行分析【反射中使用】编译检查：通过注解让编译器实现基本的编译检查【Override】3、JDK中预定义的注解@Override ：检测该方法是否是覆盖自父类(接口)的@Deprecated ：表示该部分内容已过时@SuppressWarnings ：压制警告一般传递参数all @SuppressWarnings("all")4、自定义注解4.1、格式元注解 public @interface 注解名称{ // 属性列表（抽象方法） }4.2、本质注解本质上是一个接口，该接口默认继承Annotation接口 java.lang.annotation.Annotation4.3、属性属性就是接口中的抽象方法。要求属性的返回值类型有下列取值基本数据类型String枚举注解以上类型的数组定义了属性，在使用时需要给属性赋值如果定义属性时，使用default关键字给属性默认初始化值，则使用注解时，可以不进行属性的赋值。如果只有一个属性需要赋值，并且属性的名称是value，则value可以省略，直接定义值即可。数组赋值时，值使用{}包裹。如果数组中只有一个值，则{}可以省略。4.4、元注解元注解是用于描述注解的注解。@Target ：描述注解能够作用的位置ElementType取值：TYPE：可以作用于类上METHOD：可以作用于方法上FIELD：可以作用于成员变量上@Retention ：描述注解被保留的阶段@Retention(RetentionPolicy.RUNTIME) ：当前被描述的注解，会保留到class字节码文件中，并被JVM读取到@Documented ：描述注解是否被抽取到api文档中@Inherited ：描述注解是否被子类继承5、在程序中使用注解的属性值获取注解定义的位置的对象 （Class，Method,Field）获取指定的注解 getAnnotation(Class)自动在内存中生成了一个该注解接口的子类实现对象调用注解中的抽象方法获取配置的属性值public class Person { public void eat() { System.out.println("eat..."); } }public class Student { public void study() { System.out.println("study..."); } }/** * 自定义注解 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Pro { String className(); String methodName(); }@Pro(className = "com.junlong.reflect.Person", methodName = "eat") public class ReflectPro { // 不改变该类的任何代码，就可以创建任意对象，并执行其任意方法 public static void main(String[] args) throws Exception { // 1. 解析注解 // 1.1 获取该类的字节码文件对象 Class<ReflectPro> reflectClass = ReflectPro.class; // 1.2 获取指定注解 Pro annotation = reflectClass.getAnnotation(Pro.class); // 1.3 调用注解中的抽象方法，获取属性 String className = annotation.className(); String methodName = annotation.methodName(); // 2. 反射 // 2.1 加载类的字节码文件进内存 Class<?> aClass = Class.forName(className); // 2.2 创建对象 Object o = aClass.newInstance(); // 2.2 获取方法 Method method = aClass.getMethod(methodName); // 2.3 执行方法 method.invoke(o); } }6、示例：使用注解的测试框架@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Check { }public class Calculator { @Check public void add() { String s = null; s.toString(); System.out.println("1 + 1 = " + (1 + 1)); } @Check public void sub() { System.out.println("2 - 1 = " + (2 - 1)); } public void mul() { System.out.println("5 * 2 = " + (5 * 2)); } @Check public void div() { int a = 3 / 0; System.out.println("3 / 2 = " + (3 / 2)); } }public class TestCheck { public static void main(String[] args) throws IOException { // 1. 获取Calculator类的字节码文件对象 Calculator calculator = new Calculator(); Class<? extends Calculator> aClass = calculator.getClass(); // 2. 获取所有的方法 Method[] methods = aClass.getMethods(); // 3. 判断方法是否有@Check注解，有注解则执行 int count = 0; // 异常次数 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("bug.txt", true)); // 记录日志 bufferedWriter.write("======================="); bufferedWriter.newLine(); bufferedWriter.write(new Date().toString()); bufferedWriter.newLine(); bufferedWriter.write("======================="); bufferedWriter.newLine(); bufferedWriter.newLine(); for (Method method : methods) { if (method.isAnnotationPresent(Check.class)) { try { method.invoke(calculator); } catch (Exception e) { // 4. 对于异常的方法，记录日志 count++; bufferedWriter.write(method.getName() + " 方法出异常了！"); bufferedWriter.newLine(); bufferedWriter.write("名称：" + e.getCause().getClass().getSimpleName()); bufferedWriter.newLine(); bufferedWriter.write("原因：" + e.getCause().getMessage()); bufferedWriter.newLine(); bufferedWriter.write("----------------------------"); bufferedWriter.newLine(); } } } bufferedWriter.write("本次测试共出现了 " + count + " 次异常！"); bufferedWriter.newLine(); bufferedWriter.newLine(); bufferedWriter.newLine(); bufferedWriter.flush(); bufferedWriter.close(); } }感谢小伙伴们的关注！你的点赞、评论、关注、收藏是对博主的最大鼓励！持续更新JavaSE学习笔记！欢迎订阅专栏！
Java的反射（reflection）机制是指在程序的运行状态中，可以构造任意一个类的对象，可以了解任意一个对象所属的类，可以了解任意一个类的成员变量和方法，可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。1、获取Class对象的方法Class.forName(“全类名”)：将字节码文件加载进内存，返回Class对象多用于配置文件，将类名定义在配置文件中。读取文件，加载类类名.class：通过类名的属性class获取多用于参数的传递对象.getClass()：getClass()方法在Object类中定义着多用于对象的获取字节码的方式【注意】同一个字节码文件(*.class)在一次程序运行过程中，只会被加载一次，不论通过哪一种方式获取的Class对象都是同一个。2、Class对象功能2.1、获取成员变量Field[] getFields() ：获取所有public修饰的成员变量Field getField(String name) ：获取指定名称的 public修饰的成员变量Field[] getDeclaredFields() ：获取所有的成员变量，不考虑修饰符Field getDeclaredField(String name) ：获取指定名称的成员变量，不考虑修饰符2.2、获取构造方法Constructor<?>[] getConstructors()Constructor<?> getConstructor(Class<?>... parameterTypes)Constructor<?>[] getDeclaredConstructors()Constructor<?> getDeclaredConstructor(Class<?>... parameterTypes)2.3、获取成员方法Method[] getMethods()Method getMethod(String name, Class<?>... parameterTypes)Method[] getDeclaredMethods()Method getDeclaredMethod(String name, Class<?>... parameterTypes)2.4、获取全类名String getName()3、Field：成员变量对象设置值void set(Object obj, Object value)获取值get(Object obj)忽略访问权限修饰符的安全检查setAccessible(true) ：暴力反射4、Constructor：构造方法对象创建对象T newInstance(Object... initargs)【注意】如果使用空参数构造方法创建对象，操作可以简化：使用Class对象的newInstance方法5、Method：方法对象执行方法Object invoke(Object obj, Object... args)获取方法名String getName()6、示例编写一个类（该类是一个框架），在不改变该类的任何代码的前提下，使用该类创建任意对象，并执行对象的任意方法。public class Person { public void eat() { System.out.println("eat..."); } }public class Student { public void study() { System.out.println("study..."); } }#pro.properties className=com.junlong.reflect.Student methodName=studypublic class Reflect { // 不改变该类的任何代码，就可以创建任意对象，并执行其任意方法 public static void main(String[] args) throws Exception { // 1. 加载配置文件 // 1.1 获取类加载器 ClassLoader classLoader = Reflect.class.getClassLoader(); // 1.2 获取配置文件 InputStream is = classLoader.getResourceAsStream("pro.properties"); // 1.3 创建Properties对象 Properties pro = new Properties(); pro.load(is); String className = pro.getProperty("className"); String methodName = pro.getProperty("methodName"); // 2. 获取class类对象 Class<?> aClass = Class.forName(className); // 2.1 创建对象 Object o = aClass.newInstance(); // 3. 执行方法 // 3.1 获取方法对象 Method method = aClass.getMethod(methodName); // 3.2 执行 method.invoke(o); } }感谢小伙伴们的关注！你的点赞、评论、关注、收藏是对博主的最大鼓励！持续更新JavaSE学习笔记！欢迎订阅专栏！
JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立，逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架（regression testing framework）。Junit测试是程序员测试，即所谓白盒测试，因为程序员知道被测试的软件如何（How）完成功能和完成什么样（What）的功能。Junit是一套框架，继承TestCase类，就可以用Junit进行自动测试了。1、测试分类黑盒测试：不需要写代码，给输入值，看程序是否能够输出期望的值。白盒测试：需要写代码的。关注程序具体的执行流程。2、Junit使用2.1、使用步骤定义一个测试类(测试用例)【建议】 1. 测试类名：被测试的类名Test 1. 包名：xxx.xxx.xx.test定义测试方法：可以独立运行【建议】 1. 方法名：test测试的方法名 1. 返回值：void 1. 参数列表：空参给方法加@Test注解导入junit依赖环境2.2、判定结果红色：失败绿色：成功一般使用断言操作来处理结果： Assert.assertEquals(期望的结果,运算的结果);2.3、补充@Before：修饰的方法会在测试方法之前被自动执行，一般用于资源申请。@After：饰的方法会在测试方法执行之后自动被执行，一般用于资源释放。2.4、示例public class Calculator { public int add(int a, int b) { return a + b; } public int sub(int a, int b) { // int c = 3 / 0; return a - b; } }public class CalculatorTest { @Before public void init() { System.out.println("init..."); } @After public void close() { System.out.println("close..."); } @Test public void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(1, 2); System.out.println(result); Assert.assertEquals(3, result); } @Test public void testSub() { Calculator calculator = new Calculator(); int result = calculator.sub(3, 5); System.out.println(result); Assert.assertEquals(-2, result); } }感谢小伙伴们的关注！你的点赞、评论、关注、收藏是对博主的最大鼓励！持续更新JavaSE学习笔记！欢迎订阅专栏！
1、函数式接口1.1、概念有且仅有一个抽象方法的接口。适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda，所以函数式接口就是可 以适用于Lambda使用的接口。从应用层面来讲，Java中的Lambda可以被当做是匿名内部类的“语法糖”，但是二者在原理上是不同的。1.2、格式只要确保接口中有且仅有一个抽象方法即可，接口中抽象方法的 public abstract 可以省略。修饰符 interface 接口名称 { public abstract 返回值类型 方法名称(可选参数信息); // 其他非抽象方法内容 }1.3、@FunctionalInterface注解该注 解可用于一个接口的定义上。一旦使用该注解来定义接口，编译器将会强制检查该接口是否确实有且仅有一个抽象方法，否则将会报错。【注意】即使不使用该注解，只要满足函数式接口的定义，这仍然是一个函数式接口，使用起来都一样。函数式接口一般用作方法的参数或返回值。2、函数式编程在兼顾面向对象特性的基础上，Java语言通过 Lambda表达式 与 方法引用 等，实现函数式编程。2.1、Lambda的延迟执行Lambda表达式延迟执行，提升性能。实际上使用内部类也可以达到同样的效果，只是将代码操作延迟到了另外一个对象当中通过调用方法 来完成。而是否调用其所在方法是在条件判断之后才执行的。2.2、使用Lambda作为参数和返回值Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数 式接口类型，那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数，其实就是使用函数式 接口作为方法参数。如果一个方法的返回值类型是一个函数式接口，那么就可以直接返回一个Lambda表达式。3、常用函数式接口主要在java.util.function包中提供。还有Runnbale接口，Conparator接口。3.1、Supplier接口java.util.function.Supplier：生产数据。抽象方法T get()：用来获取一个泛型参数指定类型的对 象数据。求数组元素的最大值public static void main(String[] args) { int[] arr = {3, 7, 45, 12, 90, 6}; int max = getMax(() -> { int maxNum = arr[0]; for (int value : arr) { if (value > maxNum) { maxNum = value; } } return maxNum; }); System.out.println("最大值是：" + max); } public static int getMax(Supplier<Integer> sup) { return sup.get(); }3.2、Consumer接口java.util.function.Consumer：消费一个数据， 其数据类型由泛型决定。抽象方法void accept(T t)：消费一个指定泛型的数据。默认方法adnThen() ：消费数据的时候，首先做一个操作， 然后再做一个操作，实现组合。格式化打印信息下面的字符串数组当中存有多条信息，请按照格式“ 姓名：XX。性别：XX。 ”的格式将信息打印出来。要求将打印姓 名的动作作为第一个 Consumer 接口的Lambda实例，将打印性别的动作作为第二个 Consumer 接口的Lambda实 例，将两个 Consumer 接口按照顺序“拼接”到一起。public static void main(String[] args) { String[] arr = {"张三,男", "李四,男", "王五,男", "小学妹,男"}; printInfo(info -> System.out.print("姓名：" + info.split(",")[0] + "，"), info -> System.out.println("性别：" + info.split(",")[1] + "。"), arr); } private static void printInfo(Consumer<String> one, Consumer<String> two, String[] arr) { for (String s : arr) { one.andThen(two).accept(s); } }3.3、Predicate接口java.util.function.Predicate：对某种类型的数据进行判断，返回boolean值。抽象方法boolean test(T t)：对指定类型数据进行判断的方法，符合条件返回true，不符合返回false。默认方法and() ：与or() ：或negate() ：非集合信息筛选数组当中有多条“姓名+性别”的信息如下，请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合 ArrayList 中，需要同时满足两个条件： 1. 必须为女生； 2. 姓名为4个字。public static void main(String[] args) { String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" }; List<String> list = filter(s -> "女".equals(s.split(",")[1]), s -> s.split(",")[0].length() == 4, array); System.out.println(list); } private static List<String> filter(Predicate<String> one, Predicate<String> two, String[] arr) { ArrayList<String> list = new ArrayList<>(); for (String s : arr) { if (one.and(two).test(s)) { list.add(s); } } return list; }3.4、Function接口java.util.function.Function<T, R>：根据一个类型的数据得到另一个类型的数据，前者称为前置条件， 后者称为后置条件。抽象方法R apply(T t) ：根据类型T的参数获取类型R的结果。默认方法andThen() ：用来进行组合操作。自定义函数模型拼接使用 Function 进行函数模型的拼接，按照顺序需要执行的多个函数操作为： String str = “小师妹,20”;将字符串截取数字年龄部分，得到字符串；将上一步的字符串转换成为int类型的数字；将上一步的int数字累加100，得到结果int数字。public static void main(String[] args) { String str = "小师妹,20"; int age = getAge(s -> s.split(",")[1], s -> Integer.parseInt(s), s -> s += 100, str); System.out.println(age); } private static int getAge(Function<String, String> one, Function<String, Integer> two, Function<Integer, Integer> three, String str) { return one.andThen(two).andThen(three).apply(str); }感谢小伙伴们的关注！你的点赞、评论、关注、收藏是对博主的最大鼓励！持续更新JavaSE学习笔记！欢迎订阅专栏！
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包，在接收端按照规定好的协议把包进行解析，从而提取出对应的信息，达到通信的目的。中间最主要的就是数据包的组装，数据包的过滤，数据包的捕获，数据包的分析，最后再做一些处理，代码、开发工具、数据库、服务器架设和网页设计这5部分都要接触。1、软件结构C/S结构 ：全称为Client/Server结构，是指客户端和服务器结构。常见程序有ＱＱ、迅雷等软件。B/S结构 ：全称为Browser/Server结构，是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。网络编程，就是在一定的协议下，实现两台计算机 的通信的程序。2、网络通信协议网络通信协议：：通信协议是对计算机必须遵守的规则，只有遵守这些规则，计算机之间才能进行通信。协议中对数据的传输格式、传输速率、传输步骤等做了 统一规定，通信双方必须同时遵守，最终完成数据交换。TCP/IP协议：传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol)，是 Internet最基本、最广泛的协议。它定义了计算机如何连入因特网，以及数据如何在它们之间传输的标准。它 的内部包含一系列的用于处理数据通信的协议，并采用了4层的分层模型，每一层都呼叫它的下一层所提供的 协议来完成自己的需求。2.1、通信协议分类（传输层）java.net：包中包含的类和接口，提供低层次的通信细节。可以直接使用这 些类和接口，来专注于网络程序开发，而不用考虑通信的细节。TCP：传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接 的通信协议，即传输数据之前， 在发送端和接收端建立逻辑连接，然后再传输数据，它提供了两台计算机之间可靠无差错的数据传输。TCP协议可 以保证传输数据的安全。应用十分广泛，例如下载文件、浏览网页等。三次握手：TCP协议中，在发送数据的准备阶段，客户端与服务器之间的三次交互，以保证连接的可靠。第一次握手，客户端向服务器端发出连接请求，等待服务器确认；第二次握手，服务器端向客户端回送一个响应，通知客户端收到了连接请求；第三次握手，客户端再次向服务器端发送确认信息，确认连接。UDP：用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接 的协议。传输数据时，不需 要建立连接，不管对方端服务是否启动，直接将数据、数据源和目的地都封装在数据包中，直接发送。每个 数据包的大小限制在64k以内。它是不可靠协议，因为无连接，所以传输速度快，但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等3、网络通信三要素通信协议：计算机网络通信必须遵守的规则。IP地址：指互联网协议地址（Internet Protocol Address），俗称IP。IP地址用来给一个网络中的计算机设 备做唯一的编号。端口号：用两个字节表示的整数，它的取值范围是0~65535。可以唯一标识设备中的进程（应用程序）。，0~ 1023之间的端口号用于一些知名的网 络服务和应用。利用 协议 + IP地址 + 端口号 三元组合，就可以标识网络中的进程了，那么进程间的通信就可以利用这个标识与其 它进程进行交互。4、TCP通信程序4.1、概述TCP通信能实现两台计算机之间的数据交互，通信的两端，要严格区分为客户端（Client）与服务端（Server）。两端通信时步骤：服务端程序，需要事先启动，等待客户端的连接。客户端主动连接服务器端，连接成功才能通信。服务端不可以主动连接客户端。在Java中，提供了两个类用于实现TCP通信程序：客户端： java.net.Socket 类表示。创建 Socket 对象，向服务端发出连接请求，服务端响应请求，两者建立连接开始通信。服务端： java.net.ServerSocket 类表示。创建 ServerSocket 对象，相当于开启一个服务，并等待客户端的连接。服务端accept() 接收客户端Socket对象，和客户端进行交互。4.2、Socket类java.net.Socket：该类实现客户端套接字，套接字指的是两台设备之间通讯的端点（包含IP和端口号的网络单位）。4.3、构造方法public Socket(String host, int port) ：创建套接字对象并将其连接到指定主机上的指定端口号。如果指 定的host是null ，则相当于指定地址为回送地址。回送地址(127.x.x.x) 是本机回送地址（Loopback Address），主要用于网络软件测试以及本 地机进程间通信，无论什么程序，一旦使用回送地址发送数据，立即返回，不进行任何网络传输。4.4、成员方法public InputStream getInputStream() ：返回此套接字的输入流。如果此Scoket具有相关联的通道，则生成的InputStream 的所有操作也关联该通道。关闭生成的InputStream也将关闭相关的Socket。public OutputStream getOutputStream() ：返回此套接字的输出流。如果此Scoket具有相关联的通道，则生成的OutputStream 的所有操作也关联该通道。关闭生成的OutputStream也将关闭相关的Socket。public void close() ：关闭此套接字。一旦一个socket被关闭，它不可再使用。关闭此socket也将关闭相关的InputStream和OutputStream。public void shutdownOutput() ：禁用此套接字的输出流。任何先前写出的数据将被发送，随后终止输出流。【注意】使用Socket对象获取流；创建Socket对象是，客户端和服务端进行3次握手连接。如果服务端未启动，则抛出异常。流的read()方法读取结束后会阻塞，需要调用Socket对象的shutdownOutput()方法终止流。4.5、ServerSocket类java.io.ServerSocket：这个类实现了服务器套接字，该对象等待通过网络的请求。4.6、构造方法public ServerSocket(int port) ：使用该构造方法在创建ServerSocket对象时，就可以将其绑定到一个指 定的端口号上，参数port就是端口号。4.7、成员方法public Socket accept() ：侦听并接受连接，返回一个新的Socket对象，用于和客户端实现通信。该方法 会一直阻塞直到建立连接。5、文件上传案例客户端public class FileUploadClient { public static void main(String[] args) throws IOException { System.out.println("客户端已启动！资源发送中..."); // 创建输入流，读取本地文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("aa.jpeg")); // 创建Socket对象 Socket socket = new Socket("127.0.0.1", 3000); // 创建输出流，写出数据到服务器端 BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] b = new byte[1024 * 8]; int len; while ((len = bis.read(b)) != -1) { bos.write(b, 0, len); bos.flush(); } // 终止输出流 socket.shutdownOutput(); System.out.println("发送完毕！"); // 解析回写 InputStream in = socket.getInputStream(); byte[] back = new byte[1024]; int read = in.read(back); System.out.println(new String(back, 0, read)); in.close(); // 释放资源 bos.close(); bis.close(); socket.close(); } }服务端public class FileUploadServer { public static void main(String[] args) throws IOException { System.out.println("服务端已启动！"); // 创建ServerSocket对象 ServerSocket serverSocket = new ServerSocket(3000); while (true) { // 接收服务端请求 Socket accept = serverSocket.accept(); System.out.println("文件接收中..."); new Thread(() -> { try { // 创建输入流，接收客户端数据 BufferedInputStream bis = new BufferedInputStream(accept.getInputStream()); // 创建输出流，将接收到的数据写出到硬盘 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\IMG\\" + System.currentTimeMillis() + ".jpeg")); byte[] b = new byte[1024 * 8]; int len; while ((len = bis.read(b)) != -1) { bos.write(b, 0, len); } // 信息回写 OutputStream out = accept.getOutputStream(); out.write("接收成功！！！".getBytes()); out.close(); // 释放资源 bos.close(); bis.close(); accept.close(); System.out.println("文件上传成功"); } catch (IOException e) { e.printStackTrace(); } }).start(); } } }6、模拟B\S服务器案例public class Server { public static void main(String[] args) throws IOException { // 创建ServerSocket对象 ServerSocket serverSocket = new ServerSocket(3000); while (true) { // 接收客户端Socket Socket socket = serverSocket.accept(); // 在多线程中处理客户端请求 new Thread(new Web(socket)).start(); } } private static class Web implements Runnable { private Socket socket; public Web(Socket socket) { this.socket = socket; } @Override public void run() { try { // 转换流读取请求的第一行 BufferedReader readerWeb = new BufferedReader(new InputStreamReader(socket.getInputStream())); String req = readerWeb.readLine(); // 获取请求路径 String path = req.split(" ")[1].substring(1); // 读取服务端文件 FileInputStream fis = new FileInputStream(path); // 向浏览器端写回数据 OutputStream out = socket.getOutputStream(); out.write("HTTP/1.1 200 OK\r\n".getBytes()); out.write("Content‐Type:text/html\r\n".getBytes()); out.write("\r\n".getBytes()); byte[] b = new byte[1024 * 3]; int len; while ((len = fis.read(b)) != -1) { out.write(b, 0, len); } // 释放资源 out.close(); fis.close(); readerWeb.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }感谢小伙伴们的关注！你的点赞、评论、关注、收藏是对博主的最大鼓励！持续更新JavaSE学习笔记！欢迎订阅专栏！
流是一种抽象概念，它代表了数据的无结构化传递。按照流的方式进行输入输出，数据被当成无结构的字节序列或字符序列。从流中取得数据的操作称为提取操作，而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说，IO流就是以流的方式进行输入输出。1、IO概述Java中I/O操作指使用 java.io 包下的类或者接口，进行输入、输出操作。输入即读取数据，输出即写 出数据。1.1、IO分类按数据流的方向：输入流 ：把数据从 其他设备 上读取到 内存 中的流。输出流 ：把数据从 内存 中写出到 其他设备 上的流。按数据的类型：字节流 ：以字节为单位，读写数据的流。字符流 ：以字符为单位，读写数据的流。IO的顶级父类 输入流输出流字节流字节输入流：InputStream字节输出流：OutoutStream字符流字符输入流：Reader字符输出流：Writer2、字节流字节流可以传输任意类型的文件数据。流在传递时底层传递的都是二进制数据。2.1、字节输出流OutputStreamjava.io.OutputStream：抽象类，表示字节输出流的所有类的父类，将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。public void close() ：关闭此输出流并释放与此流相关联的任何系统资源。public void flush() ：刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b) ：将 b.length字节从指定的字节数组写入此输出流。正数（0-127）：查询ASCII码表；负数：两个字节一组，查系统默认码表（gbk）。public void write(byte[] b, int off, int len) ：：从指定的字节数组写入 len字节，从偏移量 off开始输 出到此输出流。public abstract void write(int b) ：将指定的字节输出流。每次可以写出一个字节数据。【注意】虽然参数为int类型四个字节，但是只会保留一个字节的信息写出。当完成流的操作时，必须调用close()方法，释放系统资源。2.2、FileOutputStreamjava.io.FileOutputStream：类，是文件输出流，用于将数据写出到文件。构造方法public FileOutputStream(File file) ：创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name) ：创建文件输出流以指定的名称写入文件。【注意】创建一个流对象时，必须传入一个文件路径。该路径下，如果没有这个文件，会创建该文件。如果有这个文 件，会清空这个文件的数据。public FileOutputStream(File file, boolean append) ：创建文件输出流以写入由指定的 File对象表示的 文件。public FileOutputStream(String name, boolean append) ：创建文件输出流以指定的名称写入文件。【注意】这两个构造方法，参数中都需要传入一个boolean类型的值， true 表示追加数据， false 表示清空原有数据。写出换行回车符 \r 和换行符 \n回车符：回到一行的开头（return）。换行符：下一行（newline）。系统中的换行Windows系统里，每行结尾是 回车+换行 ，即 \r\nUnix系统里，每行结尾只有 换行 ，即 \nMac系统里，每行结尾是 回车 ，即 \r 。从 Mac OS X开始与Linux统一。2.3、字节输入流InputStreamjava.io.InputStream：抽象类是表示字节输入流的所有类的超类，可以读取字节信息到内存中。它定义了字节输入 流的基本共性功能方法。public void close() ：关闭此输入流并释放与此流相关联的任何系统资源。public abstract int read() ：从输入流读取数据的下一个字节。每次可以读取一个字节的数据，提升为int类型，读取到文件末尾，返回 -1。public int read(byte[] b) ：从输入流中读取一些字节数，并将它们存储到字节数组 b中 。每次读取b的长度（一般定义为1024的整数倍）个字节到数组中，返回读取到的有效字节个数，读 取到末尾时，返回 -1 。使用数组读取，每次读取多个字节，减少了系统间的IO操作次数，从而提高了读写的效率，建议开发中使 用。【注意】，当完成流的操作时，必须调用close()方法，释放系统资源。2.4、FileInputStreamjava.io.FileInputStream：类，是文件输入流，从文件中读取字节。构造方法public FileInputStream(File file) ：通过打开与实际文件的连接来创建一个 FileInputStream ，该文件由文件系 统中的 File对象 file命名。public FileInputStream(String name) ： 通过打开与实际文件的连接来创建一个 FileInputStream ，该文件由文件 系统中的路径名 name命名。2.5、图片复制案例public static void main(String[] args) throws IOException { // 1 创建流对象 // 1.1 指定数据源 FileInputStream fis = new FileInputStream("D:\\a.jpeg"); // 1.2 指定目的地 FileOutputStream fos = new FileOutputStream("aa.jpeg"); // 2 读取数据 // 2.1 定义数组 byte[] bytes = new byte[1024]; // 2.2 定义长度 int len; // 2.3 循环读取 while ((len = fis.read(bytes)) != -1) { // 写出数据，每次写有效长度个 fos.write(bytes, 0, len); } // 3 关闭资源 fos.close(); fis.close(); }3、字符流一个中文字符可能占用多个字节存储。所以Java提供一些字符流类，以字符为单位读写数据，专门用于处理文本文件。字符流，只能操作文本文件，不能操作图片，视频等非文本文件。单纯读或者写文本文件时 使用字符流 其他情况使用字节流。3.1、字符输入流Readerjava.io.Reader：抽象类是表示用于读取字符流的所有类的超类，可以读取字符信息到内存中。它定义了字符输入 流的基本共性功能方法。public void close() ：关闭此流并释放与此流相关联的任何系统资源。public int read() ：从输入流读取一个字符。每次可以读取一个字符的数据，提升为int类型，读取到文件末尾，返回 -1。public int read(char[] cbuf) ：从输入流中读取一些字符，并将它们存储到字符数组 cbuf中 。每次读取b的长度个字符到数组中，返回读取到的有效字符个数， 读取到末尾时，返回 -1。3.2、FileReaderjava.io.FileReader：类，是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。【注意】继承自InputStreamReader extends Reader。字符编码：字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表；idea中UTF-8。字节缓冲区：一个字节数组，用来临时存储字节数据。构造方法public FileReader(File file) ：创建一个新的 FileReader ，给定要读取的File对象。public FileReader(String name) ：创建一个新的 FileReader ，给定要读取的文件的名称。【注意】创建一个流对象时，必须传入一个文件路径。类似于FileInputStream 。3.3、字符输出流Writerjava.io.Writer：抽象类是表示用于写出字符流的所有类的超类，将指定的字符信息写出到目的地。它定义了字节 输出流的基本共性功能方法。public void close() ：先刷新缓冲区，然后通知系统释放资源。流对象不可以再被使用了。public void flush() ：刷新该流的缓冲区，流对象可以继续使用。public void write(int c) ：写入单个字符。public abstract void write(char[] cbuf) ：写入字符数组。public void write(char[] cbuf, int off, int len) ：写入字符数组的某一部分,off数组的开始索引,len 写的字符个数。public void write(String str) ：写入字符串。public void write(String str, int off, int len) ：写入字符串的某一部分,off字符串的开始索引,len写的字符个数。3.4、FileWriterjava.io.FileWriter：类，是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。【注意】集成自 OutputStreamWriter extends Writer构造方法public FileWriter(File file) ：创建一个新的 FileWriter，给定要读取的File对象。public FileWriter(String name) ：创建一个新的 FileWriter，给定要读取的文件的名称。【注意】创建一个流对象时，必须传入一个文件路径，类似于FileOutputStream。虽然参数为int类型四个字节，但是只会保留一个字符的信息写出。未调用close方法，数据只是保存到了缓冲区，并未写出到文件中。追加写和换行与FileOutoutStream类似。4、IO异常处理JDK1.7以前：在finally中释放资源try{...}catch(异常变量){...}finally{...}jdk1.7： try-with-resource 语句，确保了每个资源在语句结束时关闭。所谓的资源 （resource）是指在程序完成后，必须关闭的对象。try(创建流对象的语句，如果有多个语句，使用;隔开) { // 读写数据 } catch(异常变量) { // 异常处理 }JDK1.9： try-with-resource 语句增加了引入对象的方式。被引入的对象，同样可以自动关闭， 无需手动close。// 被final修饰的对象 final Resource resource1 = new Resource("resource1"); // 普通对象 Resource resource2 = new Resource("resource2"); // 引入方式：创建新的变量保存 try (Resource r1 = resource1; Resource r2 = resource2) { // 使用对象 }// 被final修饰的对象 final Resource resource1 = new Resource("resource1"); // 普通对象 Resource resource2 = new Resource("resource2"); // 引入方式：直接引入 try (resource1; resource2) { // 使用对象 }5、缓冲流缓冲流（高效流），对基本的流进行增强，增加缓冲区，提高效率，减少系统的IO次数。字节缓冲流：BufferedInputStream，BufferedOutputStream字符缓冲流：BufferedReader，BufferedWriter缓冲流的原理：在创建流对象时，创建一个内置的默认大小的缓冲区数组，通过缓冲区读写，减少系统IO 次数，从而提高读写的效率。关闭缓冲流时会自动关闭基本的流。注意输出流需要flush()。5.1、字节缓冲流字节缓冲输入流构造方法public BufferedInputStream(InputStream in) ：创建一个新的缓冲输入流。public BufferedInputStream(InputStream in, int size) ：创建一个新的缓冲输入流。并指定缓冲区大小。字节缓冲输出流构造方法public BufferedOutputStream(OutputStream out) ：创建一个新的缓冲输出流。public BufferedOutputStream(OutputStream out, int size) ：创建一个新的缓冲输出流。并指定缓冲区大 小。5.2、字符缓冲流字符缓冲输入流构造方法public BufferedReader(Reader in) ：创建一个新的缓冲输入流。public BufferedReader(Reader in, int size) ：创建一个新的缓冲输入流。并指定缓冲区大小。特有方法public String readLine() ：读一行文字。不包含终止符，末尾返回null。字符缓冲输出流构造方法public BufferedWriter(Writer out) ：创建一个新的缓冲输出流。public BufferedWriter(Writer out, int size) ：创建一个新的缓冲输出流。并指定缓冲区大小。特有方法public void newLine() ：写行分隔符，由系统属性定义符号。5.3、文本排序练习3.侍中、侍郎郭攸之、费祎、董允等，此皆良实，志虑忠纯，是以先帝简拔以遗陛下。愚以为宫中之事，事无大小，悉以咨之，然后施行，必得裨补阙漏，有所广益。 8.愿陛下托臣以讨贼兴复之效，不效，则治臣之罪，以告先帝之灵。若无兴德之言，则责攸之、祎、允等之慢，以彰其咎；陛下亦宜自谋，以咨诹善道，察纳雅言，深追先帝遗诏，臣不胜受恩感激。 4.将军向宠，性行淑均，晓畅军事，试用之于昔日，先帝称之曰能，是以众议举宠为督。愚以为营中之事，悉以咨之，必能使行阵和睦，优劣得所。 2.宫中府中，俱为一体，陟罚臧否，不宜异同。若有作奸犯科及为忠善者，宜付有司论其刑赏，以昭陛下平明之理，不宜偏私，使内外异法也。 1.先帝创业未半而中道崩殂，今天下三分，益州疲弊，此诚危急存亡之秋也。然侍卫之臣不懈于内，忠志之士忘身于外者，盖追先帝之殊遇，欲报之于陛下也。诚宜开张圣听，以光先帝遗德，恢弘志士之气，不宜妄自菲薄，引喻失义，以塞忠谏之路也。 9.今当远离，临表涕零，不知所言。 6.臣本布衣，躬耕于南阳，苟全性命于乱世，不求闻达于诸侯。先帝不以臣卑鄙，猥自枉屈，三顾臣于草庐之中，咨臣以当世之事，由是感激，遂许先帝以驱驰。后值倾覆，受任于败军之际，奉命于危难之间，尔来二十有一年矣。 7.先帝知臣谨慎，故临崩寄臣以大事也。受命以来，夙夜忧叹，恐付托不效，以伤先帝之明，故五月渡泸，深入不毛。今南方已定，兵甲已足，当奖率三军，北定中原，庶竭驽钝，攘除奸凶，兴复汉室，还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益，进尽忠言，则攸之、祎、允之任也。 5.亲贤臣，远小人，此先汉所以兴隆也；亲小人，远贤臣，此后汉所以倾颓也。先帝在时，每与臣论此事，未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军，此悉贞良死节之臣，愿陛下亲之信之，则汉室之隆，可计日而待也。public static void main(String[] args) throws IOException { // 创建map集合，存储读取到的行信息，用于排序。key是序号，value是文字。 HashMap<String, String> lineMap = new HashMap<>(); // 创建字符缓冲输入流 BufferedReader br = new BufferedReader(new FileReader("in.txt")); // 创建字符缓冲输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt")); // 读取数据 String line = null; while ((line = br.readLine()) != null) { // 解析文本 String[] split = line.split("\\."); // 保存到map集合中 lineMap.put(split[0], split[1]); } // 关闭输入流 br.close(); // 写出数据 for (int i = 1; i < lineMap.size(); i++) { String key = String.valueOf(i); String value = lineMap.get(key); bw.write(key + ". " + value); bw.newLine(); } // 关闭输出流 bw.close(); }5.4、学习案例[scode type="blue"]继承自父类的共性成员方法:[/scode]public void close() ：关闭此输出流并释放与此流相关联的任何系统资源。public void flush() ：刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)：将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len) ：从指定的字节数组写入 len字节，从偏移量 off开始输出到此输出流。public abstract void write(int b) ：将指定的字节输出流。[scode type="blue"]构造方法:[/scode]BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流，以将数据写入指定的底层输出流。BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流，以将具有指定缓冲区大小的数据写入指定的底层输出流。[scode type="blue"]参数:[/scode]OutputStream out:字节输出流我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率int size:指定缓冲流内部缓冲区的大小,不指定默认[scode type="blue"]使用步骤(重点):[/scode]创建FileOutputStream对象,构造方法中绑定要输出的目的地创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中释放资源(会先调用flush方法刷新数据,第4部可以省略)6、转换流按照某种规则，将字符存储到计算机中，称为编码 。反之，将存储在计算机中的二进制数按照 某种规则解析显示出来，称为解码 。字符编码 Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。字符集 Charset ：也叫编码表。是一个系统支持的所有字符的集合，包括各国家文字、标点符号、图形符 号、数字等。计算机要准确的存储和识别各种字符集符号，需要进行字符编码，一套字符集必然至少有一套字符编码。常见字符 集有ASCII字符集、GBK字符集、Unicode字符集等。当指定了编码，它所对应的字符集自然就指定了。6.1、InputStreamReaderjava.io.InputStreamReader：是Reader的子类，是从字节流到字符流的桥梁。它读取字节，并使用指定 的字符集将其解码为字符。它的字符集可以由名称指定，也可以接受平台的默认字符集。构造方法public InputStreamReader(InputStream in) ：创建一个使用默认字符集的字符流。public InputStreamReader(InputStream in, String charsetName) ：创建一个指定字符集的字符流。6.2、OutputStreamWriterjava.io.OutputStreamWriter：是Writer的子类，是从字符流到字节流的桥梁。使用指定的字符集将字符 编码为字节。它的字符集可以由名称指定，也可以接受平台的默认字符集。构造方法public OutputStreamWriter(OutputStream out) ：创建一个使用默认字符集的字符流。public OutputStreamWriter(OutputStream out, String charsetName) ：创建一个指定字符集的字符流。6.3、转换文件编码练习将GBK编码的文本文件，转换为UTF-8编码的文本文件。指定GBK编码的转换流，读取文本文件。使用UTF-8编码的转换流，写出文本文件。public static void main(String[] args) throws IOException { // 定义文件路径 String srcFile = "file_GBK.txt"; String destFile = "file_UTF8.txt"; // 创建转换输入流 InputStreamReader in = new InputStreamReader(new FileInputStream(srcFile), "GBK"); // 创建转换输出流 OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(destFile)); // 读取数据 char[] cbuf = new char[1024]; int len; while ((len = in.read(cbuf)) != -1) { // 写出数据 out.write(cbuf, 0, len); } // 释放资源 out.close(); in.close(); }7、序列化流7.1、概述Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象，该字节序列包含该 对象的数据 、 对象的 类型 和 对象中存储的属性 等信息。字节序列写出到文件之后，相当于文件中持久保存了一个对象的信息。反之，该字节序列还可以从文件中读取回来，重构对象，对它进行反序列化。 对象的数据 、 对象的类型 和 对象中 存储的数据 信息，都可以用来在内存中创建对象。【注意】被static修饰的成员变量先于对象存在，不能被序列化；被transient修饰的成员变量不能被序列化。7.2、ObjectOutputStreamjava.io.ObjectOutputStream：类，继承自OuputStream，将Java对象的原始数据类型写出到文件,实现对象的持久存储。构造方法public ObjectOutputStream(OutputStream out) ：创建一个指定OutputStream的ObjectOutputStream。特有成员方法public final void writeObject(Object obj) ：将指定的对象写出。序列化操作（序列化操作必须满足以下两个条件）该类必须实现 java.io.Serializable 接口， Serializable 是一个标记接口，不实现此接口的类将不会使任 何状态序列化或反序列化，会抛出 NotSerializableException 。该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的，则该属性必须注明是瞬态的，使用 transient 关键字修饰。7.3、ObjectInputStreamjava.io.ObjectInputStream：反序列化流，将之前使用ObjectOutputStream序列化的原始数据恢复为对象。构造方法public ObjectInputStream(InputStream in) ：创建一个指定InputStream的ObjectInputStream。特有成员方法public final Object readObject() ：读取一个对象。反序列化操作（反序列化操作必须满足以下两个条件）该类必须实现Serializable接口；该类必须存在对应的class文件。如果找不到对应的class文件，则会抛出一个 ClassNotFoundException 异常。【注意】当JVM反序列化对象时，能找到class文件，但是class文件在序列化对象之后发生了修改，那么反序列化操 作也会失败，抛出一个 InvalidClassException 异常。发生这个异常的原因如下：该类的序列版本号与从流中读取的类描述符的版本号不匹配；解决： private static final long serialVersionUID = 1L;该类包含未知数据类型；该类没有可访问的无参数构造方法。Serializable 接口给需要序列化的类，提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序 列化的对象和对应类是否版本匹配。7.4、序列化结合练习将存有多个自定义对象的集合序列化操作，保存到 list.txt 文件中。反序列化 list.txt ，并遍历集合，打印对象信息。public class Student implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }public static void main(String[] args) { // 创建list集合 ArrayList<Student> students = new ArrayList<>(); // 创建Student对象 students.add(new Student("张三", 18)); students.add(new Student("李四", 18)); students.add(new Student("王五", 18)); students.add(new Student("小师妹", 18)); // 序列化 serialize(students); // 反序列化 reSerialize("list.txt"); } private static void serialize(ArrayList<Student> students) { ObjectOutputStream out = null; try { out = new ObjectOutputStream(new FileOutputStream("list.txt")); out.writeObject(students); } catch (IOException e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } } private static void reSerialize(String s) { ObjectInputStream in = null; try { in = new ObjectInputStream(new FileInputStream(s)); try { ArrayList<Student> list = (ArrayList<Student>) in.readObject(); for (Student student : list) { System.out.println(student); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); }finally { try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } }8、打印流java.io.PrintStream：类，继承自OutputStream，该类能够方便地打印各种数据类型的值，是一种便捷的输出方式。【注意】该流只写，不读；不会抛出IOException；构造方法public PrintStream(File file) ：使用指定的文件对象创建一个新的打印流。public PrintStream(OutputStream out) ：使用指定的输出流创建一个新的打印流。public PrintStream(String fileName) ：使用指定的文件名创建一个新的打印流。特有方法public void print()public void println()【注意】 1. 这两个方法在控制台输出任意类型数据，不查编码表，原样输出； 1. 如果调用父类的write方法，则会查询编码表。 128.1、改变打印流的方向System.out 就是 PrintStream 类型的，只不过它的流向是系统规定的，打印在控制台上。使用 System.setOut(PrintStream ps) 方法可以改变打印流的方向。public static void main(String[] args) { PrintStream ps = null; try { ps = new PrintStream("ps.txt"); System.setOut(ps); System.out.println(48); ps.println(65); ps.write(97); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (ps != null) { ps.close(); } } }感谢小伙伴们的关注！你的点赞、评论、关注、收藏是对博主的最大鼓励！持续更新JavaSE学习笔记！欢迎订阅专栏！