导读
一直用着的
2020
2020
2020科技版在今天终于再也没办法用科技打开了,虽然用VScode
能进行一定的操作,但还是由于被IDEA
惯坏了,新建任何东西都得折腾一下。最终,用上了
2023.1
2023.1
2023.1版本。新建项目的时候,本着学习的心态,让他自己生成了新手教程,然后发现了一些问题,去查阅了一番源码,做笔记如下。
问题发现
2023 2023 2023版在生成新手教程的代码的时候,会给你这些:
// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.
public class Main {
public static void main(String[] args) {
// Press Alt+Enter with your caret at the highlighted text to see how
// IntelliJ IDEA suggests fixing it.
System.out.println("Hello and welcome!");
// Press Shift+F10 or click the green arrow button in the gutter to run the code.
for (int i = 1; i <= 5; i++) {
// Press Shift+F9 to start debugging your code. We have set one breakpoint
// for you, but you can always add more by pressing Ctrl+F8.
System.out.println("i = " + i);
}
}
}
其实是很基础的一些快捷键用法,非常详细,非常贴心。
就在我准备全删掉写自己的东西的时候,我突然发现编译器在printf
上给了一个提示:
冗余调用`printf`
同时建议我修改为print
。
所以这些是有差别的吗?
print在官方源码中是这么写的:
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
嗯?有点简短啊。不确定,再看看。
write
方法又是这么定义的:
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
突然一下难度升了好高!别急,一句句来。
ensureOpen
方法是这么定义的:
private void ensureOpen() throws IOException {
if (out == null)
throw new IOException("Stream closed");
}
看来是为了保证IO流正常运行。那究其原因又是什么?
我们可以看到定义这个ensureOpen
方法的类叫PrintStream
,继承自FilterOutputStream
,而FilterOutputStream
又继承自虚拟类OutputStream
。
那么结果就很显而易见了,最终还是为了保证OutputStream
对象是非空的,如果为空后面的就都没办法了。
然后就是textOut.write()
了。textOut
是PrintStream
中定义的BufferedWriter
类对象,也就是说,这其中最重要的写操作还是源自于BufferWriter
类。
是不是觉得好像被OutputStream
骗了?
而这个write
方法是这么定义的:
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
可以看到,在这里是针对只写一个整型数字的方法。
在这里writeBuffer
是一个字符串型数组char[]
,而WRITE_BUFFER_SIZE
默认是1024的长度,最后也是用write
方法输出了writeBuffer
中从
0
0
0到
1
1
1之间的字符。
当然,真正的写操作还是这个:
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
在这里,首先就是传入参数
- 待输出字符串
str
- 起点
off
- 输出长度
len
在这里首先就是检查长度是否超过了默认的长度
1024
1024
1024,然后就将cbuf
长度为
1024
1024
1024,否则直接让cbuf
为len
。在经过这个步骤之后,就开始针对字符串str
执行getChars
方法,源码是:
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
这里,官方给的注释是:
Copies characters from this string into the destination character array.
即,将调用getChars
方法的字符串str
从srcBegin
开始复制到dst
字符串从dstBegin
开始到srcEnd-srcBegin
这么长的一段。
那么就是说,对于str
,从off
开始将off+len
这么长一段复制到cbuf
从
0
0
0开始的地方,最后write
方法写cbuf
中
0
0
0到len
这么长的一段。
好了,到尽头了。这就是从源码能看到最基础的部分了。
不难看出这一整段完全是字符操作,就包括System.arraycopy
方法中出现的value
也是char[]
类型。
println
那么当然,我们从学校里第一节课接触到java
的过程中,从这里开始的println
又是如何呢?
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
出现了!关键字:print
方法!
也就是说println
跟print
是完全一样的原理,就只是多一个newLine
方法。而newLine
方法是这样的:
public void newLine() throws IOException {
write(lineSeparator);
}
而lineSeparator
的定义又是:
public BufferedWriter(Writer out, int sz) {
super(out);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.out = out;
cb = new char[sz];
nChars = sz;
nextChar = 0;
lineSeparator = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
到这里,也是源码的极限了。不过也能看到,这也是字符操作。
printf
那么跟C\C++
完全同名的printf
是类似的吗?
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
看起来并不是。我们再仔细看看:
public PrintStream format(String format, Object ... args) {
try {
synchronized (this) {
ensureOpen();
if ((formatter == null)
|| (formatter.locale() != Locale.getDefault()))
formatter = new Formatter((Appendable) this);
formatter.format(Locale.getDefault(), format, args);
}
} catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
} catch (IOException x) {
trouble = true;
}
return this;
}
同样也是ensureOpen
,然后就是出现了一个formatter
进行处理:
public Formatter format(Locale l, String format, Object ... args) {
ensureOpen();
// index of last argument referenced
int last = -1;
// last ordinary index
int lasto = -1;
FormatString[] fsa = parse(format);
for (int i = 0; i < fsa.length; i++) {
FormatString fs = fsa[i];
int index = fs.index();
try {
switch (index) {
case -2: // fixed string, "%n", or "%%"
fs.print(null, l);
break;
case -1: // relative index
if (last < 0 || (args != null && last > args.length - 1))
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
case 0: // ordinary index
lasto++;
last = lasto;
if (args != null && lasto > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[lasto]), l);
break;
default: // explicit index
last = index - 1;
if (args != null && last > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
}
} catch (IOException x) {
lastException = x;
}
}
return this;
}
还是print
,依然是字符操作。
但是不同的是,这里面利用Formatter
进行字符串的格式化操作。所以对于代码的执行效率而言,确实相对直接调用print
方法,也是会出现非常多的其他操作拖慢了时间。
总结
综上,可以说,print
、printf
、println
这三个基本上都是基于print
方法的改动,只有功能上的改动。也就是说,IDEA
提示的【冗余调用】其实也就只是单纯的会因为Formatter
被拖慢了运行效率。