第四章
-
在 Java 中,同一个类文件,仅可存在于一个 public 修饰类,且该 .java 文件要与public修饰类同名,否则将会报
-
递归的本质就是用压栈与出栈操作 :
def dict(x):
if x==1:
return 1
else:
return x*dict(x-1)
a=dict(3)
print(a)
/*对于这个代码:
(1) 首先调用函数dict(3),此时将函数dict以及变量x的值为3,这些压入栈上。并执行dict函数
(2) if判断完之后调用函数dict(2),同样的再将函数dict和变量x的值为2压入栈上。并执行dict函数
(3) 再循环一次步骤2,然后就结束,下面就是执行阶段。
(4) 首先执行最上面一层,就是dict(1),返回值1.
(5) 然后依次进行。*/当递归调用时每次调用自己时可以看做是压栈过程,当递归条件满足结束时,递归一级一级的返回时可以看做是出栈的过程。
-
Java 中修饰类中属性、方法修饰符:public、private、protected、default (默认)
修饰符是有四种的,所以在类里,如果不加就是默认类型[ 使用:仅在同一包内可见 ]
-
final修饰符有什么作用?
final是java中的关键字,可以修饰类、方法和变量
被final修饰的类不可以被继承
被final修饰的变量最多仅能赋值一次,且不能被改变。
被final修饰的方法不能被重写。
-
构造函数
构造器不能是abstract, static, final, native, strictfp, 或者synchronized的
构造器不是通过继承得到的,所以没有必要把它声明为final的。
构造器总是关联一个对象而被调用,所以把它声明为static是没有意义的(每当创建一个对象构造函数自动被调用)
构造方法没有函数返回值,甚至连void类型也不是。
-
假设没有static关键字,那意味着需要用生成一个实例后才可以调用这个Main方法,而Main方法是程序入口点,你没有进入Main方法,自然无法生成一个实例,既然没有实例,那就无法调用Main函数,岂不矛盾?所以Main函数被设置为static.
不能在main方法中打印this关键字的信息,这时想起了之前的知识,不能在静态方法中调用this。理由很简单,this表示“这个对象”,也就是声明一个类的对象,然而静态方法是不属于某一个特定对象而是属于这个类的。
-
java application
在Java语言中,能够独立运行的程序称为Java应用程序(Application)。 Java语言还有另外一种程序——Applet程序。Applet程序(也称Java小程序)是运行于各种网页文件中,用于增强网页的人机交互、动画显示、声音播放等功能的程序。 Java Applet和Java Application在结构方面的主要区别表现在: (1)运行方式不同。Java Applet程序不能单独运行,它必须依附于一个用HTML语言编写的网页并嵌入其中,通过与Java兼容的浏览器来控制执行。 Java Application是完整的程序,可以独立运行,只要有支持Java的虚拟机,它就可以独立运行而不需要其他文件的支持。 (2)运行工具不同。运行Java Applet程序的解释器不是独立的软件,而是嵌在浏览器中作为浏览器软件的一部分。Java Application程序被编译以后,用普通的Java 解释器就可以使其边解释边执行,而Java Applet必须通过网络浏览器或者Applet观察器才能执行。
我们入门第一步写的HelloWorld就是javaapplication
Application程序执行时,为什么不能带后缀名?
java命令是执行一个类。 若写 java xxx.yyy 是代表要运行 package xxx 中的class yyy 里面的 main(String[]) 所以当你写 java xxx.class 时, 它会以为要找一个叫xxx的package里面的一个叫class的class.
-
垃圾回收 finalize
在Java中,对象什么时候可以被垃圾回收?
-
当一个对象到GC Roots不可达时,在下一个垃圾回收周期中尝试回收该对象,如果该对象重写了finalize()方法,并在这个方法中成功自救(将自身赋予某个引用),那么这个对象不会被回收。但如果这个对象没有重写finalize()方法或者已经执行过这个方法,也自救失败,该对象将会被回收。
-
当没有任何对象的引用指向该对象时+在下次垃圾回收周期来到时=>对象才会被回收
-
如何证明一个垃圾对象被释放了:
-
-
关于类中变量的初始化 dadada
Java尽力保证:所有变量在使用前都得到恰当的初始化。
-
这个的意义是什么
public class Test {
public static void print(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[i]:"+args[i]);
}
}
public static void main(String[] args) {
print(args);
}
}
### 第五章
1. 封装的目的在于保护信息,使用它的主要优点如下。
>- 保护类中的信息,它可以阻止在外部定义的代码随意访问内部代码和数据。
>- 隐藏细节信息
>- 有助于建立各个系统之间的松耦合关系,提高系统的独立性.
>- 提高软件的复用率,降低成本。
>
>Java 封装是如何实现的:
>
> . 修改属性的可见性来限制对属性的访问(一般限制为private)
>
> 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问
>
>
2. 单例模式
> 单例模式有很多种写法 懒汉模式 就是书上的那个:
>
>```java
>
>public class Singleton{
> private static Singleton instance = null;
> private Singleton(){}
> public static Singleton newInstance(){
> if(null == instance){
> instance = new Singleton();
> }
> return instance;
> }
>
>```
>
>懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题; [单例模式VS静态类 ]( https://www.cnblogs.com/cielosun/p/6582333.html )
3.
-
java 子类父类相互转换
Java 子类强转父类
父类引用指向子类对象:
java中子类强转父类,实际上依然是子类;
该引用只能调用父类中定义的方法和变量;
如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;
Java 父类强转子类
只有父类对象本身就是用子类new出来的时候, 才可以在将来被强制转换为子类对象.
一、父类引用指向子类对象时
1、若子类覆盖了某方法,则父类引用调用子类重新定义的新方法[!!!!!!]
2、若子类未覆盖某方法,则父类引用调用父类本身的旧方法
3、若子类覆盖了某属性,但父类引用仍调用父类本身的旧属性[!!!!!!!]
4、若子类未覆盖某属性,则父类引用调用父类本身的旧属性
5、父类引用不能访问子类新定义的方法
(看1,3 java 属性可以隐藏 方法没有隐藏的概念)
二、子类引用指向自身对象时
1、若子类覆盖了某方法,则子类引用调用子类重新定义的新方法
2、若子类未覆盖某方法,则子类引用调用父类本身的旧方法
3、若子类覆盖了某属性,则子类引用调用子类重新定义的新属性
4、若子类未覆盖某属性,则子类引用调用父类本身的旧属性
5、子类引用可以访问子类新定义的方法
和super不一样啊啊啊啊啊super人家调用的就是父类的信息啊
不管有没有被覆盖[就因为被覆盖了才出来的]super.变量 super.函数
class Country {
String name;
void value() {
name = "China";
System.out.println("这里是父类");
}
}
class City extends Country {
String name;
void value() {
name = "Shanghai";
super.value(); // 调用父类的方法 System.out.println("这里是父类");
System.out.println(name); // shang hai
System.out.println(super.name);// bei jing
}
public static void main(String[] args) {
City c = new City();
c.value();
}
}class Country {
String name = "China";;
void value() {
System.out.println("这里是父类name: "+name);
}
}
class City extends Country {
String name;
void value() {
name = "Shanghai";
super.value(); //这里是父类name: China
super.name = "China changeed";
super.value(); //这里是父类name: China changeed
System.out.println(name); // shang hai
System.out.println(super.name);// China changeed
//同书本p76 想说的是 在子类中更改了父类的域变量,接下来这个类用这个变量都是更改过的了【不会影响其他子类/因为是域变量嘛 各个子类都复制到自己的区域了 不影响】
//回到该子类 更改之后在立马super.父类方法(子类已覆盖):"相当于将父类代码复制到此处,所用的域变量均为父类域变量" but父类域变量刚刚被我更改了!!
//改了 这个真的改了 不更改就都还是china
//but this还是指的是子类对象【ppt10张】
}
public static void main(String[] args) {
City c = new City();
c.value();
}
}super 不同于 父类声明子类变量这个事情
-
对象实例化过程 书本p106 博客
-
抽象类
抽象类的使用原则如下: (1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public; (2)抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理; (3)抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类; (4)子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。);
抽象类可以没有抽象方法和抽象域变量。。。
如果一个类要被声明为static的,只有一种情况,就是静态内部类
-
接口定义用关键字interface,而不是用class,interface前的修饰符要么为public,要么为缺省
接口中的字段(域)的值存储在该接口的静态存储区域内,使用接口名.字段或实现类.字段均可访问 [因为接口的域变量都是 public static final ]【在实现类里就相当于静态变量了】
一个接口可以继承多个接口,但接口不能继承类 [类当然能实现接口,..实现类]
接口有什么用?
-
实现多重继承
-
接口是一种标准,使用者按照接口使用,实验者按照接口实现,当实现者内部发生变化时,只要接口不变,使用者就不必更改代码。
-
扩展性强
-
applet当中的text field每输入一个字符 在一个label当中都能动态刷新跟踪
import java.applet.Applet;
import java.awt.Label;
import java.awt.TextField;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class MyApplet2 extends Applet implements KeyListener {
TextField tf = new TextField(10);
Label l = new Label("这里显示输入的字符 ");
public void init() {
tf.addKeyListener(this);
add(tf);
add(l);
setSize(500, 100);
tf.addKeyListener(this);
}
public void keyPressed(KeyEvent arg0) { //按下键盘
}
public void keyReleased(KeyEvent arg0) {//释放按键
} //这两个函数不能删
public void keyTyped(KeyEvent ke) { //敲击完立马显示
l.setText(tf.getText() + ke.getKeyChar());
}
}
//键盘事件类(KeyEvent)是容器内的任意组件获得焦点时,组件发生键击事件,当按下
//释放或键入某一个键时,组件对象将产生该事件。使用键盘事件必须给组件添加一个
//KeyListener 接口的事件处理器,该接口包含以下 3 个方法。
//
// void keyPressed(KeyEvent e):按下按键时发生。
// void keyReleased(KeyEvent e):松开按键时发生。
// void keyTyped(KeyEvent e):敲击键盘,发生在按键按下后,按键放开前。-
什么是数据隐藏?如何证明子类对父类同名方法进行重新定义,只能是方法的覆盖,而不是方法的隐藏: 在子类对父类的继承中,如果子类的成员变量和父类的成员变量同名,此时称为子类隐藏(override)了父类的成员变量
变量是隐藏,父类引用(=子类对象之后).变量还是父类的变量
而方法不是 此时父类引用.方法就只还是子类的方法了 所以是直接覆盖掉了
1、若子类覆盖了某方法,则父类引用调用子类重新定义的新方法[!!!!!!]
3、若子类覆盖了某属性,但父类引用仍调用父类本身的旧属性[!!!!!!!]
-
第九章 线程
-
什么是进程,什么是线程?
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。
一个程序至少一个进程,一个进程至少一个线程。
进程线程的区别
1、地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
2、资源拥有:同一进程内的线程共享本进程的资源,但是进程之间的资源是独立的。
3、一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
4、进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
5、执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
6、线程是处理器调度的基本单位,但是进程不是。[ 进程是资源分配的最小单位,线程是程序执行的最小单位 ]
7、两者均可并发执行。
-
通过调用Thread类的start()方法来启动一个线程
每个线程都是通过某个特定Thread对象所对应的方法run() 来完成其操作的,方法run()称为线程体
-
start 和 run 方法解释:
1) start: 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。 2) run: 如果直接用Run方法,书p153 这只是调用一个方法而已,程序中依然只有主线程--这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。
-
-
先调用start后调用run,这么麻烦,为了不直接调用run?就是为了实现多线程的优点,没这个start不行。
1.start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 2.run()方法当作普通方法的方式调用。(程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。)
-
synchronized 写的挺好的
如果程序是单线程的,就不必担心此线程在执行时被其他线程“打扰”,就像在现实世界中,在一段时间内如果只能完成一件事情,不用担心做这件事情被其他事情打扰。但是,如果程序中同时使用多线程,好比现实中的“两个人同时通过一扇门”,这时就需要控制,否则容易引起阻塞。
为了处理这种共享资源竞争,可以使用同步机制。所谓同步机制,指的是两个线程同时作用在一个对象上,应该保持对象数据的统一性和整体性。Java提供 synchronized 关键字,为防止资源冲突提供了内置支持。共享资源一般是文件、输入/输出端口或打印机。
当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才e3能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
-
线程是什么?进程是什么?二者有什么区别和联系?
线程是CPU独立运行和独立调度的基本单位; 进程是资源分配的基本单位; 联系: 进程和线程都是操作系统所运行的程序运行的基本单元。 区别:
进程具有独立的空间地址,一个进程崩溃后,在保护模式下不会对其它进程产生影响。 线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。
3、线程和进程的关系以及区别?
进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。 (2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 (3)处理机分给线程,即真正在处理机上运行的是线程。 (4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.
进程与线程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位 (2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行 (3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源. (4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
通信同步问题
如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
sleep和wait的区别有: 1,这两个方法来自不同的类分别是Thread和Object 2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在 任何地方使用 synchronized(x){ x.notify() //或者wait() } 4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
sleep()方法是Thread类里面的,主要的意义就是让当前线程停止执行,让出cpu给其他的线程,但是不会释放对象锁资源以及监控的状态,当指定的时间到了之后又会自动恢复运行状态。
wait()方法是Object类里面的,主要的意义就是让线程放弃当前的对象的锁,进入等待此对象的等待锁定池,只有针对此对象调动notify方法后本线程才能够进入对象锁定池准备获取对象锁进入运行状态
wait方法依赖于同步,而sleep方法可以直接调用
1 sleep() 使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会执行, 同时sleep函数不会释放锁资源. sleep可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会
2 yield() 只是使当前线程重新回到可执行状态,所以执行yield()线程有可能在进入到可执行状态后马上又被执行. 只能使同优先级的线程有执行的机会。同样, yield()也不会释放锁资源.
sleep和yield的区别在于, sleep可以使优先级低的线程得到执行的机会, 而yield只能使同优先级的线程有执行的机会.
2.wait()方法 在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。 waite()和notify()必须在synchronized函数或synchronized block中进行调用。
synchronized, wait, notify 线程的同步需要依靠上面两个函数和一个同步块实现.
(1)调用wait方法后,线程是会释放对monitor对象的所有权的。
(2)一个通过wait方法阻塞的线程,必须同时满足以下两个条件才能被真正执行:
线程需要被唤醒(超时唤醒或调用notify/notifyll)。
线程唤醒后需要竞争到锁(monitor)。
为什么会产生死锁? 在多线程环境里, 产生死锁的原因是由于几个线程同时共用一个同步资源. 这是产生死锁的前提,
产生死锁的原因, 所有线程都在等待共享资源的释放.
https://www.cnblogs.com/dolphin0520/p/3920385.html
什么是线程安全?为什么会产生线程安全问题?如何解决线程安全问题
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
当一个类被多个线程进行访问并且正确运行,它就是线程安全的
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
出现原因 1)某一个操作不是原子性的操作 2)同一时间有多个线程同时执行这个操作
1、 使用synchronized同步代码块,或者用Lock锁 2、不共享状态: 3、不可变对象:可以使用final修饰的对象保证线程安全 4. 使用线程安全的类
内置锁
Java内置锁通过synchronized关键字使用,使用其修饰方法或者代码块,就能保证方法或者代码块以同步方式执行。使用起来非常近简单,就像下面这样:
// synchronized关键字用法示例
public synchronized void add(int t){// 同步方法
this.v += t;
}
public static synchronized void sub(int t){// 同步静态方法
value -= t;
}
public int decrementAndGet(){
synchronized(obj){// 同步代码块
return --v;
}
}
这就是内置锁的全部用法,你已经学会了。
内置锁使用起来非常方便,不需要显式的获取和释放,任何一个对象都能作为一把内置锁。使用内置锁能够解决大部分的同步场景。“任何一个对象都能作为一把内置锁”也意味着出现synchronized关键字的地方,都有一个对象与之关联,具体说来:
-
当synchronized作用于普通方法是,锁对象是this;
-
当synchronized作用于静态方法是,锁对象是当前类的Class对象;
-
当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj。
十四章 IO流
-
流从流动方向上看:一般分为输入流和输出流
•输入流:如System.in是一个InputStream类型输入流
•输出流:如System.out 是一个PrintStream类型输出流
-
从键盘输入字符:
byte[] buffer = new byte[512];
int count = System.in.read(buffer);
//接下来就可以应用啦
//for(int i=0;i<count;i++) {
//System.out.print(" "+(char)buffer[i]);
//}读取文件:
FileInputStream in = new FileInputStream("D:\211\child-5000.dat"); int count = 512,c= 0; //函数里的变量要自己赋值 系统不会默认赋初值的[域变量系统会自动~] byte[] b = new byte[count]; while((c = in.read(b,0,count))!= -1) { System.out.println(new String(b,0,c)); } in.close();
写入文件
int count = 512,n=512; byte[] b = new byte[n]; //我的天啊啊啊啊啊啊啊啊啊注意这里是小写的byte!! count = System.in.read(b);//从键盘输入的 都存在缓冲区【数组b】中 是有返回值的!!! FileOutputStream out = FileOutputStream("write.txt"); out.write(b,0,count); //从b中读 写入out中 out.close();
BufferedReader 用于缓存字符流,可以一行一行的读
BufferedReader keyin = new BufferedReader(new InputStreamReader(System.in)); String c1; int i = 0; int[] e = new int[10];//10 [] 所以是arraysize while(i<10) { c1 = keyin.readLine(); e[i] = Integer.parseInt(c1); i++; } //输入10次 for(i = 0; i<10;i++) { System.out.print(e[i]); } //BufferedReader的readline() 通过下列字符之一即可认为某行已终止:换行 (' ')、回车 (' ') 或回车后直接跟着换行 //返回值是字符串 包括此行内容 不包括任何行终止符或者null,如果流的末尾已到达 如果到达流末尾, 就返回null
java DataOutputSteam / DataInputStream
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("test.txt"))); //数据输出流 允许应用程序以与机器无关方式将Java基本数据类型写到底层输出流。 dos.writeInt(3); dos.writeDouble(3.14); dos.writeUTF("hello"); dos.close();//将指定的基本数据类型以字节的方式写入到输出流 就是这些很简单的类型 这样 就写入到流里了 DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream("test.txt"))); //数据输入流 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型 System.out.println(dis.readInt()); System.out.println(dis.readDouble()); System.out.println(dis.readUTF()); dis.close(); //从流里一读 读出来就是想用的简单格式 //你把BufferedInputStream/BufferedOutputStream删掉也可以:实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。 //https://blog.csdn.net/zhaoyanjun6/article/details/54894451
我说一下为什么这么写[上文就是流装配]:DateInputStream是FilterInputStream的子类,要使用过滤流,必须把他连接在某个输入输出流上,所以要往里绑
缓冲流,“把节点流捆绑到缓冲流上可以提高读写效率” 所以要使用缓冲流加快效率的话,格式是
BufferedInputStream(InoutStream xx【里面这个参数就是节点流了 】)
PrintWriter
//printwriter 可以向该字符流写入Java基本数据类型 public static void main(String[]gs) throws FileNotFoundException { PrintWriter out = new PrintWriter(new BufferedOutputStream(new FileOutputStream("test1.txt"))); out.println("hello world"); out.println(3); out.close(); //书p222 当调用他的println()方法或者是字符串本身有换行====》自动执行flush() }
将字符串用装配流的方式输入到屏幕上
PrintWriter outted = new PrintWriter(new OutputStreamWriter(System.out),true); outted.println("abc"); //PrintWriter(Writer out,boolean autoFlush) //PrintWriter(OutputStream out, boolean autoFlush) //当autoFlush为true时,当调用println方法会自动清空缓冲区 s
对象串行化
串行化(Serialization):又称序列化,将实现了Seriallizable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,后者又称反序列化
串行化的目的:便于介质存储和网络传输
使用ObjectInputStream类和ObjectOutputStream类
关于byte char string
char与byte的区别: https://blog.csdn.net/luoweifu/article/details/7770588
一 char和string的区别https://blog.csdn.net/qauchangqingwei/article/details/80831797
1 char是表示的是字符,定义的时候用单引号,只能存储一个字符。例如; char='d'. 而String表示的是字符串,定义的时候用双引号,可以存储一个或者多个字符。例如: String=“we are neuer”。 2 char是基本数据类型,而String是个类,属于引用数据类型。String类可以调用方法,具有面向对象的特征。
关于书上 p227文件过滤器的例题讲解: https://blog.csdn.net/lj_pyt/article/details/44830761
关于file类 http://c.biancheng.net/view/1133.html
File 类不具有从文件读取信息和向文件写入信息的功能,它仅描述文件本身的属性。
( 注意:假设在 Windows 操作系统中有一文件
D:javaspacehello.java
,在 Java 中使用的时候,其路径的写法应该为D:/javaspace/hello.java
或者D:\javaspace\hello.java
。 )File类提供了如下三种形式构造方法。使用任意一个构造方法都可以创建一个 File 对象,然后调用其提供的方法对文件进行操作
directory = new File("Destination"); if (!directory.exists()) { directory.mkdir(); // 如果没有就新建 }
IO流总结: https://www.cnblogs.com/QQ846300233/p/6046388.html
第十五章 Java网络通信
-
为什么称TCP是面向连接的可靠的协议
[1] 确认和重传机制:建立连接时三次握手同步双方的“序列号 + 确认号 + 窗口大小信息”,是 确认重传、流控的基础 传输过程中,如果Checksum校验失败、丢包或延时,发送端重传 [2] 数据排序 :TCP有专门的序列号SN字段,可提供数据re-order [3] 流量控制:窗口和计时器的使用。TCP窗口中会指明双方能够发送接收的最大数据量 [4] 拥塞控制
TCP的拥塞控制由4个核心算法组成。
“慢启动”(Slow Start)
“拥塞避免”(Congestion avoidance)
“快速重传 ”(Fast Retransmit)
“快速恢复”(Fast Recovery)
-
关于这章对IO流的查漏补缺
1.BufferedReader
BufferedReader 是缓冲字符输入流。它继承于Reader。
int read() int read(char[] buffer, int offset, int length) String readLine()
BufferReader的作用是为其它Reader提供缓冲功能。创建BufferReader时,我们会通过它的构造函数指定某个Reader为参数。 如果到达流末尾, 就返回null
BufferedReader br = new BufferedReader("c:/test.txt");
(BufferReader会将该Reader中的数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从Reader中读取下一部分的数据。 )
br=new BufferedReader(new FileReader(fileName)); String str = null; while((str = br.readLine()) != null){ //System.out.println(str);//此时str就保存了一行字符串 }
书上的socket通信:代码
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in)); //BuferedReader内的参数要是字符流 而input streamReader内的参数要是字节流 BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream())) //套接字的getInput/OutputStream()方法返回的是字节流 String s1 = sin.readline(); String s2 = is.readline(); //input输入流read 别人发过来de
-
PrintStream
PrintStream继承了FilterOutputStream.是"装饰类"的一种,所以属于字节流体系中
(与PrintStream相似的流PrintWriter继承于Writer,属于字符流体系中)
为其他的输出流添加功能.使它们能够方便打印各种数据值的表示形式.此外,值得注意的是:
与其他流不同的是,PrintStream流永远不会抛出异常.因为做了try{}catch(){}会将异常捕获,出现异常情况会在内部设置标识,通过checkError()获取此标识. PrintStream流有自动刷新机制,例如当向PrintStream流中写入一个字节数组后自动调用flush()方法.
// 将“输出流out”作为PrintStream的输出流,不会自动flush,并且采用默认字符集 // 所谓“自动flush”,就是每次执行print(), println(), write()函数,都会调用flush()函数; // 而“不自动flush”,则需要我们手动调用flush()接口。 PrintStream(OutputStream out) // 将“输出流out”作为PrintStream的输出流,自动flush,并且采用默认字符集。 PrintStream(OutputStream out, boolean autoFlush)
PrintStream和DataOutputStream异同点
相同点:都是继承与FileOutputStream,用于包装其它输出流。
不同点:
(01) PrintStream和DataOutputStream 都可以将数据格式化输出;但它们在“输出字符串”时的编码不同。
PrintStream是输出时采用的是用户指定的编码(创建PrintStream时指定的),若没有指定,则采用系统默认的字符编码。而DataOutputStream则采用的是UTF-8。
(02) 它们的写入数据时的异常处理机制不同。
DataOutputStream在通过write()向“输出流”中写入数据时,若产生IOException,会抛出。 而PrintStream在通过write()向“输出流”中写入数据时,若产生IOException,则会在write()中进行捕获处理;并设置trouble标记(用于表示产生了异常)为true。用户可以通过checkError()返回trouble值,从而检查输出流中是否产生了异常。
(03) 构造函数不同
DataOutputStream的构造函数只有一个:DataOutputStream(OutputStream out)。即它只支持以输出流out作为“DataOutputStream的输出流”。 而PrintStream的构造函数有许多:和DataOutputStream一样,支持以输出流out作为“PrintStream输出流”的构造函数;还支持以“File对象”或者“String类型的文件名对象”的构造函数。 而且,在PrintStream的构造函数中,能“指定字符集”和“是否支持自动flush()操作”。
(04) 目的不同
DataOutputStream的作用是装饰其它的输出流,它和DataInputStream配合使用:允许应用程序以与机器无关的方式从底层输入流中读写java数据类型。 而PrintStream的作用虽然也是装饰其他输出流,但是它的目的不是以与机器无关的方式从底层读写java数据类型;而是为其它输出流提供打印各种数据值表示形式,使其它输出流能方便的通过print(), println()或printf()等输出各种格式的数据。
书上的示例
PrintStream os = new PrintStream(new BufferedOutputStream(socket.getOutputStream()))
-
PrintWriter
具有自动行刷新的缓冲字符输出流,特点是可以按行写出字符串,并且可以自动行刷新.
在文件操作方面:
PW支持两个直接对文件写操作的构造方法:
PrintWriter(File f)传文件名: PrintWriter pw = new PrintWriter("f://aaa.txt");
PrintWriter(String s)传路径
(1)print(String str):向文件写入一个字符串。
(2)print(char[] ch):向文件写入一个字符数组。
(3)print(char c):向文件写入一个字符。
(4)print(int i):向文件写入一个int型值。
PrintWriter os = new PrintWriter(socket.getOutputStream()); os.println(line); os.flush();
-
-
第八章 Java常用类库
-
String
为什么说String是不可变的:
-
String s = "ABCabc"; s = "123456"; System.out.println("s = " + s); //123456
s只是一个引用,s=“123456”; 它指向了一个具体的对象,当这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,
不可变针对的是对象
看源码得到: 【value,offset和count这三个变量都是private的,没有公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。】
但是在String中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring, replace, replaceAll, toLowerCase
```java String a = "ABCabc"; System.out.println("a = " + a); a = a.replace('A', 'a');//!! System.out.println("a = " + a); // aBCabc //a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个新对象重新赋给a
```java String ss = "123456"; System.out.println("ss = " + ss);//ss = 123456 ss.replace('1', '0');//!!! System.out.println("ss = " + ss);//ss = 123456 //方法内部重新创建新的String对象,并且返回这个新的对象【上面那个例子就是重新返回的值又赋给a了 所以输出变了】,原来的对象是不会被改变的
【所以看新输出到底是啥 就看TA有没有被重新赋值】
关于初始化:
String 类有 11 种构造方法,这些方法提供不同的参数来初始化字符串,比如提供一个字符数组参数:
char[] helloArray = { 'r', 'u', 'n', 'o', 'o', 'b'}; String helloString = new String(helloArray);
String方法
byte[] getBytes() 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 byte[] getBytes(String charsetName) 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 char[] toCharArray() 将此字符串转换为一个新的字符数组。 String toString() 返回此对象本身(它已经是一个字符串!)
-
1、length() 方法是针对字符串来说的,要求一个字符串的长度就要用到它的length()方法;
-
2、length 属性是针对 Java 中的数组来说的,要求数组的长度可以用其 length 属性;
-
3、Java 中的 size() 方法是针对泛型集合说的, 如果想看这个泛型有多少个元素, 就调用此方法来查看
String s1 = "abc"; // 常量池 String s2 = new String("abc"); // 堆内存中 System.out.println(s1==s2); // false两个对象的地址值不一样。 System.out.println(s1.equals(s2)); // true
String str1 = "hello world"; String str2 = new String("hello world"); String str3 = "hello world"; String str4 = new String("hello world"); System.out.println(str1==str2);//false System.out.println(str1==str3);//true System.out.println(str2==str4);//false
String str1 = "hello world"; 和 String str3 = "hello world"; 都在编译期间生成了字面常量和符号引用,运行期间字面常量 "hello world" 被存储在运行时常量池(当然只保存了一份)。通过这种方式来将 String 对象跟引用绑定的话,JVM 执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。 众所周知,通过 new 关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过 new 来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。
String s1="a"+"b"+"c"; String s2="abc"; System.out.println(s1==s2);//true System.out.println(s1.equals(s2));//true //java 中常量优化机制,编译时 s1 已经成为 abc 在常量池中查找创建,s2 不需要再创建。
String s1="ab"; String s2="abc"; String s3=s1+"c"; System.out.println(s3==s2); // false System.out.println(s3.equals(s2)); // true //先在常量池中创建 ab ,地址指向 s1, 再创建 abc ,指向 s2。对于 s3,先创建StringBuilder(或 StringBuffer)对象,通过 append 连接得到 abc ,再调用 toString() 转换得到的地址指向 s3。故 (s3==s2) 为 false。
。。。。。所以 下面这个例子:
String str1 = "HelloFlyapi"; String str4 = "Hello"; String str5 = "Flyapi"; String str7 = str4 + str5; String str8 = "Hello"+ "Flyapi"; System.out.println(str1 == str7);//false System.out.println(str1 == str8);//true
其中前三句变量存储的是常量池中的引用地址。
第四句执行时,JVM会在堆(heap)中创建一个以str4为基础的一个StringBuilder对象,然后调用StringBuilder的append()方法完成与str5的合并,之后会调用toString()方法在堆(heap)中创建一个String对象,并把这个String对象的引用赋给str7。
常见String面试题
String str = new String(“abc”)创建了多少个实例?
这个问题其实是不严谨的,but:
解: 创建了两个
1、当加载类时,”abc”被创建并驻留在了字符创常量池中(如果先前加载中没有创建驻留 过)。
2、当执行此句时,因为”abc”对应的String实例已经存在于字符串常量池中,所以JVM会将此实例复制到会在堆(heap)中并返回引用地址。
https://segmentfault.com/a/1190000009888357#comment-area 可以看一下 讲的还不错 部分存疑
【常量池中一个对象。堆内存中一个对象。堆内存的对象是常量池中的副本。 】
Java:String、StringBuffer 和 StringBuilder 的区别
String:字符串常量,字符串长度不可变。Java中String 是immutable(不可变)的。用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。
StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用 StringBuffer,如果想转成 String 类型,可以调用 StringBuffer 的 toString() 方法。Java.lang.StringBuffer 线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。
StringBuilder:字符串变量(非线程安全)。在内部 StringBuilder 对象被当作是一个包含字符序列的变长数组。
基本原则:
-
如果要操作少量的数据用 String ;
-
单线程操作大量数据用StringBuilder ;
-
多线程操作大量数据,用StringBuffer。
使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
String S1 = “This is only a” + “ simple” + “ test”; StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个 String S1 = “This is only a” + “ simple” + “test”; 其实就是: String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如: String S2 = “This is only a”; String S3 = “ simple”; String S4 = “ test”; String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做 //相加一次生成一个新对象
关于这个执行时间https://blog.csdn.net/qq_37856300/article/details/84340288 后半截
整个StringBuilder的append方法 不会重新生成新的StringBuilder对象
StringBuilder的toString方法 : 方法直接new 一个String对象,将StringBuilder对象的value进行一个拷贝,重新生成一个对象
public String toString(){ return new String(value,0,count); }
String、StringBuffer、StringBuilder 比较:【PPT上的】 String、StringBuffer、StringBuilder相同点 1、内部实现基于字符数组,封装了对字符串处理的各种操作 2、可自动检测数组越界等运行时异常 String、StringBuffer、StringBuilder不同点 1、String内部实现基于常量字符数组,内容不可变; StringBuffer、StringBuilder基于普通字符数组,数组 大小可根据 字符串的实际长度自动扩容,内容可变 2、性能方面,对于字符串的处理,相对来说 StringBuilder>StringBuffer>String 3、StringBuffer线程安全;StringBuilder非线程安全
java 泛型 https://blog.csdn.net/s10461/article/details/53941091
Java toString()方法
toString()方法返回反映这个对象的字符串
因为toString方法是Object里面已经有了的方法,而所有类都是继承Object,所以“所有对象都有这个方法”。
它通常只是为了方便输出,比如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法
var n = 17; n.toString(2);//'10001'
public static class A { public String toString() { return "this is A"; } } public static void main(String[] args) { A obj = new A(); System.out.println(obj);//this is A }
值得注意的是, !!!!!若希望将StringBuffer在屏幕上显示出来, 则必须首先调用toString方法把它变成字符串常量, 因为PrintStream的方法println()不接受StringBuffer类型的参数. 【StringBuffer转String】
StringBuffer MyStrBuff1 = new StringBuffer(); MyStrBuff1.append("Hello, Guys!"); System.out.println(MyStrBuff1.toString());
String MyStr = new StringBuffer().append("hello").toString(); MyStr = new StringBuffer().append(MyStr).append(" Guys!").toString(); System.out.println(MyStr);//hello Guys!
-
了解字符串
"将unicode字符集转为本地字符集(如GB2312或GBK)的过程叫做编码,反之叫做解码"
Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等
因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),0 - 255被用来表示大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122。
如果要表示中文,显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。
类似的,日文和韩文等其他语言也有这个问题。为了统一所有文字的编码,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
-
为什么String被设计成不可变性
1.字符串常量池的需要
2.String哈希码的唯一性,可缓存
3.String多线程安全
4.String常作为参数传递(网络,数据库,安全性)
-
2,常见构造方法 public String():空构造 public String(byte[] bytes):把字节数组转成字符串 public String(byte[] bytes,int index,int length):把字节数组的一部分转成字符串 public String(char[] value):把字符数组转成字符串 public String(char[] value,int index,int count):把字符数组的一部分转成字符串 public String(String original):把字符串常量值转成字符串
java return this 返回当前对象的引用
https://blog.csdn.net/qq_38521014/article/details/90416718
https://zhidao.baidu.com/question/77602318.html
https://zhidao.baidu.com/question/617994061089104092.html