前几天和棒棒童鞋讨论Java(TA学的是C++)的时候,他提到一个浅拷贝和深拷贝的问题,当时的我一脸懵圈,感觉自己学Java居然不知道这个知识点,于是今天研究了一番Java中的浅拷贝和深拷贝,下面来做一下总结:

一、定义

调研过程中发现普遍的解释如下:

我在用代码实战之后总结出的定义是:

浅拷贝,就是创建了一个新对象,这个新对象以及新对象中的基本类型都被分配了新的存储空间,从此与原对象毫无瓜葛;但是原对象中的引用类型没有被分配新的空间,新对象和原对象都指向同一片内存,任何一方的改变都会影响到对方。

深拷贝,也是创建了一个新的对象,不过这个新对象以及新对象中的所有属性都被分配了新的空间,从此彻底与原对象毫无关联,任何一方的改变都不会影响到对方。

用一个很生活化的例子来解释,就好比张三本来是和李四是男女朋友关系,但是后来张三和李四分手了,找了一个新的对象王五,这个王五就相当于是李四的拷贝

浅拷贝就是李四和王五都有自己的姓名、年龄、职业,且互不影响,但是张三和李四还有一个共同的qq号,且他们分手之后李四把这个qq号告诉了王五,并且这时李四和王五都能使用该号,这时李四和王五任何一方在该号中的操作对对方都是可见的,而且他们看到的所有变化都是一样的,比如修改qq昵称,签名等;

深拷贝就是张三和李四分手后,他也给了王五一个qq号,不过这个qq号是一个新申请的,是他和王五专用的,这时李四和王五的姓名、年龄、职业以及他们和张三的共用qq号都是不同的,任何一方的改变都不会影响到对方。

这里的张三,就是用来起到对李四和王五开辟空间的作用。

二、理解

下面我用代码一步一步来解释浅拷贝和深拷贝的实现:

1、先来看看普通的赋值操作

第一步:创建一个普通的Student类

/**
* 创建一个学生对象
*
* @createtime 2017年3月21日 上午10:15:20
* @description
*/
public class Student {
String name; int age; String gender; public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
} 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;
} public String getGender() {
return gender;
} public void setGender(String gender) {
this.gender = gender;
}
}

第二步:创建两个对象,将第一个赋值给第二个,然后比较他们是否相等

/**
* 普通的赋值
* @createtime 2017年3月21日 下午2:15:49
* @description
*/
public class GeneralCopy {
public static void main(String[] args) {
/**
* 普通对象的赋值
* 结论:
* 1、将对象a赋值给对象b时,对象a和对象b都指向同一片内存地址
* 2、任何一个对象的改变都会引起另一个对象的改变
* 3、普通的对象赋值就是浅拷贝
*/
System.out.println("---------普通的对象赋值---------");
Student a = new Student("zhangsan", 20, "man");
Student b = a;
b.name = "lisi";
System.out.println(a==b);//true
System.out.println(a.name);//lisi
a.name = "zhangsan1";
System.out.println(a==b);//true
System.out.println(b.name);//zhangsan1
}
}

从代码可以看出,当把a赋值给b时,a和b是相等的,即它们指向同一片内存空间,所以a的改变会引起b的改变;b的改变也会引起a的改变,由此看来普通的赋值应该是个浅拷贝

2、使用克隆的方式,用一个对象sca克隆另一个对象scb

第一步:先创建一个实现Cloneable接口的类,并且该类重写了clone方法

/**
* @createtime 2017年3月21日 下午2:25:17
* @description 实现了cloneable接口
*/
public class StudentClone implements Cloneable{
public String name;
public int 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;
}
public StudentClone(String name, int age) {
this.name = name;
this.age = age;
} /**
* 重写clone方法
*/
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
} }
}

第二步:使用clone方法用sca克隆一个scb的实例,并比较他们是否相同:

/**
* 普通的赋值
* @createtime 2017年3月21日 下午2:15:49
* @description
*/
public class GeneralCopy {
public static void main(String[] args) { /**
* 对象的拷贝
* 结论:
* 1、拷贝对象时新建了一个实例
* 2、任何一个对象的改变都不会引起另一个对象的改变
* 3、但这不能说明克隆就是深拷贝
* 4、无引用变量时克隆为深拷贝
*/
System.out.println("---------无引用变量的克隆---------");
StudentClone sca = new StudentClone("zhangsan",20);
StudentClone scb = (StudentClone) sca.clone();
scb.name = "lisi";
System.out.println(sca==scb);//false
System.out.println(sca.name);//zhangsan
sca.name = "zhangsan1";
System.out.println(sca==scb);//false
System.out.println(scb.name);//lisi
}
}

以上代码做了四件事:
(1)创建一个StudentClone对象的实例sca

(2)用sca克隆一个新的实例scb

(3)先改变克隆实例scb的name属性值,比较这两个实例是否相等以及sca的name属性值是否被改变

(4)再改变原实例sca的name属性值,比较这两个实例是否相等以及scb的name属性值是否被改变

根据测试结果可以看出:

克隆原对象时,创建了一个新的实例,病为之分配了新的空间,原实例和克隆实例指向的是不同的内存空间,所以他们不相等;

改变任何一个实例的基本类型属性值,都不会改变原实例或克隆实例的该属性;

由此可以得出如同代码注释中的结论:

即没有引用类型的变量时,克隆就是深拷贝

3、含有引用变量的拷贝,引用变量没实现Cloneable接口

第一步:创建一个普通的Student类,不实现Cloneable接口(就是上面的Student类)

/**
* 创建一个学生对象
*
* @createtime 2017年3月21日 上午10:15:20
* @description
*/
public class Student {
String name; int age; String gender; public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
} 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;
} public String getGender() {
return gender;
} public void setGender(String gender) {
this.gender = gender;
}
}

第二步:创建一个AddAttrStudent类,实现Cloneable接口,并且添加Student引用类型的变量

/**
* @createtime 2017年3月21日 下午2:41:26
* @description 含有一个未实现cloneable接口的引用变量
*/
public class AddAttrStudent implements Cloneable{
public String name; public Student student;//引用变量 public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public AddAttrStudent(String name, Student student) {
this.name = name;
this.student = student;
}
/**
* 实现Cloneable接口,重写clone方法
*/
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}

第三步:创建一个AddAttrStudent实例,并克隆一个新实例,然后分情况比较

/**
* 普通的赋值
* @createtime 2017年3月21日 下午2:15:49
* @description
*/
public class GeneralCopy {
public static void main(String[] args) {
/**
* 含有引用变量的拷贝(引用变量没实现cloneable接口)
* 结论:
* 1、拷贝对象建立了一个实例
* 2、其中一个对象的普通变量改变时不会引起另一个对象普通变量的改变
* 3、其中一个对象的引用变量的改变引起另一个对象引用变量的改变
* 4、有引用变量且引用变量未实现cloeable接口时克隆为浅拷贝
*/
System.out.println("---------有引用变量的克隆---------");
Student stu = new Student("张三",20,"男");
AddAttrStudent aas1 = new AddAttrStudent("克隆1",stu);
AddAttrStudent aas2 = (AddAttrStudent) aas1.clone();
System.out.println(aas1==aas2);//false
System.out.println(aas1.name);//克隆1
System.out.println(aas2.name);//克隆1
System.out.println(aas1.student == aas2.student);//true
System.out.println(aas1.student.name);//张三
System.out.println(aas2.student.name);//张三
System.out.println("------改变原对象-------");
aas1.name ="克隆2";
aas1.student.name = "张三1";
System.out.println(aas1==aas2);//false
System.out.println(aas1.name);//克隆2
System.out.println(aas2.name);//克隆1
System.out.println(aas1.student == aas2.student);//true
System.out.println(aas1.student.name);//张三1
System.out.println(aas2.student.name);//张三1
System.out.println("------改变拷贝对象-------");
aas2.name ="克隆";
aas2.student.name = "李四";
System.out.println(aas1==aas2);//false
System.out.println(aas1.name);//克隆2
System.out.println(aas2.name);//克隆
System.out.println(aas1.student == aas2.student);//true
System.out.println(aas1.student.name);//李四
System.out.println(aas2.student.name);//李四 }
}

以上代码有三个比较过程

第一个:用原实例克隆一个新实例时,克隆实例除了内存地址外,其他的都跟原实例完全相同

第二个:改变原实例对象的name属性和其引用变量student的name属性,此时除了两个实例的内存地址和name属性值之外,其他方面两个实例保持一致,包括被改变的引用变量student,其内存地址和name属性值;

第三个:改变克隆实例对象的name属性和其引用变量student的name属性,此时除了两个实例的内存地址和name属性值之外,其他方面两个实例保持一致,包括被改变的引用变量student,其内存地址和name属性值;

由以上结论可以看出:

原实例和克隆实例,基本类型的name属性值和各自的内存空间地址保持各自的值不受对方影响;引用类型的属性和内存空间都会受对方的影响,并和对方保持一致,所以可以得出如下结论:

有引用变量且引用变量未实现Cloeable接口时克隆为浅拷贝

4、含有引用变量的拷贝,且引用变量也实现了cloneable接口

这个例子与上面的唯一区别就是引用变量也实现了Cloneable接口,并且重写了clone方法

第一步:创建一个StudentClone类,实现Cloneable接口,重写clone方法

/**
* @createtime 2017年3月21日 下午2:25:17
* @description 实现了cloneable接口
*/
public class StudentClone implements Cloneable{
public String name;
public int 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;
}
public StudentClone(String name, int age) {
this.name = name;
this.age = age;
} /**
* 重写clone方法
*/
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}

第二步:创建一个AddAttrStudentClone类,实现Cloneable接口,添加StudentClone类的引用变量studentClone,且调用studentClone的clone方法

/**
* @createtime 2017年3月21日 下午2:57:42
* @description 含有实现了cloneable接口的引用对象StudentClone
*/
public class AddAttrStudentClone implements Cloneable{
public String name;
public StudentClone studentClone; public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} public StudentClone getStudentClone() {
return studentClone;
}
public void setStudentClone(StudentClone studentClone) {
this.studentClone = studentClone;
} public AddAttrStudentClone(String name, StudentClone studentClone) {
this.name = name;
this.studentClone = studentClone;
} /**
* 重写clone方法,同时调用引用对象的clone方法
*/
public Object clone(){
try {
AddAttrStudentClone aasc = (AddAttrStudentClone) super.clone();
aasc.studentClone = (StudentClone) studentClone.clone();
return aasc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
} }
}

第三步:创建实例,克隆实例,分情况比较

/**
* @createtime 2017年3月21日 下午2:15:49
* @description
*/
public class GeneralCopy {
public static void main(String[] args) {
/**
* 含有引用变量的拷贝(引用变量也实现了cloneable接口)
* 结论:
* 1、拷贝对象创建了一个新的实例
* 2、其中一个对象的任何变量改变时都不会引起另一个对象的变量改变
* 3、拷贝的对象和源对象之间没有任何关系,它们是两个完全独立的个体,即深拷贝
* 4、想要实现深拷贝,在重写clone时还有拷贝它里面的引用变量
* 缺点:这种实现深拷贝的方式要让所有的引用变量都实现cloneable接口,并且要递归地clone所有引用变量,比较复杂
* 解决方法:序列化--反序列化
*/
System.out.println("---------有实现cloneabe接口的引用变量的克隆---------");
StudentClone sc = new StudentClone("张三", 20);
AddAttrStudentClone aasc1 = new AddAttrStudentClone("克隆1", sc);
//克隆对象
AddAttrStudentClone aasc2 = (AddAttrStudentClone) aasc1.clone(); System.out.println(aasc1==aasc2);//false
System.out.println(aasc1.name);//克隆1
System.out.println(aasc2.name);//克隆1
System.out.println(aasc1.studentClone == aasc2.studentClone);//false
System.out.println(aasc1.studentClone.name);//张三
System.out.println(aasc2.studentClone.name);//张三
//改变克隆对象
aasc2.name = "克隆2";
aasc2.studentClone.name = "李四";
System.out.println(aasc1==aasc2);//false
System.out.println(aasc1.name);//克隆1
System.out.println(aasc2.name);//克隆2
System.out.println(aasc1.studentClone == aasc2.studentClone);//false
System.out.println(aasc1.studentClone.name);//张三
System.out.println(aasc2.studentClone.name);//李四 //改变原对象
aasc1.name ="克隆3";
aasc1.studentClone.name = "张三2";
System.out.println(aasc1==aasc2);//false
System.out.println(aasc1.name);//克隆3
System.out.println(aasc2.name);//克隆2
System.out.println(aasc1.studentClone == aasc2.studentClone);//false
System.out.println(aasc1.studentClone.name);//张三2
System.out.println(aasc2.studentClone.name);//李四 }
}

以上代码有三个比较过程

第一个:用原实例克隆一个新实例时,克隆实例除了内存地址外,其他的都跟原实例完全相同;

第二个:改变原实例对象的name属性和其引用变量StudentClone的name属性,此时两个实例的内存地址、基本类型变量、引用类型变量的内存空间地址,引用类型变量的name属性都保持各自的值,不随对方的改变而改变;

第三个:改变原实例对象的name属性和其引用变量StudentClone的name属性,此时两个实例的内存地址、基本类型变量、引用类型变量的内存空间地址,引用类型变量的name属性都保持各自的值,不随对方的改变而改变;

通过比较结果可以看出,无论原实例和克隆实例怎样改变,它们都各自保持自己的值,不受对方的影响,实现了深拷贝,所以得出如下结论:

要用克隆实现深拷贝,则所有类都要实现Cloneable接口,并重写clone方法,并且在包含引用类型变量的类中,要调用其重写的clone方法

根据上面的结论来看,要用克隆方法实现深拷贝很繁琐,当引用变量的包含很深时,就要让所有类型都实现Cloneable接口,且重写clone方法,这显然是一件很繁琐的工作,所以要采取更简单的方法来实现深拷贝,Java中解决深拷贝问题最好的方式就是采用序列化方式,这样各种类均不用实现Cloneable接口的,直接序列化反序列化就可以了,下面我们来实现这个方法.

5、用序列化实现深拷贝

第一步:创建StaffNoSerial类,实现Serializable接口

/**
* @createtime 2017年3月21日 下午3:35:26
* @description 被序列化的员工类
*/
public class StaffNoSerial implements Serializable{
public String name;
public int 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;
} public StaffNoSerial(String name, int age) {
this.name = name;
this.age = age;
} }

第二步:创建一个部门类DepartmentSeria,实现Serializable接口,包含引用变量staffNoSerial

/**
* @createtime 2017年3月21日 下午3:34:24
* @description 被序列化的部门类
*/
public class DepartmentSeria implements Serializable{
public String name;
public StaffNoSerial staffNoSerial; public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public StaffNoSerial getStaffNoSerial() {
return staffNoSerial;
}
public void setStaffNoSerial(StaffNoSerial staffNoSerial) {
this.staffNoSerial = staffNoSerial;
}
public DepartmentSeria(String name, StaffNoSerial staffNoSerial) {
this.name = name;
this.staffNoSerial = staffNoSerial;
} }

第三步:创建一个公司类CompanySerial,实现Serializable接口,包含引用变量departmentNoSerial

/**
* @createtime 2017年3月21日 下午3:33:26
* @description 被序列化的公司类
*/
public class CompanySerial implements Serializable{
public String name;
public DepartmentSeria departmentNoSerial;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public DepartmentSeria getDepartmentNoSerial() {
return departmentNoSerial;
}
public void setDepartmentNoSerial(DepartmentSeria departmentNoSerial) {
this.departmentNoSerial = departmentNoSerial;
} public CompanySerial(String name, DepartmentSeria departmentNoSerial) {
this.name = name;
this.departmentNoSerial = departmentNoSerial;
} }

第四步:序列化实现深拷贝,并分情况比较

/**
* @createtime 2017年3月21日 下午3:38:33
* @description 序列化实现深度拷贝
*/
public class DeepCopySerial { public static void deepCopy() throws Exception{
StaffNoSerial staff = new StaffNoSerial("张三",20); DepartmentSeria department = new DepartmentSeria("研发类", staff); CompanySerial company = new CompanySerial("阿里巴巴", department); CompanySerial companyCopy = null;
/**
* 第一种:文件输入输出流方式
*/
FileOutputStream fos = new FileOutputStream(new File("F:/deep.txt"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(company); FileInputStream fis = new FileInputStream(new File("F:/deep.txt"));
ObjectInputStream ois = new ObjectInputStream(fis);
companyCopy = (CompanySerial) ois.readObject(); /**
* 第二种:字节数组输入输出流方式
*/
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// ObjectOutputStream oos1 = new ObjectOutputStream(baos);
// oos1.writeObject(company);
//
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// ObjectInputStream ois1 = new ObjectInputStream(bais);
// companyCopy = (CompanySerial) ois1.readObject(); //沒做修改的情況下
System.out.println(company==companyCopy);//false
System.out.println(company.name);//阿里巴巴
System.out.println(companyCopy.name);//阿里巴巴
System.out.println(company.departmentNoSerial == companyCopy.departmentNoSerial);//false
System.out.println(company.departmentNoSerial.name);//研发类
System.out.println(companyCopy.departmentNoSerial.name);//研发类
System.out.println(company.departmentNoSerial.staffNoSerial == companyCopy.departmentNoSerial.staffNoSerial);//false
System.out.println(company.departmentNoSerial.staffNoSerial.name);//张三
System.out.println(companyCopy.departmentNoSerial.staffNoSerial.name);//张三 System.out.println("\n修改原對象\n");
company.name = "拷贝原阿里巴巴";
company.departmentNoSerial.name = "拷贝原研发类";
company.departmentNoSerial.staffNoSerial.name = "拷贝原张三";
System.out.println(company==companyCopy);//false
System.out.println(company.name);//拷贝原阿里巴巴
System.out.println(companyCopy.name);//阿里巴巴
System.out.println(company.departmentNoSerial == companyCopy.departmentNoSerial);//false
System.out.println(company.departmentNoSerial.name);//拷贝原研发类
System.out.println(companyCopy.departmentNoSerial.name);//研发类
System.out.println(company.departmentNoSerial.staffNoSerial == companyCopy.departmentNoSerial.staffNoSerial);//false
System.out.println(company.departmentNoSerial.staffNoSerial.name);//拷贝原张三
System.out.println(companyCopy.departmentNoSerial.staffNoSerial.name);//张三 System.out.println("\n修改拷贝對象\n");
companyCopy.name = "腾讯";
companyCopy.departmentNoSerial.name = "财务类";
companyCopy.departmentNoSerial.staffNoSerial.name = "李四";
System.out.println(company==companyCopy);//false
System.out.println(company.name);//拷贝原阿里巴巴
System.out.println(companyCopy.name);//腾讯
System.out.println(company.departmentNoSerial == companyCopy.departmentNoSerial);//false
System.out.println(company.departmentNoSerial.name);//拷贝原研发类
System.out.println(companyCopy.departmentNoSerial.name);//财务类
System.out.println(company.departmentNoSerial.staffNoSerial == companyCopy.departmentNoSerial.staffNoSerial);//false
System.out.println(company.departmentNoSerial.staffNoSerial.name);//拷贝原张三
System.out.println(companyCopy.departmentNoSerial.staffNoSerial.name);//李四 }
}

第五步:运行例子,分析比较结果

/**
* @createtime 2017年3月21日 下午3:57:36
* @description 测试深拷贝
*/
public class DeepCopy {
public static void main(String[] args) {
try {
DeepCopySerial.deepCopy();
} catch (Exception e) {
e.printStackTrace();
}
}
}

通过比较可以看出,效果与4中的深拷贝是一样的,原实例和拷贝实例是两个完全不同的实例,它们有各自的变量,不管是基本类型还是引用类型,都不受对方的约束,想怎么变就怎么变。

第四步代码中有两种用流实现的方式,一种是文件流,另一种是字节数组流方式,两种都可以用来实现深拷贝。

以上就是本次我对Java中深拷贝和浅拷贝的全部理解以及实践,下面是我参考的一篇文章:

http://www.jb51.net/article/88916.htm

本集完   2017-3-21 21:46

Java问题解读系列之IO相关---Java深拷贝和浅拷贝的更多相关文章

  1. Java问题解读系列之String相关---String类为什么是final的?

    今天看到一篇名为<Java开发岗位面试题归类汇总>的博客,戳进去看了一下题目,觉得有必要夯实一下基本功了,所以打算边学边以博客的形式归纳总结,每天一道题, 并将该计划称为java问题解读系 ...

  2. Java问题解读系列之基础相关---抽象类和接口

    今天来说一波自己对Java中抽象类和接口的理解,含参考内容: 一.抽象类 1.定义: public abstract class 类名{} Java语言中所有的对象都是用类来进行描述,但是并不是所有的 ...

  3. Java问题解读系列之基础相关---含继承时的执行顺序

    今天来研究一下含继承.静态成员.非静态成员时Java程序的执行顺序: 一.不含继承,含有静态变量.静态代码块 创建一个子类,该类包含静态变量.静态代码块.静态方法.构造方法 /** * @create ...

  4. Java问题解读系列之String相关---String、StringBuffer、StringBuilder的区别

    今天的题目是String.StringBuffer和StringBuilder的区别: 首先还是去官方的API看看对这三种类型的介绍吧,Go...... 一.继承类和实现接口情况 1.String类 ...

  5. Java问题解读系列之String相关---String类的常用方法?

    今天的题目是:String类的常用方法? 首先,我们在eclipse中定义一个字符串,然后使用alt+/就会出现String类的所有方法,如下图所示: 下面我就挑选一些常用的方法进行介绍: 首先定义两 ...

  6. [转]Java多线程干货系列—(一)Java多线程基础

    Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们 ...

  7. Java多线程干货系列—(一)Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...

  8. Java基础扫盲系列(三)— Java内省技术

    前言 Java内省技术属于Java基础体系的的一部分,但是很多人都不甚了解.笔者也是在学习Spring源码的过程中遇到该技术模块的.为了完善技术体系,本文将全面的学习该技术.在提到Java内省技术,就 ...

  9. java对象深复制、浅复制(深拷贝、浅拷贝)的理解

    先看一个例子 User user1 = new User(); user1.setId("111"); Map<String, User> map1 = new Has ...

随机推荐

  1. 前端CSS样式操作

    目录 字体和文字 设置标签的宽高 字体属性 文字的属性 文字对齐 text-align 文字装饰 text-decoration 首行缩进 text-indent 背景属性 背景图片 边框 画圆 di ...

  2. 洛谷 2197 nim游戏

    题目描述 甲,乙两个人玩Nim取石子游戏. nim游戏的规则是这样的:地上有n堆石子(每堆石子数量小于10000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取.每次只能从一堆里 ...

  3. KNN算法和实现

    KNN要用到欧氏距离 KNN下面的缺点很容易使分类出错(比如下面黑色的点) 下面是KNN算法的三个例子demo, 第一个例子是根据算法原理实现 import matplotlib.pyplot as ...

  4. gnome-tweak-tool设置gnome参数, 修改CENTOS7桌面图标大小

    GNOME Tweak Tool 是 GNOME 3 的优化配置工具,为我们带来 GNOME Shell 扩展安装功能,方便Linux用户对 Gnome Shell 进行一些调整. 主要功能有:安装, ...

  5. ORA-01790: 表达式必须具有与对应表达式相同的数据类型

    出现这种错误,要先看一下是不是sql中有用到连接:union,unio all之类的,如果有,需要注意相同名称字段的数据类型一定要相同.

  6. redies安装

    第一步下载redies 下载地址:https://github.com/microsoftarchive/redis/releases 第二步修改文件夹名为redies 第三步运行redies 打开一 ...

  7. python使用SUDS调用webservice

    最近做接口对接,遇到了.net开发的webservice接口,因为python第一次与webservice对接,连问带查,最后使用suds库来实现了 1.安装suds mac: sudo pip in ...

  8. MYSQL实现列拼接,即同一个字段,多条记录拼接成一条

    一.首先,新建三张表 DROP TABLE IF EXISTS `article`; CREATE TABLE `article` ( `id` ) unsigned NOT NULL AUTO_IN ...

  9. sas单变量的特征分析

    sas单变量的特征分析 大炮,我有个烦恼,我领导最近老叫我单变量结合因变量分析,但是都是分段分析,我总是写proc sql然后group by ,但是这个过程好无聊啊,有木有什么新的代码,让我可以分析 ...

  10. c++ string 字符串

    转载   http://www.renfei.org/blog/introduction-to-cpp-string.html 运算符重载 + 和 +=:连接字符串 =:字符串赋值 >.> ...