bat 判断文件存在字符_批处理if判断文件存在_java判断文件是否存在的方法

Java技术迷|出品

标识接口是没有任何方法和属性的接口。标识接口不对实现它的类有任何语义上的要求,它仅仅表明实现它的类属于一个特定的类型。

Java中的标记接口有很多,这里介绍其中三个比较经典的标记接口:

1.Serializable

2.Cloneable3.RandomAccess

Serializable

这个接口相信大家都不陌生,该接口用于实现序列化,实现了该接口的类就是可序列化的(序列化指的是将对象的数据写入文件),来看看Serializable接口的源代码:

public interface Serializable {
}

Serializable内部其实没有任何内容,所以它实质上是一个标记型的接口,用于表示某些类可以被序列化。下面就来简单地使用一下Serializable接口,首先创建一个普通的Java类:

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private String name;
private Integer age;
}

然后编写一段序列化代码:

public static void writeObject() throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\Desktop\test.txt"));
User user = new User("张三", 20);
oos.writeObject(user);
oos.close();
}

执行该方法,程序报错:

Exception in thread "main" java.io.NotSerializableException: com.wwj.collection_.User

这是因为User类没有实现序列化接口,看看底层源码是如何对其进行检验的。首先查看writeObject方法:

public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}

enableOverride是类的一个成员变量,初始值为false,所以接着执行writeObject0方法:

private void writeObject0(Object obj, boolean unshared)
throws IOException{
......
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
}

方法很长,我们挑重要的看,首先判断当前的object是否为String类型,若为String,则执行writeString;其次判断是否为Array类型,接着判断是否为Enum,最后看看object是否为Serializable类型,如果该object实现了Serializable接口,那么就可以正确执行之后的方法,若是没有实现,则抛出NotSerializableException异常。明白其原理后,我们就需要让User类实现Serializable接口:

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Serializable {
private String name;
private Integer age;
}

此时再次执行序列化方法即可将对象内容存入文件:

$ cat test.txt
▒▒srcom.wwj.collection_.User▒I]a▒▒4LagetLjava/lang/Integer;LnametLjava/lang/String;xpsrjava.lang.Integer⠤▒▒▒8Ivaluexrjava.lang.Number▒▒▒
▒▒▒xpt张三

也可以将文件中的内容反序列化成一个对象,代码如下:

public static void readObject() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\Desktop\test.txt"));
User user = (User) ois.readObject();
System.out.println(user);
ois.close();
}

运行结果:

User(name=张三, age=20)

Cloneable

该接口与Serializable接口一样,都是标记型接口:

public interface Cloneable {
}

当一个类实现了Cloneable接口时,就可以使用该类的clone方法对其进行克隆,否则,就会抛出CloneNotSupportedException异常。克隆指的是根据已有的数据,创建一份完全一样的副本,一个类要想能够被克隆,则必须实现Cloneable接口并重写clone方法,比如:

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Cloneable {
private String name;
private Integer age;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

此时即可对User对象进行克隆:

public static void main(String[] args) throws Exception {
User user = new User("张三", 20);
User user2 = (User) user.clone();
System.out.println(user);
System.out.println(user2);
}

运行结果:

User(name=张三, age=20)
User(name=张三, age=20)

然而这样的克隆方式是有局限性的,比如我们修改一下User类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Cloneable {
private String name;
private Integer age;
private Address address;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

@Data @NoArgsConstructor @AllArgsConstructor public class Address { private String province; private String city; }

此时我们再去克隆一下试试:
```java
public static void main(String[] args) throws Exception {
Address address = new Address("浙江", "杭州");
User user = new User("张三", 20,address);
User user2 = (User) user.clone();
System.out.println(user);
System.out.println(user2);
}

运行结果:

User(name=张三, age=20, address=Address(province=浙江, city=杭州))
User(name=张三, age=20, address=Address(province=浙江, city=杭州))

看着运行结果好像并没有产生什么问题,接下来我们尝试修改其中一个对象的属性值:

public static void main(String[] args) throws Exception {
Address address = new Address("浙江", "杭州");
User user = new User("张三", 20,address);
User user2 = (User) user.clone();
user.setName("李四");
address.setCity("金华");
System.out.println(user);
System.out.println(user2);
}

运行结果:

User(name=李四, age=20, address=Address(province=浙江, city=金华))
User(name=张三, age=20, address=Address(province=浙江, city=金华))

通过运行结果能够发现两点,首先name属性值的修改并没有影响到user2,但是Address中city属性值的修改却影响到了它,这种现象就叫做浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

通过一张图来理解一下:

批处理if判断文件存在_java判断文件是否存在的方法_bat 判断文件存在字符

对于基本类型,user2会将原值拷贝一份,放入自己的对象中,而对于引用类型,它只会拷贝内存地址,也就是说,user2中的Address属性实际上只是引用了user中的内容,现在修改user中的值:

java判断文件是否存在的方法_bat 判断文件存在字符_批处理if判断文件存在

由于name值是一份真的拷贝,所以user中对name的修改并不会影响到user2,但修改了user中的city,此时读取user2的city时,它读取的仍然是user中的city,所以就出现了刚才的现象。

要想解决这一问题,我们可以使用深拷贝,若想实现深拷贝,我们就需要改造刚才的程序,首先要让Address类也实现Cloneable接口:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address implements Cloneable{
private String province;
private String city;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

接着需要改造一下User类的clone方法:

Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Cloneable {
private String name;
private Integer age;
private Address address;

@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
Address address = (Address) user.address.clone();
user.setAddress(address);
return user;
}
}

首先我们仍然通过父类的clone方法克隆出一个新的User对象,但我们知道,这个User对象仅仅是拷贝了基本数据类型,即:name和age的值,对于引用类型Address,只是拷贝了它的内存地址,若想让引用类型也能够完全拷贝值,就需要手动调用User对象中Address的clone方法,并将克隆得到的Address对象赋值给属性address;需要注意的是,如果Address类中仍然有引用类型变量,那么就需要改造Address的clone方法java判断文件是否存在的方法,并手动克隆出引用类型变量,将其赋值。现在重新运行之前的代码:

public static void main(String[] args) throws Exception {
Address address = new Address("浙江", "杭州");
User user = new User("张三", 20,address);
User user2 = (User) user.clone();
user.setName("李四");
address.setCity("金华");
System.out.println(user);
System.out.println(user2);
}

结果如下:

User(name=李四, age=20, address=Address(province=浙江, city=金华))
User(name=张三, age=20, address=Address(province=浙江, city=杭州))

其原理图如下:

批处理if判断文件存在_java判断文件是否存在的方法_bat 判断文件存在字符

经过深拷贝克隆出来的对象是完全相互独立的,无论user对象如何变化,user2都不会受到影响。

RandomAccess

该接口也是一个标记接口:

public interface RandomAccess {
}

若某个类实现了RandomAccessjava判断文件是否存在的方法,则表明该类支持快速的随机访问,其目的是允许通用算法更改其行为,以便在应用于随机访问列表(访问索引)或顺序访问列表(迭代器)时提供良好的性能。比如Java集合中的ArrayList就实现了该接口,而且对于实现了该接口的List,其随机访问列表的效率要高于顺序访问列表,我们可以尝试着验证一下。先来看看随机访问列表的耗时:

public static void main(String[] args) throws Exception {
List list = new ArrayList();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
long start = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
Integer num = list.get(i);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

运行结果:

8

再看看顺序访问列表的耗时:

public static void main(String[] args) throws Exception {
List list = new ArrayList();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
Iterator iterator = list.iterator();
long start = System.currentTimeMillis();
while(iterator.hasNext()){
Integer num = iterator.next();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

运行结果:

17

而对于没有实现RandomAccess接口的LinkedList,它的效率又会是如何呢?首先测试随机访问列表:

public static void main(String[] args) throws Exception {
List list = new LinkedList();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
long start = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
Integer num = list.get(i);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

运行结果:

11076

然而测试顺序访问列表:

public static void main(String[] args) throws Exception {
List list = new LinkedList();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
Iterator iterator = list.iterator();
long start = System.currentTimeMillis();
while(iterator.hasNext()){
Integer num = iterator.next();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

运行结果:

36

由此可以看出,LinkedeList的随机和顺序访问效率都很低,其中随机访问效率低的离谱。