Java基础集合多线程JVM

Java基础 重点记录 构造器 Constructor 是否可被 override? Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多 个构造函数的情况。 重载和重写的区别 重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。 重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。 重写发生在运行时。因为在编译时,编译器是无法知道我们到底是调用父类的方法还是子类的方法,相反的,只有在实际运行的时候,我们才知道应该调用哪个方法。 重载发生在编译时。在编译过程中,编译器必须根据参数类型以及长度来确定到底是调用的哪个方法,这也是Java编译时多态的体现。 Java 面向对象编程三大特性: 封装 继承 多态 继承 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是⽆法访问,只是拥有。 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 子类可以用自己的方式实现父类的方法。(以后介绍)。 多态 多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作(调用同一个方法)。 在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的? 简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串, private final char value[] ,所以 String 对象是不可变的。 StringBuffer 对方法加了同步锁synchronized或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。 StringBuffer/StringBuilder 每次都会对对象本身进行操作,而不是像String生成新的对象并改变对象引用。 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 在 Java 中定义一个不做事且没有参数的构造方法的作用 规定:Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。 因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 ...

January 22, 2025 · 10 min

Java9之后内码为什么由UTF-16改为UTF-8

扩展性更强 UTF-8采用可变长度设计,长度为1-6个字节。UTF-16也为可变长设计,长度为2|4字节。UTF-8编码更加灵活和可扩展,能够支持未来出现的新字符和符号。 在之前的Java(9以前)版本中,Java平台采用的是16位Unicode编码(UTF-16)来表示字符和字符串,这种编码方式使用一个或两个16位的code units表示一个Unicode字符。虽然UTF-16编码可以表示所有的Unicode字符,但它并没有完全解决Unicode字符集的扩展问题。如果将来有新的Unicode字符需要添加到字符集中,可能需要修改UTF-16编码方案,从而导致兼容性和可移植性问题。 兼容性更好 UTF-8是一种非常广泛使用的字符编码方案,被支持和应用于众多操作系统、程序和协议中。使用UTF-8作为内部编码,可以更好地与其他系统和组件进行交互,并避免出现兼容性问题。 尽管Java平台之前采用的UTF-16编码也被广泛支持和应用,但在某些情况下,如文本传输和存储等场景,UTF-8编码具有更好的兼容性和互操作性。此外,许多开发人员在编写Java程序时也会使用UTF-8编码,因此将Java平台的内部编码改为UTF-8可以方便开发人员进行文本处理和编程。 更节省空间 相比于UTF-16编码,UTF-8编码在存储英文字符和标点符号等ASCII字符时,只需要使用一个字节,而不是两个字节。这样可以在存储和传输文本数据时节省空间和带宽。在处理大量字符串时可以节省内存使用。 尤其是在存储大量英文字符和标点符号的场景下,UTF-8编码能够显著地减少存储和传输的数据量,提高系统的效率和性能。 提高性能 Java9的String的内部value数组由char数组类型改为byte数字类型。byte占用空间为char的一半,这意味着更多的数据可以缓存在CPU缓存中,从而加速字符串操作的执行速度。

January 9, 2025 · 1 min

StringPool(字符串常量池)

字符串常量池的解释 字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定哪些是要放入字符串常量池。具体来说,只有使用双引号("")创建的字符串字面量才会被认为是常量,从而被放入常量池中。 在运行时,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。调用该方法会判断字符串常量池是否有该字面量,如没有,会在字符串常量池创建。之后,会返回字符串常量池中的对象。 通过new创建的字符串字面量,会先将,无论是否在字符串常量池存在,都会创建新的对象且不放入字符串常量池。 String str1 = "Hello world!"; String str2 = "Hello world!"; String str3 = new String("Hello world!"); String str4 = new String("Hello world!"); String str5 = str4.intern();//运行过程中把字符串添加到String Pool,并返回String Pool中的对象 System.out.println(str1 == str2);//运行结果:true。 System.out.println(str1 == str3);//运行结果:false。 System.out.println(str3 == str4);//运行结果:false。 System.out.println(str1 == str5);//运行结果:true。 System.out.println(str4 == str5);//运行结果:false。 字符串常量池长度 在 jdk6中,StringTable的长度是固定的,就是1009的长度。所以如果常量池中的字符串过多,会产生hash冲突,导致链表变长,降低查询效率。 在jdk7中,StringTable的长度可以通过一个参数指定: -XX:StringTableSize=99991 字符串常量池的位置 不同版本的Java虚拟机(JVM)可能会采用不同的方式来实现字符串常量池,并且在不同的JVM实现中,字符串常量池的位置也可能会发生变化。 JDK 6 字符串常量池存在于运行时常量池,运行时常量池存在方法区。方法区的实现为永久代(PermGen)。 PermGen默认大小只有4m,这种设计可能会导致PermGen空间溢出(java.lang.OutOfMemoryError: PermGen space)的问题,并且在频繁加载大量类文件时容易出现性能问题。 JDK 7 为了解决这些问题,从 JDK 7 开始,字符串常量池被移动到了堆内存中。 JDK 8 还是在堆内存中。 字符串的拼接 如果定义字符串时有拼接的表达式,编译器会根据表达式的参数是否为"“直接引用来判断是否将字符串字面量放入字符串常量池。 如表达式"Hello”+" world!",编译器会将两个字符串拼接成一个新的字符串"Hello world!",然后对比这个新字符串对象和常量池中的字符串字面量"Hello world!“是否相同。由于Java字符串常量池的特性,当两个字符串的内容相同时,它们所引用的字符串对象实例也是相同的。因此,表达式"Hello”+" world!" == “Hello world!“的结果为true。 ...

January 9, 2025 · 2 min

String的value数组不可变的好处

缓存hash值 String中用value数组来存储字符串信息,用hash来缓存value的hash值。如果value不是final,每次修改后需要重新计算hash,失去了缓存的意义,影响程序运行效率。 //java9及之后采用byte[],之前是char[] private final byte[] value; private int hash; // Default to 0 public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { hash = h = isLatin1() ? StringLatin1.hashCode(value) : StringUTF16.hashCode(value); } return h; } String Pool(常量池)复用需要 String Pool可以缓存已经创建的String对象。只有 String 是不可变的,才可能使用 String Pool。 安全性 由于String对象在创建后不可被修改,因此对于String类型的参数,其不可变性可以保证参数不会被意外或恶意地改变。 在网络连接过程中,一般需要传递服务器地址、端口号、用户名、密码等各种参数。如果这些参数使用可变的String类型来表示,那么在连接过程中,这些参数可能会被改变,导致实际连接的主机不同于预期。例如,某个客户端使用一个可变的String类型来表示服务器地址,然后在连接过程中修改了该字符串,将原本应该连接的服务器地址改成了另一个地址,从而导致最终连接到了错误的服务器上。 如果使用不可变的String类型来表示这些参数,则可以避免上述问题的出现。由于String对象不可被修改,因此无论在何种情况下都可以确保连接所需的参数不会在连接过程中被更改,从而确保连接行为的正确性和稳定性。 多线程 由于String的不可变,String具备线程安全性,可以在多线程中使用。

January 9, 2025 · 1 min

Java8一个汉字占几个字节

总结 不同的编码格式占字节数是不同的,UTF-8编码下一个中文所占字节大多常用字是3个字节、少部分是4个字节。UTF-16编码下一个中文所占用字节大多常用字是2个字节,少部分是4字节。 Java存储字节码使用UTF-8节省处理字符串所占用,运行使用UTF-16节省处理字符串时间。 名词解释 字节:byte 字符:char code unit:编码中最小单位,UTF-8中为1字节;UTF-16中为2字节。 code point:Unicode字符集中所对应的唯一编号,即一个文字的唯一编号。 内码(internal encoding):程序内部使用的字符编码,特别是某种语言实现其char或String类型在内存里用的内部编码。 外码(external encoding):程序与外部交互时外部使用的字符编码。“外部”相对“内部”而言;不是char或String在内存里用的内部编码的地方都可以认为是“外部”。例如,外部可以是序列化之后的char或String,或者外部的文件、命令行参数之类的。 编码格式 ASCII 码 128个 ISO-8859-1 256个 GB2312 6763个汉字 GBK 兼容GB2312,能表示2W+汉字 GB18030 使用不广泛 UTF-16 Unicode(Universal Code 统一码):定义文字编码 UTF:定义Unicode在计算机中的转换。 UTF-16:设计之初是定长2个字节,后面变为可变长2|4字节。一个字符(char)用两个字节表示,简化字符串转换操作,提高效率。 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。 编码对应关系 GBK编码 英语字母等需要1个字节,1个汉字对应2个字节。 UTF-16 编码 code unit为2个字节,英语字母等需要2个字节,中文需要2|4字节。 UTF-8 编码 code unit为1个字节,英文等字母需要一个字节,中文变为3|4个字节。 乱码 中文变成了看不懂的字符 例如,字符串“淘!我喜欢!”变成了“Ì Ô £ ¡Î Ò Ï²»¶ £ ¡”。 I/O输出方字符串采用GBK编码成字节数组,I/O接收方收到后对其用ISO-8859-1节码。模拟代码如下。 String str = "淘!我喜欢!"; byte[] gbkBytes = str.getBytes("GBK"); String isoStr = new String(gbkBytes, "ISO-8859-1"); System.out.println(isoStr); 编码过程如下图所示。 ...

January 7, 2025 · 1 min

Java Ftp连接池

某项目由于网络关系,传输文件通过ftp方式,即第三方实时把碎片文件放到ftp,我方需要把ftp文件下载下来进行业务处理。每个文件在100KB左右,每日文件数量在300万左右。前同事直接用在网上找的ftp demo,每次上传/下载文件创建新的连接,且单线程处理文件导致处理性能在1个文件每秒左右。当我们发现时,堆积的文件已经快撑爆了ftp服务器。 所以需要对现有代码进行优化。将现有的单线程改为多线程,每处理一个文件就启动一个线程来处理。创建ftp连接池,为每个线程提供单独的ftp连接。当线程下载/上传完成一个文件后,立即将连接还给Ftp连接池,提供连接池复用效率。 优化Ftp服务器配置 如果采用多线程消费ftp上文件,并且采用ftp连接池维护ftp连接,需要确认ftp服务的配置是否支持多个连接。项目采用vsftpd,所以需要查看/etc/vsftpd/vsftpd.conf配置文件。 pasv_max_port=0 设置在PASV工作方式下,数据连接可以使用的端口范围的上界。默认值为0,表示任意端口。 pasv_mim_port=0 设置在PASV工作方式下,数据连接可以使用的端口范围的下界。默认值为0,表示任意端口。 max_clients=0 设置vsftpd允许的最大连接数,默认为0,表示不受限制。若设置为150时,则同时允许有150个连接,超出的将拒绝建立连接。只有在以standalone模式运行时才有效。 max_per_ip=0 设置每个IP地址允许与FTP服务器同时建立连接的数目。默认为0,不受限制。通常可对此配置进行设置,防止同一个用户建立太多的连接。只有在以standalone模式运行时才有效。 配置完成后重启ftp服务。如果ftp连接失败,可以检查是否是防火墙/安全策略限制了端口放行。 连接池 引入Maven <!-- https://mvnrepository.com/artifact/commons-net/commons-net --> <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>3.10.0</version> </dependency> Ftp连接配置 import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @Configuration @Data public class FtpProperties { @Value("${ftp.hostname}") private String host; @Value("${ftp.username}") private String username; @Value("${ftp.password}") private String password; @Value("${ftp.port}") private int port; @Value("${ftp.timeout.default.seconds:1200}") private Integer defaultTimeoutSecond = 1200; @Value("${ftp.timeout.connect.seconds:1800}") private Integer connectTimeoutSecond = 1800; @Value("${ftp.timeout.data.seconds:2400}") private Integer dataTimeoutSecond = 2400; @Value("${ftp.charSet:UTF-8}") private String charSet = "UTF-8"; @Value("${ftp.factory.thread.times:2}") private Integer threadTimes = 2; } Ftp连接对象 import lombok.extern.slf4j.Slf4j; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import java.io.IOException; import java.io.OutputStream; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.stream.Stream; /** * ftp连接 */ @Slf4j public class FtpConnection { private final FTPClient ftp = new FTPClient(); private final AtomicBoolean isConnected = new AtomicBoolean(false); /** * 构造函数 */ public FtpConnection(FtpProperties ftpProperties) { ftp.setDefaultTimeout(ftpProperties.getDefaultTimeoutSecond() * 1000); ftp.setConnectTimeout(ftpProperties.getConnectTimeoutSecond() * 1000); ftp.setDataTimeout(ftpProperties.getDataTimeoutSecond() * 1000); ftp.setCharset(Charset.forName(ftpProperties.getCharSet())); ftp.setControlEncoding(ftpProperties.getCharSet()); //被动模式 ftp.enterLocalPassiveMode(); try { initConnect(ftpProperties.getHost(), ftpProperties.getPort(), ftpProperties.getUsername(), ftpProperties.getPassword()); } catch (IOException e) { isConnected.set(false); log.error("init ftp client error", e); } } /** * 初始化连接 */ private void initConnect(String host, int port, String user, String password) throws IOException { try { ftp.connect(host, port); } catch (UnknownHostException ex) { throw new IOException("Can't find FTP server '" + host + "'"); } int reply = ftp.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { disconnect(); throw new IOException("Can't connect to server '" + host + "'"); } if (!ftp.login(user, password)) { isConnected.set(false); disconnect(); throw new IOException("Can't login to server '" + host + "'"); } else { isConnected.set(true); } } public List<String> fileNames(String path) { try { //获取文件列表使用这个接口最好,命令行效率更高 String[] strings = ftp.listNames(path); return strings == null ? new ArrayList<>() : Stream.of(strings).collect(Collectors.toList()); } catch (IOException e) { log.error("ftp listNames error.path = {}", path); return new ArrayList<>(); } } public String download(String path, String ftpFileName, String localPath, boolean deleteSuccessFile, boolean deleteErrorFile) { String fileName = localPath + ftpFileName.substring(path.length()); boolean deleted = false; try (OutputStream os = Files.newOutputStream(Paths.get(fileName))) { //保存文件 boolean success = ftp.retrieveFile(ftpFileName, os); if (success) { //是否需要删除源文件 if (deleteSuccessFile) { deleted = delete(ftpFileName); } return fileName; } } catch (Exception e) { log.error("ftp download error", e); if (deleteErrorFile) { //对于下载失败的文件直接删除 deleted = delete(ftpFileName); } } finally { if (deleteSuccessFile || deleteErrorFile) { log.debug("ftp download then delete file {}", deleted ? "success" : "failed"); } } return null; } public boolean delete(String ftpFileName) { try { ftp.deleteFile(ftpFileName); return true; } catch (IOException e) { log.error("delete ftp file error", e); } return false; } /** * 关闭连接 */ public void disconnect() { if (ftp.isConnected()) { try { ftp.logout(); ftp.disconnect(); isConnected.set(false); } catch (IOException e) { log.error("ftp disconnect error", e); } } } /** * 设置工作路径 */ private boolean setWorkingDirectory(String dir) { if (!isConnected.get()) { return false; } //如果目录不存在创建目录 try { if (createDirectory(dir)) { return ftp.changeWorkingDirectory(dir); } } catch (IOException e) { log.error("set working directory error", e); } return false; } /** * 是否连接 */ public boolean isConnected() { return isConnected.get(); } /** * 创建目录 */ private boolean createDirectory(String remote) throws IOException { boolean success = true; String directory = remote.substring(0, remote.lastIndexOf("/") + 1); // 如果远程目录不存在,则递归创建远程服务器目录 if (!directory.equalsIgnoreCase("/") && !ftp.changeWorkingDirectory(directory)) { int start = 0; int end; if (directory.startsWith("/")) { start = 1; } end = directory.indexOf("/", start); do { String subDirectory = remote.substring(start, end); if (!ftp.changeWorkingDirectory(subDirectory)) { if (ftp.makeDirectory(subDirectory)) { ftp.changeWorkingDirectory(subDirectory); } else { log.error("mack directory error :/" + subDirectory); return false; } } start = end + 1; end = directory.indexOf("/", start); // 检查所有目录是否创建完毕 } while (end > start); } return success; } } Ftp工厂 import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.locks.ReentrantLock; /** * ftp连接工厂 */ @Slf4j @NoArgsConstructor public class FtpFactory { /** * 存放ftp连接有界队列 */ private ArrayBlockingQueue<FtpConnection> arrayBlockingQueue; private FtpProperties ftpProperties; /** * 最大连接数在初始化的时候固定,除非删除,否则不能新增连接 */ private int maxSize; /** * 工厂含有的连接数(包括已经取出的) */ private int ftpSize = 0; private final ReentrantLock lock = new ReentrantLock(false); public FtpFactory(FtpProperties ftpProperties) { this.ftpProperties = ftpProperties; //最大数量为处理器线程数*配置的倍数 this.maxSize = Runtime.getRuntime().availableProcessors() * ftpProperties.getThreadTimes(); this.arrayBlockingQueue = new ArrayBlockingQueue<>(maxSize); //初始化一个客户端,节约资源。后续需要更多再追加。 this.fill(1); } /** * 扩容工厂的连接池 * * @param size 扩容连接数量至 */ public void fill(int size) { if (size <= 0) { return; } final ReentrantLock lock = this.lock; lock.lock(); try { for (int i = 0; i < size; i++) { if (ftpSize >= size || ftpSize >= maxSize) { break; } //表示如果可能的话,将 e 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false FtpConnection connection = new FtpConnection(ftpProperties); boolean offer = this.safeOffer(connection); if (!offer) { break; } else { ftpSize++; } } log.info("Fill ftpConnection end, size is {}.", ftpSize); } finally { lock.unlock(); } } /** * 将ftp连接放入队列,如果超过队列大小将会销毁连接 * * @param connection * @return 是否加入成功 */ private boolean safeOffer(FtpConnection connection) { final ReentrantLock lock = this.lock; lock.lock(); try { boolean offer = arrayBlockingQueue.offer(connection); if (!offer) { log.debug("offer ftpConnection failed"); connection.disconnect(); } return offer; } finally { lock.unlock(); } } /** * 获取连接(阻塞) */ public FtpConnection getFtp() { FtpConnection poll; try { //取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 Blocking 有新的对象被加入为止 poll = arrayBlockingQueue.take(); } catch (InterruptedException e) { log.error("getFtpConnection error", e); return null; } return poll; } /** * 释放连接 * * @param ftp */ public void safeRelease(FtpConnection ftp) { this.safeOffer(ftp); } /** * 关闭连接 */ public void close() { final ReentrantLock lock = this.lock; lock.lock(); try { for (FtpConnection connection : arrayBlockingQueue) { connection.disconnect(); } arrayBlockingQueue.clear(); ftpSize = 0; } finally { lock.unlock(); } } } 工厂使用 使用方式 获取连接 FtpConnection connection = ftpFactory.getFtp(); 归还连接 ftpFactory.safeRelease(connection); 扩容连接池 ftpFactory.fill(50); 50代表想要50个连接,注意有最大限制。最大限制为FtpFactory中的maxSize,可以通过ftp.factory.thread.times配置为系统线程数的倍数。 代码示例 public void yourJob(FtpFactory ftpFactory, String ftpFilePath) { try { //拿到一个连接 FtpConnection connection = ftpFactory.getFtp(); List<String> names = connection.fileNames(ftpFilePath); //归还连接 ftpFactory.safeRelease(connection); if (!ObjectUtils.isEmpty(names)) { ftpFactory.fill(names.size()); CountDownLatch countDownLatch = new CountDownLatch(names.size()); Logger.info("本次文件路径 {}, 总数 {}, 开始处理", ftpFilePath, names.size()); long start = System.currentTimeMillis(); for (String fileName : names) { ftpThreadPool.execute( () -> { //这里传入ftpFactory是为了从工厂拿到连接和归还连接 yourService.downloadAndAnalysis(ftpFactory, ftpFilePath, fileName); countDownLatch.countDown(); }); } countDownLatch.await(); double seconds = (System.currentTimeMillis() - start) / 1000.0; Logger.info("完成文件路径 {}, 总数 {}, 耗时 {} 秒, 速度 {} 条/s", ftpFilePath, names.size(), seconds, names.size() / seconds); } else { Logger.error("ftp文件为空ftpFilePath:{}", ftpFilePath); } } catch (Exception e) { Logger.error("同步图片数据异常", e); } } 参考 ftp配置文件 Java 多线程实现FTP批量上传文件 vsftpd并发参数调优

November 7, 2024 · 5 min