容器
1.(比较器)自定义顺序的TreeSet
题目:默认情况下,TreeSet中的数据是从小到大排序的,不过TreeSet的构造方法支持传入一个Comparator,使其倒序输出:
Comparator<? super Integer> c;
private ArrayList<Integer> list=new ArrayList<>();
public TreeSet(Comparator<? super Integer> c) {
this.c=c;
}
//正序输出
public TreeSet() {
this.c=new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;//正就正序输出,负就反序输出
}
};
}
public void add(int n) {
list.add(n);
}
@Override
public String toString() {
// TODO Auto-generated method stub
Collections.sort(list,c);
return list.toString();
}
public static void main(String[] args) {
TreeSet t=new TreeSet(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO Auto-generated method stub
return o2-o1;
}
});
//TreeSet t=new TreeSet();
t.add(1);
t.add(5);
t.add(3);
System.out.println(t);
}
java库里面是有TreeSet函数的。我这里重新写了一个。
总结: 重载构造器里面传递是对象参数,Collections.sort()需要的是一个对象,这个对象能够提供比较哪一个参数的信息。与之前遇到的相似,重载函数是把内容传递给成员变量(类属性),通过这种方式去改变成员变量的值。此外,也可以不传递给成员变量,可以仅仅无用的工作。
2.(几种Set)用LinkedHashSet实现PI的函数
题解:1.第一种思路是利用LinkedHashSet的元素不重复,且按照插入顺序排序,那么其size()最多为10,那么就可以采取取余计算,代码如下:
LinkedHashSet<Integer> numberSet2 =new LinkedHashSet<Integer>();
boolean pi=true;
double p=Math.PI;
while(pi){
if(numberSet2.size()==10) break;
numberSet2.add((int) (p%10));
p*=10;
}
System.out.println(numberSet2);
输出结果为:
[3, 1, 4, 5, 9, 2, 6, 8, 7, 0]
上面的题解虽然可以,但是有一个问题就是其准确度有待考察,因为double类型只能够保证小数点后15位为精确的,超过的部分就是不准确的,所以*10不太准确,从这个算法来看0可能是系统带来的误差。
java中double只考虑小数点15位,所以在运算的时候也是优先15位的运算,在转为字符数组的时候就是小数点15位。
观察如下代码:
double dou=(double)(10)/3.;
System.out.println(dou);
for(int i=0;i<1000;i++) {
System.out.print((int)(dou%10));
dou*=10;
}
运算结果:
3333333333333334408602464042028080024004086208044040
题解:2.可以先将Math.PI转换为字符数组,然后将字符数组元素添加到LinkedHashSet中去。在这里面首先得知道转换为字符数字的时候,PI的位数就是double提供的位数,而题解1中的位数随着*10运算,不断显示小数点最后的数字。
LinkedHashSet<Character> numberSet3 =new LinkedHashSet<Character>();
String str = String.valueOf(Math.PI);
System.out.println(str);
String k=str.replace(".", "");
char[] cha=k.toCharArray();
for(char c:cha) {
numberSet3.add(c);
}
System.out.println(numberSet3);
题解:3.与2相似,容器中加入的不是字符而是字符串
LinkedHashSet<String> numberSet4=new LinkedHashSet<>();
String str = String.valueOf(Math.PI);
String k=str.replace(".", "");
for(int i=0;i<k.length();i++) {
numberSet4.add(String.valueOf(k.charAt(i)));
}
System.out.println(numberSet4);
IO
1.遍历文件夹
题目:(文件对象)一般说来操作系统都会安装在C盘,所以会有一个 C:\WINDOWS目录。遍历这个目录下所有的文件(不用遍历子目录)找出这些文件里,最大的和最小(非0)的那个文件,打印出他们的文件名
题解:1.我自己的想法就是对于定位问题,可以使用HashMap来做(快速),从而来解决由此来带的各种问题,最终从解决问题的角度看,还有一些不足,通过get(key)函数,获取到的File对象只有一个,万一要是有两个呢?put函数在放key相同时会覆盖掉之前的那个键对值。
代码如下:
//时间复杂度为O(n)
File file3=new File("C:/Windows");
File[] file33=file3.listFiles();
long s=0;
TreeSet<Long> filelength=new TreeSet<>();
for(int i=0;i<file33.length;i++) {
filelength.add(file33[i].length());
}
if (filelength.first()==0) {
s=filelength.higher((long) 0);
System.out.println("最小字节单位"+filelength.higher((long) 0));
} else {
System.out.println("最小字节单位0");
}
System.out.println("最大字节单位"+filelength.last());
HashMap<Long,File> hashmap=new HashMap<>();
for(int i=0;i<file33.length;i++) {
hashmap.put(file33[i].length(), file33[i]);
}
System.out.println(hashmap.get(s));//最小文件
System.out.println(hashmap.get(filelength.last()));//最大文件
题解:2.采用递归的办法去找到字节最大和最小的文件,并输出字节长度
①
//时间复杂度为O(n)
public static void searchfile(File[] file,long minl) {
long minlength=minl;
boolean s=true;
int k=0;
for(int i=0;i<file.length;i++) {
if (s) {
if (file[i].length() < minlength && file[i].length() != 0) {
System.out.println("最小字节为:" + file[i].length() + " 位置在: " + file[i]);
s = false;
}
}
//代码块A
if (!s) {
if (i != 0&&k==0) {
i = 0;
k=1;
continue;
}
if (file[i-1].length() > file[i].length()) {
File temp = file[i-1];
file[i-1] = file[i];
file[i] = temp;
}
if(i==file.length-1)
System.out.println("最大字节为:" + file[file.length-1].length() + "位置在:" + file[0]);
}
}
//代码块B
// if (!s) {
// for (int j = 0; j < file.length - 1; j++) {
// if (file[j].length() > file[j+1].length()) {
// File temp = file[j];
// file[j] = file[j+1];
// file[j+1] = temp;
// }
// }
// System.out.println("最大字节为:" + file[file.length-1].length() + "位置在:" + file[0]);
// }
if(s) searchfile(file,++minlength); //这是一个非常容易出错的地方++a
}
public static void main(String[] args) {
// TODO Auto-generated method stub
File file=new File("C:/Windows");
File[] files=file.listFiles();
long time1=System.currentTimeMillis();
searchfile(files,0);
long time2=System.currentTimeMillis();
System.out.println("运行时间为:"+(time2-time1)+"ms");
}
通过构造一个递归函数,输出是文件数组和最小字节,递归就是能够创造一个无限循环,直至满足条件循环终止!所以必须得找一个循环终止的条件。上面的办法计算量还有点不好的地方在于,如果文件最小字节比较大,从0开始将会导致计算时间很久,所以如果将文件本身的字节长度放进输入参数里面去,将会减少运算次数。
②
//时间复杂度为O(n)
public class TestFileD {
int i=0;
File minf;
File maxf;
long minl=Integer.MAX_VALUE;
long maxl=0;
public static void main(String[] args) {
File file=new File("C:/Windows");
File[] files=file.listFiles();
TestFileD f=new TestFileD();
long time1=System.currentTimeMillis();
f.search(files);
long time2=System.currentTimeMillis();
System.out.println("最小文件: "+f.minf+" ,最小文件长度 "+f.minf.length());
System.out.println("最大文件: "+f.maxf+" ,最大文件长度 "+f.maxf.length());
System.out.println(time2-time1);
}
private void search(File[] files) {
if(i>=files.length) {
return ;
}else {
if(files[i].length()<minl&&files[i].length()>0) {
minf=files[i];
minl=files[i].length();
}
if(files[i].length()>maxl) {
maxf=files[i];
maxl=files[i].length();
}
}
if(i++<files.length) search(files);
}
}
总结: 递归是一种看不见“for”的循环,因为递归需要满足一定的条件才能进行递归,递归最后就是总是得有一个结束的条件,否则就进入了死循环。在递归里面,1.每一次递归要干什么事情,这件事情必须是基础性的;2.每一次递归有无条件,这决定了递归的有限性;3.在下一次递归中,如何利用前面递归得到的结果。
2.写入数据到文件
题目:OutputStream是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。FileOutputStream 是OutputStream子类,以FileOutputStream 为例向文件写出数据。注: 如果文件d:/lol2.txt不存在,写出操作会自动创建该文件。但是如果是文件 d:/xyz/lol2.txt,而目录xyz又不存在,会抛出异常
File nfil=new File("F:/Dashuaige/cdk/fhg/ds/wwd/nfil.txt");
if(nfil.getParentFile().mkdirs()) {
System.out.println("创建了文件"+!nfil.getParentFile().mkdirs());
}
System.out.println(nfil.getParent());
byte[] k= {
'a','b'};
try {
FileOutputStream fuck = new FileOutputStream(nfil);
try {
fuck.write(k);
} catch (IOException e) {
e.printStackTrace();
}
try {
fuck.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
利用mkdirs()函数,创建F:/Dashuaige/cdk/fhg/ds/wwd这一系列的文件夹,包括wwd也是一个文件夹,如果wwd在父目录下已经被命名过了,那么创建失败。如果父文件夹不存在,那么将会创建父文件夹。如果父文件夹存在,那么只创建自身命名的文件夹(例:ds存在,wwd不存在,那么将创建wwd)。
比较:mkdis()函数,如果父文件夹存在,那么创建;父文件夹不存在,创建无效。
3.拆分文件
题目:找到一个大于100k的文件,按照100k为单位,拆分成多个子文件,并且以编号作为文件名结束。比如文件 eclipse.exe,大小是309k。拆分之后,成为eclipse.exe-0;eclipse.exe-1;eclipse.exe-2;eclipse.exe-3
题解:
public static void main(String[] args) throws IOException {
get("F:/Wangchuang/execise.docx");
}
//主体程序输出
public static void get(String filepath) throws IOException {
File file=new File(filepath);
FileInputStream filestream=new FileInputStream(file);
if(file.length()*1024>Integer.MAX_VALUE) {
System.out.println("文件内存太大");
}else {
byte[] filebyte = new byte[(int) file.length()];
filestream.read(filebyte);
filestream.close();
int count=(int)(file.length()/1024/100)+1;
for(int k=0;k<count;k++) {
writefile(file,k).write(get(filebyte,k*1024*100,(k+1)*1024*100));
}
}
}
//返回每一个文件要读入的字节数组
public static byte[] get(byte[] filebyte,int start,int end) {
if(end>filebyte.length) {
end=filebyte.length;
}
byte[] exebyte=new byte[end-start];
for(int i=start;i<end;i++) {
exebyte[i-start]=filebyte[i];
}
return exebyte;
}
//创建文件
public static FileOutputStream writefile(File file,int i) throws FileNotFoundException {
File exe=new File(file.getParent()+"/eclipse.exe-"+i);
return new FileOutputStream(exe);
}
4.合并文件
题目:将上一题分解的文件合并起来,并查看能否运行。
题解:1.分别将文件以字节流的形式read到一个字节数组当中;将字节数组write到一个新的文件当中。2.所以最关键的部分是第一步,怎么将文件以字节流的形式放到字节数组中。
①
public static void body(String filepath) throws IOException {
File file=new File(filepath);
File[] files=file.listFiles();
long size=0;
for(File f:files) {
size=size+f.length();
}
byte[] bossbyte=new byte[(int) size];
System.out.println(size);
File bossfile=new File(filepath+"/boss.docx");
//得到bossbyte
int k=0;
for (int j = 0; j < files.length; j++) {
if(j==0) {
k=0;
}else {
k=k+(int) files[j-1].length();
}
for(int i=0;i<files[j].length();i++) {
bossbyte[i+k]=insertbyte(files[j])[i];
}
}
FileOutputStream bossfilestream=new FileOutputStream(bossfile);
bossfilestream.write(bossbyte);
bossfilestream.close();
}
//用byte
public static byte[] insertbyte(File file) throws IOException {
byte[] bytes=new byte[(int) file.length()];
FileInputStream fos=new FileInputStream(file);
fos.read(bytes);
fos.close();
return bytes;
}
public static void main(String[] args) throws IOException {
body("F:/Wangchuang/eclipse");
}
这个程序运行时间很久,运行了38523ms。里面有大量的循环,执行循环要花费时间很久。
对这个算法做一点修改,运算速度提高十倍:
byte[] byte11=insertbyte(files[j]);
long time1=System.currentTimeMillis();
for(int i=0;i<files[j].length();i++) {
bossbyte[i+k]=byte11[i];
}
long time2=System.currentTimeMillis();
执行循环的时候,每一次循环都要调用函数,现在只调用一次,就把时间提高了十倍。
②
public static void getbody(String filepath) throws IOException {
StringBuffer sb=new StringBuffer();
File file=new File(filepath);
File files[]=file.listFiles();
String s="";
for(int i=0;i<files.length;i++) {
sb.append(new String(filereadtobyte(files[i]),"ISO-8859-1"));
}
System.out.println("s的长度为:"+s.length());
File bossfile =new File(filepath+"/boss.docx");
byte[] bossbyte=new String(sb).getBytes("ISO-8859-1");
System.out.println(bossbyte.length);
FileOutputStream fos=new FileOutputStream(bossfile);
fos.write(bossbyte);
fos.close();
}
//文件以字节流形式read到字节数组当中
public static byte[] filereadtobyte(File file) throws IOException {
FileInputStream filein=new FileInputStream(file);
byte[] bytes=new byte[(int) (file.length())];
filein.read(bytes);
filein.close();
return bytes;
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
long time1=System.currentTimeMillis();
getbody("F:/Wangchuang/eclipse");
long time2=System.currentTimeMillis();
System.out.println(time2-time1+"ms");
}
这个程序运行时间只有23ms,因为里面避免了很多循环,基本都是用API函数完成的。但是这里面有一个问题,解决了一个困扰了我很久的bug,见文章:
字节数组转换为字符串的时候会发生数据损失
③ArrayList
5.文件加密
题目:加密算法:
数字:如果不是9的数字,在原来的基础上加1,比如5变成6, 3变成4;如果是9的数字,变成0
字母字符: 如果是非z字符,向右移动一个,比如d变成e, G变成H;如果是z,z->a, Z-A。字符需要保留大小写
非字母字符:比如’,&^ 保留不变,中文也保留不变
/*
* @加密
* @20200914
*/
public class Suibi {
//采用系统默认编码方式GBK
public static void encodeFile(File encodingFile, File encodedFile) {
try(FileReader fr=new FileReader(encodingFile);
FileWriter fw=new FileWriter(encodedFile);){
String s=getString();
char[] chars=new char[(int)encodingFile.length()];
fr.read(chars);
System.out.println("加密前文件内容:\n"+new String(chars));
for(int i=0;i<chars.length;i++) {
chars[i]=getencoding(chars[i],s