当前位置: 首页 > news >正文

JavaSE——高级篇

反射

  • 目的:编译时动态获取信息

  • 原理:JVM运行时,会对所有的对象都创建一个Class对象,这个Class对象包含了类的所有信息,因此可以动态地获取类的信息

  • 应用场景:动态创建实例,减少重复的代码

  • 工作中反射的例子

    1. Json反序列化技术,将Jaon字符串转化为相应实例,使用反射就可以一次覆盖所有实体类的反序列化方法
    2. AOP使用JDK动态代理或CGLIB,通过反射创建目标对象的代理
    3. IOC识别带有@Component@Service等注解的类代替创建实例,覆盖所有类的new方法
    4. JMockit使用反射创建Mock对象,覆盖对应类的new方法

获取Class

//方法1(推荐)
Class<?> aClass =.class;
//方法2
Class<?> aClass = Class.forName("对象全类名");
//方法3
Class<?> aClass = 对象.getClass();

获取成员信息

  • 成员信息分为三类

    1. Field:成员信息对象
    2. Method:方法信息对象
    3. Constructor:构造器成员对象
public final class Class<T>{public native Class<? super T> getSuperclass(); //获取父类public Field[] getFields(){...} //获取所有公共属性public Field getField(String name){...} //获取指定的公共属性public Field[] getDeclaredFields(){...} //获取所有的属性(包含私有)public Field getDeclaredField(String name){...} //获取指定的属性(包含私有)
}public final class Class<T>{public Method[] getMethods(){...} //获取所有公共方法public Method getMethod(String name, Class<?>... parameterTypes){...} //获取指定公共方法,参数是入参类型.classpublic Method[] getDeclaredMethods(){...} //获取所有的方法(包含私有)public Method getDeclaredMethod(String name, Class<?>... parameterTypes){...} //获取指定的方法(包含私有)public Constructor<?>[] getConstructors(){...} //获取所有的公共构造方法public Constructor<T> getConstructor(Class<?>... parameterTypes){...} //获取指定公共构造方法,无入参即无参构造public Constructor<?>[] getDeclaredConstructors(){...} //获取所有的构造方法(包含私有)public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes){...} //获取指定构造方法(私有)
}

调用方法

  • 一般步骤

    1. 创建Class实例
    2. 通过Constructor创建对象
    3. 获取Method实例
    4. 调用Method.invoke()方法
  • 不建议使用Class.newInstance()方创建对象

public final class Method{public Object invoke(Object obj, Object... args) {...} // 第一个参数是创建的对象,之后是方法的入参,无参为nullpublic void setAccessible(boolean flag) {...} // 如果Method对象对应的方法权限是private,调用前需要先设置为truepublic Type[] getGenericParameterTypes() {...} // 获取方法入参类型public TypeVariable<Method>[] getTypeParameters() {...} // 获取方法的泛型信息
}

举例:覆盖测试类异常情形

  1. Dao层要对异常情况做检查(例如连接失败、SQL非法等),确保异常出现时都会抛出
  2. 所有的Dao类都要专门做一个DaoExceptionTest类,测试其中所有方法是否抛出异常,这很麻烦
  3. 要求只做一个DaoSQLExceptionTest类,模拟数据库异常时,利用反射覆盖所有Dao类的所有方法,实现“代码瘦身”
<dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId> <!--Reflections封装了一些反射操作,用于扫描类和配置文件--><version>0.10.2</version>
</dependency>
public class DaoSQLExceptionTest {static private final Set<Class<? extends BaseDao>> daoClasses;static private final Map<Type, Object> vlueMap = new HashMap<>();static {// 创建 Reflections 实例并指定扫描器Reflections reflections = new Reflections("com.wyh.dao");// getSubTypesOf:获取所有子类daoClasses = reflections.getSubTypesOf(BaseDao.class);//初始化参数MapdefaultVlueMap.put(Integer.class, 0);defaultVlueMap.put(int.class, 0);defaultVlueMap.put(Long.class, 0L);defaultVlueMap.put(long.class, 0L);defaultVlueMap.put(Float.class, 0.0F);defaultVlueMap.put(float.class, 0.0F);defaultVlueMap.put(Double.class, 0.0);defaultVlueMap.put(double.class, 0.0);defaultVlueMap.put(Character.class, 'a');defaultVlueMap.put(char.class, 'a');defaultVlueMap.put(String.class, "abc");}@Injectableprivate DruidDataSource mockDataSource;@BeforeMethodpublic void setUp() throws SQLException {new Expectations() {{mockDataSource.getConnection(); //模拟连接异常result = new SQLException("Simulated connection failure");}};}@Testpublic void testException() throws Exception {for (Class<? extends BaseDao> daoClass : daoClasses) {// 创建 DAO 实例BaseDao dao= daoClass.getDeclaredConstructor().newInstance();// 使用反射获取所有方法for (Method method : daoClass.getDeclaredMethods()) {try {method.invoke(dao, getMethodParameters(method)); // 验证异常Assert.fail(); //防止mock失效} catch (Exception e) {Assert.assertTrue(e.getCause() instanceof PersistenceException);}}}}private Object[] getMethodParameters(Method method) {Type[] parameterTypes = method.getGenericParameterTypes(); //获取入参类型Object[] parameters = new Object[parameterTypes.length];for (int i = 0; i < parameterTypes.length; i++) { //根据入参类型构建实参数组Type parameterType = parameterTypes[i];parameters[i] = defaultVlueMap.getOrDefault(parameterType, null); //不在Map列举范围内的类型入参null}return parameters;}
}

File类

  • File实例指代一个文件或目录,其方法可以对文件整体进行操作,但不能访问文件的内容,需要通过IO流才可以读写文件中的内容

创建File

  • 路径格式写法:推荐使用相对路径,根目录就是工程根路径(即.idea所在目录)

    1. 正斜杠写法 "resource/file.txt"
    2. 反斜杠写法 "resource\\file.txt"
    3. File类提供了系统分隔符(推荐) "resource"+File.separator+"file.txt"
public class File{public File(String pathname) {...} //创建File实例,系统会识别是绝对路径还是相对路径;若是相对路径,则根目录是当前项目public File(String parent, String child) {...}  //相对路径创建File实例,根目录就是parentpublic File(File parent, String child) {...} //相对路径创建File实例,根目录就是parent
}

操作File

public class File{public boolean createNewFile() throws IOException {...} //在系统中真正创建文件,如果已存在返回falsepublic boolean mkdir() {...} //创建目录,如果已存在返回falsepublic boolean exists() {...} //检查路径下是否存在文件/目录public boolean isFile() {...} //检查路径下存在且是文件public boolean isDirectory() {...} //检查路径下存在且是目录public long length() {...} //获取文件大小字节,目录返回0public boolean delete() {...} //删除文件或空目录public String getName() {...} //获取文件或目录名public String getPath() {...} //获取相对路径public String getAbsolutePath() {...} //获取绝对路径(推荐)//创建临时文件,JVM关闭时销毁,prefix为文件名至少三个字符,suffix为后缀名,默认.tmp,目录系统自动指定public static File createTempFile(String prefix, String suffix) {...} public static File createTempFile(String prefix, String suffix, File directory) {...} //手动指定临时文件目录public File[] listFiles() {...} //返回目录下的所有子文件和子目录,如果对象是文件则返回nullpublic File[] listFiles(FileFilter filter) {...} //根据FileFilter接口实现类返回boolean,获取筛选的File数组
}

举例:遍历所有文件

需求:查询多层级目录中有多少文件

public class Main {static Long fileNum = 0L;public static void searchFiles(File file) { //递归,深度优先//递归终点if (!file.isDirectory()) {fileNum ++;return;}//递归调用for (File f : file.listFiles()) {searchFiles(f);}}public static void main(String[] args) throws Exception {searchFiles(new File("E:\\JavaCourse"));System.out.println(fileNum);}
}

Files类

  • Files类是对于File的核心工具类,提供了丰富的静态方法
  • 常见的文件操作建议使用此工具类,因为NIO方式更高效
List<String> lines = Files.readAllLines(Paths.get("example.txt")); //读取所有行byte[] bytes = Files.readAllBytes(Paths.get("example.txt")); //读取所有字节Files.write(Paths.get("output.txt"), List.of("Hello", "World"), StandardOpenOption.CREATE); //写入字符串Files.write(Paths.get("binary.dat"), "Hello, World!".getBytes()); //写入字节数组Files.size(Paths.get("example.txt")); //获取文件大小Files.copy(Paths.get("source.txt"), Paths.get("target.txt")); //复制文件Files.move(Paths.get("old.txt"), Paths.get("new.txt")); //移动文件Files.delete(Paths.get("example.txt")); //删除文件Stream<Path> stream = Files.walk(Paths.get("path/to/dir")); //递归遍历所有文件,返回Stream流Files.newInputStream(file.toPath()); //快速获取一个文件输入流

Path类

  • Path类用于防护攻击者利用 ../ 和分隔符组合尝试跳出或跳进目标目录

  • 防护路径攻击步骤

    1. 使用Paths.get()表示路径
    2. 用户输入路径拼接使用Path.resolve()+Path.normalize(),防止路径遍历
    3. 限制文件操作范围Path.startsWith(),确保路径在允许的目录内
// Paths.get()创建Path对象,如果传入多个参数会自动拼接,且自动处理不同操作系统的路径分隔符(/ 或 \)
Path path1 = Paths.get("C:/data/file.txt");  // Windows 路径
Path path2 = Paths.get("/home/user/docs/file.txt");  // Linux/macOS 路径
Path path3 = Paths.get("data", "subdir", "file.txt");  // 相对路径(自动拼接)
Path relativePath = Paths.get("data/file.txt"); //如果没有根目录就默认相对路径,toAbsolutePath()转换为绝对路径
Path basePath = Paths.get("/home/user");
// 安全拼接路径,避免直接使用 +
Path path = basePath.resolve("docs/file.txt");  // /home/user/docs/file.txt
// 获取路径信息
System.out.println("文件名: " + path.getFileName());  
System.out.println("父目录: " + path.getParent());    
System.out.println("根目录: " + path.getRoot());      
System.out.println("路径字符串: " + path.toString()); 
//移除多余的 .. 和 .,防止路径遍历攻击
Path normalizedPath = Paths.get("/home/user/../docs/file.txt").normalize(); // /home/docs/file.tx
// 检查 fullPath 是否仍在 basePath 下
if (!path.startsWith(basePath)) {throw new SecurityException("路径访问被禁止!");
}

举例:路径遍历防护

用户上传文件时,恶意输入 ../../etc/passwd 试图覆盖系统文件

public class FileDownloadSecurity {public static void main(String[] args) throws Exception {String baseDir = "/var/www/uploads";  // 允许访问的目录String userInput = "../../etc/passwd";  // 用户恶意输入// 安全拼接路径Path basePath = Paths.get(baseDir).normalize().toRealPath();Path userPath = Paths.get(userInput).normalize();Path fullPath = basePath.resolve(userPath);// 检查路径是否在允许范围内if (!fullPath.normalize().startsWith(basePath)) {throw new SecurityException("非法路径访问!");}// 检查文件扩展名(可选)String fileName = fullPath.getFileName().toString();if (!fileName.matches(".*\\.(jpg|png|pdf)")) {throw new SecurityException("不支持的文件类型!");}System.out.println("安全路径: " + fullPath);}
}

IO流

  • IO流的目的:实现内存与外界数据相互通信

    外界--->字节码--->内存--->字节码--->外界

    |------输入流------|------输出流------|

  • IO流的使用场景

    1. JVM内部数据都是运行在内存中的,内存内部数据通信一般不需要使用IO流
    2. JVM与外界数据通信需要IO流实现,例如内存与硬盘之间通信、服务器与客户端通信、压缩/解压缩
  • Java为IO流提供了原始类InputStream/OutputStream,根据传输的数据类型不同,衍生出了许多子类

    1. 文件字节流:针对于一般的文件类型
    2. 文件字符流:针对于纯文本的文件类型
    3. 数据字节流:针对于Java基本数据类型
    4. 转换字符流:针对于字符串类型的数据
    5. 缓冲字节/字符流:针对于数据量较大的场景
    6. 打印字节/字符流:针对于只需要输出打印字符串的场景
    7. 压缩/解压缩字节流:针对于压缩包文件类型
  • 常见IO流类线程安全问题

    流类型常见类线程安全?关键原因
    文件流FileInputStreamFileOutputStream多线程同时读写同一文件流会导致数据混乱或文件指针冲突。
    缓冲流BufferedInputStreamBufferedOutputStream内部缓冲区非线程安全,多线程同时操作会导致数据错误。
    打印流PrintStream(如 System.out)、PrintWriter部分PrintStreamprint()/println() 线程安全,但 write() 不安全;PrintWriter 不安全。
    转换流InputStreamReaderOutputStreamWriter字符编码转换逻辑非线程安全,多线程同时操作会导致编码错误。
    数据流DataInputStreamDataOutputStream读写基本数据类型的方法非线程安全,多线程同时操作会导致数据解析错误。
    Socket 流Socket.getInputStream()Socket.getOutputStream()Socket 流是网络通信的通道,多线程同时读写会导致数据混乱或协议错误。
    压缩流GZIPInputStream GZIPOutputStream它们的设计基于单线程流式操作,内部状态(如压缩/解压缩缓冲区、位流指针)在多线程环境下会被并发

字符流与字节流

  • 字节和字符

    1. 字节是一种二进制码,计算机底层只有二进制形式的数据
    2. 其他各种类型数据都是根据一定规则由字节码转换而来的“虚假数据”,真正的数据类型只有字节
    3. 字符是为了可读性,根据字符编码规则将字节码转换而来的一种文本类型
  • **字符和字节的转换原理:字符 <---> Unicode <--编码规则--> 字节/字节数组 <---> 内存 **

    1. 同一个字符的Unicode是唯一的,字节码因不同的编码可能不一致
    2. 字节流不需要考虑编码,字符流要注意编码问题
  • 常见字符编码规则

    1. ASCII编码:仅包含英文及符号,一个英文字母对应一个字节('a' --- 97
    2. GBK编码:包含汉字,兼容ASCII,一个中文对应长度为2的字节数组
    3. UTF-8编码(推荐):支持全球语言,兼容ACII,一个中文对应长度为3的字节数组('你' --- [-28, -67, -96]

文件流

  • 文件写的方式
    1. 追加写:创建输出流时,如果文件存在且已有数据,则接着数据最后写
    2. 覆盖写:创建输出流时,如果文件存在且已有数据,则覆盖原有数据重新写

文件字节流

class FileInputStream extends InputStream{ //文件字节输入流public FileInputStream(String name) {...} //获取构造输入流,name是文件路径public FileInputStream(File file) {...} //获取构造输入流,file是文件对象public int read() {...} //每次读取一个字节,每调用一次都会读取下一个字节,返回获取的字节值,到流末就返回-1public int read(byte[] buffer) {...} //每次读取一组字节,更新在buffer中,返回字节数,到流末就返回-1public byte[] readAllBytes() {...} //读取所有字节保存在一个字节数组中,返回此数组,要求jdk9以上public void close() {...} //IO流使用完毕一定要关闭
}
class FileOutputStream extends OutputStream{ //文件字节输出流public FileOutputStream(String name) {...} //name是文件路径,不需要手动创建文件,但不能创建目录public FileOutputStream(String name, boolean append) {...} //设置输出流写的方式是追加写,默认覆盖写public FileOutputStream(File file) {...} //获取构造输出流,file是文件对象public FileOutputStream(File file, boolean append) {...} //设置输出流写的方式是追加写,默认覆盖写public void write(int b) {...} //写入一个字节,入参也可以是英文字母,其他符号字符会乱码public void write(byte b[], int off, int len) {...} //写入字节数组中的一部分,配合一次读取一组字节使用public void close() {...} //IO流使用完毕一定要关闭
}

举例:备份小文件

现有文件user.txt,要求对此文件做备份,备份文件名为userBackUp

try (FileInputStream fileInputStream = new FileInputStream("user.txt");FileOutputStream fileOutputStream = new FileOutputStream("userBackUp.txt")) {byte[] bytes = new byte[512]; //每次读取0.5kb字节while (true) {int len = fileInputStream.read(bytes);if (len == -1){break;}fileOutputStream.write(bytes,0,len); //只移动数据,不对数据有改动,不会乱码}fileOutputStream.write("\r\n".getBytes()); //最后换行
}

文件字符流

public class FileReader extends InputStreamReader{ //文件输入字符流public FileReader(String fileName) {...} //构造方法,参数是文件路径,默认编码规则与系统编码一致public FileReader(File file) {...} //构造方法,参数是文件对象public int read() {...} //一次读取一个字符,返回Unicode值,到流末返回-1public int read(char cbuf[]) {...} //一次读取一组字符,返回实际读取到的字符数,到流末返回-1public void close() {...} //IO流使用完毕一定要关闭
}
public class FileWriter extends InputStreamWriter{ //文件输出字符流public FileReader(String fileName) {...} //构造方法,参数是文件路径public FileReader(String name, boolean append) {...} //设置输出流写的方式是追加写,默认覆盖写public FileReader(File file) {...} //构造方法,参数是文件对象public FileReader(File file, boolean append) {...} //设置输出流写的方式是追加写,默认覆盖写public void write(int c) {...} //写入一个字符,入参是Unicode值public void write(char cbuf[], int off, int len) {...} //写入字符数组的一部分,配合一次读取一组字符使用public void write(String str) {...} //写入字符串public void write(String str, int off, int len) {...} //写入字符串一部分public void flush() {...} //刷新输入流,写入数据后,必须刷新或者关闭输入流,写入操作才会真正生效public void close() {...} //IO流使用完毕一定要关闭
}

举例:去除文件中的汉字

try (FileReader reader = new FileReader("properties.txt");FileWriter fileWriter = new FileWriter("newProperties.txt", true)) {while (true) {int read = reader.read();char c = (char) read;if (read == -1) {break;}if (c >= '\u4E00' && c <= '\u9FA5') { //字符合法才写入fileWriter.write(read);fileWriter.flush(); //可以实时显示写入情况}}
} catch (IOException e) {e.printStackTrace();
}

缓冲流

  • 目的:极大地提高了原IO流的读取性能

  • 原理:缓冲机制

    1. 内部维护一个字节数组(默认大小通常为 8KB),通过批量读取数据到缓冲区,减少 I/O 操作次数
    2. 普通字节流一次读取一定会触发IO接口,缓冲流只有缓冲区满了才会触发
    3. 小量数据情况下,缓冲流效率不一定比普通字节流高
  • 缓冲流应用场景

    1. 数据量较大时建议加上缓冲流
    2. 如果需要一行一行地读数据时,用缓冲流的readLine()方法

字节缓冲流

public class BufferedInputStream{ //缓冲字节输入流:必须包装另一个 InputStream(如 FileInputStream),不能直接使用public BufferedInputStream(InputStream in) {...} //构造缓冲输入流,默认缓冲8kbpublic BufferedInputStream(InputStream in, int size) {...} //设置缓冲区大小public int read() {...} //读取一个字节public int read(byte b[]) {...} //读取一组字节public void close() {...} //包装的流都会关闭,执行一次即可
}
public class BufferOutputStream{ //缓冲字节输出流:必须包装另一个 OutputStream(如 FileOutputStream),不能直接使用public BufferedOutputStream(OutputStream out) {...} //构造缓冲输出流,默认缓冲8kb,写方式由OutputStream指定public BufferedOutputStream(OutputStream out, int size) {...} //设置缓冲区大小public void write(int b) {...}public void write(int b) {...} //写入一个字节,入参是ASCII码public void write(byte b[], int off, int len) {...} //写入字节数组中的一部分public void flush() {...} //刷新输入流,写入数据后,必须刷新或者关闭输入流,写入操作才会真正生效public void close() {...} //包装的流都会关闭,执行一次即可
}

举例:备份大文件

try (BufferedInputStream bufferIn = new BufferedInputStream(new FileInputStream("E:"+File.separator+"长视频.mp4"));
BufferedOutputStream bufferOut = new BufferedOutputStream(new FileOutputStream("长视频备份.mp4"))) {byte[] bytes = new byte[2048];while (true){int len = bufferIn.read(bytes);if(len == -1){break;}bufferOut.write(bytes,0,len);}
} catch (IOException e) {throw new RuntimeException(e);
}

字符缓冲流

  • BufferedReader.readLine()方法不会读取换行符,此方法的返回值作为写入数据时,需要手动换行或者加上换行符
public class BufferedReader{ //字符缓冲输入流:必须包装一个 `Reader` 对象(如 `FileReader`、`InputStreamReader`)public BufferedReader(Reader in) {...} //缓冲输入流构造方法,默认缓冲8kb,编码由Reader决定public BufferedReader(Reader in, int sz) {...} //设置缓冲区大小,单位bytepublic String readLine() {...} //读取一行字符串直至换行符(不含),返回内容,到流末返回nullpublic int read() {...} //一次读取一个字符,返回Unicode值,到流末返回-1public int read(char cbuf[]) {...} //一次读取多个字符,返回实际读取到的字符数,到流末返回-1public void close() {...} //包装的流都会关闭,执行一次即可
}
public class BufferWriter{ //字符缓冲输出流:必须包装一个 `Writer` 对象(如 `FileWriter`、`OutputStreamWriter`)public BufferedWriter(Writer out) {...} //缓冲输出流构造方法,默认缓冲8kb,编码、写入方式由Reader决定public BufferedWriter(Writer out, int sz) {...} //设置缓冲区大小,单位bytepublic void write(int c) {...} //写入一个字符,入参是Unicode值public void write(char cbuf[], int off, int len) {...} //写入字符数组的一部分public void write(String str) {...} //写入字符串public void write(String str, int off, int len) {...} //写入字符串一部分public void newLine() {...} //换行,相当于\n\rpublic void flush() {...} //刷新输入流,写入数据后,必须刷新或者关闭输入流,写入操作才会真正生效public void close() {...} //包装的流都会关闭,执行一次即可
}

举例:删除配置文件注释

String inputFile = "config.yml";
String outputFile = "simpleConfig.yml"; //将删除注释后的文件保存到simpleConfig.yml
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {String line;while (true) {line = reader.readLine();if (line == null) {break;}//注释行或者空行直接跳过if (line.trim().startsWith("#") || line.trim().isEmpty()) {continue;}//移除行内注释(假设注释以 # 开头)String cleanedLine = line.replaceAll("#.*", "");if (!cleanedLine.isEmpty()) {writer.write(cleanedLine);writer.newLine(); //readLine不读取换行符,需要手动换行}}
} catch (IOException e) {throw new RuntimeException(e);
}

打印流

  • 打印流是文件输出流的包装,意在更方便地进行数据输出,例如记录日志

    1. 可以设置自动刷新
    2. 可以快接地格式化输出字符串
    3. 打印流内部包装了缓冲流,效率也很高
  • 使用场景:只需快速打印文本类型数据时建议使用打印流

  • 重定向打印流

    1. sout方法本质上就是使用PrintStream打印流,只不过是打印到控制台里
    2. 重定向打印流后,sout就不会打印在控制台,而是打印到指定的文件中
    public final class System {public final static PrintStream out = null; //JVM启动时会调用静态代码块将out初始化到控制台public static void setOut(PrintStream out) {...} //重定向打印流
    }
    

字节打印流

public class PrintStream extends FilterOutputStream{ //打印流必须包装一个已有的输出流,不能直接使用public PrintStream(OutputStream out) {...} //构造方法,默认不立即刷新,打印编码UTF-8public PrintStream(OutputStream out, boolean autoFlush) {...} //一次打印完是否立即刷新public PrintStream(OutputStream out, boolean autoFlush, String encoding) {...} //如果直接打印字符串,可以指定编码public void println(XXX x) {...} //打印x数据字符串形式(x可以是任意类型)对应的字节,并换行public PrintWriter printf(String format, Object ... args) {...} //格式化字符串并打印public void write(int b/byte b[], int off, int len) {...} //打印字节public void close() {...} //包装的流都会关闭,执行一次即可    
}

字符打印流

public class PrintWriter{ //打印流必须包装一个已有的输出流,不能直接使用public PrintWriter (Writer out) {...} //构造方法,默认不立即刷新,打印编码UTF-8public PrintWriter(Writer out, boolean autoFlush) {...} //指定打印完立即刷新public PrintStream(OutputStream out, boolean autoFlush, String encoding) {...} //可以指定编码public void println(XXX x) {...} //打印x数据字符串形式(x可以是任意类型),并换行public void write(int c/char cbuf[], int off, int len) {...} //打印字符public void write(String str/String str, int off, int len) {...} //打印字符串public PrintWriter printf(String format, Object ... args) {...} //格式化字符串并打印public void close() {...} //包装的流都会关闭,执行一次即可
}

举例:记录日志

public class LogHelper {private static final PrintWriter writer;private static final String fileName = "myProject.log";static {try {writer = new PrintWriter(new FileWriter(fileName, true), true);} catch (IOException e) {throw new RuntimeException(e);}}public static void info(String s) {//时间 [等级] 日志记录位置: 日志内容writer.printf("%s [INFO] %s: %s%n",LocalDateTime.now(),Thread.currentThread().getStackTrace()[2], //2游标返回上一级调用位置s);}public static void warn(Exception e) {//时间 [等级] 异常处理位置\n 异常调用链writer.printf("%s [WARN] %s%n",LocalDateTime.now(),Thread.currentThread().getStackTrace()[2]);e.printStackTrace(writer); //可以指定输出流}
}//效果
2025-06-19T19:21:40.961 [INFO] com.wyh.utils.Check.method(Check.java:14): 流程关键节点日志,方便后期维护
2025-06-19T19:21:40.970 [WARN] com.wyh.utils.Check.main(Check.java:9)
java.lang.RuntimeException: 原始异常at com.wyh.utils.Check.origin(Check.java:18)at com.wyh.utils.Check.method(Check.java:15)at com.wyh.utils.Check.main(Check.java:7)

转换流

  • 目的:处理编码不一致的问题

  • 转换原理

    1. 转换流是从字节流包装而来的字符流
    2. 外界--->编码规则--->内存--->编码规则--->外界
  • 应用场景

    1. 跨平台文本处理时编码可能不一致
    2. 读写编码不一致的文本文件
    3. 与数据库进行数据交互时编码可能不一致

转换输入流

  • 转换流必须包装一个已有的字节流(如 FileInputStream),不能直接使用
public class InputStreamReader{public InputStreamReader(InputStream in) {...} //读取数据,默认UTF-8public InputStreamReader(InputStream in, Charset cs) {...} //读取数据,并声明该数据的编码public InputStreamReader(InputStream in, String charsetName) {...} //指定原数据的编码public int read() {...} //一次读取一个字符,返回Unicode值,到流末返回-1public int read(char cbuf[]) {...} //一次读取一组字符,返回实际读取到的字符数,到流末返回-1public void close() {...} //包装的流都会关闭,执行一次即可
}

转换输出流

  • 转换流必须包装一个已有的字节流(如 FileOutputStream),不能直接使用
public class OutputStreamWriter{public OutputStreamWriter(OutputStream out) {} //写数据,该数据默认以UTF-8格式转换为字节保存public OutputStreamWriter(OutputStream out, Charset cs) {} //可以指定写数据的编码方式public OutputStreamWriter(OutputStream in, String charsetName) {...} //可以指定写数据的编码方式public void write(int c) {...} //写入一个字符,入参是Unicode值public void write(char cbuf[], int off, int len) {...} //写入字符数组的一部分,配合一次读取一组字符使用public void write(String str) {...} //写入字符串public void write(String str, int off, int len) {...} //写入字符串一部分public void close() {...} //包装的流都会关闭,执行一次即可
}

举例:结合缓冲流转换编码格式

老项目文件old.txt是GBK格式,现要求转换为UTF-8格式文件modern.txt

//缓冲流在最外层
try(InputStreamReader input = new InputStreamReader(new FileInputStream("old.txt"), "GBK");BufferedReader reader = new BufferedReader(input);OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream("modern.txt"), StandardCharsets.UTF_8);BufferedWriter writer = new BufferedWriter(output)) {while (true){char[] chars = new char[1024];int len = reader.read(chars);if (len == -1){break;}writer.write(chars,0,len);}
}

数据流

  • 目的:一种字节流,传输Java基本数据类型

数据输入流

public class DataInputStream{public DataInputStream(InputStream in) {...} //必须包装一个已有的输入流public final boolean readBoolean() {...} //读取布尔类型数据并返回public final byte readByte() {...} //读取Java字节类型数据并返回public final int readInt() {...} //读取整数类型数据并返回public final double readDouble() {...} //读取浮点类型数据并返回public final String readUTF() {...} //根据UTF-8编码,将字节转换为字符串,并返回public void close() {...} //包装的流都会关闭,执行一次即可
}

数据输出流

public class DataOutputStream{public DataOutputStream(OutputStream out) {...} //必须包装一个已有的输入流public final void writeByte(int v) {...} //写入Java字节型数据public final void writeBoolean(boolean v) {...} //写入布尔型数据public final void writeInt(int v) {...} //写入整型数据public final void writeDouble(double v) {...} //写入浮点型数据public final boolean readUTF(String str) {...} //根据UTF-8,将字符串转化为字节数组写入内存public void close() {...} //包装的流都会关闭,执行一次即可
}

举例:数据流必须保证输入输出顺序一致

  • 使用数据流,必须保证数据类型写入顺序和读取顺序一致,否则会解析出错误的数据
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data_stream.bin"))) {// 写入布尔值和整型值dos.writeBoolean(true);   // 写入布尔值dos.writeInt(123);        // 写入整型值dos.writeBoolean(false);  // 写入另一个布尔值dos.writeInt(456);        // 写入另一个整型值
} catch (IOException e) {e.printStackTrace();
}
// 从文件读取数据
try (DataInputStream dis = new DataInputStream(new FileInputStream("data_stream.bin"))) {// 读取布尔值和整型值(顺序必须与写入一致)System.out.println(dis.readInt()); //顺序不一致,读出来的结果变成了16777216  System.out.println(dis.readBoolean()); System.out.println(dis.readBoolean()); System.out.println(dis.readInt());     
} catch (IOException e) {e.printStackTrace();
}

压缩/解压缩流

  • 目的:压缩/解压文件,如果仅复制压缩文件用文件流即可
  • 原理:将文件的字节序列中相同部分“合并”,极大地减少了空间消耗

ZipEntry

  • ZipEntry类是表示 ZIP 文件中的一个条目,可以是文件或目录,通过名称中的分隔符识别文件层级
public class ZipEntry{public ZipEntry(String name) {...} //构造方法,入参以/结尾就是目录条目,否则为文件条目public String getName() {...} //返回条目基于zip根目录的相对路径public long getSize() {...} //获取条目未压缩的大小public long getCompressedSize() {...} //获取条目压缩后的大小public boolean isDirectory() {...} //条目是否为目录条目
}

压缩输入流(解压)

public class ZipInputStream{public ZipInputStream(InputStream in) {...} //必须包装一个已有的输入流,默认编码UTF-8public ZipInputStream(InputStream in, Charset charset) {...} //可以设置压缩文件编码格式public ZipEntry getNextEntry() {...} //获取压缩目录中的当前条目(目录/文件),每次调用就会读取下一个条目,直到返回nullpublic int read(byte[] b) {...} //解压当前文件条目,每次读取一组字节,结束返回-1,不能用于目录条目,在getNextEntry后用public void closeEntry() {...} //关闭当前条目public void close() {...} //IO流使用完毕一定要关闭
}

压缩输出流(压缩)

public class ZipOutputStream{public ZipOutputStream(OutputStream out) {...} //必须包装一个已有的输出流,默认编码UTF-8public ZipOutputStream(OutputStream out, Charset charset) {...} //可以设置压缩文件编码格式public void putNextEntry(ZipEntry e) {...} //开始写入一个新的ZIP条目,文件或目录由入参ZipEntry决定public void write(byte[] b, int off, int len) {...} //写入条目的数据,在putNextEntry文件条目后使用public void closeEntry() {...} //结束写入当前条目public void close() {...} //IO流使用完毕一定要关闭
}

压缩炸弹

  • 压缩炸弹(Zip Bomb)是一种恶意构造的压缩文件,它解压前并不大,但一旦解压会变超级大,严重的可能会导致相同崩溃

  • 压缩炸弹防护措施

    1. 在解压之前,检查压缩文件的大小
    2. 在解压过程中,监控解压后的文件大小,可以随时终止解压
    3. 使用经过验证的第三方库(如Apache Commons Compress)可以提供更多的安全功能和选项来防止压缩炸弹
private static final long MAX_ZIP_SIZE = 100 * 1024 * 1024; // 解压后一旦超过100MB就报异常public static void checkZipFile(File zipFile) throws IOException { //方法一:解压前检查压缩包大小try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {ZipEntry entry;long totalSize = 0; //压缩包大小计数器while ((entry = zis.getNextEntry()) != null) { //遍历压缩目录if (entry.isDirectory()) {continue;}totalSize += entry.getCompressedSize();if (totalSize > MAX_ZIP_SIZE) {throw new IOException("Zip file is too large");}zis.closeEntry();}}
}public static void unzip(File zipFile, File srcFile) throws IOException { //方法二:边解压边检查try (FileInputStream fis = new FileInputStream(zipFile);ZipInputStream zis = new ZipInputStream(fis)) {ZipEntry entry;long totalSize = 0; //压缩包大小计数器while ((entry = zis.getNextEntry()) != null) {if (entry.isDirectory()) { continue;}try (FileOutputStream fos = new FileOutputStream(new File(srcFile, entry.getName()))) {byte[] buffer = new byte[1024];int len;while ((len = zis.read(buffer)) != -1) {fos.write(buffer, 0, len);totalSize += len;if (totalSize > MAX_UNZIPPED_SIZE) {throw new IOException("Unzipped size is too large");}}zis.closeEntry();}}}
}

举例:备份压缩日志

  1. 开启压缩流:流关闭前所有写入的条目都在此目录中
  2. 开启条目
  3. 在文件条目下写入数据
  4. 关闭流:压缩完成
try (ZipOutputStream zipOutputStream = new ZipOutputStream("backup.zip"); //所有写入的文件都会在backup.zip压缩包中FileInputStream fileInputStream = new FileInputStream("log.txt")) {LocalDateTime t = LocalDateTime.now(); //将日期作为压缩包内的层级依据String path = t.getYear() + File.separator + t.getMonth().getValue() + File.separator + t.getDayOfMonth();zipOutputStream.putNextEntry(new ZipEntry(path + File.separator + "log.txt")); //开启条目,准备写入log.txt文件byte[] bytes = new byte[1024];while (true) { //写入数据int read = fileInputStream.read(bytes);if (read == -1) {break;}zipOutputStream.write(bytes);}zipOutputStream.closeEntry();
}

举例:压缩指定类型文件

压缩一个多层级目录,目录中有各种各样的文件,现只保留.mp4文件

public static void main(String[] args) throws IOException {File outputFile = new File("E:\\video.zip");File inputFile = new File("E:\\JavaCourse\\02-二阶段");try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFile));ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream)) { //再包一个缓冲流,提升性能startZip(inputFile, zipOutputStream, ""); //压缩流输出抽离在外面,避免因递归频繁地开启关闭压缩流} catch (IOException e) {throw new RuntimeException(e);}
}private static void startZip(File inputFile, ZipOutputStream zipOutputStream, String parentPath) throws IOException {if (!inputFile.isDirectory() && inputFile.getName().endsWith(".mp4")) { //只压缩视频文件byte[] bytes = new byte[2048];int len;zipOutputStream.putNextEntry(new ZipEntry(parentPath + File.separator + inputFile.getName()));try (FileInputStream fileInputStream = new FileInputStream(inputFile);BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {while ((len = bufferedInputStream.read(bytes)) != -1) {zipOutputStream.write(bytes, 0, len);}}zipOutputStream.closeEntry();return;}if (inputFile.isDirectory()) {parentPath = parentPath + File.separator + inputFile.getName(); //是目录,将目录层级记录下来,保证结构一致for (File f : inputFile.listFiles()) {startZip(f, zipOutputStream, parentPath);}}
}

序列化

  • 目的:传输Java对象类型的数据

  • 原理

    1. 目前没有针对Java对象类型的IO流,因此需要将对象转换为字节码/字符串,再通过对应的IO流传输
    2. 序列化仅指此转换的过程,后续IO流操作的实现另说
  • 序列化分类

    1. Java原生序列化:使用字节流(如Socket流)
    2. Json序列化:对象转换为Json格式字符串,进而通过字符流实现
  • 应用场景

    1. 原生序列化支持所有对象(ThreadSocketFile等类只能使用原生序列化),但性能、可读性低、有安全风险;
    2. Json序列化性能高,可读性高,适合绝大部分场景

JDK原生序列化

  • JDK原生序列化是将Java对象转换为字节数组

  • 开启原生序列化:让一个类可序列化,只需实现java.io.Serializable接口即可

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable {private Long id;private String name;
    }
    
  • 如果没有开启序列化,直接将对象通过字节流传输,会报NotSerializableException异常

    public class Main {public static void main(String[] args) {User user = new User(30L,"Alice"); //假设User类没有实现Serializabletry (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {oos.writeObject(user);} catch (IOException e) {e.printStackTrace();}}
    }java.io.NotSerializableException: com.wyh.entity.User //序列化异常at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at Main.main(Main.java:24)
    

Json序列化

  • Json序列化本质是将对象转化为Json格式的字符串,常见第三方库有Jackson、Gson、FastJson

  • Jackson:spring生态默认封装Jackson

    <dependency><groupId>com.fasterxml.jackson.core</groupId> <!--如果是spring boot项目会自带--><artifactId>jackson-databind</artifactId><version>2.15.2</version> <!-- 使用最新版本 -->
    </dependency>
    
    /*
    **Json属性映射
    1. @JsonProperty:设置映射别名
    2. @JsonInclude:什么情况下不序列化此成员
    3. @JsonFormat:对于时间数据类型,设置输出时间格式
    4. @JsonIgnore:json序列化时忽略此成员
    */
    @Data
    @ToString
    @AllArgsConstructor
    public class User{@JsonProperty("id") //格式化后的json数据的对应key就是"ID"private Integer userId;@JsonInclude(JsonInclude.Include.NON_NULL) //姓名为空就忽略姓名private String userName;@JsonIgnore //不转换status属性private Integer status;@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss",locale = "zh") //指定格式,并指定时区为中国private Date time;
    }
    
    public class ObjectMapper { // ObjectMapper:Jackson 的核心类public  String writeValueAsString(Object object) {...} //转换为JSON字符串public  <T> T readValue(String text, Class<T> clazz) {...} //根据class将JSON字符串反序列化为Java实例public <T> T readValue(JsonParser p, TypeReference<T> valueTypeRef) {...} //多个实例时可以反序列化为对象列表
    }ObjectMapper objectMapper = new ObjectMapper();
    User user = objectMapper.readValue("jsonStr",User.class);
    Map<String, String> map = objectMapper.readValue("jsonStr", new TypeReference<Map<String,String>>() {});
    
  • FastJson:当前最流行三方库

    <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version> <!-- 使用最新稳定版本 -->
    </dependency>
    
    /*
    **Json属性映射@JSONField注解
    1. name:映射别名
    2. format:对于时间数据类型,设置输出时间格式
    3. serialize:是否序列化
    4. deserialize:是否反序列化
    5. jsonDirect:是否直接序列化字段值(跳过 getter/setter,默认 false)
    */
    public class User {@JSONField(name = "user_name") // 序列化为 JSON 时的字段名private String name;@JSONField(format = "yyyy-MM-dd") // 日期格式化private Date birthday;
    }
    
    public abstract class JSON {public static String toJSONString(Object object) {...} //转换为JSON字符串public static <T> T parseObject(String text, Class<T> clazz) {...} //根据class将JSON字符串反序列化为Java实例public static <T> T parseObject(String text, TypeReference<T> type) {...} // 根据type将字符串转换为泛型,如Mappublic static <T> List<T> parseArray(String text, Class<T> clazz) {...} //多个实例时可以反序列化为对象列表
    }User user = JSON.parseObject("jsonStr",User.class);
    Map<String, String> map = JSON.parseObject("jsonStr", new TypeReference<Map<String,String>>() {});
    

Apache Commons Lang 3

  • Apache Commons Lang 3 是 Apache Commons 的工具库,提供了大量用于简化 Java 开发的实用方法,比JDK原生API更好用

    <dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.18.0</version>
    </dependency>
    

StringUtils

  • StringUtilsString 类更安全、功能更强大, 避免NullPointerException
方法说明
StringUtils.isEmpty(str)检查字符串是否为 null 或空(""
StringUtils.isBlank(str)检查字符串是否为 null、空或仅包含空白字符
StringUtils.trim(str)去除字符串首尾空白字符
StringUtils.abbreviate(str, maxWidth)截断字符串(超出部分用 ... 代替)
StringUtils.capitalize(str)首字母大写
StringUtils.uncapitalize(str)首字母小写
StringUtils.join(array, delimiter)拼接数组为字符串
StringUtils.split(str, delimiter)按分隔符拆分字符串
StringUtils.replace(str, searchStr, replacement)替换字符串
StringUtils.contains(str, searchStr)检查是否包含子串
StringUtils.substringBetween(str, open, close)提取两个标记之间的子串

ObjectUtils

方法说明
ObjectUtils.defaultIfNull(obj, defaultValue)如果对象为 null,返回默认值
ObjectUtils.firstNonNull(obj1, obj2, ...)返回第一个非 null 对象
ObjectUtils.equals(obj1, obj2)安全比较两个对象是否相等
ObjectUtils.hashCode(obj)安全计算对象的 hashCode
ObjectUtils.toString(obj)安全转换为字符串(null 返回空字符串)
ObjectUtils.min(a, b, c...) / ObjectUtils.max(a, b, c...)返回最小/最大值(支持 Comparable

ArrayUtils

方法说明
ArrayUtils.isEmpty(array)检查数组是否为 null 或空
ArrayUtils.contains(array, element)检查数组是否包含元素
ArrayUtils.add(array, element)向数组末尾添加元素
ArrayUtils.remove(array, index)移除指定索引的元素

CollectionUtils

  • ObjectUtils 提供对 Object 的安全操作,避免 NullPointerException
方法说明
CollectionUtils.isEmpty(collection)检查集合是否为 null 或空
CollectionUtils.isNotEmpty(collection)检查集合是否非空
CollectionUtils.union(a, b)合并两个集合
CollectionUtils.intersection(a, b)取两个集合的交集
CollectionUtils.filter(collection, predicate)过滤集合

EnumUtils

  • 简化枚举的操作,包括枚举值的解析、验证、获取枚举列表等
方法说明
EnumUtils.getEnumIgnoreCase(Class<E> enumClass, String enumName)根据名称安全获取枚举(不区分大小写)
EnumUtils.getEnum(Class<E> enumClass, String enumName)根据名称安全获取枚举(区分大小写)
EnumUtils.getEnumMap(Class<E> enumClass)转为Map,key为成员名称,value为枚举成员

ExceptionUtils

  • ExceptionUtils 提供异常堆栈信息的提取和格式化
方法说明
ExceptionUtils.getRootCause(throwable)获取异常的根原因
ExceptionUtils.getStackTrace(throwable)获取异常堆栈字符串

reflect

  • 封装了 Java 原生反射 API 的复杂操作,简化了字段、方法、构造器的访问与调用,减少 NullPointerException 风险
方法签名功能描述
Field getField(Class<?> cls, String fieldName)获取类及其父类的公共字段(包括继承的)
Field getDeclaredField(Class<?> cls, String fieldName)获取类自身的任意字段(包括私有,但不包括继承的)
Object readField(Field field, Object target)读取字段值(自动处理权限)
void writeField(Field field, Object target, Object value)修改字段值(自动处理权限)
List<Field> getAllFieldsList(Class<?> cls)获取类及其父类的所有字段(按继承顺序返回)
方法签名功能描述
Method getAccessibleMethod(Method method)确保方法可访问(处理私有方法)
Object invokeMethod(Object object, String methodName, Object... args)调用方法(自动匹配参数类型,支持继承链)
Object invokeExactMethod(Object object, String methodName, Object... args)精确匹配参数类型调用方法
List<Method> getAllMethodsList(Class<?> cls)获取类及其父类的所有方法(按继承顺序返回)
方法签名功能描述
Object invokeConstructor(Class<?> cls, Object... args)通过参数创建实例(自动匹配构造器)
Constructor<?> getAccessibleConstructor(Class<?> cls, Class<?>... parameterTypes)获取可访问的构造器
http://www.xdnf.cn/news/1324891.html

相关文章:

  • Java面试宝典:Redis 入门与应用
  • Poisson分布:稀有事件建模的理论基石与演进
  • 用随机森林填补缺失值:原理、实现与实战
  • 力扣hot100:移动零问题的巧妙解决:双指针与原地交换策略(283)
  • 开发避坑指南(28):Spring Boot端点检查禁用失效解决方案
  • Vue3 中使用 Element Plus 完整指南
  • Spring AI Alibaba 项目接入兼容 OpenAI API 的大模型
  • 杂记 05
  • 母猪姿态转换行为识别:计算机视觉与行为识别模型调优指南
  • Android使用Kotlin协程+Flow实现打字机效果
  • Python 作用域 (scope) 与闭包 (closure)
  • 【学习嵌入式-day-27-进程间通信】
  • Docker常见指令速查
  • 用户认证技术
  • STL库——string(类函数学习)
  • SQL详细语法教程(六)存储+索引
  • AI心理助手开发文档
  • 在python中等号左边的都是对象,在matlab中等号a = 3+2 a就是个变量
  • 力扣hot100:盛最多水的容器:双指针法高效求解最大容量问题(11)
  • openfeign 只有接口如何创建bean的
  • Linux设备树简介
  • vue3入门-v-model、ref和reactive讲解
  • Leetcode 16 java
  • Effective C++ 条款49:了解new-handler的行为
  • 力扣 hot100 Day77
  • 单片机驱动LCD显示模块LM6029BCW
  • 机器翻译论文阅读方法:顶会(ACL、EMNLP)论文解析技巧
  • STM32学习笔记14-I2C硬件控制
  • 大数据计算引擎(四)—— Impala
  • Fluss:颠覆Kafka的面向分析的实时流存储