P1162填涂颜色
---------------------------------------------------------------------------------------------------------------------------------
解题思路:
*1.从外围区域开始拓展,输入区域为(1~~n)*(1~~n);外围区域为(0~~n+1)*(0~~n+1)
2.从原点(0,0)开始进行拓展,当拓展遇到1时就不再进行拓展;
3.拓展的点都在标记数组中标记为0;
4.被'1'围住的数据不作标记;
---------------------------------------------------------------------------------------------------------------------------------
#include <iostream>
using namespace std;
int a[32][32], b[32][32];
int dx[5] = { -1,1,0,0 };//对数据进行拓展(上,下,左,右)
int dy[5] = { 0,0,-1,1 };
int n, i, j;
void dfs(int p, int q) {
int i;
if (p<0 || p>n + 1 || q<0 || q>n + 1 || b[p][q] != 0)//不在范围为内的数据则不标记
return;//作用相当于break;
b[p][q] = 1;
for (i = 1; i <= 4; i++)
dfs(p + dx[i], q + dy[i]);//进一步拓展;
}
int main() {
cin >> n;//输入n,表示n*n的矩阵;
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++) {
cin >> a[i][j];
if (a[i][j]==0)//如果拓展到的数据为0则在标记数组里标记为0
b[i][j] = 0;
else b[i][j] = 1;//如果输入数据为一则标记该点为一;
}
dfs(0, 0);//进行拓展
for (i = 1; i <= n; i++) {//输出部分
for (j = 1; j <= n; j++)
if (b[i][j] == 0)
cout << 2 << ' ';//如果没有被便利,则输出2;
else
cout << a[i][j] << ' ';
cout << endl;
}
}
文件与IO
File
如果我们想使用 Java 代码来操作文件,就需要使用到 java.io.File
类,它是文件和文件目录的抽象表示形式。
在创建 File 类对象的时候,需要指定路径,这样 File 对象就可以与硬盘上的文件或目录建立映射,通过操作 File 类对象来实现对文件或目录的操作。当然指定路径的文件或目录可能不存在,可以通过 File 对象的方法进行判断或创建文件。
使用 File 对象能新建、删除、重命名文件和目录,但不能访问文件内容本身,如果需要访问文件内容本身,则需要使用后面的输入/输出流。
下面演示一下 File 类的使用。
1 创建 File 对象
可以通过文件的绝对路径来创建文件对象,下面在 C 盘 Document 文件夹下创建一个 text.txt 的 File 对象:
File file = new File("C:\\Document\\test.txt");
也可以使用相对路径:
File file = new File("test.txt");
上面表示在当前工作目录下创建 text.txt 的 File 对象,在当前项目中,工作目录就是项目的根目录。
也可以通过指定父路径和当前文件文件的名称来创建:
File file = new File("C:\\Document", "test.txt");
关于绝对路径和相对路径
绝对路径是指从文件系统的根目录(在Windows中通常是某个驱动器,如C:\
;在Unix或Linux中通常是/
)开始,到目标文件或目录的完整路径。它不会依赖于当前工作目录的位置。
示例:
- 在Windows中:
C:\Users\UserName\Documents\file.txt
- 在Unix或Linux中:
/home/username/documents/file.txt
相对路径是指从当前工作目录开始,到目标文件或目录的路径。它会依赖于当前工作目录的位置。
示例:
- 如果当前工作目录是
C:\Users\UserName
,那么相对路径Documents\file.txt
指向C:\Users\UserName\Documents\file.txt
。 - 如果当前工作目录是
/home/username
,那么相对路径documents/file.txt
指向/home/username/documents/file.txt
。
2 常用操作
有了 File 对象,就可以进行下面的一些列操作了。
package com.doubibiji;
import java.io.File;
import java.io.IOException;
public class FileTest {
public static void main(String[] args) throws IOException {
// 在项目根目录下创建 test.txt 对象
File file = new File("test.txt");
// 创建文件
file.createNewFile();
// 判断文件是否存在
System.out.println(file.exists());
// 判断文件是否是目录
System.out.println(file.isDirectory());
// 判断是否是文件
System.out.println(file.isFile());
// 判断文件是否可读
System.out.println(file.canRead());
// 判断文件是否可写
System.out.println(file.canWrite());
// 判断是否是隐藏文件
System.out.println(file.isHidden());
// 获取文件的绝对路径
System.out.println(file.getAbsolutePath());
// 删除文件,这里屏蔽是因为上面创建了文件,这里又删除了,好像什么都没有发生,屏蔽是为了看到这个文件
// file.delete();
}
}
3 创建目录
在实际的开发中,我们会经常遇到这样的需求,判断某个路径是否存在,如果不存在就创建这个目录 。
下面使用代码实现:
public static void main(String[] args) {
// 指定要创建的目录
String filePath = "C:\\docs\\images";
File file = new File(filePath);
// 判断目录是否存在
if (!file.exists()) {
// 创建目录,会按照结构一层一层创建
boolean result = file.mkdirs();
if (result) {
System.out.println("目录创建成功: " + filePath);
} else {
System.out.println("目录创建失败: " + filePath);
}
} else {
System.out.println("目录已存在: " + filePath);
}
}
5 文件过滤器
上面通过 listFiles()
方法列出目录下所有的文件,我们也可以在列出文件的时候,通过过滤器,列出想要的文件。
例如,下面列出目录下所有的 .jpg
文件
public static void main(String[] args) {
File file = new File("C:\\docs\\images");
if (file.isDirectory()) {
// 获取目录下所有的文件
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File subFile) {
return subFile.isFile() && subFile.getName().endsWith("jpg");
}
});
// 获取到所有的jpg文件,就可以针对这些文件进行操作,例如下面删除这些文件
for (File jpgFile : files) {
System.out.println(jpgFile.delete());
}
} else {
System.out.println("不是目录");
}
}
7 删除目录下的所有文件
如果一个目录下有文件,是无法通过 file.delete()
来删除的。如果要删除一个目录,需要将这个目录下所有的文件和目录都删掉,然后才能删除该目录。这里就涉及到递归删除了,因为该文件夹下还可能有文件夹。
下面写了一个方法 deleteFile()
,可以用来删除文件或目录:
public class FileTest {
public static void main(String[] args) {
File directoryToDelete = new File("docs/images");
// 删除目录
deleteFile(directoryToDelete);
}
/**
* 删除文件或目录
*/
public static void deleteFile(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File subFile : files) {
deleteFile(subFile); // 递归删除子文件和子目录
}
}
}
// 在此点,目录应该是空的(或本身就不存在),可以安全删除
boolean success = file.delete();
if (success) {
System.out.println("文件已删除: " + file.getAbsolutePath());
} else {
System.out.println("删除文件失败: " + file.getAbsolutePath());
}
}
}
在上面的 deleteFile
方法中,使用了递归删除。
8 File.separator
File.separator 是 Java 中表示系统默认文件目录分隔符的静态变量。
在 Windows 系统中,文件目录分隔符为 \
,而在 Linux 和 UNIX 系统中,文件目录分隔符为 /
。使用 File.separator
可以确保在任何系统下都能正确表示文件路径,从而避免了因为系统差异导致的路径错误问题。
例如,要在 temp 目录下建立一个名为 test.txt 的文件,可以这样做:
File myFile = new File("C:" + File.separator + "temp" + File.separator + "test.txt");
1
这样,无论是在Windows系统还是Linux系统,都能正确地创建文件。在编程时,应尽量使用这些与系统相关的字段,以确保代码的跨平台兼容性。
9 常用方法
下面列出 File 对象常用的一些方法:
方法 | 含义 |
---|---|
file.createNewFile(); | 创建文件,创建成功返回true,如果存在就不创建了,返回false |
file.mkdir(); | 创建文件夹,创建成功返回true,如果存在就不创建了,返回false |
file.mkdirs("cc/dd"); | 创建多级目录,创建成功返回true,如果存在就不创建了,返回false |
file.renameTo(); | 如果路径相同,就是改名,如果路径不同,就是改名并剪切 |
file.delete(); | 删除,Java中删除不走回收站,要删除一个文件夹,请注意该文件夹内不能包含文件或文件夹 |
file.setReadable(false); | 设置文件不可读 |
file.canRead(); | windows系统认为所有的文件都是可读的,可读表示是否可以使用IO流读取 |
file.getAbsolutePath(); | 获取绝对路径 |
file.getPath(); | 获取构造方法中传入的路径,如果传给构造方法的是绝对路径就返回的是绝对路径,如果传递给的是相对路径就返回的是相对路径 |
file.list(); | 获取文件夹下的所有文件或文件夹的名称 |
file.listFiles(); | 获取文件夹下的所有文件或文件夹的File对象 |
IO流
上面在使用 File 对象来进行操作的时候,是无法对文件的内容进行操作的,如果要对文件进行读写,就需要用到 IO 流。
Java对数据的操作是通过流的方式,用于操作流的类都在IO包中;IO流用来处理设备之间的数据传输的,为 数据源
和 目的地
建立一个输送通道,也就是从哪里读取数据和将数据写到哪里;
流按照流向分为两种:
输入流
程序从输入流读取数据源数据。数据源包括外界(键盘、文件、网络…),即是将数据源的数据读入到程序的通信通道。
输出流
程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道;
流按照类型分为两种:
字节流
数据流中最小的数据单元是字节,字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的;
字符流
数据流中最小的数据单元是字符,字符流只能操作纯字符数据, Java中的字符是Unicode编码,一个字符占用两个字节。
字节流可以操作任何数据了,为什么还需要字符流?
为了方便,可以使用不同的编码进行读取和写出;因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。
1 IO操作主要的类
1 File(文件特征与管理)
用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
2 InputStream(二进制格式操作)
抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
3 OutputStream(二进制格式操作)
抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
InputStream和OutputStream是字节流的最顶层的抽象父类,需要使用其子类来实现功能。
4 Reader(文本格式操作)
抽象类,基于字符的输入操作。
5 Writer(文本格式操作)
抽象类,基于字符的输出操作。
Reader和Writer是字符流的最顶层的抽象父类,需要使用其子类来实现功能。
通过名称就可以分辨出是字节流还是字符流,以Stream还是Reader/Writer结尾。
6 RandomAccessFile(随机文件操作)
一个独立的类,直接继承至Object,它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
字节流
1 FileInputStream
FileInputStream
用于读取文件数据。
1 读取一个字节
有一个 xxx.txt
文件,其中的内容为:abc
FileInputStream fis = new FileInputStream("xxx.txt");
int x = fis.read();
System.out.println(x);
fis.close();
fis.read()
方法会读取文件的一个字节,复制给 int x
,最终打印的结果是 97
。
数据存储在硬盘上是以二进制保存的,字母 a
对应的二进制数据以十进制表示就是 97
,可以查看ASCII码表查看。
fis.read()
每读取一次,指针会向后移动,读取下一个字节。当读到文件的末尾,没有数据时,会返回 -1
,表示文件结束。
2 一个一个读取所有字节
使用一个循环,使用 read()
方法一直读取,直到读取到的内容是 -1
。
FileInputStream fis = new FileInputStream("xxx.txt");
int b;
while((b = fis.read()) != -1) {
System.out.println(b);
}
fis.close();
read()
方法读取的是一个字节,为什么返回是 int
,而不是 byte
?
因为字节输入流可以操作任意文件,比如图片音频等,这些文件底层都是以二进制形式存储的,如果每次都读取返回 byte
,有时候可能在中间的时候遇到 11111111
,那么这个 11111111
是byte类型的 -1
,程序是遇到 -1
就会停止不读了,后面的数据就读取不到了,所以在读取的时候用int类型接收,如果 11111111
会在前面补上 24 个 0
凑足int的4个字节,那么 byte
类型的 -1
就变成int类型的 255
了,这样就可以保证整个数据读完,而结束标记的 -1
就是int类型。
2 FileOutputStream
FileOutputStream
用于写入数据到文件。FileInputStream,FileOutputStream只是读和写的区别。
1 写入一个字节
FileOutputStream fos = new FileOutputStream("yyy.txt"); //创建字节输出流对象,如果没有就自动创建文件
fos.write(97);
fos.write(98);
fos.write(99);
fos.close();
write()
方法会写出一个字节,虽然写出的是 int
数,但是到文件上是一个字节,会自动去除 int
前三个 8位。所以最终 yyy.txt
文件的内容为:abc
。
上面在写入的时候,是会覆盖原来文件中的内容,如果想在原来的内容后面追加内容,可以在创建 FileOutputStream
指定第二个参数:
new FileOutputStream("yyy.txt", true); //第二个参数表示追加
3 IO流功能的核心代码
下面使用 FileInputStream
和 FileOutputStream
来完成文件的复制。
文件的复制也就是读取一个文件的内容,写入到另一个文件。
复制文件功能的样例:
//创建输入流对象
FileInputStream fis = new FileInputStream("aaa.jpg");
//创建输出流对象
FileOutputStream fos = new FileOutputStream("bbb.jpg");
int b;
//不断的读取每一个字节
while ((b = fis.read()) != -1) {
//将每一个字节写出
fos.write(b);
}
//关闭流释放资源
fis.close();
fos.close();
#4 缓存数组
上面的代码都是一个字节一个字节的读取和写出,效率非常低,如何才能提高效率呢?可以使用一个字节数组,一次读取多个字节,一次写出多个字节。如下:
xxx.txt
的内容为:abc
// 创建输入流对象
FileInputStream fis = new FileInputStream("xxx.txt");
// 设置一个长度为2的缓存数组
byte[] arr = new byte[2];
// 读取两个字节
int a = fis.read(arr);
System.out.println("a:" + a);
// 查看一下读取的内容
for (byte b : arr) {
System.out.println(b);
}
System.out.println("-----------");
// 再次读取两个字节
int c = fis.read(arr);
System.out.println("c:" + c);
// 查看一下读取的内容
for (byte b : arr) {
System.out.println(b);
}
//关闭流释放资源
fis.close();
read(byte[])
方法会读取 byte[]
长度的字节到该字节数组中,同时返回读取的长度。
xxx.txt
的内容为 abc
,为何第二次读取却读取到了99和98?因为第一次读取到两个字节为97和98,第二次读取的时候,将第三个字节读取覆盖数组的第一个位置,后面没有读取的内容了,但数组后面的内容是之前读取到的内容,并没有被修改。所以在写出数据的时候一定要注意,通过使用 read(byte[])
方法的返回值,即读取的长度来控制写入。
5 完整的输入输出流
通过使用一个数组来读取,可以提高读取的效率,下面循环来读取就可以了。
完整的文件输入输出流代码如下:
//创建输入流对象
FileInputStream fis = new FileInputStream("xxx.txt");
//创建输出流对象
FileOutputStream fos = new FileOutputStream("yyy.txt");
byte[] arr = new byte[2];
int len;
while ((len = fis.read(arr)) != -1) {
//控制写入长度
fos.write(arr, 0, len);
}
//关闭流释放资源
fis.close();
fos.close();
fis.read(arr)
返回读取长度,如果等于 -1
表示读取结束,写出的时候,将数组从第0个写出到len个长度。
数组的尺寸一般定义为1024的整数倍1024 * n。
1 使用Buffered读取写入
使用方法如下:
使用 BufferedInputStream 对 FileInputStream 进行一层包装即可。
//创建输入流对象,关联文件
FileInputStream fis = new FileInputStream("xxx.txt");
//创建输出流对象,指定输入对象
FileOutputStream fos = new FileOutputStream("yyy.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//定义的数组的意思是每次读取1024个字节到缓冲区
byte[] arr = new byte[1024];
int len;
while ((len = bis.read(arr)) != -1) {
bos.write(arr, 0, len);
}
//只需要关闭包装后的流对象
bis.close();
bos.close();
在关闭资源的时候,只需要关闭包装后的流对象即可,因为可以这样创建BufferedInputStream:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("xxx.txt"));
7 flush方法
在写入文件的时候,会将文件的内容写入到缓冲区,如果想将缓冲区的内容立刻写入到文件,可以调用输出流的 flush()
方法。
flush 和 close 方法的区别:
close()
方法具备刷新的功能,在关闭流之前,会先刷新一次缓冲区,将缓冲区的字节全都刷新到文件上,然后在关闭。 flush()
方法具备刷新缓冲区,将缓冲区数据写到文件上的功能,刷新完成之后可以继续写。如果想将数据写出,就需要 flush
缓冲区。
8 流的标准异常处理
我们使用 IO 流的时候,会有一些异常,在整个处理的过程中,需要考虑资源的关闭,如果不及时关闭资源,会造成资源泄露,进而引发性能问题或内存溢出等错误。
标准的流的异常处理如下:
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("xxx.txt");
fos = new FileOutputStream("yyy.txt");
byte[] arr = new byte[1024];
int len;
while ((len = fis.read(arr)) != -1) {
fos.write(arr, 0, len);
}
} catch (IOException e) {
// 处理读写文件时抛出的异常
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
// 处理fis.close()抛出的异常
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
// 处理fos.close()抛出的异常
e.printStackTrace();
}
}
}
try finally嵌套,能关闭一个资源就关闭一个资源,上面是1.6版本的写法。
Java1.7版本对输入输出流进行了优化,提供了自动关闭的功能
try (
FileInputStream fis = new FileInputStream("xxx.txt");
FileOutputStream fos = new FileOutputStream("yyy.txt");
)
{
byte[] arr = new byte[1024];
int len;
while ((len = fis.read(arr)) != -1) {
fos.write(arr, 0, len);
}
}
代码在执行完大括号中的内容后,会自动关闭小括号中的对象。
因为FileInputStream和FileOutputStream实现了AutoCloseable接口,所以在小括号中是无法创建我们自己编写的对象的,除非我们自己编写的对象也实现了AutoCloseable接口:
static class MyClass implements AutoCloseable {
@Override
public void close() {
System.out.println("我关闭了");
}
}
try (
FileInputStream fis = new FileInputStream("xxx.txt");
FileOutputStream fos = new FileOutputStream("yyy.txt");
MyClass myClass = new MyClass();
)
{
...
}
InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
try {
prop.load(in);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
} catch (Exception e) {
e.printStackTrace();
}