一、String对象中的replace和replaceAll的区别?
//replace方法:支持字符和字符串的替换
public String replace(char oldChar, char newChar)
public String replace(CharSequence target, CharSequence replacement)
//replaceAll方法:基于正则表达式的字符串替换
public String replaceAll(String regex, String replacement)
//测试代码
String str = "AAA|BBB|CCC";
str = str.replace("|", ";");
System.out.println("==========" + str); //==========AAA;BBB;CCC
String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", ";");
System.out.println("==========" + str); //==========AAA;BBB;CCC
String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", "\\;");
System.out.println("==========" + str); //==========AAA;BBB;CCC
二、String类中split()方法的使用
//最常用的split()方法也就是单个参数的方法
public String[] split(String regex) {
return split(regex, 0);
}
//重载方法
public String[] split(String regex, int limit) {
// Try fast splitting without allocating Pattern object
String[] fast = Pattern.fastSplit(regex, this,limit);
if (fast != null) {
return fast;
}
return Pattern.compile(regex).split(this, limit);
}
//测试代码
String str = "AAA|BBB|CCC|";
String[] strArr = str.split("\\|");
System.out.println("==========" + Arrays.toString(strArr) + ",数组长度为:" + strArr.length);
//打印结果:==========[AAA, BBB, CCC],数组长度为:3
String str = ";;AAA;BBB;CCC";
String[] strArr = str.split(";");
System.out.println("==========" + Arrays.toString(strArr) + ",数组长度为:" + strArr.length);
//打印结果:==========[, , AAA, BBB, CCC],数组长度为:5
String str = ";;AAA;BBB;;CCC;";
String[] strArr = str.split(";");
System.out.println("==========" + Arrays.toString(strArr) + ",数组长度为:" + strArr.length);
//打印结果:==========[, , AAA, BBB, , CCC],数组长度为:6
三、Arrays.asList() 详解
/**
* 1、该方法是将数组转化成List集合的方法
* 2、该方法适用于对象型数据的数组(String、Integer...)
* 3、该方法不建议使用于基本数据类型的数组(byte,short,int,long,float,double,boolean)
* 4、该方法将数组与List列表链接起来:当更新其一个时,另一个自动更新
* 5、方法得到的List的长度是不可改变的,不支持add()、remove()、clear()等方法
* 6、Arrays.asList(strArray)返回值是java.util.Arrays类中一个私有静态内部类java.util.Arrays.ArrayList,它并非java.util.ArrayList类
* 7、java.util.Arrays.ArrayList类具有 set(),get(),contains()等方法,但是不具有添加add()或删除remove()方法
*/
//测试代码
String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", "\\;");
String[] strArr = str.split(";");
List<String> strList = Arrays.asList(strArr);
//程序就会抛出异常(java.lang.UnsupportedOperationException)
strList.remove("BBB");
//如果List只是用来遍历,用Arrays.asList()就可以了
//如果List还要添加或删除元素,需要new一个java.util.ArrayList
//测试代码
String str = "AAA|BBB|CCC";
str = str.replaceAll("\\|", "\\;");
String[] strArr = str.split(";");
List<String> strList = new ArrayList<>(Arrays.asList(strArr));
strList.remove("BBB");
System.out.println("==========" + strList + ",strList长度为:" + strList.size());
//打印结果:==========[AAA, CCC],strList长度为:2
四、List的remove()方法使用
//list删除会导致当前元素之后的元素位置发生改变
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
for (int i = 0; i < list.size(); i++) {
if ("bb".equals(list.get(i))) {
list.remove(i);
}
}
System.out.println(Arrays.toString(list.toArray()));
//你认为应该输出的打印结果:[aa, cc]
//实际输出的打印结果:[aa, bb, cc]
//正确使用方法1:倒序循环
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
for (int i = list.size() - 1; i >= 0; i--) {
if ("bb".equals(list.get(i))) {
list.remove(i);
}
}
System.out.println(Arrays.toString(list.toArray()));
//正确使用方法2:顺序循环时,删除当前位置的值,下一个值就会补到当前位置,所以需要执行i–操作
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
for (int i = 0; i < list.size(); i++) {
if ("bb".equals(list.get(i))) {
list.remove(i);
i--;
}
}
System.out.println(Arrays.toString(list.toArray()));
//正确使用方法3:使用迭代器的remove()方法
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("bb".equals(it.next())) {
//使用list的remove,会发生java.util.ConcurrentModificationException异常
//list.remove("bb");
it.remove();
}
}
System.out.println(Arrays.toString(list.toArray()));
五、SimpleDateFormat线程不安全问题
//问题复现
public class SimpleDateFormatTest {
// 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};
MyThread[] myThreads = new MyThread[5];
// 创建线程
for (int i = 0; i < 5; i++) {
myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
}
// 启动线程
for (int i = 0; i < 5; i++) {
myThreads[i].start();
}
}
public static class MyThread extends Thread {
private SimpleDateFormat simpleDateFormat;
//要转换的日期字符串
private String dateString;
private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
this.simpleDateFormat = simpleDateFormat;
this.dateString = dateString;
}
@Override
public void run() {
try {
Date date = simpleDateFormat.parse(dateString);
String newDate = simpleDateFormat.format(date);
if (!newDate.equals(dateString)) {
System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
+ " 转换成的日期为:" + newDate);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}
//控制台输出:
//ThreadName=Thread-2 报错了,日期字符串:2020-09-12 转换成的日期为:2020-09-13
//ThreadName=Thread-0 报错了,日期字符串:2020-09-10 转换成的日期为:2202-09-12
问题原因分析:
SimpleDateFormart 继承自 DateFormart,在 DataFormat 类内部有一个 Calendar 对象引用,
SimpleDateFormat 转换日期都是靠这个 Calendar 对象来操作的,比如 parse(String),
format(date) 等类似的方法,Calendar 在用的时候是直接使用的,而且是改变了 Calendar 的值,这样情况在多线程下就会出现线程安全问题,如果 SimpleDateFormart 是静态的话,那么多个 thread 之间就会共享这个 SimpleDateFormart,同时也会共享这个 Calendar 引用,那么就出现数据赋值覆盖情况,也就是线程安全问题。
//解决方法1:每次使用就创建一个新的 SimpleDateFormat
public class SimpleDateFormatTest {
// 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};
MyThread[] myThreads = new MyThread[5];
// 创建线程
for (int i = 0; i < 5; i++) {
myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
}
// 启动线程
for (int i = 0; i < 5; i++) {
myThreads[i].start();
}
}
public static class MyThread extends Thread {
private SimpleDateFormat simpleDateFormat;
//要转换的日期字符串
private String dateString;
private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
this.simpleDateFormat = simpleDateFormat;
this.dateString = dateString;
}
@Override
public void run() {
try {
Date date = DateUtil.parse(dateString);
String newDate = DateUtil.format(date);
if (!newDate.equals(dateString)) {
System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
+ " 转换成的日期为:" + newDate);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
private static class DateUtil {
private static final String formatPattern = "yyyy-MM-dd";
private static Date parse(String dateString) throws ParseException {
return new SimpleDateFormat(formatPattern).parse(dateString);
}
private static String format(Date date) {
return new SimpleDateFormat(formatPattern).format(date);
}
}
}
//解决方法2:使用synchronized 锁
public class SimpleDateFormatTest {
// 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};
MyThread[] myThreads = new MyThread[5];
// 创建线程
for (int i = 0; i < 5; i++) {
myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
}
// 启动线程
for (int i = 0; i < 5; i++) {
myThreads[i].start();
}
}
public static class MyThread extends Thread {
private SimpleDateFormat simpleDateFormat;
//要转换的日期字符串
private String dateString;
private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
this.simpleDateFormat = simpleDateFormat;
this.dateString = dateString;
}
@Override
public void run() {
try {
Date date = DateUtil.parse(dateString);
String newDate = DateUtil.format(date);
if (!newDate.equals(dateString)) {
System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
+ " 转换成的日期为:" + newDate);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
private static class DateUtil {
private static Date parse(String dateString) throws ParseException {
synchronized (simpleDateFormat) {
return simpleDateFormat.parse(dateString);
}
}
private static String format(Date date) {
synchronized (simpleDateFormat) {
return simpleDateFormat.format(date);
}
}
}
}
//解决方法3:ThreadLocal(推荐使用)
public class SimpleDateFormatTest {
// 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
String[] dateStringArray = new String[]{"2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};
MyThread[] myThreads = new MyThread[5];
// 创建线程
for (int i = 0; i < 5; i++) {
myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
}
// 启动线程
for (int i = 0; i < 5; i++) {
myThreads[i].start();
}
}
public static class MyThread extends Thread {
private SimpleDateFormat simpleDateFormat;
//要转换的日期字符串
private String dateString;
private MyThread(SimpleDateFormat simpleDateFormat, String dateString) {
this.simpleDateFormat = simpleDateFormat;
this.dateString = dateString;
}
@Override
public void run() {
try {
Date date = DateUtil.parse(dateString);
String newDate = DateUtil.format(date);
if (!newDate.equals(dateString)) {
System.out.println("ThreadName=" + this.getName() + " 报错了,日期字符串:" + dateString
+ " 转换成的日期为:" + newDate);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
private static class DateUtil {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
private static Date parse(String dateString) throws ParseException {
return threadLocal.get().parse(dateString);
}
private static String format(Date date) {
return threadLocal.get().format(date);
}
}
}